|
1 | 1 | package org.unicitylabs.sdk.smt; |
2 | 2 |
|
3 | 3 | import java.math.BigInteger; |
| 4 | +import java.util.Objects; |
4 | 5 |
|
5 | 6 | /** |
6 | 7 | * Path utilities for the radix sparse Merkle trees. |
7 | 8 | */ |
8 | 9 | public final class SparseMerkleTreePathUtils { |
9 | 10 |
|
| 11 | + /** |
| 12 | + * Region size in bytes: the SMT key length, since the region holds a full 256-bit key prefix. A |
| 13 | + * fixed protocol constant that must match the JS SDK; it is not a tunable parameter. |
| 14 | + */ |
| 15 | + private static final int REGION_LENGTH = 32; |
| 16 | + |
10 | 17 | private SparseMerkleTreePathUtils() { |
11 | 18 | } |
12 | 19 |
|
13 | 20 | /** |
14 | | - * Region committed by an interior node: the node's {@code depth}-bit key prefix packed into 32 |
15 | | - * bytes least-significant-byte-first. {@code path} is the absolute node/key path (leading |
16 | | - * sentinel bit at {@code depth}); its low {@code depth} bits are the prefix. |
| 21 | + * Region committed by an interior node: the `depth`-bit common prefix of all leaves in the node's |
| 22 | + * sub-tree. The `i`th lowest bit of path will be the `i mod 8`th lowest bit in the `i div 8`th |
| 23 | + * byte of the returned 32-byte array (so the packing is little-endian); the remaining bits of the array |
| 24 | + * are set to zero. |
17 | 25 | * |
18 | 26 | * @param path absolute node or key path |
19 | 27 | * @param depth bifurcation depth of the node |
20 | 28 | * @return 32-byte region |
| 29 | + * @throws NullPointerException if {@code path} is {@code null} |
21 | 30 | */ |
22 | 31 | public static byte[] pathToRegion(BigInteger path, int depth) { |
23 | | - BigInteger bits = path.and(BigInteger.ONE.shiftLeft(depth).subtract(BigInteger.ONE)); |
24 | | - byte[] region = new byte[32]; |
25 | | - for (int j = 0; j * 8 < depth; j++) { |
26 | | - region[j] = (byte) bits.shiftRight(8 * j).and(BigInteger.valueOf(0xff)).intValue(); |
| 32 | + Objects.requireNonNull(path, "path cannot be null"); |
| 33 | + |
| 34 | + byte[] region = new byte[REGION_LENGTH]; |
| 35 | + int fullBytes = depth / 8; |
| 36 | + int remainderBits = depth % 8; |
| 37 | + |
| 38 | + BigInteger bits = path; |
| 39 | + for (int j = 0; j < fullBytes && j < REGION_LENGTH; j++, bits = bits.shiftRight(8)) { |
| 40 | + region[j] = bits.byteValue(); |
| 41 | + } |
| 42 | + if (remainderBits > 0 && fullBytes < REGION_LENGTH) { |
| 43 | + region[fullBytes] = (byte) (bits.byteValue() & ((1 << remainderBits) - 1)); |
27 | 44 | } |
28 | 45 | return region; |
29 | 46 | } |
|
0 commit comments