From 7b7ac231c14e7ff55ce3dc5fe6dc83f77c4798fb Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Mon, 27 Apr 2026 21:11:52 +0000 Subject: [PATCH] #57, #55 Update inclusion proof verification, add cbor tags --- .../sdk/api/CertificationData.java | 261 +++++----- .../sdk/api/CertificationRequest.java | 116 +++-- .../sdk/api/InclusionCertificate.java | 165 +++++++ .../unicitylabs/sdk/api/InclusionProof.java | 221 +++++---- .../java/org/unicitylabs/sdk/api/StateId.java | 19 +- .../unicitylabs/sdk/api/bft/InputRecord.java | 33 +- .../org/unicitylabs/sdk/api/bft/ShardId.java | 123 +++++ .../sdk/api/bft/ShardTreeCertificate.java | 196 ++++---- .../sdk/api/bft/UnicityCertificate.java | 452 +++++++++--------- .../unicitylabs/sdk/api/bft/UnicitySeal.java | 44 +- .../sdk/api/bft/UnicityTreeCertificate.java | 38 +- .../UnicityCertificateVerification.java | 12 +- ...nputRecordCurrentHashVerificationRule.java | 24 - .../sdk/predicate/EncodedPredicate.java | 138 +++--- .../{mtree => smt}/BranchExistsException.java | 2 +- .../sdk/{mtree => smt}/CommonPath.java | 2 +- .../LeafOutOfBoundsException.java | 2 +- .../MerkleTreePathVerificationResult.java | 2 +- .../sdk/{mtree => smt}/plain/Branch.java | 2 +- .../{mtree => smt}/plain/FinalizedBranch.java | 2 +- .../plain/FinalizedLeafBranch.java | 2 +- .../plain/FinalizedNodeBranch.java | 2 +- .../sdk/{mtree => smt}/plain/LeafBranch.java | 2 +- .../sdk/{mtree => smt}/plain/NodeBranch.java | 2 +- .../plain/PendingLeafBranch.java | 2 +- .../plain/PendingNodeBranch.java | 2 +- .../plain/SparseMerkleTree.java | 8 +- .../plain/SparseMerkleTreePath.java | 4 +- .../plain/SparseMerkleTreePathStep.java | 2 +- .../plain/SparseMerkleTreeRootNode.java | 9 +- .../org/unicitylabs/sdk/smt/radix/Branch.java | 26 + .../sdk/smt/radix/FinalizedBranch.java | 16 + .../sdk/smt/radix/FinalizedLeafBranch.java | 80 ++++ .../sdk/smt/radix/FinalizedNodeBranch.java | 88 ++++ .../unicitylabs/sdk/smt/radix/LeafBranch.java | 16 + .../unicitylabs/sdk/smt/radix/NodeBranch.java | 23 + .../sdk/smt/radix/PendingLeafBranch.java | 54 +++ .../sdk/smt/radix/PendingNodeBranch.java | 47 ++ .../sdk/smt/radix/SparseMerkleTree.java | 126 +++++ .../sdk/{mtree => smt}/sum/Branch.java | 2 +- .../{mtree => smt}/sum/FinalizedBranch.java | 2 +- .../sum/FinalizedLeafBranch.java | 4 +- .../sum/FinalizedNodeBranch.java | 2 +- .../sdk/{mtree => smt}/sum/LeafBranch.java | 4 +- .../sdk/{mtree => smt}/sum/NodeBranch.java | 2 +- .../{mtree => smt}/sum/PendingLeafBranch.java | 4 +- .../{mtree => smt}/sum/PendingNodeBranch.java | 2 +- .../sum/SparseMerkleSumTree.java | 8 +- .../sum/SparseMerkleSumTreePath.java | 4 +- .../sum/SparseMerkleSumTreePathStep.java | 2 +- .../sum/SparseMerkleSumTreeRootNode.java | 4 +- .../sdk/transaction/MintTransaction.java | 283 ++++++----- .../unicitylabs/sdk/transaction/Token.java | 41 +- .../unicitylabs/sdk/transaction/TokenId.java | 9 +- .../sdk/transaction/TokenType.java | 9 +- .../sdk/transaction/TransferTransaction.java | 49 +- .../InclusionProofVerificationRule.java | 68 ++- .../InclusionProofVerificationStatus.java | 3 +- .../org/unicitylabs/sdk/util/BitString.java | 86 +++- .../sdk/util/InclusionProofUtils.java | 67 +-- .../unicitylabs/sdk/util/LongConverter.java | 33 ++ .../unicitylabs/sdk/TestAggregatorClient.java | 189 ++++---- .../sdk/api/InclusionProofFixture.java | 5 +- .../sdk/api/InclusionProofTest.java | 57 +-- .../sdk/api/bft/UnicityCertificateTest.java | 5 +- .../sdk/api/bft/UnicityCertificateUtils.java | 113 +++-- .../sdk/{mtree => smt}/CommonPathTest.java | 2 +- .../plain/MerkleTreePathTest.java | 7 +- .../plain/SparseMerkleTreePathFixture.java | 2 +- .../plain/SparseMerkleTreeTest.java | 17 +- .../sum/SparseMerkleSumTreeTest.java | 6 +- 71 files changed, 2190 insertions(+), 1266 deletions(-) create mode 100644 src/main/java/org/unicitylabs/sdk/api/InclusionCertificate.java create mode 100644 src/main/java/org/unicitylabs/sdk/api/bft/ShardId.java delete mode 100644 src/main/java/org/unicitylabs/sdk/api/bft/verification/rule/InputRecordCurrentHashVerificationRule.java rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/BranchExistsException.java (90%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/CommonPath.java (97%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/LeafOutOfBoundsException.java (87%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/MerkleTreePathVerificationResult.java (97%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/plain/Branch.java (91%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/plain/FinalizedBranch.java (85%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/plain/FinalizedLeafBranch.java (98%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/plain/FinalizedNodeBranch.java (98%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/plain/LeafBranch.java (83%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/plain/NodeBranch.java (86%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/plain/PendingLeafBranch.java (96%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/plain/PendingNodeBranch.java (97%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/plain/SparseMerkleTree.java (95%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/plain/SparseMerkleTreePath.java (97%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/plain/SparseMerkleTreePathStep.java (98%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/plain/SparseMerkleTreeRootNode.java (97%) create mode 100644 src/main/java/org/unicitylabs/sdk/smt/radix/Branch.java create mode 100644 src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedBranch.java create mode 100644 src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedLeafBranch.java create mode 100644 src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedNodeBranch.java create mode 100644 src/main/java/org/unicitylabs/sdk/smt/radix/LeafBranch.java create mode 100644 src/main/java/org/unicitylabs/sdk/smt/radix/NodeBranch.java create mode 100644 src/main/java/org/unicitylabs/sdk/smt/radix/PendingLeafBranch.java create mode 100644 src/main/java/org/unicitylabs/sdk/smt/radix/PendingNodeBranch.java create mode 100644 src/main/java/org/unicitylabs/sdk/smt/radix/SparseMerkleTree.java rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/sum/Branch.java (91%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/sum/FinalizedBranch.java (90%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/sum/FinalizedLeafBranch.java (95%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/sum/FinalizedNodeBranch.java (98%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/sum/LeafBranch.java (66%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/sum/NodeBranch.java (87%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/sum/PendingLeafBranch.java (91%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/sum/PendingNodeBranch.java (97%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/sum/SparseMerkleSumTree.java (96%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/sum/SparseMerkleSumTreePath.java (98%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/sum/SparseMerkleSumTreePathStep.java (98%) rename src/main/java/org/unicitylabs/sdk/{mtree => smt}/sum/SparseMerkleSumTreeRootNode.java (97%) create mode 100644 src/main/java/org/unicitylabs/sdk/util/LongConverter.java rename src/test/java/org/unicitylabs/sdk/{mtree => smt}/CommonPathTest.java (95%) rename src/test/java/org/unicitylabs/sdk/{mtree => smt}/plain/MerkleTreePathTest.java (91%) rename src/test/java/org/unicitylabs/sdk/{mtree => smt}/plain/SparseMerkleTreePathFixture.java (91%) rename src/test/java/org/unicitylabs/sdk/{mtree => smt}/plain/SparseMerkleTreeTest.java (96%) rename src/test/java/org/unicitylabs/sdk/{mtree => smt}/sum/SparseMerkleSumTreeTest.java (96%) diff --git a/src/main/java/org/unicitylabs/sdk/api/CertificationData.java b/src/main/java/org/unicitylabs/sdk/api/CertificationData.java index 7d035b3..58caccb 100644 --- a/src/main/java/org/unicitylabs/sdk/api/CertificationData.java +++ b/src/main/java/org/unicitylabs/sdk/api/CertificationData.java @@ -1,148 +1,157 @@ package org.unicitylabs.sdk.api; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; import org.unicitylabs.sdk.crypto.MintSigningService; import org.unicitylabs.sdk.crypto.hash.DataHash; -import org.unicitylabs.sdk.crypto.hash.DataHasher; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; import org.unicitylabs.sdk.predicate.EncodedPredicate; import org.unicitylabs.sdk.predicate.Predicate; import org.unicitylabs.sdk.predicate.builtin.PayToPublicKeyPredicateUnlockScript; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.transaction.MintTransaction; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.util.HexConverter; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + /** * Certification data. */ public class CertificationData { + public static final long CBOR_TAG = 39031; + private static final int VERSION = 1; + + private final Predicate lockScript; + private final DataHash sourceStateHash; + private final DataHash transactionHash; + private final byte[] unlockScript; + + CertificationData( + Predicate lockScript, + DataHash sourceStateHash, + DataHash transactionHash, + byte[] unlockScript + ) { + this.lockScript = lockScript; + this.sourceStateHash = sourceStateHash; + this.transactionHash = transactionHash; + this.unlockScript = Arrays.copyOf(unlockScript, unlockScript.length); + } + + public int getVersion() { + return CertificationData.VERSION; + } + + public Predicate getLockScript() { + return this.lockScript; + } + + /** + * Get source state hash. + * + * @return source state hash + */ + public DataHash getSourceStateHash() { + return this.sourceStateHash; + } + + /** + * Get transaction hash. + * + * @return transaction hash + */ + public DataHash getTransactionHash() { + return this.transactionHash; + } + + public byte[] getUnlockScript() { + return Arrays.copyOf(this.unlockScript, this.unlockScript.length); + } + + /** + * Create CertificationData from CBOR bytes. + * + * @param bytes CBOR bytes + * @return CertificationData + */ + public static CertificationData fromCbor(byte[] bytes) { + CborDeserializer.CborTag tag = CborDeserializer.decodeTag(bytes); + if (tag.getTag() != CertificationData.CBOR_TAG) { + throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); + } + List data = CborDeserializer.decodeArray(tag.getData()); + + int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); + if (version != CertificationData.VERSION) { + throw new CborSerializationException(String.format("Unsupported version: %s", version)); + } + + return new CertificationData( + EncodedPredicate.fromCbor(data.get(1)), + new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(data.get(2))), + new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(data.get(3))), + CborDeserializer.decodeByteString(data.get(4)) + ); + } + + public static CertificationData fromMintTransaction(MintTransaction transaction) { + SigningService signingService = MintSigningService.create(transaction.getTokenId()); + + return CertificationData.fromTransaction( + transaction, + PayToPublicKeyPredicateUnlockScript.create(transaction, signingService).getSignature() + .encode() + ); + } + + public static CertificationData fromTransaction(Transaction transaction, byte[] unlockScript) { + return new CertificationData( + transaction.getLockScript(), + transaction.getSourceStateHash(), + transaction.calculateTransactionHash(), + unlockScript + ); + } + + /** + * Convert the certification data to CBOR bytes. + * + * @return CBOR bytes + */ + public byte[] toCbor() { + return CborSerializer.encodeTag( + CertificationData.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(CertificationData.VERSION), + EncodedPredicate.fromPredicate(this.getLockScript()).toCbor(), + CborSerializer.encodeByteString(this.sourceStateHash.getData()), + CborSerializer.encodeByteString(this.transactionHash.getData()), + CborSerializer.encodeByteString(this.unlockScript) + ) + ); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof CertificationData)) { + return false; + } + CertificationData that = (CertificationData) o; + return this.lockScript.isEqualTo(that.lockScript) + && Objects.equals(this.sourceStateHash, that.sourceStateHash) + && Objects.equals(this.transactionHash, that.transactionHash) + && Arrays.equals(this.unlockScript, that.unlockScript); + } - private final Predicate lockScript; - private final DataHash sourceStateHash; - private final DataHash transactionHash; - private final byte[] unlockScript; - - CertificationData( - Predicate lockScript, - DataHash sourceStateHash, - DataHash transactionHash, - byte[] unlockScript - ) { - this.lockScript = lockScript; - this.sourceStateHash = sourceStateHash; - this.transactionHash = transactionHash; - this.unlockScript = Arrays.copyOf(unlockScript, unlockScript.length); - } - - public Predicate getLockScript() { - return this.lockScript; - } - - /** - * Get source state hash. - * - * @return source state hash - */ - public DataHash getSourceStateHash() { - return this.sourceStateHash; - } - - /** - * Get transaction hash. - * - * @return transaction hash - */ - public DataHash getTransactionHash() { - return this.transactionHash; - } - - public byte[] getUnlockScript() { - return Arrays.copyOf(this.unlockScript, this.unlockScript.length); - } - - /** - * Create CertificationData from CBOR bytes. - * - * @param bytes CBOR bytes - * @return CertificationData - */ - public static CertificationData fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); - - return new CertificationData( - EncodedPredicate.fromCbor(data.get(0)), - new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(data.get(1))), - new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(data.get(2))), - CborDeserializer.decodeByteString(data.get(3)) - ); - } - - public static CertificationData fromMintTransaction(MintTransaction transaction) { - SigningService signingService = MintSigningService.create(transaction.getTokenId()); - - return CertificationData.fromTransaction( - transaction, - PayToPublicKeyPredicateUnlockScript.create(transaction, signingService).getSignature() - .encode() - ); - } - - public static CertificationData fromTransaction(Transaction transaction, byte[] unlockScript) { - return new CertificationData( - transaction.getLockScript(), - transaction.getSourceStateHash(), - transaction.calculateTransactionHash(), - unlockScript - ); - } - - /** - * Calculate leaf value for Merkle tree. - * - * @return leaf value - */ - public DataHash calculateLeafValue() { - return new DataHasher(HashAlgorithm.SHA256) - .update(this.toCbor()) - .digest(); - } - - /** - * Convert the certification data to CBOR bytes. - * - * @return CBOR bytes - */ - public byte[] toCbor() { - return CborSerializer.encodeArray( - EncodedPredicate.fromPredicate(this.getLockScript()).toCbor(), - CborSerializer.encodeByteString(this.sourceStateHash.getData()), - CborSerializer.encodeByteString(this.transactionHash.getData()), - CborSerializer.encodeByteString(this.unlockScript) - ); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof CertificationData)) { - return false; + @Override + public String toString() { + return String.format( + "CertificationData{lockScript=%s, sourceStateHash=%s, transactionHash=%s, unlockScript=%s}", + this.lockScript, this.sourceStateHash, this.transactionHash, + HexConverter.encode(this.unlockScript)); } - CertificationData that = (CertificationData) o; - return this.lockScript.isEqualTo(that.lockScript) - && Objects.equals(this.sourceStateHash, that.sourceStateHash) - && Objects.equals(this.transactionHash, that.transactionHash) - && Arrays.equals(this.unlockScript, that.unlockScript); - } - - @Override - public String toString() { - return String.format( - "CertificationData{lockScript=%s, sourceStateHash=%s, transactionHash=%s, unlockScript=%s}", - this.lockScript, this.sourceStateHash, this.transactionHash, - HexConverter.encode(this.unlockScript)); - } } diff --git a/src/main/java/org/unicitylabs/sdk/api/CertificationRequest.java b/src/main/java/org/unicitylabs/sdk/api/CertificationRequest.java index 90281d5..ed30a89 100644 --- a/src/main/java/org/unicitylabs/sdk/api/CertificationRequest.java +++ b/src/main/java/org/unicitylabs/sdk/api/CertificationRequest.java @@ -6,63 +6,73 @@ * Submit certification request. */ public class CertificationRequest { + public static final long CBOR_TAG = 39030; + private static final int VERSION = 1; - private final StateId stateId; - private final CertificationData certificationData; + private final StateId stateId; + private final CertificationData certificationData; - /** - * Create certification request. - * - * @param stateId state id - * @param certificationData transaction hash - */ - private CertificationRequest( - StateId stateId, - CertificationData certificationData - ) { - this.stateId = stateId; - this.certificationData = certificationData; - } + /** + * Create certification request. + * + * @param stateId state id + * @param certificationData transaction hash + */ + private CertificationRequest( + StateId stateId, + CertificationData certificationData + ) { + this.stateId = stateId; + this.certificationData = certificationData; + } - /** - * Get state id. - * - * @return state id - */ - public StateId getStateId() { - return this.stateId; - } + public int getVersion() { + return CertificationRequest.VERSION; + } - /** - * Get certification data. - * - * @return certification data - */ - public CertificationData getCertificationData() { - return this.certificationData; - } + /** + * Get state id. + * + * @return state id + */ + public StateId getStateId() { + return this.stateId; + } - /** - * Create certification request. - * - * @param certificationData certification data - * @return certification request - */ - public static CertificationRequest create(CertificationData certificationData) { - return new CertificationRequest(StateId.fromCertificationData(certificationData), - certificationData); - } + /** + * Get certification data. + * + * @return certification data + */ + public CertificationData getCertificationData() { + return this.certificationData; + } - /** - * Convert the request to a CBOR bytes. - * - * @return CBOR bytes - */ - public byte[] toCBOR() { - return CborSerializer.encodeArray( - this.stateId.toCbor(), - this.certificationData.toCbor(), - CborSerializer.encodeUnsignedInteger(0) - ); - } + /** + * Create certification request. + * + * @param certificationData certification data + * @return certification request + */ + public static CertificationRequest create(CertificationData certificationData) { + return new CertificationRequest(StateId.fromCertificationData(certificationData), + certificationData); + } + + /** + * Convert the request to a CBOR bytes. + * + * @return CBOR bytes + */ + public byte[] toCBOR() { + return CborSerializer.encodeTag( + CertificationRequest.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(CertificationRequest.VERSION), + this.stateId.toCbor(), + this.certificationData.toCbor(), + CborSerializer.encodeUnsignedInteger(0) + ) + ); + } } diff --git a/src/main/java/org/unicitylabs/sdk/api/InclusionCertificate.java b/src/main/java/org/unicitylabs/sdk/api/InclusionCertificate.java new file mode 100644 index 0000000..389525b --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/api/InclusionCertificate.java @@ -0,0 +1,165 @@ +package org.unicitylabs.sdk.api; + +import org.unicitylabs.sdk.crypto.hash.DataHash; +import org.unicitylabs.sdk.crypto.hash.DataHasher; +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; +import org.unicitylabs.sdk.smt.radix.FinalizedBranch; +import org.unicitylabs.sdk.smt.radix.FinalizedLeafBranch; +import org.unicitylabs.sdk.smt.radix.FinalizedNodeBranch; +import org.unicitylabs.sdk.util.BitString; +import org.unicitylabs.sdk.util.HexConverter; +import org.unicitylabs.sdk.util.LongConverter; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class InclusionCertificate { + private static final int BITMAP_SIZE = 32; + private static final int MAX_DEPTH = 255; + + private final byte[] bitmap; + private final List siblings; + + + private InclusionCertificate(byte[] bitmap, List siblings) { + this.bitmap = bitmap; + this.siblings = siblings; + } + + public static InclusionCertificate create(FinalizedNodeBranch root, byte[] key) { + FinalizedBranch node = root; + + ArrayList siblings = new ArrayList<>(); + byte[] bitmap = new byte[InclusionCertificate.BITMAP_SIZE]; + BigInteger keyPath = BitString.fromBytesReversedLSB(key).toBigInteger(); + + while (node != null) { + if (node instanceof FinalizedLeafBranch) { + FinalizedLeafBranch leaf = (FinalizedLeafBranch) node; + if (!Arrays.equals(leaf.getKey(), key)) { + throw new RuntimeException(String.format("Leaf not found for key: %s", HexConverter.encode(key))); + } + + return new InclusionCertificate(bitmap, siblings); + } + + FinalizedNodeBranch nodeBranch = (FinalizedNodeBranch) node; + boolean isRight = keyPath.testBit(nodeBranch.getDepth()); + FinalizedBranch sibling = isRight ? nodeBranch.getLeft() : nodeBranch.getRight(); + if (sibling != null) { + bitmap[nodeBranch.getDepth() / 8] |= (byte) (1 << nodeBranch.getDepth() % 8); + siblings.add(sibling.getHash()); + } + + node = isRight ? nodeBranch.getRight() : nodeBranch.getLeft(); + } + + throw new RuntimeException("Could not construct inclusion certificate: Invalid path"); + } + + public static InclusionCertificate decode(byte[] bytes) { + if (bytes.length < InclusionCertificate.BITMAP_SIZE) { + throw new IllegalArgumentException("Inclusion Certificate bitmap is invalid."); + } + + int siblingBytesLength = bytes.length - InclusionCertificate.BITMAP_SIZE; + if (siblingBytesLength % HashAlgorithm.SHA256.getLength() != 0) { + throw new IllegalArgumentException("Inclusion Certificate siblings are misaligned."); + } + + int siblingsCount = 0; + for (int i = 0; i < InclusionCertificate.BITMAP_SIZE; i++) { + int x = bytes[i]; + x = x - ((x >>> 1) & 0x55); + x = (x & 0x33) + ((x >>> 2) & 0x33); + x = (x + (x >>> 4)) & 0x0f; + siblingsCount += x; + } + + if (siblingBytesLength / HashAlgorithm.SHA256.getLength() != siblingsCount) { + throw new IllegalArgumentException("Inclusion Certificate siblings count does not match bitmap."); + } + + ArrayList siblings = new ArrayList<>(); + for (int i = InclusionCertificate.BITMAP_SIZE; i < bytes.length; i += HashAlgorithm.SHA256.getLength()) { + siblings.add(new DataHash(HashAlgorithm.SHA256, Arrays.copyOfRange(bytes, i, i + HashAlgorithm.SHA256.getLength()))); + } + + return new InclusionCertificate(Arrays.copyOfRange(bytes, 0, InclusionCertificate.BITMAP_SIZE), siblings); + } + + public byte[] encode() { + byte[] bytes = new byte[InclusionCertificate.BITMAP_SIZE + this.siblings.size() * HashAlgorithm.SHA256.getLength()]; + System.arraycopy(this.bitmap, 0, bytes, 0, InclusionCertificate.BITMAP_SIZE); + int offset = InclusionCertificate.BITMAP_SIZE; + for (DataHash sibling : this.siblings) { + byte[] data = sibling.getData(); + System.arraycopy(data, 0, bytes, offset, data.length); + offset += data.length; + } + return bytes; + } + + public boolean verify(StateId leafKey, DataHash leafValue, DataHash expectedRootHash) { + byte[] key = leafKey.getData(); + byte[] value = leafValue.getData(); + + DataHash hash = new DataHasher(HashAlgorithm.SHA256) + .update(new byte[]{0x00}) + .update(key) + .update(value) + .digest(); + + BigInteger keyPath = BitString.fromBytesReversedLSB(key).toBigInteger(); + BigInteger bitmapPath = BitString.fromBytesReversedLSB(this.bitmap).toBigInteger(); + + int position = this.siblings.size(); + for (int depth = InclusionCertificate.MAX_DEPTH; depth >= 0; depth--) { + if (!bitmapPath.testBit(depth)) continue; + + position -= 1; + if (position < 0) return false; + + DataHash sibling = this.siblings.get(position); + + byte[] left, right; + if (keyPath.testBit(depth)) { + left = sibling.getData(); + right = hash.getData(); + } else { + left = hash.getData(); + right = sibling.getData(); + } + + hash = new DataHasher(HashAlgorithm.SHA256) + .update(new byte[]{0x01}) + .update(LongConverter.encode(depth)) + .update(left) + .update(right) + .digest(); + } + + return position == 0 && hash.equals(expectedRootHash); + } + + + @Override + public boolean equals(Object o) { + if (!(o instanceof InclusionCertificate)) return false; + InclusionCertificate that = (InclusionCertificate) o; + return Objects.deepEquals(this.bitmap, that.bitmap) && Objects.equals(this.siblings, that.siblings); + } + + @Override + public int hashCode() { + return Objects.hash(Arrays.hashCode(this.bitmap), this.siblings); + } + + @Override + public String toString() { + return String.format("InclusionCertificate{bitmap=%s, siblings=%s}", HexConverter.encode(this.bitmap), this.siblings); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/api/InclusionProof.java b/src/main/java/org/unicitylabs/sdk/api/InclusionProof.java index 28d4e08..4bd38d0 100644 --- a/src/main/java/org/unicitylabs/sdk/api/InclusionProof.java +++ b/src/main/java/org/unicitylabs/sdk/api/InclusionProof.java @@ -1,112 +1,135 @@ package org.unicitylabs.sdk.api; -import java.util.List; -import java.util.Objects; -import java.util.Optional; import org.unicitylabs.sdk.api.bft.UnicityCertificate; -import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + /** * Represents a proof of inclusion or non-inclusion in a sparse merkle tree. */ public class InclusionProof { + public static final long CBOR_TAG = 39033; + private static final int VERSION = 1; + + private final InclusionCertificate inclusionCertificate; + private final CertificationData certificationData; + private final UnicityCertificate unicityCertificate; + + InclusionProof( + CertificationData certificationData, + InclusionCertificate inclusionCertificate, + UnicityCertificate unicityCertificate + ) { + Objects.requireNonNull(unicityCertificate, "Unicity certificate cannot be null."); + + this.inclusionCertificate = inclusionCertificate; + this.certificationData = certificationData; + this.unicityCertificate = unicityCertificate; + } + + public int getVersion() { + return InclusionProof.VERSION; + } + + /** + * Get merkle tree path. + * + * @return merkle tree path + */ + public InclusionCertificate getInclusionCertificate() { + return this.inclusionCertificate; + } + + /** + * Get unicity certificate. + * + * @return unicity certificate + */ + public UnicityCertificate getUnicityCertificate() { + return this.unicityCertificate; + } + + /** + * Get certification data on inclusion proof, null on non inclusion proof. + * + * @return authenticator + */ + public Optional getCertificationData() { + return Optional.ofNullable(this.certificationData); + } + + /** + * Create inclusion proof from CBOR bytes. + * + * @param bytes CBOR bytes + * @return inclusion proof + */ + public static InclusionProof fromCbor(byte[] bytes) { + CborDeserializer.CborTag tag = CborDeserializer.decodeTag(bytes); + if (tag.getTag() != InclusionProof.CBOR_TAG) { + throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); + } + List data = CborDeserializer.decodeArray(tag.getData()); + + int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); + if (version != InclusionProof.VERSION) { + throw new CborSerializationException(String.format("Unsupported version: %s", version)); + } + + return new InclusionProof( + CborDeserializer.decodeNullable(data.get(1), CertificationData::fromCbor), + CborDeserializer.decodeNullable(data.get(2), (inclusionCertificate) -> + InclusionCertificate.decode(CborDeserializer.decodeByteString(inclusionCertificate)) + ), + UnicityCertificate.fromCbor(data.get(3)) + ); + } + + /** + * Convert inclusion proof to CBOR bytes. + * + * @return CBOR bytes + */ + public byte[] toCbor() { + return CborSerializer.encodeTag( + InclusionProof.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(InclusionProof.VERSION), + CborSerializer.encodeOptional(this.certificationData, CertificationData::toCbor), + CborSerializer.encodeOptional(this.inclusionCertificate, (inclusionCertificate) -> + CborSerializer.encodeByteString(inclusionCertificate.encode()) + ), + this.unicityCertificate.toCbor() + ) + ); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof InclusionProof)) { + return false; + } + InclusionProof that = (InclusionProof) o; + return Objects.equals(this.inclusionCertificate, that.inclusionCertificate) && Objects.equals( + this.certificationData, + that.certificationData); + } + + @Override + public int hashCode() { + return Objects.hash(InclusionProof.VERSION, this.inclusionCertificate, this.certificationData); + } - private final SparseMerkleTreePath merkleTreePath; - private final CertificationData certificationData; - private final UnicityCertificate unicityCertificate; - - InclusionProof( - SparseMerkleTreePath merkleTreePath, - CertificationData certificationData, - UnicityCertificate unicityCertificate - ) { - Objects.requireNonNull(merkleTreePath, "Merkle tree path cannot be null."); - Objects.requireNonNull(unicityCertificate, "Unicity certificate cannot be null."); - - this.merkleTreePath = merkleTreePath; - this.certificationData = certificationData; - this.unicityCertificate = unicityCertificate; - } - - /** - * Get merkle tree path. - * - * @return merkle tree path - */ - public SparseMerkleTreePath getMerkleTreePath() { - return this.merkleTreePath; - } - - /** - * Get unicity certificate. - * - * @return unicity certificate - */ - public UnicityCertificate getUnicityCertificate() { - return this.unicityCertificate; - } - - /** - * Get certification data on inclusion proof, null on non inclusion proof. - * - * @return authenticator - */ - public Optional getCertificationData() { - return Optional.ofNullable(this.certificationData); - } - - /** - * Create inclusion proof from CBOR bytes. - * - * @param bytes CBOR bytes - * @return inclusion proof - */ - public static InclusionProof fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); - - return new InclusionProof( - SparseMerkleTreePath.fromCbor(data.get(1)), - CborDeserializer.decodeNullable(data.get(0), CertificationData::fromCbor), - UnicityCertificate.fromCbor(data.get(2)) - ); - } - - /** - * Convert inclusion proof to CBOR bytes. - * - * @return CBOR bytes - */ - public byte[] toCbor() { - return CborSerializer.encodeArray( - CborSerializer.encodeOptional(this.certificationData, CertificationData::toCbor), - this.merkleTreePath.toCbor(), - this.unicityCertificate.toCbor() - ); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof InclusionProof)) { - return false; + @Override + public String toString() { + return String.format( + "InclusionProof{certificationData=%s, inclusionCertificate=%s, unicityCertificate=%s}", + this.inclusionCertificate, + this.certificationData, this.unicityCertificate); } - InclusionProof that = (InclusionProof) o; - return Objects.equals(this.merkleTreePath, that.merkleTreePath) && Objects.equals( - this.certificationData, - that.certificationData); - } - - @Override - public int hashCode() { - return Objects.hash(this.merkleTreePath, this.certificationData); - } - - @Override - public String toString() { - return String.format( - "InclusionProof{merkleTreePath=%s, certificationData=%s, unicityCertificate=%s}", - this.merkleTreePath, - this.certificationData, this.unicityCertificate); - } } diff --git a/src/main/java/org/unicitylabs/sdk/api/StateId.java b/src/main/java/org/unicitylabs/sdk/api/StateId.java index ec99162..7fdf8fd 100644 --- a/src/main/java/org/unicitylabs/sdk/api/StateId.java +++ b/src/main/java/org/unicitylabs/sdk/api/StateId.java @@ -1,6 +1,5 @@ package org.unicitylabs.sdk.api; -import java.util.Objects; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.DataHasher; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; @@ -9,9 +8,10 @@ import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.transaction.Transaction; -import org.unicitylabs.sdk.util.BitString; import org.unicitylabs.sdk.util.HexConverter; +import java.util.Objects; + public class StateId { private final DataHash hash; @@ -24,10 +24,6 @@ public byte[] getData() { return this.hash.getData(); } - public byte[] getImprint() { - return this.hash.getImprint(); - } - public static StateId fromCbor(byte[] bytes) { return new StateId( new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(bytes))); @@ -63,15 +59,6 @@ public byte[] toCbor() { return CborSerializer.encodeByteString(this.getData()); } - /** - * Converts the StateId to a BitString. - * - * @return The BitString representation of the StateId. - */ - public BitString toBitString() { - return BitString.fromStateId(this); - } - @Override public boolean equals(Object o) { if (!(o instanceof StateId)) { @@ -93,6 +80,6 @@ public int hashCode() { */ @Override public String toString() { - return String.format("StateId[%s]", HexConverter.encode(this.getImprint())); + return String.format("StateId[%s]", HexConverter.encode(this.getData())); } } \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/InputRecord.java b/src/main/java/org/unicitylabs/sdk/api/bft/InputRecord.java index bdb898b..93c6de5 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/InputRecord.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/InputRecord.java @@ -5,6 +5,7 @@ import java.util.Objects; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer.CborTag; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.HexConverter; @@ -12,8 +13,9 @@ * Input record for UnicityCertificate. */ public class InputRecord { + public static final long CBOR_TAG = 39002; + private static final int VERSION = 1; - private final int version; private final long roundNumber; private final long epoch; private final byte[] previousHash; @@ -25,7 +27,6 @@ public class InputRecord { private final byte[] executedTransactionsHash; InputRecord( - int version, long roundNumber, long epoch, byte[] previousHash, @@ -39,7 +40,6 @@ public class InputRecord { Objects.requireNonNull(hash, "Hash cannot be null"); Objects.requireNonNull(summaryValue, "Summary value cannot be null"); - this.version = version; this.roundNumber = roundNumber; this.epoch = epoch; this.previousHash = previousHash; @@ -51,13 +51,8 @@ public class InputRecord { this.executedTransactionsHash = executedTransactionsHash; } - /** - * Get version. - * - * @return version - */ public int getVersion() { - return this.version; + return InputRecord.VERSION; } /** @@ -151,10 +146,17 @@ public byte[] getExecutedTransactionsHash() { */ public static InputRecord fromCbor(byte[] bytes) { CborTag tag = CborDeserializer.decodeTag(bytes); + if (tag.getTag() != InputRecord.CBOR_TAG) { + throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); + } List data = CborDeserializer.decodeArray(tag.getData()); + int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); + if (version != InputRecord.VERSION) { + throw new CborSerializationException(String.format("Unsupported version: %s", version)); + } + return new InputRecord( - CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(), CborDeserializer.decodeUnsignedInteger(data.get(1)).asLong(), CborDeserializer.decodeUnsignedInteger(data.get(2)).asLong(), CborDeserializer.decodeNullable(data.get(3), CborDeserializer::decodeByteString), @@ -174,9 +176,9 @@ public static InputRecord fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeTag( - 1008, + InputRecord.CBOR_TAG, CborSerializer.encodeArray( - CborSerializer.encodeUnsignedInteger(this.version), + CborSerializer.encodeUnsignedInteger(InputRecord.VERSION), CborSerializer.encodeUnsignedInteger(this.roundNumber), CborSerializer.encodeUnsignedInteger(this.epoch), CborSerializer.encodeOptional(this.previousHash, CborSerializer::encodeByteString), @@ -196,7 +198,7 @@ public boolean equals(Object o) { return false; } InputRecord that = (InputRecord) o; - return Objects.equals(this.version, that.version) && Objects.equals(this.roundNumber, + return Objects.equals(this.roundNumber, that.roundNumber) && Objects.equals(this.epoch, that.epoch) && Objects.deepEquals(this.previousHash, that.previousHash) && Objects.deepEquals(this.hash, that.hash) && Objects.deepEquals(this.summaryValue, @@ -208,7 +210,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(this.version, this.roundNumber, this.epoch, + return Objects.hash(InputRecord.VERSION, this.roundNumber, this.epoch, Arrays.hashCode(this.previousHash), Arrays.hashCode(this.hash), Arrays.hashCode(this.summaryValue), this.timestamp, Arrays.hashCode(this.blockHash), @@ -217,10 +219,9 @@ public int hashCode() { @Override public String toString() { - return String.format("InputRecord{version=%s, roundNumber=%s, epoch=%s, previousHash=%s, " + return String.format("InputRecord{roundNumber=%s, epoch=%s, previousHash=%s, " + "hash=%s, summaryValue=%s, timestamp=%s, blockHash=%s, sumOfEarnedFees=%s, " + "executedTransactionsHash=%s}", - this.version, this.roundNumber, this.epoch, this.previousHash != null ? HexConverter.encode(this.previousHash) : null, diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/ShardId.java b/src/main/java/org/unicitylabs/sdk/api/bft/ShardId.java new file mode 100644 index 0000000..faea735 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/api/bft/ShardId.java @@ -0,0 +1,123 @@ +package org.unicitylabs.sdk.api.bft; + +import java.util.Arrays; +import java.util.Objects; + +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; + +public class ShardId { + + private final byte[] bits; + private final int length; + + private ShardId(byte[] bits, int length) { + this.bits = Arrays.copyOf(bits, bits.length); + this.length = length; + } + + public byte[] getBits() { + return Arrays.copyOf(this.bits, this.bits.length); + } + + public int getLength() { + return this.length; + } + + public static ShardId decode(byte[] data) { + if (data.length == 0) { + throw new CborSerializationException("Invalid ShardId encoding: empty input"); + } + + int lastByte = data[data.length - 1] & 0xff; + + for (int i = 8; i > 0; i--) { + if ((lastByte & 1) == 1) { + if (i == 1) { + return new ShardId( + Arrays.copyOfRange(data, 0, data.length - 1), + (data.length - 1) * 8 + ); + } + + byte[] bits = Arrays.copyOfRange(data, 0, data.length); + bits[data.length - 1] = (byte) (((lastByte >> 1) << (8 - i + 1)) & 0xff); + return new ShardId(bits, (data.length - 1) * 8 + i - 1); + } + + lastByte >>= 1; + } + + throw new CborSerializationException( + "Invalid ShardId encoding: last byte doesnt contain end marker"); + } + + public byte[] encode() { + int byteCount = this.length / 8; + int bitCount = this.length % 8; + byte[] result = new byte[byteCount + 1]; + System.arraycopy(this.bits, 0, result, 0, byteCount); + if (bitCount == 0) { + result[byteCount] = (byte) 0b10000000; + } else { + int v = this.bits[byteCount] & (~(0xff >> bitCount) & 0xff); + result[byteCount] = (byte) ((v | (1 << (7 - bitCount))) & 0xff); + } + return result; + } + + public int getBit(int index) { + if (index < 0 || index >= this.length) { + throw new IndexOutOfBoundsException("ShardId bit index out of bounds"); + } + return ((this.bits[index / 8] & 0xff) >> (7 - (index % 8))) & 1; + } + + public boolean isPrefixOf(byte[] data) { + int fullBytes = this.length / 8; + int remainingBits = this.length % 8; + + for (int i = 0; i < fullBytes; i++) { + if (this.bits[i] != data[i]) { + return false; + } + } + + if (remainingBits > 0) { + int mask = 0xff & (0xff << (8 - remainingBits)); + return (this.bits[fullBytes] & mask) == (data[fullBytes] & mask); + } + + return true; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ShardId)) { + return false; + } + ShardId that = (ShardId) o; + return this.length == that.length && Arrays.equals(this.bits, that.bits); + } + + @Override + public int hashCode() { + return Objects.hash(this.length, Arrays.hashCode(this.bits)); + } + + @Override + public String toString() { + int fullBytes = this.length / 8; + int remainingBits = this.length % 8; + StringBuilder result = new StringBuilder(); + for (int i = 0; i < fullBytes; i++) { + String bin = Integer.toBinaryString(this.bits[i] & 0xff); + result.append("00000000", 0, 8 - bin.length()).append(bin); + } + if (remainingBits > 0) { + String bin = Integer.toBinaryString(this.bits[fullBytes] & 0xff); + String padded = "00000000".substring(0, 8 - bin.length()) + bin; + result.append(padded, 0, remainingBits); + } + return result.toString(); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/ShardTreeCertificate.java b/src/main/java/org/unicitylabs/sdk/api/bft/ShardTreeCertificate.java index e34e5f3..16dc1a9 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/ShardTreeCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/ShardTreeCertificate.java @@ -1,102 +1,122 @@ package org.unicitylabs.sdk.api.bft; +import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; +import org.unicitylabs.sdk.serializer.cbor.CborSerializer; + import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; -import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; -import org.unicitylabs.sdk.serializer.cbor.CborSerializer; -import org.unicitylabs.sdk.util.HexConverter; /** * Shard tree certificate. */ public class ShardTreeCertificate { + public static final long CBOR_TAG = 39003; + private static final int VERSION = 1; + + private final ShardId shard; + private final List siblingHashList; + + ShardTreeCertificate(ShardId shard, List siblingHashList) { + Objects.requireNonNull(shard, "Shard cannot be null"); + Objects.requireNonNull(siblingHashList, "Sibling hash list cannot be null"); + + this.shard = shard; + this.siblingHashList = siblingHashList.stream() + .map(hash -> Arrays.copyOf(hash, hash.length)) + .collect(Collectors.toList()); + } + + public int getVersion() { + return ShardTreeCertificate.VERSION; + } + + /** + * Get shard. + * + * @return shard + */ + public ShardId getShard() { + return this.shard; + } + + /** + * Get sibling hash list. + * + * @return sibling hash list + */ + public List getSiblingHashList() { + return this.siblingHashList.stream() + .map(hash -> Arrays.copyOf(hash, hash.length)) + .collect(Collectors.toList()); + } + + /** + * Create shard tree certificate from CBOR bytes. + * + * @param bytes CBOR bytes + * @return shard tree certificate + */ + public static ShardTreeCertificate fromCbor(byte[] bytes) { + CborDeserializer.CborTag tag = CborDeserializer.decodeTag(bytes); + if (tag.getTag() != ShardTreeCertificate.CBOR_TAG) { + throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); + } + List data = CborDeserializer.decodeArray(tag.getData()); + + int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); + if (version != ShardTreeCertificate.VERSION) { + throw new CborSerializationException(String.format("Unsupported version: %s", version)); + } + + return new ShardTreeCertificate( + ShardId.decode(CborDeserializer.decodeByteString(data.get(1))), + CborDeserializer.decodeArray(data.get(2)).stream() + .map(CborDeserializer::decodeByteString) + .collect(Collectors.toList()) + ); + } + + /** + * Convert shard tree certificate to CBOR bytes. + * + * @return CBOR bytes + */ + public byte[] toCbor() { + return CborSerializer.encodeTag( + ShardTreeCertificate.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(ShardTreeCertificate.VERSION), + CborSerializer.encodeByteString(this.shard.encode()), + CborSerializer.encodeArray( + this.siblingHashList.stream() + .map(CborSerializer::encodeByteString) + .toArray(byte[][]::new) + ) + ) + ); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ShardTreeCertificate)) { + return false; + } + ShardTreeCertificate that = (ShardTreeCertificate) o; + return Objects.deepEquals(this.shard, that.shard) && Objects.equals( + this.siblingHashList, that.siblingHashList); + } + + @Override + public int hashCode() { + return Objects.hash(ShardTreeCertificate.VERSION, this.shard, this.siblingHashList); + } - private final byte[] shard; - private final List siblingHashList; - - ShardTreeCertificate(byte[] shard, List siblingHashList) { - Objects.requireNonNull(shard, "Shard cannot be null"); - Objects.requireNonNull(siblingHashList, "Sibling hash list cannot be null"); - - this.shard = Arrays.copyOf(shard, shard.length); - this.siblingHashList = siblingHashList.stream() - .map(hash -> Arrays.copyOf(hash, hash.length)) - .collect(Collectors.toList()); - } - - /** - * Get shard. - * - * @return shard - */ - public byte[] getShard() { - return Arrays.copyOf(this.shard, this.shard.length); - } - - /** - * Get sibling hash list. - * - * @return sibling hash list - */ - public List getSiblingHashList() { - return this.siblingHashList.stream() - .map(hash -> Arrays.copyOf(hash, hash.length)) - .collect(Collectors.toList()); - } - - /** - * Create shard tree certificate from CBOR bytes. - * - * @param bytes CBOR bytes - * @return shard tree certificate - */ - public static ShardTreeCertificate fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); - - return new ShardTreeCertificate( - CborDeserializer.decodeByteString(data.get(0)), - CborDeserializer.decodeArray(data.get(1)).stream() - .map(CborDeserializer::decodeByteString) - .collect(Collectors.toList()) - ); - } - - /** - * Convert shard tree certificate to CBOR bytes. - * - * @return CBOR bytes - */ - public byte[] toCbor() { - return CborSerializer.encodeArray( - CborSerializer.encodeByteString(this.shard), - CborSerializer.encodeArray( - this.siblingHashList.stream() - .map(CborSerializer::encodeByteString) - .toArray(byte[][]::new) - ) - ); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof ShardTreeCertificate)) { - return false; + @Override + public String toString() { + return String.format("ShardTreeCertificate{shard=%s, siblingHashList=%s}", + this.shard, this.siblingHashList); } - ShardTreeCertificate that = (ShardTreeCertificate) o; - return Objects.deepEquals(this.shard, that.shard) && Objects.equals( - this.siblingHashList, that.siblingHashList); - } - - @Override - public int hashCode() { - return Objects.hash(Arrays.hashCode(this.shard), this.siblingHashList); - } - - @Override - public String toString() { - return String.format("ShardTreeCertificate{shard=%s, siblingHashList=%s}", - HexConverter.encode(this.shard), this.siblingHashList); - } } diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/UnicityCertificate.java b/src/main/java/org/unicitylabs/sdk/api/bft/UnicityCertificate.java index 8d5b6cb..d29d44a 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/UnicityCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/UnicityCertificate.java @@ -1,241 +1,247 @@ package org.unicitylabs.sdk.api.bft; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.DataHasher; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; -import org.unicitylabs.sdk.serializer.cbor.CborDeserializer.CborTag; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.HexConverter; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + /** * Unicity certificate. */ public class UnicityCertificate { + public static final long CBOR_TAG = 39001; + private static final int VERSION = 1; + + private final InputRecord inputRecord; + private final byte[] technicalRecordHash; + private final byte[] shardConfigurationHash; + private final ShardTreeCertificate shardTreeCertificate; + private final UnicityTreeCertificate unicityTreeCertificate; + private final UnicitySeal unicitySeal; + + UnicityCertificate( + InputRecord inputRecord, + byte[] technicalRecordHash, + byte[] shardConfigurationHash, + ShardTreeCertificate shardTreeCertificate, + UnicityTreeCertificate unicityTreeCertificate, + UnicitySeal unicitySeal + ) { + Objects.requireNonNull(inputRecord, "Input record cannot be null"); + Objects.requireNonNull(shardConfigurationHash, "Shard configuration hash cannot be null"); + Objects.requireNonNull(shardTreeCertificate, "Shard tree certificate cannot be null"); + Objects.requireNonNull(unicityTreeCertificate, "Unicity tree certificate cannot be null"); + Objects.requireNonNull(unicitySeal, "Unicity seal cannot be null"); + + this.inputRecord = inputRecord; + this.technicalRecordHash = technicalRecordHash != null + ? Arrays.copyOf(technicalRecordHash, technicalRecordHash.length) + : null; + this.shardConfigurationHash = Arrays.copyOf( + shardConfigurationHash, + shardConfigurationHash.length + ); + this.shardTreeCertificate = shardTreeCertificate; + this.unicityTreeCertificate = unicityTreeCertificate; + this.unicitySeal = unicitySeal; + } + + /** + * Get the certificate version. + * + * @return certificate version + */ + public int getVersion() { + return UnicityCertificate.VERSION; + } + + /** + * Get the input record. + * + * @return input record + */ + public InputRecord getInputRecord() { + return this.inputRecord; + } + + /** + * Get the technical record hash. + * + * @return technical record hash + */ + public byte[] getTechnicalRecordHash() { + return this.technicalRecordHash != null + ? Arrays.copyOf(this.technicalRecordHash, this.technicalRecordHash.length) + : null; + } + + /** + * Get the shard configuration hash. + * + * @return shard configuration hash + */ + public byte[] getShardConfigurationHash() { + return Arrays.copyOf(this.shardConfigurationHash, this.shardConfigurationHash.length); + } + + /** + * Get the shard tree certificate. + * + * @return shard tree certificate + */ + public ShardTreeCertificate getShardTreeCertificate() { + return this.shardTreeCertificate; + } + + /** + * Get the unicity tree certificate. + * + * @return unicity tree certificate + */ + public UnicityTreeCertificate getUnicityTreeCertificate() { + return this.unicityTreeCertificate; + } + + /** + * Get the unicity seal. + * + * @return unicity seal + */ + public UnicitySeal getUnicitySeal() { + return this.unicitySeal; + } + + /** + * Calculate the root hash of the shard tree certificate. + * + * @param inputRecord input record + * @param technicalRecordHash technical record hash + * @param shardConfigurationHash shard configuration hash + * @param shardTreeCertificate shard tree certificate + * @return root hash + */ + public static DataHash calculateShardTreeCertificateRootHash( + InputRecord inputRecord, + byte[] technicalRecordHash, + byte[] shardConfigurationHash, + ShardTreeCertificate shardTreeCertificate + ) { + + DataHash rootHash = new DataHasher(HashAlgorithm.SHA256) + .update(inputRecord.toCbor()) + .update( + CborSerializer.encodeOptional(technicalRecordHash, CborSerializer::encodeByteString)) + .update(CborSerializer.encodeByteString(shardConfigurationHash)) + .digest(); + + ShardId shardId = shardTreeCertificate.getShard(); + List siblingHashes = shardTreeCertificate.getSiblingHashList(); + for (int i = 0; i < siblingHashes.size(); i++) { + boolean isRight = shardId.getBit(shardId.getLength() - 1 - i) == 1; + if (isRight) { + rootHash = new DataHasher(HashAlgorithm.SHA256) + .update(CborSerializer.encodeByteString(siblingHashes.get(i))) + .update(CborSerializer.encodeByteString(rootHash.getData())) + .digest(); + } else { + rootHash = new DataHasher(HashAlgorithm.SHA256) + .update(CborSerializer.encodeByteString(rootHash.getData())) + .update(CborSerializer.encodeByteString(siblingHashes.get(i))) + .digest(); + } + } + + return rootHash; + + } + + /** + * Create unicity certificate from CBOR bytes. + * + * @param bytes CBOR bytes + * @return unicity certificate + */ + public static UnicityCertificate fromCbor(byte[] bytes) { + CborDeserializer.CborTag tag = CborDeserializer.decodeTag(bytes); + if (tag.getTag() != UnicityCertificate.CBOR_TAG) { + throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); + } + List data = CborDeserializer.decodeArray(tag.getData()); + + int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); + if (version != UnicityCertificate.VERSION) { + throw new CborSerializationException(String.format("Unsupported version: %s", version)); + } + + return new UnicityCertificate( + InputRecord.fromCbor(data.get(1)), + CborDeserializer.decodeNullable(data.get(2), CborDeserializer::decodeByteString), + CborDeserializer.decodeByteString(data.get(3)), + ShardTreeCertificate.fromCbor(data.get(4)), + UnicityTreeCertificate.fromCbor(data.get(5)), + UnicitySeal.fromCbor(data.get(6)) + ); + } + + /** + * Convert unicity certificate to CBOR bytes. + * + * @return CBOR bytes + */ + public byte[] toCbor() { + return CborSerializer.encodeTag( + UnicityCertificate.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(UnicityCertificate.VERSION), + this.inputRecord.toCbor(), + CborSerializer.encodeOptional(this.technicalRecordHash, + CborSerializer::encodeByteString), + CborSerializer.encodeByteString(this.shardConfigurationHash), + this.shardTreeCertificate.toCbor(), + this.unicityTreeCertificate.toCbor(), + this.unicitySeal.toCbor() + )); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof UnicityCertificate)) { + return false; + } + UnicityCertificate that = (UnicityCertificate) o; + return Objects.equals(this.inputRecord, + that.inputRecord) && Objects.deepEquals(this.technicalRecordHash, + that.technicalRecordHash) && Objects.deepEquals(this.shardConfigurationHash, + that.shardConfigurationHash) && Objects.equals(this.shardTreeCertificate, + that.shardTreeCertificate) && Objects.equals(this.unicityTreeCertificate, + that.unicityTreeCertificate) && Objects.equals(this.unicitySeal, that.unicitySeal); + } - private final int version; - private final InputRecord inputRecord; - private final byte[] technicalRecordHash; - private final byte[] shardConfigurationHash; - private final ShardTreeCertificate shardTreeCertificate; - private final UnicityTreeCertificate unicityTreeCertificate; - private final UnicitySeal unicitySeal; - - UnicityCertificate( - int version, - InputRecord inputRecord, - byte[] technicalRecordHash, - byte[] shardConfigurationHash, - ShardTreeCertificate shardTreeCertificate, - UnicityTreeCertificate unicityTreeCertificate, - UnicitySeal unicitySeal - ) { - Objects.requireNonNull(inputRecord, "Input record cannot be null"); - Objects.requireNonNull(shardConfigurationHash, "Shard configuration hash cannot be null"); - Objects.requireNonNull(shardTreeCertificate, "Shard tree certificate cannot be null"); - Objects.requireNonNull(unicityTreeCertificate, "Unicity tree certificate cannot be null"); - Objects.requireNonNull(unicitySeal, "Unicity seal cannot be null"); - - this.version = version; - this.inputRecord = inputRecord; - this.technicalRecordHash = technicalRecordHash != null - ? Arrays.copyOf(technicalRecordHash, technicalRecordHash.length) - : null; - this.shardConfigurationHash = Arrays.copyOf( - shardConfigurationHash, - shardConfigurationHash.length - ); - this.shardTreeCertificate = shardTreeCertificate; - this.unicityTreeCertificate = unicityTreeCertificate; - this.unicitySeal = unicitySeal; - } - - /** - * Get the certificate version. - * - * @return certificate version - */ - public int getVersion() { - return this.version; - } - - /** - * Get the input record. - * - * @return input record - */ - public InputRecord getInputRecord() { - return this.inputRecord; - } - - /** - * Get the technical record hash. - * - * @return technical record hash - */ - public byte[] getTechnicalRecordHash() { - return this.technicalRecordHash != null - ? Arrays.copyOf(this.technicalRecordHash, this.technicalRecordHash.length) - : null; - } - - /** - * Get the shard configuration hash. - * - * @return shard configuration hash - */ - public byte[] getShardConfigurationHash() { - return Arrays.copyOf(this.shardConfigurationHash, this.shardConfigurationHash.length); - } - - /** - * Get the shard tree certificate. - * - * @return shard tree certificate - */ - public ShardTreeCertificate getShardTreeCertificate() { - return this.shardTreeCertificate; - } - - /** - * Get the unicity tree certificate. - * - * @return unicity tree certificate - */ - public UnicityTreeCertificate getUnicityTreeCertificate() { - return this.unicityTreeCertificate; - } - - /** - * Get the unicity seal. - * - * @return unicity seal - */ - public UnicitySeal getUnicitySeal() { - return this.unicitySeal; - } - - /** - * Calculate the root hash of the shard tree certificate. - * - * @param inputRecord input record - * @param technicalRecordHash technical record hash - * @param shardConfigurationHash shard configuration hash - * @param shardTreeCertificate shard tree certificate - * @return root hash - */ - public static DataHash calculateShardTreeCertificateRootHash( - InputRecord inputRecord, - byte[] technicalRecordHash, - byte[] shardConfigurationHash, - ShardTreeCertificate shardTreeCertificate - ) { - - DataHash rootHash = new DataHasher(HashAlgorithm.SHA256) - .update(inputRecord.toCbor()) - .update( - CborSerializer.encodeOptional(technicalRecordHash, CborSerializer::encodeByteString)) - .update(CborSerializer.encodeByteString(shardConfigurationHash)) - .digest(); - - byte[] shardId = shardTreeCertificate.getShard(); - List siblingHashes = shardTreeCertificate.getSiblingHashList(); - for (int i = 0; i < siblingHashes.size(); i++) { - boolean isRight = shardId[(shardId.length - 1) - (i / 8)] == 1; - if (isRight) { - rootHash = new DataHasher(HashAlgorithm.SHA256) - .update(siblingHashes.get(i)) - .update(rootHash.getData()) - .digest(); - } else { - rootHash = new DataHasher(HashAlgorithm.SHA256) - .update(rootHash.getData()) - .update(siblingHashes.get(i)) - .digest(); - } + @Override + public int hashCode() { + return Objects.hash(UnicityCertificate.VERSION, this.inputRecord, Arrays.hashCode(this.technicalRecordHash), + Arrays.hashCode(this.shardConfigurationHash), this.shardTreeCertificate, + this.unicityTreeCertificate, this.unicitySeal); } - return rootHash; - - } - - /** - * Create unicity certificate from CBOR bytes. - * - * @param bytes CBOR bytes - * @return unicity certificate - */ - public static UnicityCertificate fromCbor(byte[] bytes) { - CborTag tag = CborDeserializer.decodeTag(bytes); - List data = CborDeserializer.decodeArray(tag.getData()); - - return new UnicityCertificate( - CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(), - InputRecord.fromCbor(data.get(1)), - CborDeserializer.decodeNullable(data.get(2), CborDeserializer::decodeByteString), - CborDeserializer.decodeByteString(data.get(3)), - ShardTreeCertificate.fromCbor(data.get(4)), - UnicityTreeCertificate.fromCbor(data.get(5)), - UnicitySeal.fromCbor(data.get(6)) - ); - } - - /** - * Convert unicity certificate to CBOR bytes. - * - * @return CBOR bytes - */ - public byte[] toCbor() { - return CborSerializer.encodeTag( - 1007, - CborSerializer.encodeArray( - CborSerializer.encodeUnsignedInteger(this.version), - this.inputRecord.toCbor(), - CborSerializer.encodeOptional(this.technicalRecordHash, - CborSerializer::encodeByteString), - CborSerializer.encodeByteString(this.shardConfigurationHash), - this.shardTreeCertificate.toCbor(), - this.unicityTreeCertificate.toCbor(), - this.unicitySeal.toCbor() - )); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof UnicityCertificate)) { - return false; + @Override + public String toString() { + return String.format("UnicityCertificate{inputRecord=%s, technicalRecordHash=%s, " + + "shardConfigurationHash=%s, shardTreeCertificate=%s, unicityTreeCertificate=%s, " + + "unicitySeal=%s}", + this.inputRecord, + this.technicalRecordHash != null ? HexConverter.encode(this.technicalRecordHash) : null, + HexConverter.encode(this.shardConfigurationHash), + this.shardTreeCertificate, + this.unicityTreeCertificate, + this.unicitySeal + ); } - UnicityCertificate that = (UnicityCertificate) o; - return Objects.equals(this.version, that.version) && Objects.equals(this.inputRecord, - that.inputRecord) && Objects.deepEquals(this.technicalRecordHash, - that.technicalRecordHash) && Objects.deepEquals(this.shardConfigurationHash, - that.shardConfigurationHash) && Objects.equals(this.shardTreeCertificate, - that.shardTreeCertificate) && Objects.equals(this.unicityTreeCertificate, - that.unicityTreeCertificate) && Objects.equals(this.unicitySeal, that.unicitySeal); - } - - @Override - public int hashCode() { - return Objects.hash(this.version, this.inputRecord, Arrays.hashCode(this.technicalRecordHash), - Arrays.hashCode(this.shardConfigurationHash), this.shardTreeCertificate, - this.unicityTreeCertificate, this.unicitySeal); - } - - @Override - public String toString() { - return String.format("UnicityCertificate{version=%s, inputRecord=%s, technicalRecordHash=%s, " - + "shardConfigurationHash=%s, shardTreeCertificate=%s, unicityTreeCertificate=%s, " - + "unicitySeal=%s}", - this.version, - this.inputRecord, - this.technicalRecordHash != null ? HexConverter.encode(this.technicalRecordHash) : null, - HexConverter.encode(this.shardConfigurationHash), - this.shardTreeCertificate, - this.unicityTreeCertificate, - this.unicitySeal - ); - } } diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/UnicitySeal.java b/src/main/java/org/unicitylabs/sdk/api/bft/UnicitySeal.java index 15e0a8b..03cfb0c 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/UnicitySeal.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/UnicitySeal.java @@ -1,23 +1,22 @@ package org.unicitylabs.sdk.api.bft; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer.CborTag; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer.CborMap; import org.unicitylabs.sdk.util.HexConverter; +import java.util.*; +import java.util.stream.Collectors; + /** * UnicitySeal represents a seal in the Unicity BFT system, containing metadata and signatures. */ public class UnicitySeal { + public static final long CBOR_TAG = 39005; + private static final int VERSION = 1; - private final int version; private final short networkId; private final long rootChainRoundNumber; private final long epoch; @@ -27,7 +26,6 @@ public class UnicitySeal { private final LinkedHashMap signatures; UnicitySeal( - int version, short networkId, long rootChainRoundNumber, long epoch, @@ -38,7 +36,6 @@ public class UnicitySeal { ) { Objects.requireNonNull(hash, "Hash cannot be null"); - this.version = version; this.networkId = networkId; this.rootChainRoundNumber = rootChainRoundNumber; this.epoch = epoch; @@ -71,7 +68,6 @@ public class UnicitySeal { */ public UnicitySeal withSignatures(Map signatures) { return new UnicitySeal( - this.version, this.networkId, this.rootChainRoundNumber, this.epoch, @@ -82,13 +78,8 @@ public UnicitySeal withSignatures(Map signatures) { ); } - /** - * Get the version. - * - * @return version - */ public int getVersion() { - return this.version; + return UnicitySeal.VERSION; } /** @@ -178,10 +169,17 @@ public Map getSignatures() { */ public static UnicitySeal fromCbor(byte[] bytes) { CborTag tag = CborDeserializer.decodeTag(bytes); + if (tag.getTag() != UnicitySeal.CBOR_TAG) { + throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); + } List data = CborDeserializer.decodeArray(tag.getData()); + int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); + if (version != UnicitySeal.VERSION) { + throw new CborSerializationException(String.format("Unsupported version: %s", version)); + } + return new UnicitySeal( - CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(), CborDeserializer.decodeUnsignedInteger(data.get(1)).asShort(), CborDeserializer.decodeUnsignedInteger(data.get(2)).asLong(), CborDeserializer.decodeUnsignedInteger(data.get(3)).asLong(), @@ -206,9 +204,9 @@ public static UnicitySeal fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeTag( - 1001, + UnicitySeal.CBOR_TAG, CborSerializer.encodeArray( - CborSerializer.encodeUnsignedInteger(this.version), + CborSerializer.encodeUnsignedInteger(UnicitySeal.VERSION), CborSerializer.encodeUnsignedInteger(this.networkId), CborSerializer.encodeUnsignedInteger(this.rootChainRoundNumber), CborSerializer.encodeUnsignedInteger(this.epoch), @@ -240,7 +238,6 @@ public byte[] toCbor() { */ public byte[] toCborWithoutSignatures() { return new UnicitySeal( - this.version, this.networkId, this.rootChainRoundNumber, this.epoch, @@ -257,7 +254,7 @@ public boolean equals(Object o) { return false; } UnicitySeal that = (UnicitySeal) o; - return Objects.equals(this.version, that.version) && Objects.equals(this.networkId, + return Objects.equals(this.networkId, that.networkId) && Objects.equals(this.rootChainRoundNumber, that.rootChainRoundNumber) && Objects.equals(this.epoch, that.epoch) && Objects.equals(this.timestamp, that.timestamp) && Objects.deepEquals(this.previousHash, that.previousHash) @@ -267,7 +264,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(this.version, this.networkId, this.rootChainRoundNumber, this.epoch, + return Objects.hash(UnicitySeal.VERSION, this.networkId, this.rootChainRoundNumber, this.epoch, this.timestamp, Arrays.hashCode(this.previousHash), Arrays.hashCode(this.hash), this.signatures); } @@ -275,9 +272,8 @@ public int hashCode() { @Override public String toString() { return String.format( - "UnicitySeal{version=%s, networkId=%s, rootChainRoundNumber=%s, epoch=%s, timestamp=%s, " + "UnicitySeal{networkId=%s, rootChainRoundNumber=%s, epoch=%s, timestamp=%s, " + "previousHash=%s, hash=%s, signatures=%s", - this.version, this.networkId, this.rootChainRoundNumber, this.epoch, diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/UnicityTreeCertificate.java b/src/main/java/org/unicitylabs/sdk/api/bft/UnicityTreeCertificate.java index 77d44ea..6f71d25 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/UnicityTreeCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/UnicityTreeCertificate.java @@ -1,31 +1,31 @@ package org.unicitylabs.sdk.api.bft; +import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; +import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import org.unicitylabs.sdk.util.HexConverter; + import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; -import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; -import org.unicitylabs.sdk.serializer.cbor.CborDeserializer.CborTag; -import org.unicitylabs.sdk.serializer.cbor.CborSerializer; -import org.unicitylabs.sdk.util.HexConverter; /** * Unicity tree certificate. */ public class UnicityTreeCertificate { + public static final long CBOR_TAG = 39004; + private static final int VERSION = 1; - private final int version; private final int partitionIdentifier; private final List steps; UnicityTreeCertificate( - int version, int partitionIdentifier, List steps ) { Objects.requireNonNull(steps, "Steps cannot be null"); - this.version = version; this.partitionIdentifier = partitionIdentifier; this.steps = List.copyOf(steps); } @@ -36,7 +36,7 @@ public class UnicityTreeCertificate { * @return version */ public int getVersion() { - return this.version; + return UnicityTreeCertificate.VERSION; } /** @@ -64,11 +64,18 @@ public List getSteps() { * @return certificate */ public static UnicityTreeCertificate fromCbor(byte[] bytes) { - CborTag tag = CborDeserializer.decodeTag(bytes); + CborDeserializer.CborTag tag = CborDeserializer.decodeTag(bytes); + if (tag.getTag() != UnicityTreeCertificate.CBOR_TAG) { + throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); + } List data = CborDeserializer.decodeArray(tag.getData()); + int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); + if (version != UnicityTreeCertificate.VERSION) { + throw new CborSerializationException(String.format("Unsupported version: %s", version)); + } + return new UnicityTreeCertificate( - CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(), CborDeserializer.decodeUnsignedInteger(data.get(1)).asInt(), CborDeserializer.decodeArray(data.get(2)).stream() .map(HashStep::fromCbor) @@ -83,9 +90,9 @@ public static UnicityTreeCertificate fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeTag( - 1014, + UnicityTreeCertificate.CBOR_TAG, CborSerializer.encodeArray( - CborSerializer.encodeUnsignedInteger(this.version), + CborSerializer.encodeUnsignedInteger(UnicityTreeCertificate.VERSION), CborSerializer.encodeUnsignedInteger(this.partitionIdentifier), CborSerializer.encodeArray(this.steps.stream() .map(HashStep::toCbor) @@ -99,20 +106,19 @@ public boolean equals(Object o) { return false; } UnicityTreeCertificate that = (UnicityTreeCertificate) o; - return Objects.equals(this.version, that.version) && Objects.equals( + return Objects.equals( this.partitionIdentifier, that.partitionIdentifier) && Objects.equals(this.steps, that.steps); } @Override public int hashCode() { - return Objects.hash(this.version, this.partitionIdentifier, this.steps); + return Objects.hash(UnicityTreeCertificate.VERSION, this.partitionIdentifier, this.steps); } @Override public String toString() { - return String.format("UnicityTreeCertificate{version=%s, partitionIdentifier=%s, steps=%s", - this.version, this.partitionIdentifier, this.steps); + return String.format("UnicityTreeCertificate{partitionIdentifier=%s, steps=%s", this.partitionIdentifier, this.steps); } /** diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/verification/UnicityCertificateVerification.java b/src/main/java/org/unicitylabs/sdk/api/bft/verification/UnicityCertificateVerification.java index 2ec2f4e..1861a58 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/verification/UnicityCertificateVerification.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/verification/UnicityCertificateVerification.java @@ -1,26 +1,20 @@ package org.unicitylabs.sdk.api.bft.verification; -import java.util.ArrayList; import org.unicitylabs.sdk.api.InclusionProof; import org.unicitylabs.sdk.api.bft.RootTrustBase; -import org.unicitylabs.sdk.api.bft.verification.rule.InputRecordCurrentHashVerificationRule; import org.unicitylabs.sdk.api.bft.verification.rule.UnicitySealHashMatchesWithRootHashRule; import org.unicitylabs.sdk.api.bft.verification.rule.UnicitySealQuorumSignaturesVerificationRule; import org.unicitylabs.sdk.util.verification.VerificationResult; import org.unicitylabs.sdk.util.verification.VerificationStatus; +import java.util.ArrayList; + public class UnicityCertificateVerification { public static UnicityCertificateVerificationResult verify(RootTrustBase trustBase, InclusionProof inclusionProof) { ArrayList> results = new ArrayList>(); - VerificationResult result = InputRecordCurrentHashVerificationRule.verify(inclusionProof); - results.add(result); - if (result.getStatus() != VerificationStatus.OK) { - return UnicityCertificateVerificationResult.fail(results); - } - - result = UnicitySealHashMatchesWithRootHashRule.verify(inclusionProof.getUnicityCertificate()); + VerificationResult result = UnicitySealHashMatchesWithRootHashRule.verify(inclusionProof.getUnicityCertificate()); results.add(result); if (result.getStatus() != VerificationStatus.OK) { return UnicityCertificateVerificationResult.fail(results); diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/verification/rule/InputRecordCurrentHashVerificationRule.java b/src/main/java/org/unicitylabs/sdk/api/bft/verification/rule/InputRecordCurrentHashVerificationRule.java deleted file mode 100644 index 7335c07..0000000 --- a/src/main/java/org/unicitylabs/sdk/api/bft/verification/rule/InputRecordCurrentHashVerificationRule.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.unicitylabs.sdk.api.bft.verification.rule; - -import org.unicitylabs.sdk.api.InclusionProof; -import org.unicitylabs.sdk.crypto.hash.DataHash; -import org.unicitylabs.sdk.util.verification.VerificationResult; -import org.unicitylabs.sdk.util.verification.VerificationStatus; - -/** - * Input record current hash verification rule. - */ -public class InputRecordCurrentHashVerificationRule { - - public static VerificationResult verify(InclusionProof inclusionProof) { - if (inclusionProof.getMerkleTreePath().getRootHash().equals( - DataHash.fromImprint(inclusionProof.getUnicityCertificate().getInputRecord().getHash()))) { - return new VerificationResult<>("InputRecordCurrentHashVerificationRule", - VerificationStatus.OK); - } - - return new VerificationResult<>("InputRecordCurrentHashVerificationRule", - VerificationStatus.FAIL); - } - -} diff --git a/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java index 1cb8b02..074934c 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java @@ -1,85 +1,95 @@ package org.unicitylabs.sdk.predicate; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.HexConverter; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + public class EncodedPredicate implements Predicate { + public static final long CBOR_TAG = 39032; - private final PredicateEngine engine; - private final byte[] code; - private final byte[] parameters; + private final PredicateEngine engine; + private final byte[] code; + private final byte[] parameters; - private EncodedPredicate(PredicateEngine engine, byte[] code, byte[] parameters) { - this.engine = engine; - this.code = code; - this.parameters = parameters; - } + private EncodedPredicate(PredicateEngine engine, byte[] code, byte[] parameters) { + this.engine = engine; + this.code = code; + this.parameters = parameters; + } - @Override - public PredicateEngine getEngine() { - return this.engine; - } + @Override + public PredicateEngine getEngine() { + return this.engine; + } - public static EncodedPredicate fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); - PredicateEngine engine = PredicateEngine.fromId( - CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt()); + public static EncodedPredicate fromCbor(byte[] bytes) { + CborDeserializer.CborTag tag = CborDeserializer.decodeTag(bytes); + if (tag.getTag() != EncodedPredicate.CBOR_TAG) { + throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); + } + List data = CborDeserializer.decodeArray(tag.getData()); + PredicateEngine engine = PredicateEngine.fromId( + CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt()); - return new EncodedPredicate( - engine, - CborDeserializer.decodeByteString(data.get(1)), - CborDeserializer.decodeByteString(data.get(2)) - ); - } + return new EncodedPredicate( + engine, + CborDeserializer.decodeByteString(data.get(1)), + CborDeserializer.decodeByteString(data.get(2)) + ); + } - public static EncodedPredicate fromPredicate(Predicate predicate) { - return new EncodedPredicate(predicate.getEngine(), predicate.encodeCode(), - predicate.encodeParameters()); - } + public static EncodedPredicate fromPredicate(Predicate predicate) { + return new EncodedPredicate(predicate.getEngine(), predicate.encodeCode(), + predicate.encodeParameters()); + } - @Override - public byte[] encodeCode() { - return Arrays.copyOf(this.code, this.code.length); - } + @Override + public byte[] encodeCode() { + return Arrays.copyOf(this.code, this.code.length); + } - @Override - public byte[] encodeParameters() { - return Arrays.copyOf(this.parameters, this.parameters.length); - } + @Override + public byte[] encodeParameters() { + return Arrays.copyOf(this.parameters, this.parameters.length); + } - public byte[] toCbor() { - return CborSerializer.encodeArray( - CborSerializer.encodeUnsignedInteger(this.engine.getId()), - CborSerializer.encodeByteString(this.code), - CborSerializer.encodeByteString(this.parameters) - ); - } + public byte[] toCbor() { + return CborSerializer.encodeTag( + EncodedPredicate.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(this.engine.getId()), + CborSerializer.encodeByteString(this.code), + CborSerializer.encodeByteString(this.parameters) + ) + ); + } - @Override - public boolean equals(Object o) { - if (!(o instanceof EncodedPredicate)) { - return false; + @Override + public boolean equals(Object o) { + if (!(o instanceof EncodedPredicate)) { + return false; + } + EncodedPredicate that = (EncodedPredicate) o; + return this.engine == that.engine && Arrays.equals(this.code, that.code) && Arrays.equals( + this.parameters, that.parameters); } - EncodedPredicate that = (EncodedPredicate) o; - return this.engine == that.engine && Arrays.equals(this.code, that.code) && Arrays.equals( - this.parameters, that.parameters); - } - @Override - public int hashCode() { - return Objects.hash(this.engine, Arrays.hashCode(this.code), Arrays.hashCode(this.parameters)); - } + @Override + public int hashCode() { + return Objects.hash(this.engine, Arrays.hashCode(this.code), Arrays.hashCode(this.parameters)); + } - @Override - public String toString() { - return "EncodedPredicate{" + - "engine=" + this.engine + - ", code=" + HexConverter.encode(this.code) + - ", parameters=" + HexConverter.encode(this.parameters) + - '}'; - } + @Override + public String toString() { + return "EncodedPredicate{" + + "engine=" + this.engine + + ", code=" + HexConverter.encode(this.code) + + ", parameters=" + HexConverter.encode(this.parameters) + + '}'; + } } diff --git a/src/main/java/org/unicitylabs/sdk/mtree/BranchExistsException.java b/src/main/java/org/unicitylabs/sdk/smt/BranchExistsException.java similarity index 90% rename from src/main/java/org/unicitylabs/sdk/mtree/BranchExistsException.java rename to src/main/java/org/unicitylabs/sdk/smt/BranchExistsException.java index b5694b7..c6c87aa 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/BranchExistsException.java +++ b/src/main/java/org/unicitylabs/sdk/smt/BranchExistsException.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree; +package org.unicitylabs.sdk.smt; /** * Exception thrown when a branch already exists at a given path in the merkle tree. diff --git a/src/main/java/org/unicitylabs/sdk/mtree/CommonPath.java b/src/main/java/org/unicitylabs/sdk/smt/CommonPath.java similarity index 97% rename from src/main/java/org/unicitylabs/sdk/mtree/CommonPath.java rename to src/main/java/org/unicitylabs/sdk/smt/CommonPath.java index 177438b..f92a718 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/CommonPath.java +++ b/src/main/java/org/unicitylabs/sdk/smt/CommonPath.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree; +package org.unicitylabs.sdk.smt; import java.math.BigInteger; import java.util.Objects; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/LeafOutOfBoundsException.java b/src/main/java/org/unicitylabs/sdk/smt/LeafOutOfBoundsException.java similarity index 87% rename from src/main/java/org/unicitylabs/sdk/mtree/LeafOutOfBoundsException.java rename to src/main/java/org/unicitylabs/sdk/smt/LeafOutOfBoundsException.java index d392a44..1516ae5 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/LeafOutOfBoundsException.java +++ b/src/main/java/org/unicitylabs/sdk/smt/LeafOutOfBoundsException.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree; +package org.unicitylabs.sdk.smt; /** * Exception when leaf is out of bounds. diff --git a/src/main/java/org/unicitylabs/sdk/mtree/MerkleTreePathVerificationResult.java b/src/main/java/org/unicitylabs/sdk/smt/MerkleTreePathVerificationResult.java similarity index 97% rename from src/main/java/org/unicitylabs/sdk/mtree/MerkleTreePathVerificationResult.java rename to src/main/java/org/unicitylabs/sdk/smt/MerkleTreePathVerificationResult.java index 2613d0a..ee22e73 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/MerkleTreePathVerificationResult.java +++ b/src/main/java/org/unicitylabs/sdk/smt/MerkleTreePathVerificationResult.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree; +package org.unicitylabs.sdk.smt; import java.util.Objects; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/plain/Branch.java b/src/main/java/org/unicitylabs/sdk/smt/plain/Branch.java similarity index 91% rename from src/main/java/org/unicitylabs/sdk/mtree/plain/Branch.java rename to src/main/java/org/unicitylabs/sdk/smt/plain/Branch.java index c5f1626..133b813 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/Branch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/Branch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; import java.math.BigInteger; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/plain/FinalizedBranch.java b/src/main/java/org/unicitylabs/sdk/smt/plain/FinalizedBranch.java similarity index 85% rename from src/main/java/org/unicitylabs/sdk/mtree/plain/FinalizedBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/plain/FinalizedBranch.java index b5770bf..6cdd85c 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/FinalizedBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/FinalizedBranch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; import org.unicitylabs.sdk.crypto.hash.DataHash; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/plain/FinalizedLeafBranch.java b/src/main/java/org/unicitylabs/sdk/smt/plain/FinalizedLeafBranch.java similarity index 98% rename from src/main/java/org/unicitylabs/sdk/mtree/plain/FinalizedLeafBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/plain/FinalizedLeafBranch.java index a802fa0..0ef9d4e 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/FinalizedLeafBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/FinalizedLeafBranch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; import java.math.BigInteger; import java.util.Arrays; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/plain/FinalizedNodeBranch.java b/src/main/java/org/unicitylabs/sdk/smt/plain/FinalizedNodeBranch.java similarity index 98% rename from src/main/java/org/unicitylabs/sdk/mtree/plain/FinalizedNodeBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/plain/FinalizedNodeBranch.java index de571f6..136e76f 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/FinalizedNodeBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/FinalizedNodeBranch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; import java.math.BigInteger; import java.util.Objects; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/plain/LeafBranch.java b/src/main/java/org/unicitylabs/sdk/smt/plain/LeafBranch.java similarity index 83% rename from src/main/java/org/unicitylabs/sdk/mtree/plain/LeafBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/plain/LeafBranch.java index f8650e5..c5752d6 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/LeafBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/LeafBranch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; /** * Leaf branch in a sparse merkle tree. diff --git a/src/main/java/org/unicitylabs/sdk/mtree/plain/NodeBranch.java b/src/main/java/org/unicitylabs/sdk/smt/plain/NodeBranch.java similarity index 86% rename from src/main/java/org/unicitylabs/sdk/mtree/plain/NodeBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/plain/NodeBranch.java index 2c32323..5e98afc 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/NodeBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/NodeBranch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; /** * Node branch in merkle tree. diff --git a/src/main/java/org/unicitylabs/sdk/mtree/plain/PendingLeafBranch.java b/src/main/java/org/unicitylabs/sdk/smt/plain/PendingLeafBranch.java similarity index 96% rename from src/main/java/org/unicitylabs/sdk/mtree/plain/PendingLeafBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/plain/PendingLeafBranch.java index 3e6a2aa..a4987dd 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/PendingLeafBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/PendingLeafBranch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; import java.math.BigInteger; import java.util.Arrays; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/plain/PendingNodeBranch.java b/src/main/java/org/unicitylabs/sdk/smt/plain/PendingNodeBranch.java similarity index 97% rename from src/main/java/org/unicitylabs/sdk/mtree/plain/PendingNodeBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/plain/PendingNodeBranch.java index 537cf37..ef555d9 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/PendingNodeBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/PendingNodeBranch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; import java.math.BigInteger; import java.util.Objects; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTree.java b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTree.java similarity index 95% rename from src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTree.java rename to src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTree.java index a3378b6..317400d 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTree.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTree.java @@ -1,11 +1,11 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; import java.math.BigInteger; import java.util.Arrays; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.unicitylabs.sdk.mtree.BranchExistsException; -import org.unicitylabs.sdk.mtree.CommonPath; -import org.unicitylabs.sdk.mtree.LeafOutOfBoundsException; +import org.unicitylabs.sdk.smt.BranchExistsException; +import org.unicitylabs.sdk.smt.CommonPath; +import org.unicitylabs.sdk.smt.LeafOutOfBoundsException; /** * Sparse Merkle tree implementation. diff --git a/src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreePath.java b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePath.java similarity index 97% rename from src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreePath.java rename to src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePath.java index c8ad150..5f444b7 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreePath.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePath.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; import java.math.BigInteger; import java.util.List; @@ -6,7 +6,7 @@ import java.util.stream.Collectors; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.DataHasher; -import org.unicitylabs.sdk.mtree.MerkleTreePathVerificationResult; +import org.unicitylabs.sdk.smt.MerkleTreePathVerificationResult; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.BigIntegerConverter; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreePathStep.java b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePathStep.java similarity index 98% rename from src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreePathStep.java rename to src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePathStep.java index 9531d30..2bcc53c 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreePathStep.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePathStep.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; import java.math.BigInteger; import java.util.Arrays; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeRootNode.java b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreeRootNode.java similarity index 97% rename from src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeRootNode.java rename to src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreeRootNode.java index eef37ed..c1ec36b 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeRootNode.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreeRootNode.java @@ -1,12 +1,13 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; + +import org.unicitylabs.sdk.crypto.hash.DataHash; +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; +import org.unicitylabs.sdk.smt.CommonPath; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import org.unicitylabs.sdk.crypto.hash.DataHash; -import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.unicitylabs.sdk.mtree.CommonPath; /** * Sparse merkle tree state for given root. diff --git a/src/main/java/org/unicitylabs/sdk/smt/radix/Branch.java b/src/main/java/org/unicitylabs/sdk/smt/radix/Branch.java new file mode 100644 index 0000000..0b3beb0 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/smt/radix/Branch.java @@ -0,0 +1,26 @@ +package org.unicitylabs.sdk.smt.radix; + +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; + +import java.math.BigInteger; + +/** + * Sparse merkle tree branch structure. + */ +public interface Branch { + + /** + * Get branch path from leaf to root. + * + * @return path + */ + BigInteger getPath(); + + /** + * Finalize current branch. + * + * @param hashAlgorithm hash algorithm + * @return finalized branch + */ + FinalizedBranch finalize(HashAlgorithm hashAlgorithm); +} diff --git a/src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedBranch.java b/src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedBranch.java new file mode 100644 index 0000000..603090b --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedBranch.java @@ -0,0 +1,16 @@ +package org.unicitylabs.sdk.smt.radix; + +import org.unicitylabs.sdk.crypto.hash.DataHash; + +/** + * Finalized branch in sparse merkle tree. + */ +public interface FinalizedBranch extends Branch { + + /** + * Get hash of the branch. + * + * @return hash + */ + DataHash getHash(); +} diff --git a/src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedLeafBranch.java b/src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedLeafBranch.java new file mode 100644 index 0000000..7126ba6 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedLeafBranch.java @@ -0,0 +1,80 @@ +package org.unicitylabs.sdk.smt.radix; + +import org.unicitylabs.sdk.crypto.hash.DataHash; +import org.unicitylabs.sdk.crypto.hash.DataHasher; +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; + +public class FinalizedLeafBranch implements LeafBranch, FinalizedBranch { + + private final BigInteger path; + private final byte[] key; + private final byte[] value; + private final DataHash hash; + + private FinalizedLeafBranch(BigInteger path, byte[] key, byte[] value, DataHash hash) { + this.path = path; + this.key = Arrays.copyOf(key, key.length); + this.value = Arrays.copyOf(value, value.length); + this.hash = hash; + } + + @Override + public BigInteger getPath() { + return this.path; + } + + @Override + public byte[] getKey() { + return Arrays.copyOf(this.key, this.key.length); + } + + @Override + public byte[] getValue() { + return Arrays.copyOf(this.value, this.value.length); + } + + @Override + public DataHash getHash() { + return this.hash; + } + + @Override + public FinalizedLeafBranch finalize(HashAlgorithm hashAlgorithm) { + return this; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof FinalizedLeafBranch)) { + return false; + } + FinalizedLeafBranch that = (FinalizedLeafBranch) o; + return Objects.equals(this.path, that.path) && Arrays.equals(this.value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(this.path, Arrays.hashCode(this.value)); + } + + public static FinalizedLeafBranch fromPendingLeaf( + HashAlgorithm hashAlgorithm, + PendingLeafBranch leaf + ) { + byte[] key = leaf.getKey(); + byte[] value = leaf.getValue(); + + + DataHash hash = new DataHasher(hashAlgorithm) + .update(new byte[]{0x00}) + .update(key) + .update(value) + .digest(); + + return new FinalizedLeafBranch(leaf.getPath(), key, value, hash); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedNodeBranch.java b/src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedNodeBranch.java new file mode 100644 index 0000000..a5e944f --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedNodeBranch.java @@ -0,0 +1,88 @@ +package org.unicitylabs.sdk.smt.radix; + +import org.unicitylabs.sdk.crypto.hash.DataHash; +import org.unicitylabs.sdk.crypto.hash.DataHasher; +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; +import org.unicitylabs.sdk.util.BitString; +import org.unicitylabs.sdk.util.LongConverter; + +import java.math.BigInteger; +import java.util.Arrays; + +public class FinalizedNodeBranch implements NodeBranch, FinalizedBranch { + private final BigInteger path; + private final int depth; + private final FinalizedBranch left; + private final FinalizedBranch right; + private final DataHash hash; + + private FinalizedNodeBranch( + BigInteger path, + int depth, + FinalizedBranch left, + FinalizedBranch right, + DataHash hash + ) { + this.path = path; + this.depth = depth; + this.left = left; + this.right = right; + this.hash = hash; + } + + @Override + public BigInteger getPath() { + return this.path; + } + + @Override + public int getDepth() { + return this.depth; + } + + @Override + public FinalizedBranch getLeft() { + return this.left; + } + + @Override + public FinalizedBranch getRight() { + return this.right; + } + + @Override + public DataHash getHash() { + return this.hash; + } + + public static FinalizedNodeBranch fromPendingNode(HashAlgorithm hashAlgorithm, PendingNodeBranch node) { + FinalizedBranch left = node.getLeft() != null ? node.getLeft().finalize(hashAlgorithm) : null; + FinalizedBranch right = node.getRight() != null ? node.getRight().finalize(hashAlgorithm) : null; + + if (left == null && right == null) { + return new FinalizedNodeBranch(node.getPath(), node.getDepth(), left, right, new DataHash(HashAlgorithm.SHA256, new byte[32])); + } + + if (left != null && right == null) { + return new FinalizedNodeBranch(node.getPath(), node.getDepth(), left, right, left.getHash()); + } + + if (left == null) { + return new FinalizedNodeBranch(node.getPath(), node.getDepth(), left, right, right.getHash()); + } + + DataHash hash = new DataHasher(hashAlgorithm) + .update(new byte[]{0x01}) + .update(LongConverter.encode(node.getDepth())) + .update(left.getHash().getData()) + .update(right.getHash().getData()) + .digest(); + + return new FinalizedNodeBranch(node.getPath(), node.getDepth(), left, right, hash); + } + + @Override + public FinalizedNodeBranch finalize(HashAlgorithm hashAlgorithm) { + return this; + } +} diff --git a/src/main/java/org/unicitylabs/sdk/smt/radix/LeafBranch.java b/src/main/java/org/unicitylabs/sdk/smt/radix/LeafBranch.java new file mode 100644 index 0000000..4cda9b5 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/smt/radix/LeafBranch.java @@ -0,0 +1,16 @@ +package org.unicitylabs.sdk.smt.radix; + +/** + * Leaf branch in a sparse merkle tree. + */ +public interface LeafBranch extends Branch { + + byte[] getKey(); + + /** + * Get value stored in the leaf. + * + * @return value stored in the leaf + */ + byte[] getValue(); +} diff --git a/src/main/java/org/unicitylabs/sdk/smt/radix/NodeBranch.java b/src/main/java/org/unicitylabs/sdk/smt/radix/NodeBranch.java new file mode 100644 index 0000000..109130f --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/smt/radix/NodeBranch.java @@ -0,0 +1,23 @@ +package org.unicitylabs.sdk.smt.radix; + +/** + * Node branch in merkle tree. + */ +public interface NodeBranch extends Branch { + + int getDepth(); + + /** + * Get left branch. + * + * @return left branch + */ + Branch getLeft(); + + /** + * Get right branch. + * + * @return right branch + */ + Branch getRight(); +} diff --git a/src/main/java/org/unicitylabs/sdk/smt/radix/PendingLeafBranch.java b/src/main/java/org/unicitylabs/sdk/smt/radix/PendingLeafBranch.java new file mode 100644 index 0000000..ef96f74 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/smt/radix/PendingLeafBranch.java @@ -0,0 +1,54 @@ +package org.unicitylabs.sdk.smt.radix; + +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; + +public class PendingLeafBranch implements LeafBranch { + private final BigInteger path; + private final byte[] key; + private final byte[] value; + + public PendingLeafBranch(BigInteger path, byte[] key, byte[] value) { + this.path = path; + this.key = Arrays.copyOf(key, key.length); + this.value = Arrays.copyOf(value, value.length); + } + + @Override + public BigInteger getPath() { + return this.path; + } + + @Override + public byte[] getKey() { + return Arrays.copyOf(this.key, this.key.length); + } + + @Override + public byte[] getValue() { + return Arrays.copyOf(this.value, this.value.length); + } + + @Override + public FinalizedLeafBranch finalize(HashAlgorithm hashAlgorithm) { + return FinalizedLeafBranch.fromPendingLeaf(hashAlgorithm, this); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof PendingLeafBranch)) { + return false; + } + + PendingLeafBranch that = (PendingLeafBranch) o; + return Objects.equals(this.path, that.path) && Objects.deepEquals(this.value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(this.path, Arrays.hashCode(this.value)); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/smt/radix/PendingNodeBranch.java b/src/main/java/org/unicitylabs/sdk/smt/radix/PendingNodeBranch.java new file mode 100644 index 0000000..6c34f49 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/smt/radix/PendingNodeBranch.java @@ -0,0 +1,47 @@ +package org.unicitylabs.sdk.smt.radix; + +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; + +import java.math.BigInteger; + +public class PendingNodeBranch implements NodeBranch { + private final BigInteger path; + private final int depth; + private final Branch left; + private final Branch right; + + public PendingNodeBranch(BigInteger path, int depth, Branch left, Branch right) { + this.path = path; + this.depth = depth; + this.left = left; + this.right = right; + } + + @Override + public BigInteger getPath() { + return this.path; + } + + @Override + public int getDepth() { + return this.depth; + } + + @Override + public Branch getLeft() { + return this.left; + } + + @Override + public Branch getRight() { + return this.right; + } + + @Override + public FinalizedNodeBranch finalize(HashAlgorithm hashAlgorithm) { + return FinalizedNodeBranch.fromPendingNode( + hashAlgorithm, + this + ); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/smt/radix/SparseMerkleTree.java b/src/main/java/org/unicitylabs/sdk/smt/radix/SparseMerkleTree.java new file mode 100644 index 0000000..ca096c0 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/smt/radix/SparseMerkleTree.java @@ -0,0 +1,126 @@ +package org.unicitylabs.sdk.smt.radix; + +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; +import org.unicitylabs.sdk.smt.BranchExistsException; +import org.unicitylabs.sdk.smt.CommonPath; +import org.unicitylabs.sdk.smt.LeafOutOfBoundsException; +import org.unicitylabs.sdk.util.BitString; + +import java.math.BigInteger; + +/** + * Sparse Merkle tree implementation. + */ +public class SparseMerkleTree { + + private Branch left = null; + private Branch right = null; + + private final HashAlgorithm hashAlgorithm; + + /** + * Create sparse Merkle tree with given hash algorithm. + * + * @param hashAlgorithm hash algorithm + */ + public SparseMerkleTree(HashAlgorithm hashAlgorithm) { + this.hashAlgorithm = hashAlgorithm; + } + + /** + * Add leaf to the tree at given path. + * + * @param key path of the leaf + * @param data data of the leaf + * @throws BranchExistsException if branch already exists at the path + * @throws LeafOutOfBoundsException if leaf is out of bounds + * @throws IllegalArgumentException if path is less than 1 + */ + public synchronized void addLeaf(byte[] key, byte[] data) + throws BranchExistsException, LeafOutOfBoundsException { + BigInteger path = BitString.fromBytesReversedLSB(key).toBigInteger(); + + if (path.compareTo(BigInteger.ONE) <= 0) { + throw new IllegalArgumentException("Path must be greater than 0"); + } + + boolean isRight = path.testBit(0); + Branch branch = isRight ? this.right : this.left; + Branch result = branch != null + ? SparseMerkleTree.buildTree(branch, path, 0, key, data) + : new PendingLeafBranch(path, key, data); + + if (isRight) { + this.right = result; + } else { + this.left = result; + } + } + + /** + * Calculate root of the tree. + * + * @return root node and its state + */ + public synchronized FinalizedNodeBranch calculateRoot() { + FinalizedBranch left = this.left != null ? this.left.finalize(this.hashAlgorithm) : null; + FinalizedBranch right = this.right != null ? this.right.finalize(this.hashAlgorithm) : null; + this.left = left; + this.right = right; + + return new PendingNodeBranch(BigInteger.ONE, 0, left, right).finalize(hashAlgorithm); + } + + private static Branch buildTree(Branch branch, BigInteger remainingPath, int depth, byte[] key, + byte[] value) throws BranchExistsException, LeafOutOfBoundsException { + CommonPath commonPath = CommonPath.create(remainingPath, branch.getPath()); + int commonPathLength = commonPath.getLength(); + boolean isRight = remainingPath.shiftRight(commonPathLength).testBit(0); + + if (commonPath.getPath().equals(remainingPath)) { + throw new BranchExistsException(); + } + + if (branch instanceof LeafBranch) { + if (commonPath.getPath().equals(branch.getPath())) { + throw new LeafOutOfBoundsException(); + } + + LeafBranch leafBranch = (LeafBranch) branch; + + LeafBranch oldBranch = new PendingLeafBranch( + branch.getPath().shiftRight(commonPathLength), leafBranch.getKey(), + leafBranch.getValue()); + LeafBranch newBranch = new PendingLeafBranch( + remainingPath.shiftRight(commonPathLength), key, value); + return new PendingNodeBranch(commonPath.getPath(), depth + commonPathLength, + isRight ? oldBranch : newBranch, isRight ? newBranch : oldBranch); + } + + NodeBranch nodeBranch = (NodeBranch) branch; + + // if node branch is split in the middle + if (commonPath.getPath().compareTo(branch.getPath()) < 0) { + LeafBranch newBranch = new PendingLeafBranch( + remainingPath.shiftRight(commonPathLength), key, value); + NodeBranch oldBranch = new PendingNodeBranch( + branch.getPath().shiftRight(commonPathLength), nodeBranch.getDepth(), + nodeBranch.getLeft(), nodeBranch.getRight()); + return new PendingNodeBranch(commonPath.getPath(), depth + commonPathLength, + isRight ? oldBranch : newBranch, isRight ? newBranch : oldBranch); + } + + if (isRight) { + return new PendingNodeBranch(nodeBranch.getPath(), nodeBranch.getDepth(), + nodeBranch.getLeft(), + SparseMerkleTree.buildTree(nodeBranch.getRight(), + remainingPath.shiftRight(commonPathLength), depth + commonPathLength, key, value)); + } + + return new PendingNodeBranch(nodeBranch.getPath(), nodeBranch.getDepth(), + SparseMerkleTree.buildTree(nodeBranch.getLeft(), + remainingPath.shiftRight(commonPathLength), depth + commonPathLength, key, value), + nodeBranch.getRight()); + } +} + diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/Branch.java b/src/main/java/org/unicitylabs/sdk/smt/sum/Branch.java similarity index 91% rename from src/main/java/org/unicitylabs/sdk/mtree/sum/Branch.java rename to src/main/java/org/unicitylabs/sdk/smt/sum/Branch.java index 6e0aa7a..5674bbf 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/Branch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/Branch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; import java.math.BigInteger; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/FinalizedBranch.java b/src/main/java/org/unicitylabs/sdk/smt/sum/FinalizedBranch.java similarity index 90% rename from src/main/java/org/unicitylabs/sdk/mtree/sum/FinalizedBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/sum/FinalizedBranch.java index 0df2cba..4f513e6 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/FinalizedBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/FinalizedBranch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; import java.math.BigInteger; import org.unicitylabs.sdk.crypto.hash.DataHash; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/FinalizedLeafBranch.java b/src/main/java/org/unicitylabs/sdk/smt/sum/FinalizedLeafBranch.java similarity index 95% rename from src/main/java/org/unicitylabs/sdk/mtree/sum/FinalizedLeafBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/sum/FinalizedLeafBranch.java index 2c7f772..5740918 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/FinalizedLeafBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/FinalizedLeafBranch.java @@ -1,11 +1,11 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; import java.math.BigInteger; import java.util.Objects; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.DataHasher; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTree.LeafValue; +import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTree.LeafValue; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.BigIntegerConverter; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/FinalizedNodeBranch.java b/src/main/java/org/unicitylabs/sdk/smt/sum/FinalizedNodeBranch.java similarity index 98% rename from src/main/java/org/unicitylabs/sdk/mtree/sum/FinalizedNodeBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/sum/FinalizedNodeBranch.java index b8fd936..165dd0e 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/FinalizedNodeBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/FinalizedNodeBranch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; import java.math.BigInteger; import java.util.Objects; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/LeafBranch.java b/src/main/java/org/unicitylabs/sdk/smt/sum/LeafBranch.java similarity index 66% rename from src/main/java/org/unicitylabs/sdk/mtree/sum/LeafBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/sum/LeafBranch.java index b57e43a..29e7d9a 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/LeafBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/LeafBranch.java @@ -1,6 +1,6 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; -import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTree.LeafValue; +import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTree.LeafValue; /** * Leaf branch in a sparse merkle sum tree. diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/NodeBranch.java b/src/main/java/org/unicitylabs/sdk/smt/sum/NodeBranch.java similarity index 87% rename from src/main/java/org/unicitylabs/sdk/mtree/sum/NodeBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/sum/NodeBranch.java index 1ba9a2b..357a968 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/NodeBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/NodeBranch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; /** * Node branch in sparse merkle sum tree. diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/PendingLeafBranch.java b/src/main/java/org/unicitylabs/sdk/smt/sum/PendingLeafBranch.java similarity index 91% rename from src/main/java/org/unicitylabs/sdk/mtree/sum/PendingLeafBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/sum/PendingLeafBranch.java index 6b72cda..5aa166e 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/PendingLeafBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/PendingLeafBranch.java @@ -1,9 +1,9 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; import java.math.BigInteger; import java.util.Objects; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTree.LeafValue; +import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTree.LeafValue; /** * Pending leaf branch in a sparse merkle sum tree. diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/PendingNodeBranch.java b/src/main/java/org/unicitylabs/sdk/smt/sum/PendingNodeBranch.java similarity index 97% rename from src/main/java/org/unicitylabs/sdk/mtree/sum/PendingNodeBranch.java rename to src/main/java/org/unicitylabs/sdk/smt/sum/PendingNodeBranch.java index 4dab3c1..a6370de 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/PendingNodeBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/PendingNodeBranch.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; import java.math.BigInteger; import java.util.Objects; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTree.java b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTree.java similarity index 96% rename from src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTree.java rename to src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTree.java index 8b308ff..6fb26e8 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTree.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTree.java @@ -1,12 +1,12 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; import java.math.BigInteger; import java.util.Arrays; import java.util.Objects; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.unicitylabs.sdk.mtree.BranchExistsException; -import org.unicitylabs.sdk.mtree.CommonPath; -import org.unicitylabs.sdk.mtree.LeafOutOfBoundsException; +import org.unicitylabs.sdk.smt.BranchExistsException; +import org.unicitylabs.sdk.smt.CommonPath; +import org.unicitylabs.sdk.smt.LeafOutOfBoundsException; /** * Sparse Merkle Sum Tree implementation. diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreePath.java b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePath.java similarity index 98% rename from src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreePath.java rename to src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePath.java index e4cfa2c..cc26736 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreePath.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePath.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; import java.math.BigInteger; import java.util.List; @@ -6,7 +6,7 @@ import java.util.stream.Collectors; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.DataHasher; -import org.unicitylabs.sdk.mtree.MerkleTreePathVerificationResult; +import org.unicitylabs.sdk.smt.MerkleTreePathVerificationResult; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.BigIntegerConverter; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreePathStep.java b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePathStep.java similarity index 98% rename from src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreePathStep.java rename to src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePathStep.java index 52e095a..a2a1a3a 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreePathStep.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePathStep.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; import java.math.BigInteger; import java.util.Arrays; diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreeRootNode.java b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreeRootNode.java similarity index 97% rename from src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreeRootNode.java rename to src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreeRootNode.java index d9753c6..246f9cd 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreeRootNode.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreeRootNode.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; import java.math.BigInteger; import java.util.ArrayList; @@ -6,7 +6,7 @@ import java.util.Objects; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.unicitylabs.sdk.mtree.CommonPath; +import org.unicitylabs.sdk.smt.CommonPath; /** * Sparse Merkle Sum Tree root node. diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java b/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java index 0fbcd6b..7636db2 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java @@ -3,6 +3,7 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; + import org.unicitylabs.sdk.api.InclusionProof; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.MintSigningService; @@ -14,139 +15,159 @@ import org.unicitylabs.sdk.predicate.builtin.PayToPublicKeyPredicate; import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.HexConverter; public class MintTransaction implements Transaction { - - private final MintTransactionState sourceStateHash; - private final Predicate lockScript; - private final Address recipient; - private final TokenId tokenId; - private final TokenType tokenType; - private final byte[] data; - - private MintTransaction( - MintTransactionState sourceStateHash, - Predicate lockScript, - Address recipient, - TokenId tokenId, - TokenType tokenType, - byte[] data - ) { - this.sourceStateHash = sourceStateHash; - this.lockScript = lockScript; - this.recipient = recipient; - this.tokenId = tokenId; - this.tokenType = tokenType; - this.data = data; - } - - public MintTransactionState getSourceStateHash() { - return this.sourceStateHash; - } - - public Predicate getLockScript() { - return this.lockScript; - } - - public Address getRecipient() { - return this.recipient; - } - - public TokenId getTokenId() { - return this.tokenId; - } - - public TokenType getTokenType() { - return this.tokenType; - } - - @Override - public byte[] getData() { - return this.data; - } - - @Override - public byte[] getX() { - return this.tokenId.getBytes(); - } - - public static MintTransaction create( - Address recipient, - TokenId tokenId, - TokenType tokenType, - byte[] data - ) { - Objects.requireNonNull(recipient, "Recipient cannot be null"); - Objects.requireNonNull(tokenId, "Token ID cannot be null"); - Objects.requireNonNull(tokenType, "Token type cannot be null"); - Objects.requireNonNull(data, "Data cannot be null"); - - SigningService signingService = MintSigningService.create(tokenId); - return new MintTransaction( - MintTransactionState.create(tokenId), - PayToPublicKeyPredicate.fromSigningService(signingService), - recipient, - tokenId, - tokenType, - Arrays.copyOf(data, data.length) - ); - } - - public static MintTransaction fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); - List aux = CborDeserializer.decodeArray(data.get(2)); - - return MintTransaction.create( - Address.fromCbor(data.get(0)), - TokenId.fromCbor(data.get(1)), - TokenType.fromCbor(aux.get(0)), - CborDeserializer.decodeByteString(aux.get(1)) - ); - } - - @Override - public DataHash calculateStateHash() { - return new DataHasher(HashAlgorithm.SHA256) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(this.sourceStateHash.getImprint()), - CborSerializer.encodeByteString(this.getX()) - ) - ) - .digest(); - } - - @Override - public DataHash calculateTransactionHash() { - return new DataHasher(HashAlgorithm.SHA256).update(this.toCbor()).digest(); - } - - @Override - public byte[] toCbor() { - return CborSerializer.encodeArray( - this.recipient.toCbor(), - this.tokenId.toCbor(), - CborSerializer.encodeArray(this.tokenType.toCbor(), - CborSerializer.encodeByteString(this.data)) - ); - } - - public CertifiedMintTransaction toCertifiedTransaction( - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - InclusionProof inclusionProof - ) { - return CertifiedMintTransaction.fromTransaction(trustBase, predicateVerifier, this, - inclusionProof); - } - - @Override - public String toString() { - return String.format( - "MintTransaction{sourceStateHash=%s, lockScript=%s, recipient=%s, tokenId=%s, tokenType=%s, data=%s}", - this.sourceStateHash, this.lockScript, this.recipient, this.tokenId, this.tokenType, - HexConverter.encode(this.data)); - } + public static final long CBOR_TAG = 39041; + private static final int VERSION = 1; + + private final MintTransactionState sourceStateHash; + private final Predicate lockScript; + private final Address recipient; + private final TokenId tokenId; + private final TokenType tokenType; + private final byte[] data; + + private MintTransaction( + MintTransactionState sourceStateHash, + Predicate lockScript, + Address recipient, + TokenId tokenId, + TokenType tokenType, + byte[] data + ) { + this.sourceStateHash = sourceStateHash; + this.lockScript = lockScript; + this.recipient = recipient; + this.tokenId = tokenId; + this.tokenType = tokenType; + this.data = data; + } + + public int getVersion() { + return MintTransaction.VERSION; + } + + public MintTransactionState getSourceStateHash() { + return this.sourceStateHash; + } + + public Predicate getLockScript() { + return this.lockScript; + } + + public Address getRecipient() { + return this.recipient; + } + + public TokenId getTokenId() { + return this.tokenId; + } + + public TokenType getTokenType() { + return this.tokenType; + } + + @Override + public byte[] getData() { + return this.data; + } + + @Override + public byte[] getX() { + return this.tokenId.getBytes(); + } + + public static MintTransaction create( + Address recipient, + TokenId tokenId, + TokenType tokenType, + byte[] data + ) { + Objects.requireNonNull(recipient, "Recipient cannot be null"); + Objects.requireNonNull(tokenId, "Token ID cannot be null"); + Objects.requireNonNull(tokenType, "Token type cannot be null"); + Objects.requireNonNull(data, "Data cannot be null"); + + SigningService signingService = MintSigningService.create(tokenId); + return new MintTransaction( + MintTransactionState.create(tokenId), + PayToPublicKeyPredicate.fromSigningService(signingService), + recipient, + tokenId, + tokenType, + Arrays.copyOf(data, data.length) + ); + } + + public static MintTransaction fromCbor(byte[] bytes) { + CborDeserializer.CborTag tag = CborDeserializer.decodeTag(bytes); + if (tag.getTag() != MintTransaction.CBOR_TAG) { + throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); + } + List data = CborDeserializer.decodeArray(tag.getData()); + + int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); + if (version != MintTransaction.VERSION) { + throw new CborSerializationException(String.format("Unsupported version: %s", version)); + } + List aux = CborDeserializer.decodeArray(data.get(3)); + + return MintTransaction.create( + Address.fromCbor(data.get(1)), + TokenId.fromCbor(data.get(2)), + TokenType.fromCbor(aux.get(0)), + CborDeserializer.decodeByteString(aux.get(1)) + ); + } + + @Override + public DataHash calculateStateHash() { + return new DataHasher(HashAlgorithm.SHA256) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(this.sourceStateHash.getImprint()), + CborSerializer.encodeByteString(this.getX()) + ) + ) + .digest(); + } + + @Override + public DataHash calculateTransactionHash() { + return new DataHasher(HashAlgorithm.SHA256).update(this.toCbor()).digest(); + } + + @Override + public byte[] toCbor() { + return CborSerializer.encodeTag( + MintTransaction.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(MintTransaction.VERSION), + this.recipient.toCbor(), + this.tokenId.toCbor(), + CborSerializer.encodeArray(this.tokenType.toCbor(), + CborSerializer.encodeByteString(this.data)) + ) + ); + } + + public CertifiedMintTransaction toCertifiedTransaction( + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + InclusionProof inclusionProof + ) { + return CertifiedMintTransaction.fromTransaction(trustBase, predicateVerifier, this, + inclusionProof); + } + + @Override + public String toString() { + return String.format( + "MintTransaction{sourceStateHash=%s, lockScript=%s, recipient=%s, tokenId=%s, tokenType=%s, data=%s}", + this.sourceStateHash, this.lockScript, this.recipient, this.tokenId, this.tokenType, + HexConverter.encode(this.data)); + } } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/Token.java b/src/main/java/org/unicitylabs/sdk/transaction/Token.java index 71a3808..299aa61 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/Token.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/Token.java @@ -1,11 +1,9 @@ package org.unicitylabs.sdk.transaction; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.transaction.verification.CertifiedMintTransactionVerificationRule; import org.unicitylabs.sdk.transaction.verification.CertifiedTransferTransactionVerificationRule; @@ -13,7 +11,13 @@ import org.unicitylabs.sdk.util.verification.VerificationResult; import org.unicitylabs.sdk.util.verification.VerificationStatus; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + public class Token { + public static final long CBOR_TAG = 39040; + private static final int VERSION = 1; private final CertifiedMintTransaction genesis; private final List transactions; @@ -27,6 +31,10 @@ private Token(CertifiedMintTransaction genesis) { this(genesis, List.of()); } + public int getVersion() { + return Token.VERSION; + } + public TokenId getId() { return this.genesis.getTokenId(); } @@ -48,11 +56,20 @@ public List getTransactions() { } public static Token fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); - List transactions = CborDeserializer.decodeArray(data.get(1)); + CborDeserializer.CborTag tag = CborDeserializer.decodeTag(bytes); + if (tag.getTag() != Token.CBOR_TAG) { + throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); + } + List data = CborDeserializer.decodeArray(tag.getData()); + + int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); + if (version != Token.VERSION) { + throw new CborSerializationException(String.format("Unsupported version: %s", version)); + } + List transactions = CborDeserializer.decodeArray(data.get(2)); return new Token( - CertifiedMintTransaction.fromCbor(data.get(0)), + CertifiedMintTransaction.fromCbor(data.get(1)), transactions.stream().map(CertifiedTransferTransaction::fromCbor) .collect(Collectors.toList()) ); @@ -120,10 +137,14 @@ public VerificationResult verify(RootTrustBase trustBase, } public byte[] toCbor() { - return CborSerializer.encodeArray( - this.genesis.toCbor(), - CborSerializer.encodeArray( - this.transactions.stream().map(Transaction::toCbor).toArray(byte[][]::new)) + return CborSerializer.encodeTag( + Token.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(Token.VERSION), + this.genesis.toCbor(), + CborSerializer.encodeArray( + this.transactions.stream().map(Transaction::toCbor).toArray(byte[][]::new)) + ) ); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/TokenId.java b/src/main/java/org/unicitylabs/sdk/transaction/TokenId.java index d131b3d..d7f104c 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/TokenId.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/TokenId.java @@ -1,13 +1,14 @@ package org.unicitylabs.sdk.transaction; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Objects; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.BitString; import org.unicitylabs.sdk.util.HexConverter; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Objects; + public class TokenId { private static final SecureRandom RANDOM = new SecureRandom(); @@ -38,7 +39,7 @@ public byte[] toCbor() { } public BitString toBitString() { - return new BitString(this.bytes); + return BitString.fromBytes(this.bytes); } @Override diff --git a/src/main/java/org/unicitylabs/sdk/transaction/TokenType.java b/src/main/java/org/unicitylabs/sdk/transaction/TokenType.java index ab7ff88..f2fea22 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/TokenType.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/TokenType.java @@ -1,13 +1,14 @@ package org.unicitylabs.sdk.transaction; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Objects; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.BitString; import org.unicitylabs.sdk.util.HexConverter; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Objects; + public class TokenType { private static final SecureRandom RANDOM = new SecureRandom(); @@ -38,7 +39,7 @@ public byte[] toCbor() { } public BitString toBitString() { - return new BitString(this.bytes); + return BitString.fromBytes(this.bytes); } @Override diff --git a/src/main/java/org/unicitylabs/sdk/transaction/TransferTransaction.java b/src/main/java/org/unicitylabs/sdk/transaction/TransferTransaction.java index 06e6074..0a6b5b1 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/TransferTransaction.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/TransferTransaction.java @@ -1,7 +1,5 @@ package org.unicitylabs.sdk.transaction; -import java.util.Arrays; -import java.util.List; import org.unicitylabs.sdk.api.InclusionProof; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.hash.DataHash; @@ -11,10 +9,16 @@ import org.unicitylabs.sdk.predicate.Predicate; import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.HexConverter; +import java.util.Arrays; +import java.util.List; + public class TransferTransaction implements Transaction { + public static final long CBOR_TAG = 39045; + private static final int VERSION = 1; private final DataHash sourceStateHash; private final Predicate lockScript; @@ -32,6 +36,10 @@ private TransferTransaction(DataHash sourceStateHash, Predicate lockScript, Addr this.data = data; } + public int getVersion() { + return TransferTransaction.VERSION; + } + @Override public byte[] getData() { @@ -75,14 +83,23 @@ public static TransferTransaction create(Token token, Predicate owner, Address r } public static TransferTransaction fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); + CborDeserializer.CborTag tag = CborDeserializer.decodeTag(bytes); + if (tag.getTag() != TransferTransaction.CBOR_TAG) { + throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); + } + List data = CborDeserializer.decodeArray(tag.getData()); + + int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); + if (version != TransferTransaction.VERSION) { + throw new CborSerializationException(String.format("Unsupported version: %s", version)); + } return new TransferTransaction( - new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(data.get(0))), - EncodedPredicate.fromCbor(data.get(1)), - Address.fromCbor(data.get(2)), - CborDeserializer.decodeByteString(data.get(3)), - CborDeserializer.decodeByteString(data.get(4)) + new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(data.get(1))), + EncodedPredicate.fromCbor(data.get(2)), + Address.fromCbor(data.get(3)), + CborDeserializer.decodeByteString(data.get(4)), + CborDeserializer.decodeByteString(data.get(5)) ); } @@ -113,12 +130,16 @@ public DataHash calculateTransactionHash() { @Override public byte[] toCbor() { - return CborSerializer.encodeArray( - CborSerializer.encodeByteString(this.sourceStateHash.getData()), - EncodedPredicate.fromPredicate(this.lockScript).toCbor(), - this.recipient.toCbor(), - CborSerializer.encodeByteString(this.x), - CborSerializer.encodeByteString(this.data) + return CborSerializer.encodeTag( + TransferTransaction.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(TransferTransaction.VERSION), + CborSerializer.encodeByteString(this.sourceStateHash.getData()), + EncodedPredicate.fromPredicate(this.lockScript).toCbor(), + this.recipient.toCbor(), + CborSerializer.encodeByteString(this.x), + CborSerializer.encodeByteString(this.data) + ) ); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/verification/InclusionProofVerificationRule.java b/src/main/java/org/unicitylabs/sdk/transaction/verification/InclusionProofVerificationRule.java index 9d56233..fb9b9b5 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/verification/InclusionProofVerificationRule.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/verification/InclusionProofVerificationRule.java @@ -1,14 +1,12 @@ package org.unicitylabs.sdk.transaction.verification; -import java.util.Arrays; import org.unicitylabs.sdk.api.CertificationData; import org.unicitylabs.sdk.api.InclusionProof; import org.unicitylabs.sdk.api.StateId; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.api.bft.verification.UnicityCertificateVerification; import org.unicitylabs.sdk.crypto.hash.DataHash; -import org.unicitylabs.sdk.mtree.MerkleTreePathVerificationResult; -import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePathStep; +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.util.verification.VerificationResult; @@ -17,56 +15,56 @@ public class InclusionProofVerificationRule { public static VerificationResult verify(RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, InclusionProof inclusionProof, - Transaction transaction) { - VerificationResult result = UnicityCertificateVerification.verify(trustBase, inclusionProof); - if (result.getStatus() != VerificationStatus.OK) { - return new VerificationResult<>("InclusionProofVerificationRule", - InclusionProofVerificationStatus.INVALID_TRUSTBASE, "", result); - } - - StateId stateId = StateId.fromTransaction(transaction); - MerkleTreePathVerificationResult pathVerificationResult = inclusionProof.getMerkleTreePath() - .verify(stateId.toBitString().toBigInteger()); - if (!pathVerificationResult.isPathValid()) { - return new VerificationResult<>("InclusionProofVerificationRule", - InclusionProofVerificationStatus.PATH_INVALID); - } - - if (!pathVerificationResult.isPathIncluded()) { - return new VerificationResult<>("InclusionProofVerificationRule", - InclusionProofVerificationStatus.PATH_NOT_INCLUDED); + PredicateVerifierService predicateVerifier, InclusionProof inclusionProof, + Transaction transaction) { + if (inclusionProof.getInclusionCertificate() == null) { + return new VerificationResult<>( + "InclusionProofVerificationRule", + InclusionProofVerificationStatus.INCLUSION_CERTIFICATE_MISSING + ); } CertificationData certificationData = inclusionProof.getCertificationData().orElse(null); if (certificationData == null) { return new VerificationResult<>("InclusionProofVerificationRule", - InclusionProofVerificationStatus.MISSING_CERTIFICATION_DATA); + InclusionProofVerificationStatus.MISSING_CERTIFICATION_DATA); } if (!certificationData.getTransactionHash().equals(transaction.calculateTransactionHash())) { return new VerificationResult<>("InclusionProofVerificationRule", - InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH); + InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH); + } + + StateId stateId = StateId.fromTransaction(transaction); + if (!inclusionProof.getInclusionCertificate().verify(stateId, certificationData.getTransactionHash(), new DataHash(HashAlgorithm.SHA256, inclusionProof.getUnicityCertificate().getInputRecord().getHash()))) { + return new VerificationResult<>("InclusionProofVerificationRule", + InclusionProofVerificationStatus.PATH_INVALID); } - result = predicateVerifier.verify(certificationData.getLockScript(), - certificationData.getSourceStateHash(), certificationData.getTransactionHash(), - certificationData.getUnlockScript()); + VerificationResult result = UnicityCertificateVerification.verify(trustBase, inclusionProof); if (result.getStatus() != VerificationStatus.OK) { - return new VerificationResult<>("InclusionProofVerificationRule", - InclusionProofVerificationStatus.NOT_AUTHENTICATED, "", result); + return new VerificationResult<>( + "InclusionProofVerificationRule", + InclusionProofVerificationStatus.INVALID_TRUSTBASE, + "", + result + ); } - DataHash leafValue = certificationData.calculateLeafValue(); - byte[] pathValue = inclusionProof.getMerkleTreePath().getSteps().stream().findFirst() - .flatMap(SparseMerkleTreePathStep::getData).orElse(null); - if (pathValue == null || !Arrays.equals(leafValue.getImprint(), pathValue)) { + result = predicateVerifier.verify( + transaction.getLockScript(), + transaction.getSourceStateHash(), + certificationData.getTransactionHash(), + certificationData.getUnlockScript() + ); + + if (result.getStatus() != VerificationStatus.OK) { return new VerificationResult<>("InclusionProofVerificationRule", - InclusionProofVerificationStatus.LEAF_VALUE_MISMATCH); + InclusionProofVerificationStatus.NOT_AUTHENTICATED, "", result); } return new VerificationResult<>("InclusionProofVerificationRule", - InclusionProofVerificationStatus.OK); + InclusionProofVerificationStatus.OK); } } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/verification/InclusionProofVerificationStatus.java b/src/main/java/org/unicitylabs/sdk/transaction/verification/InclusionProofVerificationStatus.java index 57724fa..c228a98 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/verification/InclusionProofVerificationStatus.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/verification/InclusionProofVerificationStatus.java @@ -2,11 +2,10 @@ public enum InclusionProofVerificationStatus { INVALID_TRUSTBASE, - LEAF_VALUE_MISMATCH, MISSING_CERTIFICATION_DATA, TRANSACTION_HASH_MISMATCH, NOT_AUTHENTICATED, - PATH_NOT_INCLUDED, + INCLUSION_CERTIFICATE_MISSING, PATH_INVALID, OK } diff --git a/src/main/java/org/unicitylabs/sdk/util/BitString.java b/src/main/java/org/unicitylabs/sdk/util/BitString.java index c996534..d5aba09 100644 --- a/src/main/java/org/unicitylabs/sdk/util/BitString.java +++ b/src/main/java/org/unicitylabs/sdk/util/BitString.java @@ -1,40 +1,91 @@ package org.unicitylabs.sdk.util; import java.math.BigInteger; -import org.unicitylabs.sdk.api.StateId; +import java.util.Arrays; /** - * Represents a bit string as a BigInteger. This class is used to ensure that leading zero bits are retained when - * converting between byte arrays and BigInteger. + * Represents a bit string as a BigInteger. This class is used to ensure that leading zero bits are + * retained when converting between byte arrays and BigInteger. */ public class BitString { private final BigInteger value; - /** - * Creates a BitString from a byte array. - * - * @param data The input data to convert into a BitString. - */ - public BitString(byte[] data) { + private BitString(byte[] data) { byte[] dataWithPrefix = new byte[data.length + 1]; dataWithPrefix[0] = 1; System.arraycopy(data, 0, dataWithPrefix, 1, data.length); this.value = new BigInteger(1, dataWithPrefix); } - public static BitString fromStateId(StateId stateId) { - return new BitString(stateId.getImprint()); + /** + * Creates a BitString from raw bytes with no bit reordering. BigInteger bit 0 is the LSB of the + * last byte. + * + * @param data input bytes + * @return BitString + */ + public static BitString fromBytes(byte[] data) { + return new BitString(Arrays.copyOf(data, data.length)); + } + + /** + * Creates a BitString for LSB-first tree routing with reversed byte order. BigInteger bit 0 is + * bit 0 (LSB) of data[0], matching getBitAtDepth LSB convention. + * + * @param data input bytes + * @return BitString + */ + public static BitString fromBytesReversedLSB(byte[] data) { + byte[] reversed = new byte[data.length]; + for (int i = 0; i < data.length; i++) { + reversed[i] = data[data.length - 1 - i]; + } + return new BitString(reversed); + } + + /** + * Creates a BitString for MSB-first tree routing with reversed byte order. BigInteger bit 0 is + * bit 7 (MSB) of data[0], matching getBitAtDepth MSB convention. + * + * @param data input bytes + * @return BitString + */ + public static BitString fromBytesReversedMSB(byte[] data) { + byte[] reversed = new byte[data.length]; + for (int i = 0; i < data.length; i++) { + int b = data[data.length - 1 - i] & 0xFF; + int bitReversed = ((b & 0x80) >> 7) + | ((b & 0x40) >> 5) + | ((b & 0x20) >> 3) + | ((b & 0x10) >> 1) + | ((b & 0x08) << 1) + | ((b & 0x04) << 3) + | ((b & 0x02) << 5) + | ((b & 0x01) << 7); + reversed[i] = (byte) bitReversed; + } + return new BitString(reversed); } /** - * Converts BitString to BigInteger by adding a leading byte 1 to input byte array. This is to ensure that the - * BigInteger will retain the leading zero bits. + * Converts BitString to BigInteger by adding a leading byte 1 to input byte array. This is to + * ensure that the BigInteger will retain the leading zero bits. * * @return The BigInteger representation of the bit string */ public BigInteger toBigInteger() { - return value; + return this.value; + } + + /** + * Converts bit string to byte array. + * + * @return The byte array representation of the bit string + */ + public byte[] toBytes() { + byte[] encoded = BigIntegerConverter.encode(this.value); + return Arrays.copyOfRange(encoded, 1, encoded.length); } /** @@ -44,11 +95,6 @@ public BigInteger toBigInteger() { */ @Override public String toString() { - String binary = value.toString(2); - // Remove the leading '1' bit we added - if (binary.length() > 1 && binary.charAt(0) == '1') { - return binary.substring(1); - } - return binary; + return this.value.toString(2).substring(1); } } \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java b/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java index 9246c53..4388068 100644 --- a/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java +++ b/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java @@ -1,9 +1,5 @@ package org.unicitylabs.sdk.util; -import java.time.Duration; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.unicitylabs.sdk.StateTransitionClient; @@ -17,6 +13,11 @@ import org.unicitylabs.sdk.util.verification.VerificationException; import org.unicitylabs.sdk.util.verification.VerificationResult; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + /** * Utility class for working with inclusion proofs. */ @@ -24,7 +25,7 @@ public class InclusionProofUtils { private static final Logger logger = LoggerFactory.getLogger(InclusionProofUtils.class); private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds( - 30); // 30 seconds should be enough for direct leader + 30); // 30 seconds should be enough for direct leader private static final Duration DEFAULT_INTERVAL = Duration.ofMillis(1000); private InclusionProofUtils() { @@ -40,13 +41,13 @@ private InclusionProofUtils() { * @return Completable future with inclusion proof */ public static CompletableFuture waitInclusionProof( - StateTransitionClient client, - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - Transaction transaction + StateTransitionClient client, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + Transaction transaction ) { return waitInclusionProof(client, trustBase, predicateVerifier, transaction, DEFAULT_TIMEOUT, - DEFAULT_INTERVAL); + DEFAULT_INTERVAL); } /** @@ -61,12 +62,12 @@ public static CompletableFuture waitInclusionProof( * @return Completable future with inclusion proof */ public static CompletableFuture waitInclusionProof( - StateTransitionClient client, - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - Transaction transaction, - Duration timeout, - Duration interval + StateTransitionClient client, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + Transaction transaction, + Duration timeout, + Duration interval ) { CompletableFuture future = new CompletableFuture<>(); @@ -75,21 +76,21 @@ public static CompletableFuture waitInclusionProof( long timeoutMillis = timeout.toMillis(); checkInclusionProof(client, trustBase, predicateVerifier, transaction, future, startTime, - timeoutMillis, - interval.toMillis()); + timeoutMillis, + interval.toMillis()); return future; } private static void checkInclusionProof( - StateTransitionClient client, - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - Transaction transaction, - CompletableFuture future, - long startTime, - long timeoutMillis, - long intervalMillis) { + StateTransitionClient client, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + Transaction transaction, + CompletableFuture future, + long startTime, + long timeoutMillis, + long intervalMillis) { if (System.currentTimeMillis() - startTime > timeoutMillis) { future.completeExceptionally(new TimeoutException("Timeout waiting for inclusion proof")); } @@ -97,21 +98,21 @@ private static void checkInclusionProof( StateId stateId = StateId.fromTransaction(transaction); client.getInclusionProof(stateId).thenAccept(response -> { VerificationResult result = InclusionProofVerificationRule.verify( - trustBase, predicateVerifier, response.getInclusionProof(), transaction); + trustBase, predicateVerifier, response.getInclusionProof(), transaction); switch (result.getStatus()) { case OK: future.complete(response.getInclusionProof()); break; - case PATH_NOT_INCLUDED: + case INCLUSION_CERTIFICATE_MISSING: CompletableFuture.delayedExecutor(intervalMillis, TimeUnit.MILLISECONDS) - .execute(() -> checkInclusionProof(client, trustBase, predicateVerifier, transaction, - future, startTime, - timeoutMillis, - intervalMillis)); + .execute(() -> checkInclusionProof(client, trustBase, predicateVerifier, transaction, + future, startTime, + timeoutMillis, + intervalMillis)); break; default: future.completeExceptionally( - new VerificationException("Inclusion proof verification failed", result)); + new VerificationException("Inclusion proof verification failed", result)); } }).exceptionally(e -> { future.completeExceptionally(e); diff --git a/src/main/java/org/unicitylabs/sdk/util/LongConverter.java b/src/main/java/org/unicitylabs/sdk/util/LongConverter.java new file mode 100644 index 0000000..f0d5293 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/util/LongConverter.java @@ -0,0 +1,33 @@ +package org.unicitylabs.sdk.util; + +/** + * Long converter to bytes. + */ +public class LongConverter { + + private LongConverter() { + } + + /** + * Encode a non-negative long as minimum-length unsigned big-endian bytes, with a minimum length + * of one byte. Zero is encoded as {@code [0x00]}, not the empty array. + * + * @param value non-negative long + * @return bytes + */ + public static byte[] encode(long value) { + if (value < 0) { + throw new IllegalArgumentException("value must be non-negative"); + } + if (value == 0) { + return new byte[]{0x00}; + } + int length = (64 - Long.numberOfLeadingZeros(value) + 7) / 8; + byte[] result = new byte[length]; + for (int i = length - 1; i >= 0; i--) { + result[i] = (byte) (value & 0xffL); + value >>>= 8; + } + return result; + } +} diff --git a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java index e61da37..e576457 100644 --- a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java +++ b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java @@ -1,108 +1,109 @@ package org.unicitylabs.sdk; -import java.util.HashMap; -import java.util.concurrent.CompletableFuture; -import org.unicitylabs.sdk.api.AggregatorClient; -import org.unicitylabs.sdk.api.CertificationData; -import org.unicitylabs.sdk.api.CertificationResponse; -import org.unicitylabs.sdk.api.CertificationStatus; -import org.unicitylabs.sdk.api.InclusionProofResponse; -import org.unicitylabs.sdk.api.InclusionProofFixture; -import org.unicitylabs.sdk.api.StateId; +import org.unicitylabs.sdk.api.*; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.api.bft.RootTrustBaseUtils; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; -import org.unicitylabs.sdk.mtree.plain.SparseMerkleTree; -import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreeRootNode; import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; +import org.unicitylabs.sdk.smt.radix.FinalizedNodeBranch; +import org.unicitylabs.sdk.smt.radix.SparseMerkleTree; import org.unicitylabs.sdk.util.verification.VerificationResult; import org.unicitylabs.sdk.util.verification.VerificationStatus; +import java.util.HashMap; +import java.util.concurrent.CompletableFuture; + public class TestAggregatorClient implements AggregatorClient { - private final RootTrustBase trustBase; - private final PredicateVerifierService predicateVerifier; - private final SparseMerkleTree sparseMerkleTree; - private final HashMap requests = new HashMap<>(); - private final SigningService signingService; - - private TestAggregatorClient(SparseMerkleTree smt, SigningService signingService) { - this.sparseMerkleTree = smt; - this.signingService = signingService; - this.trustBase = RootTrustBaseUtils.generateRootTrustBase(this.signingService.getPublicKey()); - this.predicateVerifier = PredicateVerifierService.create(this.trustBase); - } - - public RootTrustBase getTrustBase() { - return this.trustBase; - } - - /** - * Creates a new TestAggregatorClient instance with generated private key. If no private key is provided, a new one is - * generated. - */ - public static TestAggregatorClient create() { - return TestAggregatorClient.create(SigningService.generatePrivateKey()); - } - - - /** - * Creates a new TestAggregatorClient instance with private key. If no private key is provided, a new one is - * generated. - */ - public static TestAggregatorClient create(byte[] privateKey) { - return new TestAggregatorClient( - new SparseMerkleTree(HashAlgorithm.SHA256), - new SigningService(privateKey) - ); - } - - - @Override - public CompletableFuture submitCertificationRequest(CertificationData certificationData) { - try { - StateId stateId = StateId.fromCertificationData(certificationData); - - VerificationResult result = this.predicateVerifier.verify( - certificationData.getLockScript(), - certificationData.getSourceStateHash(), - certificationData.getTransactionHash(), - certificationData.getUnlockScript() - ); - - if (result.getStatus() != VerificationStatus.OK) { - return CompletableFuture.completedFuture(CertificationResponse.create(CertificationStatus.SIGNATURE_VERIFICATION_FAILED)); - } - - if (!this.requests.containsKey(stateId)) { - DataHash leafValue = certificationData.calculateLeafValue(); - this.sparseMerkleTree.addLeaf(stateId.toBitString().toBigInteger(), leafValue.getImprint()); - this.requests.put(stateId, certificationData); - } - - return CompletableFuture.completedFuture(CertificationResponse.create(CertificationStatus.SUCCESS)); - } catch (Exception e) { - throw new RuntimeException("Aggregator commitment failed", e); + private final RootTrustBase trustBase; + private final PredicateVerifierService predicateVerifier; + private final SparseMerkleTree sparseMerkleTree; + private final HashMap requests = new HashMap<>(); + private final SigningService signingService; + + private TestAggregatorClient(SparseMerkleTree smt, SigningService signingService) { + this.sparseMerkleTree = smt; + this.signingService = signingService; + this.trustBase = RootTrustBaseUtils.generateRootTrustBase(this.signingService.getPublicKey()); + this.predicateVerifier = PredicateVerifierService.create(this.trustBase); + } + + public RootTrustBase getTrustBase() { + return this.trustBase; + } + + /** + * Creates a new TestAggregatorClient instance with generated private key. If no private key is provided, a new one is + * generated. + */ + public static TestAggregatorClient create() { + return TestAggregatorClient.create(SigningService.generatePrivateKey()); + } + + + /** + * Creates a new TestAggregatorClient instance with private key. If no private key is provided, a new one is + * generated. + */ + public static TestAggregatorClient create(byte[] privateKey) { + return new TestAggregatorClient( + new SparseMerkleTree(HashAlgorithm.SHA256), + new SigningService(privateKey) + ); + } + + + @Override + public CompletableFuture submitCertificationRequest(CertificationData certificationData) { + try { + StateId stateId = StateId.fromCertificationData(certificationData); + + VerificationResult result = this.predicateVerifier.verify( + certificationData.getLockScript(), + certificationData.getSourceStateHash(), + certificationData.getTransactionHash(), + certificationData.getUnlockScript() + ); + + if (result.getStatus() != VerificationStatus.OK) { + return CompletableFuture.completedFuture(CertificationResponse.create(CertificationStatus.SIGNATURE_VERIFICATION_FAILED)); + } + + if (!this.requests.containsKey(stateId)) { + DataHash leafValue = certificationData.getTransactionHash(); + this.sparseMerkleTree.addLeaf(stateId.getData(), leafValue.getData()); + this.requests.put(stateId, certificationData); + } + + return CompletableFuture.completedFuture(CertificationResponse.create(CertificationStatus.SUCCESS)); + } catch (Exception e) { + throw new RuntimeException("Aggregator commitment failed", e); + } + } + + @Override + public CompletableFuture getInclusionProof(StateId stateId) { + FinalizedNodeBranch root = this.sparseMerkleTree.calculateRoot(); + + if (!requests.containsKey(stateId)) { + return CompletableFuture.completedFuture(InclusionProofFixture.createResponse(null, null, root.getHash(), this.signingService)); + } + + CertificationData certificationData = requests.get(stateId); + + return CompletableFuture.completedFuture( + InclusionProofFixture.createResponse( + certificationData, + InclusionCertificate.create(root, stateId.getData()), + root.getHash(), + this.signingService + ) + ); + } + + @Override + public CompletableFuture getBlockHeight() { + return CompletableFuture.completedFuture(1L); } - } - - @Override - public CompletableFuture getInclusionProof(StateId stateId) { - CertificationData certificationData = requests.get(stateId); - SparseMerkleTreeRootNode root = this.sparseMerkleTree.calculateRoot(); - return CompletableFuture.completedFuture( - InclusionProofFixture.create( - root.getPath(stateId.toBitString().toBigInteger()), - certificationData, - root.getRootHash(), - this.signingService - ) - ); - } - - @Override - public CompletableFuture getBlockHeight() { - return CompletableFuture.completedFuture(1L); - } } diff --git a/src/test/java/org/unicitylabs/sdk/api/InclusionProofFixture.java b/src/test/java/org/unicitylabs/sdk/api/InclusionProofFixture.java index 6183626..d2aab61 100644 --- a/src/test/java/org/unicitylabs/sdk/api/InclusionProofFixture.java +++ b/src/test/java/org/unicitylabs/sdk/api/InclusionProofFixture.java @@ -3,15 +3,14 @@ import org.unicitylabs.sdk.api.bft.UnicityCertificateUtils; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; -import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; public class InclusionProofFixture { - public static InclusionProofResponse create(SparseMerkleTreePath path, CertificationData certificationData, DataHash root, SigningService signingService) { + public static InclusionProofResponse createResponse(CertificationData certificationData, InclusionCertificate inclusionCertificate, DataHash root, SigningService signingService) { return new InclusionProofResponse( 1L, new InclusionProof( - path, certificationData, + inclusionCertificate, UnicityCertificateUtils.generateCertificate(signingService, root) ) ); diff --git a/src/test/java/org/unicitylabs/sdk/api/InclusionProofTest.java b/src/test/java/org/unicitylabs/sdk/api/InclusionProofTest.java index f16d5bd..ead6eea 100644 --- a/src/test/java/org/unicitylabs/sdk/api/InclusionProofTest.java +++ b/src/test/java/org/unicitylabs/sdk/api/InclusionProofTest.java @@ -11,13 +11,11 @@ import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; -import org.unicitylabs.sdk.mtree.plain.SparseMerkleTree; -import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; -import org.unicitylabs.sdk.predicate.EncodedPredicate; import org.unicitylabs.sdk.predicate.builtin.PayToPublicKeyPredicate; import org.unicitylabs.sdk.predicate.builtin.PayToPublicKeyPredicateUnlockScript; import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; -import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import org.unicitylabs.sdk.smt.radix.FinalizedNodeBranch; +import org.unicitylabs.sdk.smt.radix.SparseMerkleTree; import org.unicitylabs.sdk.transaction.Address; import org.unicitylabs.sdk.transaction.MintTransaction; import org.unicitylabs.sdk.transaction.TokenId; @@ -32,7 +30,7 @@ public class InclusionProofTest { MintTransaction transaction; PredicateVerifierService predicateVerifier; StateId stateId; - SparseMerkleTreePath merkleTreePath; + InclusionCertificate inclusionCertificate; CertificationData certificationData; RootTrustBase trustBase; UnicityCertificate unicityCertificate; @@ -54,21 +52,21 @@ public void createMerkleTreePath() throws Exception { stateId = StateId.fromCertificationData(certificationData); SparseMerkleTree smt = new SparseMerkleTree(HashAlgorithm.SHA256); - smt.addLeaf(stateId.toBitString().toBigInteger(), certificationData.calculateLeafValue().getImprint()); + smt.addLeaf(stateId.getData(), certificationData.getTransactionHash().getData()); - merkleTreePath = smt.calculateRoot().getPath(stateId.toBitString().toBigInteger()); + FinalizedNodeBranch root = smt.calculateRoot(); + inclusionCertificate = InclusionCertificate.create(root, stateId.getData()); SigningService ucSigningService = new SigningService(SigningService.generatePrivateKey()); trustBase = RootTrustBaseUtils.generateRootTrustBase(ucSigningService.getPublicKey()); - unicityCertificate = UnicityCertificateUtils.generateCertificate(ucSigningService, - merkleTreePath.getRootHash()); + unicityCertificate = UnicityCertificateUtils.generateCertificate(ucSigningService, root.getHash()); predicateVerifier = PredicateVerifierService.create(trustBase); } @Test public void testCborSerialization() { InclusionProof inclusionProof = new InclusionProof( - merkleTreePath, certificationData, + inclusionCertificate, unicityCertificate ); @@ -79,29 +77,22 @@ public void testCborSerialization() { public void testStructure() { Assertions.assertThrows(NullPointerException.class, () -> new InclusionProof( - null, - this.certificationData, - this.unicityCertificate - ) - ); - Assertions.assertThrows(NullPointerException.class, - () -> new InclusionProof( - this.merkleTreePath, this.certificationData, + this.inclusionCertificate, null ) ); Assertions.assertInstanceOf(InclusionProof.class, new InclusionProof( - this.merkleTreePath, this.certificationData, + this.inclusionCertificate, this.unicityCertificate ) ); Assertions.assertInstanceOf(InclusionProof.class, new InclusionProof( - this.merkleTreePath, null, + this.inclusionCertificate, this.unicityCertificate ) ); @@ -110,8 +101,8 @@ public void testStructure() { @Test public void testItVerifies() { InclusionProof inclusionProof = new InclusionProof( - this.merkleTreePath, this.certificationData, + this.inclusionCertificate, this.unicityCertificate ); Assertions.assertEquals( @@ -124,24 +115,7 @@ public void testItVerifies() { ).getStatus() ); - - Assertions.assertEquals( - InclusionProofVerificationStatus.PATH_NOT_INCLUDED, - InclusionProofVerificationRule.verify( - this.trustBase, - this.predicateVerifier, - inclusionProof, - MintTransaction.create( - Address.fromPredicate(transaction.getLockScript()), - TokenId.generate(), - transaction.getTokenType(), - transaction.getData() - ) - ).getStatus() - ); - - InclusionProof invalidTransactionHashInclusionProof = new InclusionProof( - this.merkleTreePath, + InclusionProof invalidTransactionHashInclusionProof = new InclusionProof( new CertificationData( this.certificationData.getLockScript(), this.certificationData.getSourceStateHash(), @@ -150,6 +124,7 @@ public void testItVerifies() { ), this.certificationData.getUnlockScript() ), + this.inclusionCertificate, this.unicityCertificate ); @@ -167,7 +142,6 @@ public void testItVerifies() { @Test public void testItNotAuthenticated() { InclusionProof invalidInclusionProof = new InclusionProof( - this.merkleTreePath, new CertificationData( this.certificationData.getLockScript(), this.certificationData.getSourceStateHash(), @@ -177,6 +151,7 @@ public void testItNotAuthenticated() { new SigningService(SigningService.generatePrivateKey()) ).encode() ), + this.inclusionCertificate, this.unicityCertificate ); @@ -194,8 +169,8 @@ public void testItNotAuthenticated() { @Test public void testVerificationFailsWithInvalidTrustbase() { InclusionProof inclusionProof = new InclusionProof( - this.merkleTreePath, this.certificationData, + this.inclusionCertificate, this.unicityCertificate ); diff --git a/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateTest.java b/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateTest.java index 027e93f..c711367 100644 --- a/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateTest.java +++ b/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateTest.java @@ -2,7 +2,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.unicitylabs.sdk.api.bft.UnicityCertificate; import org.unicitylabs.sdk.util.HexConverter; public class UnicityCertificateTest { @@ -10,11 +9,11 @@ public class UnicityCertificateTest { @Test public void testUnicityCertificateDeserializationFromCbor() { byte[] data = HexConverter.decode( - "d903ef8701d903f08a0118f70058220000ea2fc549e86b1f8666bd7a6d5dd0721b446856ad80c67ea958c4045dfb0b627c58220000ea2fc549e86b1f8666bd7a6d5dd0721b446856ad80c67ea958c4045dfb0b627c401a68d27af6f600f6582024bf5304778618efe3303e1ac36a5c23b7d9d64bbd2c2ae92ea1488ad59e9cfa58206a362e77353752942e9c8101511861ee7fac83ba0873ee88f34416535ae17c5c82418080d903f68301078182055820b206f60d312bf4422815c861e9330ddc3a29b0c744864d7bea41f94d1d34c669d903e988010319073b001a68d27af958204b3f7f036452271a1245ca79f6c2e35adc0b192e6eeb001dff35d70d1be3b2d55820380451b471673eb68cf750ceedb09755c08e3b0b7dea32d0101465970ca3a661a4783531365569753248416d33506141397a386a6f6e5a7a6676755431574a675478437062466b5634577134505353426b375663746b6d475841c5103160b79e7691cec53448949bbd93a870a38c89ad4c929aa1c8e77f2a1d9053d3f8f04e96bcb38ba234bf3dee52baad4b1df94be0b8a5e569918fb94e18ec00783531365569753248416d383931384473326e5069564c586735356b7970796f586f6977656f6b705178746e67755a6a67787a33704e455841fa3736ebdc729b31d6e3c964787dfcefcbd39286a1619665eac1313e6d35049b4385156f652ad9ac87a769d3bf7b8cc819bb5a6f838e595d2e2092df2592849400783531365569753248416d454545477976595a6e6f37686d3247733846776650656a57647076574333484c6976514435685846624e55685841e49ea9e0ca1bb21a37a203fef7bc64efc660ff208f4e572ad3d58ae1d5f1e86e7af1c030c66b3325f99886b1a1d0836487c16112c43c07248b490121d473287701783531365569753248416d4e776772753751537356526163477158647466656166316f71747a6e7658413672537a4b55313832326b75575841ecb36e24df58876277643117344d5329fc1bb22b51e9e6835ac587c894db06a11110e966fd97acd91659c778494d9b4dcdc1bd22ffb47a90e3bec38db8a7310f01"); + "d998598701d9985a8a010000f65820747276e851904305a7457d31edd9d374cdedbc7f72c4f5674da410cddb23b6c14a00000000000000000000004a00000000000000000000004a000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058200000000000000000000000000000000000000000000000000000000000000000d9985b8301418080d9985c83010080d9985d880100000000f65820bd991b4e822f177252fa79aebdec0a24546519b813fdd54e6e48fc67d12c454da1644e4f44455841a3ed5d3e5ea04d39297d5db9cc69bbd97eabf2351e6f96660b8fc3e8d106daf06e0f6336c0862a8068ebdd326474b5a0f52f7942ccf010219a619c650ea4a2a301"); UnicityCertificate unicityCertificate = UnicityCertificate.fromCbor(data); Assertions.assertEquals( - "d903ef8701d903f08a0118f70058220000ea2fc549e86b1f8666bd7a6d5dd0721b446856ad80c67ea958c4045dfb0b627c58220000ea2fc549e86b1f8666bd7a6d5dd0721b446856ad80c67ea958c4045dfb0b627c401a68d27af6f600f6582024bf5304778618efe3303e1ac36a5c23b7d9d64bbd2c2ae92ea1488ad59e9cfa58206a362e77353752942e9c8101511861ee7fac83ba0873ee88f34416535ae17c5c82418080d903f68301078182055820b206f60d312bf4422815c861e9330ddc3a29b0c744864d7bea41f94d1d34c669d903e988010319073b001a68d27af958204b3f7f036452271a1245ca79f6c2e35adc0b192e6eeb001dff35d70d1be3b2d55820380451b471673eb68cf750ceedb09755c08e3b0b7dea32d0101465970ca3a661a4783531365569753248416d33506141397a386a6f6e5a7a6676755431574a675478437062466b5634577134505353426b375663746b6d475841c5103160b79e7691cec53448949bbd93a870a38c89ad4c929aa1c8e77f2a1d9053d3f8f04e96bcb38ba234bf3dee52baad4b1df94be0b8a5e569918fb94e18ec00783531365569753248416d383931384473326e5069564c586735356b7970796f586f6977656f6b705178746e67755a6a67787a33704e455841fa3736ebdc729b31d6e3c964787dfcefcbd39286a1619665eac1313e6d35049b4385156f652ad9ac87a769d3bf7b8cc819bb5a6f838e595d2e2092df2592849400783531365569753248416d454545477976595a6e6f37686d3247733846776650656a57647076574333484c6976514435685846624e55685841e49ea9e0ca1bb21a37a203fef7bc64efc660ff208f4e572ad3d58ae1d5f1e86e7af1c030c66b3325f99886b1a1d0836487c16112c43c07248b490121d473287701783531365569753248416d4e776772753751537356526163477158647466656166316f71747a6e7658413672537a4b55313832326b75575841ecb36e24df58876277643117344d5329fc1bb22b51e9e6835ac587c894db06a11110e966fd97acd91659c778494d9b4dcdc1bd22ffb47a90e3bec38db8a7310f01", + "d998598701d9985a8a010000f65820747276e851904305a7457d31edd9d374cdedbc7f72c4f5674da410cddb23b6c14a00000000000000000000004a00000000000000000000004a000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058200000000000000000000000000000000000000000000000000000000000000000d9985b8301418080d9985c83010080d9985d880100000000f65820bd991b4e822f177252fa79aebdec0a24546519b813fdd54e6e48fc67d12c454da1644e4f44455841a3ed5d3e5ea04d39297d5db9cc69bbd97eabf2351e6f96660b8fc3e8d106daf06e0f6336c0862a8068ebdd326474b5a0f52f7942ccf010219a619c650ea4a2a301", HexConverter.encode(unicityCertificate.toCbor()) ); } diff --git a/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateUtils.java b/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateUtils.java index f265689..69e27cb 100644 --- a/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateUtils.java +++ b/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateUtils.java @@ -1,87 +1,94 @@ package org.unicitylabs.sdk.api.bft; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.List; -import java.util.Map; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.DataHasher; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.List; +import java.util.Map; + public class UnicityCertificateUtils { public static UnicityCertificate generateCertificate( - SigningService signingService, - DataHash rootHash + SigningService signingService, + DataHash rootHash ) { - InputRecord inputRecord = new InputRecord(0, 0, 0, null, rootHash.getImprint(), new byte[10], - 0, - new byte[10], 0, new byte[10]); - UnicityTreeCertificate unicityTreeCertificate = new UnicityTreeCertificate(0, 0, List.of()); + InputRecord inputRecord = new InputRecord( + 0, + 0, + null, + rootHash.getData(), + new byte[10], + 0, + new byte[10], + 0, + new byte[10] + ); + UnicityTreeCertificate unicityTreeCertificate = new UnicityTreeCertificate(0, List.of()); byte[] technicalRecordHash = new byte[32]; byte[] shardConfigurationHash = new byte[32]; ShardTreeCertificate shardTreeCertificate = new ShardTreeCertificate( - new byte[32], List.of() + ShardId.decode(new byte[]{(byte) 0b10000000}), List.of() ); DataHash shardTreeCertificateRootHash = UnicityCertificate.calculateShardTreeCertificateRootHash( - inputRecord, - technicalRecordHash, - shardConfigurationHash, - shardTreeCertificate + inputRecord, + technicalRecordHash, + shardConfigurationHash, + shardTreeCertificate ); byte[] key = ByteBuffer.allocate(4) - .order(ByteOrder.BIG_ENDIAN) - .putInt(unicityTreeCertificate.getPartitionIdentifier()) - .array(); + .order(ByteOrder.BIG_ENDIAN) + .putInt(unicityTreeCertificate.getPartitionIdentifier()) + .array(); DataHash unicitySealHash = new DataHasher(HashAlgorithm.SHA256) - .update(CborSerializer.encodeByteString(new byte[]{(byte) 0x01})) // LEAF - .update(CborSerializer.encodeByteString(key)) - .update( - CborSerializer.encodeByteString( - new DataHasher(HashAlgorithm.SHA256) - .update( - CborSerializer.encodeByteString( - shardTreeCertificateRootHash.getData() - ) + .update(CborSerializer.encodeByteString(new byte[]{(byte) 0x01})) // LEAF + .update(CborSerializer.encodeByteString(key)) + .update( + CborSerializer.encodeByteString( + new DataHasher(HashAlgorithm.SHA256) + .update( + CborSerializer.encodeByteString( + shardTreeCertificateRootHash.getData() + ) + ) + .digest() + .getData() ) - .digest() - .getData() ) - ) - .digest(); + .digest(); UnicitySeal seal = new UnicitySeal( - 0, - (short) 0, - 0L, - 0L, - 0L, - null, - unicitySealHash.getData(), - null + (short) 0, + 0L, + 0L, + 0L, + null, + unicitySealHash.getData(), + null ); return new UnicityCertificate( - 0, - new InputRecord(0, 0, 0, null, rootHash.getImprint(), new byte[10], 0, - new byte[10], 0, new byte[10]), - technicalRecordHash, - shardConfigurationHash, - shardTreeCertificate, - new UnicityTreeCertificate(0, 0, List.of()), - seal.withSignatures( - Map.of( - "NODE", - signingService.sign( - new DataHasher(HashAlgorithm.SHA256).update(seal.toCbor()).digest() - ).encode() + new InputRecord(0, 0, null, rootHash.getData(), new byte[10], 0, + new byte[10], 0, new byte[10]), + technicalRecordHash, + shardConfigurationHash, + shardTreeCertificate, + new UnicityTreeCertificate(0, List.of()), + seal.withSignatures( + Map.of( + "NODE", + signingService.sign( + new DataHasher(HashAlgorithm.SHA256).update(seal.toCbor()).digest() + ).encode() + ) ) - ) ); } } diff --git a/src/test/java/org/unicitylabs/sdk/mtree/CommonPathTest.java b/src/test/java/org/unicitylabs/sdk/smt/CommonPathTest.java similarity index 95% rename from src/test/java/org/unicitylabs/sdk/mtree/CommonPathTest.java rename to src/test/java/org/unicitylabs/sdk/smt/CommonPathTest.java index 6535860..86555c0 100644 --- a/src/test/java/org/unicitylabs/sdk/mtree/CommonPathTest.java +++ b/src/test/java/org/unicitylabs/sdk/smt/CommonPathTest.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree; +package org.unicitylabs.sdk.smt; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/unicitylabs/sdk/mtree/plain/MerkleTreePathTest.java b/src/test/java/org/unicitylabs/sdk/smt/plain/MerkleTreePathTest.java similarity index 91% rename from src/test/java/org/unicitylabs/sdk/mtree/plain/MerkleTreePathTest.java rename to src/test/java/org/unicitylabs/sdk/smt/plain/MerkleTreePathTest.java index 652f2f2..5c46e38 100644 --- a/src/test/java/org/unicitylabs/sdk/mtree/plain/MerkleTreePathTest.java +++ b/src/test/java/org/unicitylabs/sdk/smt/plain/MerkleTreePathTest.java @@ -1,15 +1,12 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.exc.ValueInstantiationException; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.unicitylabs.sdk.mtree.MerkleTreePathVerificationResult; +import org.unicitylabs.sdk.smt.MerkleTreePathVerificationResult; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.HexConverter; import java.math.BigInteger; diff --git a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreePathFixture.java b/src/test/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePathFixture.java similarity index 91% rename from src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreePathFixture.java rename to src/test/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePathFixture.java index e417528..d050c26 100644 --- a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreePathFixture.java +++ b/src/test/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePathFixture.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; import java.util.List; import org.unicitylabs.sdk.crypto.hash.DataHasher; diff --git a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java b/src/test/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreeTest.java similarity index 96% rename from src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java rename to src/test/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreeTest.java index bb70203..5dc7b85 100644 --- a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java +++ b/src/test/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreeTest.java @@ -1,17 +1,18 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; -import java.lang.reflect.Field; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.unicitylabs.sdk.mtree.BranchExistsException; -import org.unicitylabs.sdk.mtree.LeafOutOfBoundsException; -import org.unicitylabs.sdk.mtree.MerkleTreePathVerificationResult; +import org.unicitylabs.sdk.smt.BranchExistsException; +import org.unicitylabs.sdk.smt.LeafOutOfBoundsException; +import org.unicitylabs.sdk.smt.MerkleTreePathVerificationResult; import org.unicitylabs.sdk.util.HexConverter; +import java.lang.reflect.Field; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.Map; + public class SparseMerkleTreeTest { private final SparseMerkleTreeRootNode root = SparseMerkleTreeRootNode.create( diff --git a/src/test/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreeTest.java b/src/test/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreeTest.java similarity index 96% rename from src/test/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreeTest.java rename to src/test/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreeTest.java index 3898909..99427cf 100644 --- a/src/test/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreeTest.java +++ b/src/test/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreeTest.java @@ -1,9 +1,9 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.unicitylabs.sdk.mtree.MerkleTreePathVerificationResult; -import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTree.LeafValue; +import org.unicitylabs.sdk.smt.MerkleTreePathVerificationResult; +import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTree.LeafValue; import java.math.BigInteger; import java.util.Map; import java.util.Map.Entry;