Skip to content

Commit d8f8d36

Browse files
authored
Merge pull request #76 from unicitynetwork/issue-75
#75 Switch SMT inclusion verification from version 3o to 6a
2 parents 0a84705 + cedf657 commit d8f8d36

50 files changed

Lines changed: 915 additions & 1415 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/main/java/org/unicitylabs/sdk/api/InclusionCertificate.java

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@
33
import org.unicitylabs.sdk.crypto.hash.DataHash;
44
import org.unicitylabs.sdk.crypto.hash.DataHasher;
55
import org.unicitylabs.sdk.crypto.hash.HashAlgorithm;
6-
import org.unicitylabs.sdk.smt.radix.FinalizedBranch;
7-
import org.unicitylabs.sdk.smt.radix.FinalizedLeafBranch;
8-
import org.unicitylabs.sdk.smt.radix.FinalizedNodeBranch;
9-
import org.unicitylabs.sdk.util.BitString;
6+
import org.unicitylabs.sdk.smt.SparseMerkleTreePathUtils;
7+
import org.unicitylabs.sdk.smt.radix.SparseMerkleTreeRootNode;
108
import org.unicitylabs.sdk.util.HexConverter;
119
import org.unicitylabs.sdk.util.LongConverter;
1210

13-
import java.math.BigInteger;
1411
import java.util.ArrayList;
1512
import java.util.Arrays;
1613
import java.util.List;
@@ -29,35 +26,23 @@ private InclusionCertificate(byte[] bitmap, List<DataHash> siblings) {
2926
this.siblings = siblings;
3027
}
3128

32-
public static InclusionCertificate create(FinalizedNodeBranch root, byte[] key) {
33-
FinalizedBranch node = root;
29+
public static InclusionCertificate create(SparseMerkleTreeRootNode root, byte[] key) {
30+
Objects.requireNonNull(root, "root cannot be null");
31+
Objects.requireNonNull(key, "key cannot be null");
32+
if (key.length != 32) {
33+
throw new IllegalArgumentException("Key must be 32 bytes long.");
34+
}
3435

3536
ArrayList<DataHash> siblings = new ArrayList<>();
3637
byte[] bitmap = new byte[InclusionCertificate.BITMAP_SIZE];
37-
BigInteger keyPath = BitString.fromBytesReversedLSB(key).toBigInteger();
38-
39-
while (node != null) {
40-
if (node instanceof FinalizedLeafBranch) {
41-
FinalizedLeafBranch leaf = (FinalizedLeafBranch) node;
42-
if (!Arrays.equals(leaf.getKey(), key)) {
43-
throw new RuntimeException(String.format("Leaf not found for key: %s", HexConverter.encode(key)));
44-
}
45-
46-
return new InclusionCertificate(bitmap, siblings);
47-
}
4838

49-
FinalizedNodeBranch nodeBranch = (FinalizedNodeBranch) node;
50-
boolean isRight = keyPath.testBit(nodeBranch.getDepth());
51-
FinalizedBranch sibling = isRight ? nodeBranch.getLeft() : nodeBranch.getRight();
52-
if (sibling != null) {
53-
bitmap[nodeBranch.getDepth() / 8] |= (byte) (1 << nodeBranch.getDepth() % 8);
54-
siblings.add(sibling.getHash());
55-
}
56-
57-
node = isRight ? nodeBranch.getRight() : nodeBranch.getLeft();
39+
for (SparseMerkleTreeRootNode.Sibling sibling : root.getPath(key)) {
40+
int depth = sibling.getDepth();
41+
bitmap[depth >> 3] |= (byte) (0x80 >> (depth & 7));
42+
siblings.add(sibling.getHash());
5843
}
5944

60-
throw new RuntimeException("Could not construct inclusion certificate: Invalid path");
45+
return new InclusionCertificate(bitmap, siblings);
6146
}
6247

6348
public static InclusionCertificate decode(byte[] bytes) {
@@ -113,20 +98,17 @@ public boolean verify(StateId leafKey, DataHash leafValue, DataHash expectedRoot
11398
.update(value)
11499
.digest();
115100

116-
BigInteger keyPath = BitString.fromBytesReversedLSB(key).toBigInteger();
117-
BigInteger bitmapPath = BitString.fromBytesReversedLSB(this.bitmap).toBigInteger();
118-
119101
int position = this.siblings.size();
120102
for (int depth = InclusionCertificate.MAX_DEPTH; depth >= 0; depth--) {
121-
if (!bitmapPath.testBit(depth)) continue;
103+
if (SparseMerkleTreePathUtils.getBitAtDepth(this.bitmap, depth) == 0) continue;
122104

123105
position -= 1;
124106
if (position < 0) return false;
125107

126108
DataHash sibling = this.siblings.get(position);
127109

128110
byte[] left, right;
129-
if (keyPath.testBit(depth)) {
111+
if (SparseMerkleTreePathUtils.getBitAtDepth(key, depth) == 1) {
130112
left = sibling.getData();
131113
right = hash.getData();
132114
} else {
@@ -137,6 +119,7 @@ public boolean verify(StateId leafKey, DataHash leafValue, DataHash expectedRoot
137119
hash = new DataHasher(HashAlgorithm.SHA256)
138120
.update(new byte[]{0x01})
139121
.update(LongConverter.encode(depth))
122+
.update(SparseMerkleTreePathUtils.regionFromKey(key, depth))
140123
.update(left)
141124
.update(right)
142125
.digest();

src/main/java/org/unicitylabs/sdk/api/bft/UnicitySeal.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.unicitylabs.sdk.api.bft;
22

33
import org.unicitylabs.sdk.api.NetworkId;
4+
import org.unicitylabs.sdk.crypto.secp256k1.Signature;
45
import org.unicitylabs.sdk.serializer.cbor.CborDeserializer;
56
import org.unicitylabs.sdk.serializer.cbor.CborDeserializer.CborTag;
67
import org.unicitylabs.sdk.serializer.cbor.CborSerializationException;
@@ -162,7 +163,7 @@ public static UnicitySeal fromCbor(byte[] bytes) {
162163
CborDeserializer.decodeMap(data.get(7)).stream()
163164
.map(entry -> new SignatureEntry(
164165
CborDeserializer.decodeTextString(entry.getKey()),
165-
CborDeserializer.decodeByteString(entry.getValue())
166+
Signature.fromCbor(entry.getValue())
166167
))
167168
.collect(Collectors.toSet())
168169
);
@@ -191,7 +192,7 @@ public byte[] toCbor() {
191192
signatures.stream()
192193
.map(entry -> new CborMap.Entry(
193194
CborSerializer.encodeTextString(entry.getKey()),
194-
CborSerializer.encodeByteString(entry.getSignature())
195+
entry.getSignature().toCbor()
195196
)
196197
)
197198
.collect(Collectors.toSet())
@@ -258,9 +259,9 @@ public String toString() {
258259

259260
public static final class SignatureEntry {
260261
private final String key;
261-
private final byte[] signature;
262+
private final Signature signature;
262263

263-
SignatureEntry(String key, byte[] signature) {
264+
SignatureEntry(String key, Signature signature) {
264265
this.key = key;
265266
this.signature = signature;
266267
}
@@ -269,8 +270,8 @@ public String getKey() {
269270
return this.key;
270271
}
271272

272-
public byte[] getSignature() {
273-
return Arrays.copyOf(this.signature, this.signature.length);
273+
public Signature getSignature() {
274+
return this.signature;
274275
}
275276

276277
@Override
@@ -287,7 +288,7 @@ public int hashCode() {
287288

288289
@Override
289290
public String toString() {
290-
return String.format("SignatureEntry{key=%s, signature=%s}", this.key, HexConverter.encode(this.signature));
291+
return String.format("SignatureEntry{key=%s, signature=%s}", this.key, this.signature);
291292
}
292293
}
293294
}

src/main/java/org/unicitylabs/sdk/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
import org.unicitylabs.sdk.crypto.hash.DataHash;
77
import org.unicitylabs.sdk.crypto.hash.DataHasher;
88
import org.unicitylabs.sdk.crypto.hash.HashAlgorithm;
9+
import org.unicitylabs.sdk.crypto.secp256k1.Signature;
910
import org.unicitylabs.sdk.crypto.secp256k1.SigningService;
1011
import org.unicitylabs.sdk.util.verification.VerificationResult;
1112
import org.unicitylabs.sdk.util.verification.VerificationStatus;
1213

1314
import java.util.ArrayList;
14-
import java.util.Arrays;
1515
import java.util.List;
1616

1717
/**
@@ -38,13 +38,13 @@ public static VerificationResult<VerificationStatus> verify(RootTrustBase trustB
3838
int successful = 0;
3939
for (UnicitySeal.SignatureEntry entry : unicitySeal.getSignatures()) {
4040
String nodeId = entry.getKey();
41-
byte[] signature = entry.getSignature();
41+
Signature signature = entry.getSignature();
4242

4343
VerificationResult<?> result = UnicitySealQuorumSignaturesVerificationRule.verifySignature(
4444
trustBase,
4545
nodeId,
4646
signature,
47-
hash.getData()
47+
hash
4848
);
4949
results.add(result);
5050

@@ -73,8 +73,8 @@ public static VerificationResult<VerificationStatus> verify(RootTrustBase trustB
7373
private static VerificationResult<?> verifySignature(
7474
RootTrustBase trustBase,
7575
String nodeId,
76-
byte[] signature,
77-
byte[] hash
76+
Signature signature,
77+
DataHash hash
7878
) {
7979
NodeInfo node = trustBase.getRootNode(nodeId);
8080
if (node == null) {
@@ -87,7 +87,7 @@ private static VerificationResult<?> verifySignature(
8787

8888
if (!SigningService.verifyWithPublicKey(
8989
hash,
90-
Arrays.copyOf(signature, signature.length - 1),
90+
signature,
9191
node.getSigningKey()
9292
)) {
9393
return new VerificationResult<>(

src/main/java/org/unicitylabs/sdk/crypto/secp256k1/SigningService.java

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -146,30 +146,30 @@ public Signature sign(DataHash hash) {
146146
* @return true if successful
147147
*/
148148
public boolean verify(DataHash hash, Signature signature) {
149-
return verifyWithPublicKey(hash, signature.getBytes(), this.publicKey);
149+
return SigningService.verifyWithPublicKey(hash, signature, this.publicKey);
150150
}
151151

152152
/**
153-
* Verify signature with public key.
153+
* Verify secp256k1 signature against the given public key.
154154
*
155155
* @param hash data hash
156-
* @param signature signature bytes
156+
* @param signature compact signature bytes
157157
* @param publicKey public key
158158
* @return true if successful
159159
*/
160-
public static boolean verifyWithPublicKey(DataHash hash, byte[] signature, byte[] publicKey) {
161-
return SigningService.verifyWithPublicKey(hash.getData(), signature, publicKey);
160+
public static boolean verify(DataHash hash, byte[] signature, byte[] publicKey) {
161+
return SigningService.verify(hash.getData(), signature, publicKey);
162162
}
163163

164164
/**
165-
* Verify signature with public key and data hash bytes.
165+
* Verify secp256k1 signature against the given public key and data hash bytes.
166166
*
167167
* @param hash hash bytes
168-
* @param signature signature bytes
168+
* @param signature compact signature bytes
169169
* @param publicKey public key
170170
* @return true if successful
171171
*/
172-
public static boolean verifyWithPublicKey(byte[] hash, byte[] signature, byte[] publicKey) {
172+
public static boolean verify(byte[] hash, byte[] signature, byte[] publicKey) {
173173
ECPoint pubPoint = EC_SPEC.getCurve().decodePoint(publicKey);
174174
ECPublicKeyParameters pubKey = new ECPublicKeyParameters(pubPoint, EC_DOMAIN_PARAMETERS);
175175

@@ -183,6 +183,25 @@ public static boolean verifyWithPublicKey(byte[] hash, byte[] signature, byte[]
183183
return verifier.verifySignature(hash, r, s);
184184
}
185185

186+
/**
187+
* Verify a recoverable signature against an expected public key. Unlike {@link #verify(DataHash,
188+
* byte[], byte[])}, this binds the signature's recovery byte: the public key is recovered from
189+
* the signature and must equal {@code publicKey}, so a tampered recovery byte fails verification.
190+
*
191+
* @param hash data hash
192+
* @param signature recoverable signature
193+
* @param publicKey expected compressed public key
194+
* @return true if the signature verifies and the recovered public key matches {@code publicKey}
195+
*/
196+
public static boolean verifyWithPublicKey(DataHash hash, Signature signature, byte[] publicKey) {
197+
byte[] recoveredPublicKey = SigningService.recoverPublicKey(hash, signature);
198+
if (recoveredPublicKey == null || !Arrays.equals(publicKey, recoveredPublicKey)) {
199+
return false;
200+
}
201+
202+
return SigningService.verify(hash, signature.getBytes(), publicKey);
203+
}
204+
186205

187206
private byte[] toFixedLength(BigInteger value, int length) {
188207
byte[] bytes = value.toByteArray();
@@ -261,22 +280,43 @@ private static ECPoint decompressKey(BigInteger x, boolean ybit, ECCurve curve)
261280
}
262281

263282
/**
264-
* Verify signature with recovered public key - extract public key from signature.
283+
* Recover the public key from the signature's recovery byte and verify the signature against
284+
* {@code hash}. The recovered key defines the signer's identity; no expected key is supplied.
265285
*
266286
* @param hash data hash
267-
* @param signature signature
287+
* @param signature recoverable signature
268288
* @return true if successful
269289
*/
270-
public static boolean verifySignatureWithRecoveredPublicKey(DataHash hash, Signature signature) {
271-
// Extract r and s from signature
272-
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signature.getBytes(), 0, 32));
273-
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signature.getBytes(), 32, 64));
274-
275-
ECPoint recovered = recoverFromSignature(signature.getRecovery(), r, s, hash.getData());
276-
if (recovered == null || !recovered.isValid()) {
290+
public static boolean verifyWithRecoveredPublicKey(DataHash hash, Signature signature) {
291+
byte[] recoveredPublicKey = SigningService.recoverPublicKey(hash, signature);
292+
if (recoveredPublicKey == null) {
277293
return false;
278294
}
279295

280-
return verifyWithPublicKey(hash, signature.getBytes(), recovered.getEncoded(true));
296+
return SigningService.verify(hash, signature.getBytes(), recoveredPublicKey);
297+
}
298+
299+
/**
300+
* Recover the compressed public key that produced {@code signature} over {@code hash}, using the
301+
* signature's recovery byte.
302+
*
303+
* @param hash data hash
304+
* @param signature recoverable signature
305+
* @return recovered compressed public key, or {@code null} if the signature is not recoverable
306+
*/
307+
private static byte[] recoverPublicKey(DataHash hash, Signature signature) {
308+
try {
309+
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signature.getBytes(), 0, 32));
310+
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signature.getBytes(), 32, 64));
311+
312+
ECPoint recovered = recoverFromSignature(signature.getRecovery(), r, s, hash.getData());
313+
if (recovered == null || !recovered.isValid()) {
314+
return null;
315+
}
316+
317+
return recovered.getEncoded(true);
318+
} catch (Exception e) {
319+
return null;
320+
}
281321
}
282322
}

0 commit comments

Comments
 (0)