diff --git a/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java b/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java index 3ddd20d..26f5d50 100644 --- a/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java +++ b/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java @@ -1,12 +1,9 @@ package org.unicitylabs.sdk; +import org.unicitylabs.sdk.api.*; + 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.InclusionProofResponse; -import org.unicitylabs.sdk.api.StateId; /** * Client for handling state transitions of tokens, including submitting commitments and finalizing transactions. diff --git a/src/main/java/org/unicitylabs/sdk/api/BlockHeightResponse.java b/src/main/java/org/unicitylabs/sdk/api/BlockHeightResponse.java index b734175..7254a65 100644 --- a/src/main/java/org/unicitylabs/sdk/api/BlockHeightResponse.java +++ b/src/main/java/org/unicitylabs/sdk/api/BlockHeightResponse.java @@ -4,11 +4,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import java.util.Objects; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.serializer.json.JsonSerializationException; import org.unicitylabs.sdk.serializer.json.LongAsStringSerializer; +import java.util.Objects; + /** * Block height response. */ @@ -18,7 +19,7 @@ public class BlockHeightResponse { @JsonCreator private BlockHeightResponse( - @JsonProperty("blockNumber") long blockNumber + @JsonProperty("blockNumber") long blockNumber ) { this.blockNumber = blockNumber; } diff --git a/src/main/java/org/unicitylabs/sdk/api/CertificationData.java b/src/main/java/org/unicitylabs/sdk/api/CertificationData.java index a011017..2a43648 100644 --- a/src/main/java/org/unicitylabs/sdk/api/CertificationData.java +++ b/src/main/java/org/unicitylabs/sdk/api/CertificationData.java @@ -1,11 +1,7 @@ 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; @@ -13,15 +9,22 @@ import org.unicitylabs.sdk.predicate.UnlockScript; 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; @@ -29,10 +32,10 @@ public class CertificationData { private final byte[] unlockScript; CertificationData( - Predicate lockScript, - DataHash sourceStateHash, - DataHash transactionHash, - byte[] unlockScript + Predicate lockScript, + DataHash sourceStateHash, + DataHash transactionHash, + byte[] unlockScript ) { this.lockScript = lockScript; this.sourceStateHash = sourceStateHash; @@ -40,6 +43,10 @@ public class CertificationData { this.unlockScript = Arrays.copyOf(unlockScript, unlockScript.length); } + public int getVersion() { + return CertificationData.VERSION; + } + /** * Get lock script of certified transaction output. * @@ -83,13 +90,22 @@ public byte[] getUnlockScript() { * @return CertificationData */ public static CertificationData fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(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(0)), - new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(data.get(1))), - new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(data.get(2))), - CborDeserializer.decodeByteString(data.get(3)) + 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)) ); } @@ -104,9 +120,9 @@ public static CertificationData fromMintTransaction(MintTransaction transaction) SigningService signingService = MintSigningService.create(transaction.getTokenId()); return CertificationData.fromTransaction( - transaction, - PayToPublicKeyPredicateUnlockScript.create(transaction, signingService).getSignature() - .encode() + transaction, + PayToPublicKeyPredicateUnlockScript.create(transaction, signingService).getSignature() + .encode() ); } @@ -132,35 +148,28 @@ public static CertificationData fromTransaction(Transaction transaction, UnlockS */ public static CertificationData fromTransaction(Transaction transaction, byte[] unlockScript) { return new CertificationData( - transaction.getLockScript(), - transaction.getSourceStateHash(), - transaction.calculateTransactionHash(), - unlockScript + 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(); - } - /** * Serialize 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) + 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) + ) ); } @@ -171,16 +180,16 @@ public boolean equals(Object o) { } 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); + && 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)); + "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 54cd212..459f7f3 100644 --- a/src/main/java/org/unicitylabs/sdk/api/CertificationRequest.java +++ b/src/main/java/org/unicitylabs/sdk/api/CertificationRequest.java @@ -6,6 +6,8 @@ * 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; @@ -17,13 +19,17 @@ public class CertificationRequest { * @param certificationData transaction hash */ private CertificationRequest( - StateId stateId, - CertificationData certificationData + StateId stateId, + CertificationData certificationData ) { this.stateId = stateId; this.certificationData = certificationData; } + public int getVersion() { + return CertificationRequest.VERSION; + } + /** * Get state id. * @@ -50,7 +56,7 @@ public CertificationData getCertificationData() { */ public static CertificationRequest create(CertificationData certificationData) { return new CertificationRequest(StateId.fromCertificationData(certificationData), - certificationData); + certificationData); } /** @@ -59,10 +65,14 @@ public static CertificationRequest create(CertificationData certificationData) { * @return CBOR bytes */ public byte[] toCbor() { - return CborSerializer.encodeArray( - this.stateId.toCbor(), - this.certificationData.toCbor(), - CborSerializer.encodeUnsignedInteger(0) + 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/CertificationResponse.java b/src/main/java/org/unicitylabs/sdk/api/CertificationResponse.java index 79b0f5e..c7f08ed 100644 --- a/src/main/java/org/unicitylabs/sdk/api/CertificationResponse.java +++ b/src/main/java/org/unicitylabs/sdk/api/CertificationResponse.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; -import java.util.Objects; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.serializer.json.JsonSerializationException; @@ -21,7 +20,7 @@ public class CertificationResponse { */ @JsonCreator CertificationResponse( - @JsonProperty("status") CertificationStatus status + @JsonProperty("status") CertificationStatus status ) { this.status = status; } 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..35b46ec --- /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 1df8ea2..0237ee2 100644 --- a/src/main/java/org/unicitylabs/sdk/api/InclusionProof.java +++ b/src/main/java/org/unicitylabs/sdk/api/InclusionProof.java @@ -1,30 +1,39 @@ 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 SparseMerkleTreePath merkleTreePath; + private final InclusionCertificate inclusionCertificate; private final CertificationData certificationData; private final UnicityCertificate unicityCertificate; InclusionProof( - SparseMerkleTreePath merkleTreePath, - CertificationData certificationData, - UnicityCertificate unicityCertificate + CertificationData certificationData, + InclusionCertificate inclusionCertificate, + UnicityCertificate unicityCertificate ) { - this.merkleTreePath = Objects.requireNonNull(merkleTreePath, "Merkle tree path cannot be null.");; + Objects.requireNonNull(unicityCertificate, "Unicity certificate cannot be null."); + + this.inclusionCertificate = inclusionCertificate; this.certificationData = certificationData; - this.unicityCertificate = Objects.requireNonNull(unicityCertificate, "Unicity certificate cannot be null.");; + this.unicityCertificate = unicityCertificate; + } + + public int getVersion() { + return InclusionProof.VERSION; } /** @@ -32,8 +41,8 @@ public class InclusionProof { * * @return merkle tree path */ - public SparseMerkleTreePath getMerkleTreePath() { - return this.merkleTreePath; + public InclusionCertificate getInclusionCertificate() { + return this.inclusionCertificate; } /** @@ -61,12 +70,23 @@ public Optional getCertificationData() { * @return inclusion proof */ public static InclusionProof fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(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( - SparseMerkleTreePath.fromCbor(data.get(1)), - CborDeserializer.decodeNullable(data.get(0), CertificationData::fromCbor), - UnicityCertificate.fromCbor(data.get(2)) + CborDeserializer.decodeNullable(data.get(1), CertificationData::fromCbor), + CborDeserializer.decodeNullable(data.get(2), (inclusionCertificate) -> + InclusionCertificate.decode(CborDeserializer.decodeByteString(inclusionCertificate)) + ), + UnicityCertificate.fromCbor(data.get(3)) ); } @@ -76,10 +96,16 @@ public static InclusionProof fromCbor(byte[] bytes) { * @return CBOR bytes */ public byte[] toCbor() { - return CborSerializer.encodeArray( - CborSerializer.encodeOptional(this.certificationData, CertificationData::toCbor), - this.merkleTreePath.toCbor(), - this.unicityCertificate.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() + ) ); } @@ -89,21 +115,21 @@ public boolean equals(Object o) { return false; } InclusionProof that = (InclusionProof) o; - return Objects.equals(this.merkleTreePath, that.merkleTreePath) && Objects.equals( - this.certificationData, - that.certificationData); + return Objects.equals(this.inclusionCertificate, that.inclusionCertificate) && Objects.equals( + this.certificationData, + that.certificationData); } @Override public int hashCode() { - return Objects.hash(this.merkleTreePath, this.certificationData); + return Objects.hash(InclusionProof.VERSION, this.inclusionCertificate, this.certificationData); } @Override public String toString() { return String.format( - "InclusionProof{merkleTreePath=%s, certificationData=%s, unicityCertificate=%s}", - this.merkleTreePath, - this.certificationData, this.unicityCertificate); + "InclusionProof{certificationData=%s, inclusionCertificate=%s, unicityCertificate=%s}", + this.inclusionCertificate, + this.certificationData, this.unicityCertificate); } } diff --git a/src/main/java/org/unicitylabs/sdk/api/InclusionProofRequest.java b/src/main/java/org/unicitylabs/sdk/api/InclusionProofRequest.java index 7abed45..dedb252 100644 --- a/src/main/java/org/unicitylabs/sdk/api/InclusionProofRequest.java +++ b/src/main/java/org/unicitylabs/sdk/api/InclusionProofRequest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.Arrays; import java.util.Objects; @@ -19,7 +20,7 @@ public class InclusionProofRequest { */ @JsonCreator public InclusionProofRequest( - @JsonProperty("stateId") StateId stateId + @JsonProperty("stateId") StateId stateId ) { Objects.requireNonNull(stateId, "stateId cannot be null"); diff --git a/src/main/java/org/unicitylabs/sdk/api/InclusionProofResponse.java b/src/main/java/org/unicitylabs/sdk/api/InclusionProofResponse.java index 5a44091..6f0a775 100644 --- a/src/main/java/org/unicitylabs/sdk/api/InclusionProofResponse.java +++ b/src/main/java/org/unicitylabs/sdk/api/InclusionProofResponse.java @@ -1,9 +1,10 @@ package org.unicitylabs.sdk.api; -import java.util.List; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import java.util.List; + /** * Inclusion proof response. */ @@ -18,8 +19,8 @@ public class InclusionProofResponse { * @param inclusionProof inclusion proof */ InclusionProofResponse( - long blockNumber, - InclusionProof inclusionProof + long blockNumber, + InclusionProof inclusionProof ) { this.blockNumber = blockNumber; this.inclusionProof = inclusionProof; @@ -43,8 +44,8 @@ public InclusionProof getInclusionProof() { public static InclusionProofResponse fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); return new InclusionProofResponse( - CborDeserializer.decodeUnsignedInteger(data.get(0)).asLong(), - InclusionProof.fromCbor(data.get(1)) + CborDeserializer.decodeUnsignedInteger(data.get(0)).asLong(), + InclusionProof.fromCbor(data.get(1)) ); } @@ -55,8 +56,8 @@ public static InclusionProofResponse fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeArray( - CborSerializer.encodeUnsignedInteger(this.blockNumber), - this.inclusionProof.toCbor() + CborSerializer.encodeUnsignedInteger(this.blockNumber), + this.inclusionProof.toCbor() ); } diff --git a/src/main/java/org/unicitylabs/sdk/api/JsonRpcAggregatorClient.java b/src/main/java/org/unicitylabs/sdk/api/JsonRpcAggregatorClient.java index 4471f2e..1c47e0d 100644 --- a/src/main/java/org/unicitylabs/sdk/api/JsonRpcAggregatorClient.java +++ b/src/main/java/org/unicitylabs/sdk/api/JsonRpcAggregatorClient.java @@ -1,13 +1,14 @@ package org.unicitylabs.sdk.api; -import static com.google.common.net.HttpHeaders.AUTHORIZATION; +import org.unicitylabs.sdk.api.jsonrpc.JsonRpcHttpTransport; +import org.unicitylabs.sdk.util.HexConverter; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; -import org.unicitylabs.sdk.api.jsonrpc.JsonRpcHttpTransport; -import org.unicitylabs.sdk.util.HexConverter; + +import static com.google.common.net.HttpHeaders.AUTHORIZATION; /** * Default aggregator client. @@ -48,20 +49,20 @@ public JsonRpcAggregatorClient(String url, String apiKey) { */ @Override public CompletableFuture submitCertificationRequest( - CertificationData certificationData + CertificationData certificationData ) { CertificationRequest request = CertificationRequest.create( - Objects.requireNonNull(certificationData, "certificationData cannot be null")); + Objects.requireNonNull(certificationData, "certificationData cannot be null")); Map> headers = this.apiKey == null - ? Map.of() - : Map.of(AUTHORIZATION, List.of(String.format("Bearer %s", this.apiKey))); + ? Map.of() + : Map.of(AUTHORIZATION, List.of(String.format("Bearer %s", this.apiKey))); return this.transport.request( - "certification_request", - HexConverter.encode(request.toCbor()), - CertificationResponse.class, - headers + "certification_request", + HexConverter.encode(request.toCbor()), + CertificationResponse.class, + headers ); } @@ -74,11 +75,11 @@ public CompletableFuture submitCertificationRequest( @Override public CompletableFuture getInclusionProof(StateId stateId) { InclusionProofRequest request = new InclusionProofRequest( - Objects.requireNonNull(stateId, "stateId cannot be null")); + Objects.requireNonNull(stateId, "stateId cannot be null")); return this.transport - .request("get_inclusion_proof.v2", request, String.class) - .thenApply(response -> InclusionProofResponse.fromCbor(HexConverter.decode(response))); + .request("get_inclusion_proof.v2", request, String.class) + .thenApply(response -> InclusionProofResponse.fromCbor(HexConverter.decode(response))); } /** @@ -89,6 +90,6 @@ public CompletableFuture getInclusionProof(StateId state @Override public CompletableFuture getBlockHeight() { return this.transport.request("get_block_height", Map.of(), BlockHeightResponse.class) - .thenApply(BlockHeightResponse::getBlockNumber); + .thenApply(BlockHeightResponse::getBlockNumber); } } diff --git a/src/main/java/org/unicitylabs/sdk/api/StateId.java b/src/main/java/org/unicitylabs/sdk/api/StateId.java index 4cb910c..3da77e9 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,13 +8,14 @@ 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; + /** * Represents a state identifier for requests. */ -public final class StateId { +public class StateId { private final DataHash hash; @@ -32,15 +32,6 @@ public byte[] getData() { return this.hash.getData(); } - /** - * Returns the hash imprint bytes. - * - * @return state id imprint bytes - */ - public byte[] getImprint() { - return this.hash.getImprint(); - } - /** * Deserializes a state id from CBOR. * @@ -49,7 +40,7 @@ public byte[] getImprint() { */ public static StateId fromCbor(byte[] bytes) { return new StateId( - new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(bytes))); + new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(bytes))); } /** @@ -63,7 +54,7 @@ public static StateId fromCertificationData(CertificationData certificationData) Objects.requireNonNull(certificationData, "Certification data cannot be null"); return StateId.create(certificationData.getLockScript(), - certificationData.getSourceStateHash()); + certificationData.getSourceStateHash()); } /** @@ -81,13 +72,13 @@ public static StateId fromTransaction(Transaction transaction) { private static StateId create(Predicate predicate, DataHash stateHash) { DataHash hash = new DataHasher(HashAlgorithm.SHA256) - .update( - CborSerializer.encodeArray( - EncodedPredicate.fromPredicate(predicate).toCbor(), - CborSerializer.encodeByteString(stateHash.getData()) + .update( + CborSerializer.encodeArray( + EncodedPredicate.fromPredicate(predicate).toCbor(), + CborSerializer.encodeByteString(stateHash.getData()) + ) ) - ) - .digest(); + .digest(); return new StateId(hash); } @@ -101,15 +92,6 @@ public byte[] toCbor() { return CborSerializer.encodeByteString(this.getData()); } - /** - * Converts this state id to a {@link BitString}. - * - * @return bit string representation of this state id - */ - public BitString toBitString() { - return new BitString(this.getImprint()); - } - @Override public boolean equals(Object o) { if (!(o instanceof StateId)) { @@ -126,6 +108,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())); } } 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 89be419..a7bc2dd 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/InputRecord.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/InputRecord.java @@ -1,19 +1,22 @@ package org.unicitylabs.sdk.api.bft; -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.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; + /** * 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,21 +28,19 @@ public class InputRecord { private final byte[] executedTransactionsHash; InputRecord( - int version, - long roundNumber, - long epoch, - byte[] previousHash, - byte[] hash, - byte[] summaryValue, - long timestamp, - byte[] blockHash, - long sumOfEarnedFees, - byte[] executedTransactionsHash + long roundNumber, + long epoch, + byte[] previousHash, + byte[] hash, + byte[] summaryValue, + long timestamp, + byte[] blockHash, + long sumOfEarnedFees, + byte[] executedTransactionsHash ) { 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 +52,8 @@ public class InputRecord { this.executedTransactionsHash = executedTransactionsHash; } - /** - * Get version. - * - * @return version - */ public int getVersion() { - return this.version; + return InputRecord.VERSION; } /** @@ -85,7 +81,7 @@ public long getEpoch() { */ public byte[] getPreviousHash() { return this.previousHash != null ? Arrays.copyOf(this.previousHash, this.previousHash.length) - : null; + : null; } /** @@ -140,7 +136,7 @@ public long getSumOfEarnedFees() { */ public byte[] getExecutedTransactionsHash() { return this.executedTransactionsHash != null ? Arrays.copyOf(this.executedTransactionsHash, - this.executedTransactionsHash.length) : null; + this.executedTransactionsHash.length) : null; } /** @@ -151,19 +147,26 @@ 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), - CborDeserializer.decodeByteString(data.get(4)), - CborDeserializer.decodeByteString(data.get(5)), - CborDeserializer.decodeUnsignedInteger(data.get(6)).asLong(), - CborDeserializer.decodeNullable(data.get(7), CborDeserializer::decodeByteString), - CborDeserializer.decodeUnsignedInteger(data.get(8)).asLong(), - CborDeserializer.decodeNullable(data.get(9), CborDeserializer::decodeByteString) + CborDeserializer.decodeUnsignedInteger(data.get(1)).asLong(), + CborDeserializer.decodeUnsignedInteger(data.get(2)).asLong(), + CborDeserializer.decodeNullable(data.get(3), CborDeserializer::decodeByteString), + CborDeserializer.decodeByteString(data.get(4)), + CborDeserializer.decodeByteString(data.get(5)), + CborDeserializer.decodeUnsignedInteger(data.get(6)).asLong(), + CborDeserializer.decodeNullable(data.get(7), CborDeserializer::decodeByteString), + CborDeserializer.decodeUnsignedInteger(data.get(8)).asLong(), + CborDeserializer.decodeNullable(data.get(9), CborDeserializer::decodeByteString) ); } @@ -174,20 +177,20 @@ public static InputRecord fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeTag( - 1008, - CborSerializer.encodeArray( - CborSerializer.encodeUnsignedInteger(this.version), - CborSerializer.encodeUnsignedInteger(this.roundNumber), - CborSerializer.encodeUnsignedInteger(this.epoch), - CborSerializer.encodeOptional(this.previousHash, CborSerializer::encodeByteString), - CborSerializer.encodeByteString(this.hash), - CborSerializer.encodeByteString(this.summaryValue), - CborSerializer.encodeUnsignedInteger(this.timestamp), - CborSerializer.encodeOptional(this.blockHash, CborSerializer::encodeByteString), - CborSerializer.encodeUnsignedInteger(this.sumOfEarnedFees), - CborSerializer.encodeOptional(this.executedTransactionsHash, - CborSerializer::encodeByteString) - )); + InputRecord.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(InputRecord.VERSION), + CborSerializer.encodeUnsignedInteger(this.roundNumber), + CborSerializer.encodeUnsignedInteger(this.epoch), + CborSerializer.encodeOptional(this.previousHash, CborSerializer::encodeByteString), + CborSerializer.encodeByteString(this.hash), + CborSerializer.encodeByteString(this.summaryValue), + CborSerializer.encodeUnsignedInteger(this.timestamp), + CborSerializer.encodeOptional(this.blockHash, CborSerializer::encodeByteString), + CborSerializer.encodeUnsignedInteger(this.sumOfEarnedFees), + CborSerializer.encodeOptional(this.executedTransactionsHash, + CborSerializer::encodeByteString) + )); } @Override @@ -196,41 +199,40 @@ public boolean equals(Object o) { return false; } InputRecord that = (InputRecord) o; - return Objects.equals(this.version, that.version) && 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, - that.summaryValue) && Objects.equals(this.timestamp, that.timestamp) - && Objects.deepEquals(this.blockHash, that.blockHash) && Objects.equals( - this.sumOfEarnedFees, that.sumOfEarnedFees) && Objects.deepEquals( - this.executedTransactionsHash, that.executedTransactionsHash); + 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, + that.summaryValue) && Objects.equals(this.timestamp, that.timestamp) + && Objects.deepEquals(this.blockHash, that.blockHash) && Objects.equals( + this.sumOfEarnedFees, that.sumOfEarnedFees) && Objects.deepEquals( + this.executedTransactionsHash, that.executedTransactionsHash); } @Override public int hashCode() { - return Objects.hash(this.version, this.roundNumber, this.epoch, - Arrays.hashCode(this.previousHash), - Arrays.hashCode(this.hash), Arrays.hashCode(this.summaryValue), this.timestamp, - Arrays.hashCode(this.blockHash), - this.sumOfEarnedFees, Arrays.hashCode(this.executedTransactionsHash)); + 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), + this.sumOfEarnedFees, Arrays.hashCode(this.executedTransactionsHash)); } @Override public String toString() { - return String.format("InputRecord{version=%s, 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, - HexConverter.encode(this.hash), - HexConverter.encode(this.summaryValue), - this.timestamp, - this.blockHash != null ? HexConverter.encode(this.blockHash) : null, - this.sumOfEarnedFees, - this.executedTransactionsHash != null ? HexConverter.encode(this.executedTransactionsHash) - : null + return String.format("InputRecord{roundNumber=%s, epoch=%s, previousHash=%s, " + + "hash=%s, summaryValue=%s, timestamp=%s, blockHash=%s, sumOfEarnedFees=%s, " + + "executedTransactionsHash=%s}", + this.roundNumber, + this.epoch, + this.previousHash != null ? HexConverter.encode(this.previousHash) : null, + HexConverter.encode(this.hash), + HexConverter.encode(this.summaryValue), + this.timestamp, + this.blockHash != null ? HexConverter.encode(this.blockHash) : null, + this.sumOfEarnedFees, + this.executedTransactionsHash != null ? HexConverter.encode(this.executedTransactionsHash) + : null ); } } diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/RootTrustBase.java b/src/main/java/org/unicitylabs/sdk/api/bft/RootTrustBase.java index 6f51ffa..b4fe31d 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/RootTrustBase.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/RootTrustBase.java @@ -4,14 +4,15 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.serializer.json.JsonSerializationException; +import org.unicitylabs.sdk.serializer.json.LongAsStringSerializer; + import java.util.Arrays; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; -import org.unicitylabs.sdk.serializer.UnicityObjectMapper; -import org.unicitylabs.sdk.serializer.json.JsonSerializationException; -import org.unicitylabs.sdk.serializer.json.LongAsStringSerializer; /** * Root trust base information. @@ -31,16 +32,16 @@ public class RootTrustBase { @JsonCreator RootTrustBase( - @JsonProperty("version") long version, - @JsonProperty("networkId") int networkId, - @JsonProperty("epoch") long epoch, - @JsonProperty("epochStartRound") long epochStartRound, - @JsonProperty("rootNodes") Set rootNodes, - @JsonProperty("quorumThreshold") long quorumThreshold, - @JsonProperty("stateHash") byte[] stateHash, - @JsonProperty("changeRecordHash") byte[] changeRecordHash, - @JsonProperty("previousEntryHash") byte[] previousEntryHash, - @JsonProperty("signatures") Map signatures + @JsonProperty("version") long version, + @JsonProperty("networkId") int networkId, + @JsonProperty("epoch") long epoch, + @JsonProperty("epochStartRound") long epochStartRound, + @JsonProperty("rootNodes") Set rootNodes, + @JsonProperty("quorumThreshold") long quorumThreshold, + @JsonProperty("stateHash") byte[] stateHash, + @JsonProperty("changeRecordHash") byte[] changeRecordHash, + @JsonProperty("previousEntryHash") byte[] previousEntryHash, + @JsonProperty("signatures") Map signatures ) { this.version = version; this.networkId = networkId; @@ -50,16 +51,16 @@ public class RootTrustBase { this.quorumThreshold = quorumThreshold; this.stateHash = Arrays.copyOf(stateHash, stateHash.length); this.changeRecordHash = changeRecordHash == null - ? null - : Arrays.copyOf(changeRecordHash, changeRecordHash.length); + ? null + : Arrays.copyOf(changeRecordHash, changeRecordHash.length); this.previousEntryHash = previousEntryHash == null - ? null - : Arrays.copyOf(previousEntryHash, previousEntryHash.length); + ? null + : Arrays.copyOf(previousEntryHash, previousEntryHash.length); this.signatures = signatures.entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - e -> Arrays.copyOf(e.getValue(), e.getValue().length) - )); + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> Arrays.copyOf(e.getValue(), e.getValue().length) + )); } /** @@ -136,8 +137,8 @@ public byte[] getStateHash() { */ public byte[] getChangeRecordHash() { return this.changeRecordHash == null - ? null - : Arrays.copyOf(this.changeRecordHash, this.changeRecordHash.length); + ? null + : Arrays.copyOf(this.changeRecordHash, this.changeRecordHash.length); } /** @@ -147,8 +148,8 @@ public byte[] getChangeRecordHash() { */ public byte[] getPreviousEntryHash() { return this.previousEntryHash == null - ? null - : Arrays.copyOf(this.previousEntryHash, this.previousEntryHash.length); + ? null + : Arrays.copyOf(this.previousEntryHash, this.previousEntryHash.length); } /** @@ -158,11 +159,11 @@ public byte[] getPreviousEntryHash() { */ public Map getSignatures() { return Map.copyOf( - this.signatures.entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - e -> Arrays.copyOf(e.getValue(), e.getValue().length) - )) + this.signatures.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> Arrays.copyOf(e.getValue(), e.getValue().length) + )) ); } @@ -204,9 +205,9 @@ public static class NodeInfo { @JsonCreator NodeInfo( - @JsonProperty("nodeId") String nodeId, - @JsonProperty("sigKey") byte[] signingKey, - @JsonProperty("stake") long stakedAmount + @JsonProperty("nodeId") String nodeId, + @JsonProperty("sigKey") byte[] signingKey, + @JsonProperty("stake") long stakedAmount ) { this.nodeId = nodeId; this.signingKey = Arrays.copyOf(signingKey, signingKey.length); 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..49356bd --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/api/bft/ShardId.java @@ -0,0 +1,123 @@ +package org.unicitylabs.sdk.api.bft; + +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; + +import java.util.Arrays; +import java.util.Objects; + +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 d1aedc8..4b32070 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/ShardTreeCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/ShardTreeCertificate.java @@ -1,29 +1,36 @@ 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 byte[] shard; + private final ShardId shard; private final List siblingHashList; - ShardTreeCertificate(byte[] shard, 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 = Arrays.copyOf(shard, shard.length); + this.shard = shard; this.siblingHashList = siblingHashList.stream() - .map(hash -> Arrays.copyOf(hash, hash.length)) - .collect(Collectors.toList()); + .map(hash -> Arrays.copyOf(hash, hash.length)) + .collect(Collectors.toList()); + } + + public int getVersion() { + return ShardTreeCertificate.VERSION; } /** @@ -31,8 +38,8 @@ public class ShardTreeCertificate { * * @return shard */ - public byte[] getShard() { - return Arrays.copyOf(this.shard, this.shard.length); + public ShardId getShard() { + return this.shard; } /** @@ -42,8 +49,8 @@ public byte[] getShard() { */ public List getSiblingHashList() { return this.siblingHashList.stream() - .map(hash -> Arrays.copyOf(hash, hash.length)) - .collect(Collectors.toList()); + .map(hash -> Arrays.copyOf(hash, hash.length)) + .collect(Collectors.toList()); } /** @@ -53,13 +60,22 @@ public List getSiblingHashList() { * @return shard tree certificate */ public static ShardTreeCertificate fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(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( - CborDeserializer.decodeByteString(data.get(0)), - CborDeserializer.decodeArray(data.get(1)).stream() - .map(CborDeserializer::decodeByteString) - .collect(Collectors.toList()) + ShardId.decode(CborDeserializer.decodeByteString(data.get(1))), + CborDeserializer.decodeArray(data.get(2)).stream() + .map(CborDeserializer::decodeByteString) + .collect(Collectors.toList()) ); } @@ -69,13 +85,17 @@ public static ShardTreeCertificate fromCbor(byte[] 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) - ) + 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) + ) + ) ); } @@ -86,17 +106,17 @@ public boolean equals(Object o) { } ShardTreeCertificate that = (ShardTreeCertificate) o; return Objects.deepEquals(this.shard, that.shard) && Objects.equals( - this.siblingHashList, that.siblingHashList); + this.siblingHashList, that.siblingHashList); } @Override public int hashCode() { - return Objects.hash(Arrays.hashCode(this.shard), this.siblingHashList); + return Objects.hash(ShardTreeCertificate.VERSION, this.shard, this.siblingHashList); } @Override public String toString() { return String.format("ShardTreeCertificate{shard=%s, siblingHashList=%s}", - HexConverter.encode(this.shard), this.siblingHashList); + 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 a60f9cb..81ff6a2 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/UnicityCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/UnicityCertificate.java @@ -1,22 +1,24 @@ 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 int version; private final InputRecord inputRecord; private final byte[] technicalRecordHash; private final byte[] shardConfigurationHash; @@ -25,13 +27,12 @@ public class UnicityCertificate { private final UnicitySeal unicitySeal; UnicityCertificate( - int version, - InputRecord inputRecord, - byte[] technicalRecordHash, - byte[] shardConfigurationHash, - ShardTreeCertificate shardTreeCertificate, - UnicityTreeCertificate unicityTreeCertificate, - UnicitySeal unicitySeal + 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"); @@ -39,14 +40,13 @@ public class UnicityCertificate { 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; + ? Arrays.copyOf(technicalRecordHash, technicalRecordHash.length) + : null; this.shardConfigurationHash = Arrays.copyOf( - shardConfigurationHash, - shardConfigurationHash.length + shardConfigurationHash, + shardConfigurationHash.length ); this.shardTreeCertificate = shardTreeCertificate; this.unicityTreeCertificate = unicityTreeCertificate; @@ -59,7 +59,7 @@ public class UnicityCertificate { * @return certificate version */ public int getVersion() { - return this.version; + return UnicityCertificate.VERSION; } /** @@ -78,8 +78,8 @@ public InputRecord getInputRecord() { */ public byte[] getTechnicalRecordHash() { return this.technicalRecordHash != null - ? Arrays.copyOf(this.technicalRecordHash, this.technicalRecordHash.length) - : null; + ? Arrays.copyOf(this.technicalRecordHash, this.technicalRecordHash.length) + : null; } /** @@ -128,33 +128,33 @@ public UnicitySeal getUnicitySeal() { * @return root hash */ public static DataHash calculateShardTreeCertificateRootHash( - InputRecord inputRecord, - byte[] technicalRecordHash, - byte[] shardConfigurationHash, - ShardTreeCertificate shardTreeCertificate + 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(); + .update(inputRecord.toCbor()) + .update( + CborSerializer.encodeOptional(technicalRecordHash, CborSerializer::encodeByteString)) + .update(CborSerializer.encodeByteString(shardConfigurationHash)) + .digest(); - byte[] shardId = shardTreeCertificate.getShard(); + ShardId shardId = shardTreeCertificate.getShard(); List siblingHashes = shardTreeCertificate.getSiblingHashList(); for (int i = 0; i < siblingHashes.size(); i++) { - boolean isRight = shardId[(shardId.length - 1) - (i / 8)] == 1; + boolean isRight = shardId.getBit(shardId.getLength() - 1 - i) == 1; if (isRight) { rootHash = new DataHasher(HashAlgorithm.SHA256) - .update(siblingHashes.get(i)) - .update(rootHash.getData()) - .digest(); + .update(CborSerializer.encodeByteString(siblingHashes.get(i))) + .update(CborSerializer.encodeByteString(rootHash.getData())) + .digest(); } else { rootHash = new DataHasher(HashAlgorithm.SHA256) - .update(rootHash.getData()) - .update(siblingHashes.get(i)) - .digest(); + .update(CborSerializer.encodeByteString(rootHash.getData())) + .update(CborSerializer.encodeByteString(siblingHashes.get(i))) + .digest(); } } @@ -169,17 +169,24 @@ public static DataHash calculateShardTreeCertificateRootHash( * @return unicity certificate */ public static UnicityCertificate fromCbor(byte[] bytes) { - CborTag tag = CborDeserializer.decodeTag(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( - 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)) + 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)) ); } @@ -190,17 +197,17 @@ public static UnicityCertificate fromCbor(byte[] 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() - )); + 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 @@ -209,33 +216,32 @@ public boolean equals(Object o) { return false; } 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); + 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); } @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); + return Objects.hash(UnicityCertificate.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 + 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 ); } } 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 3ebce9b..73f89e6 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,18 +26,16 @@ public class UnicitySeal { private final LinkedHashMap signatures; UnicitySeal( - int version, - short networkId, - long rootChainRoundNumber, - long epoch, - long timestamp, - byte[] previousHash, - byte[] hash, - Map signatures + short networkId, + long rootChainRoundNumber, + long epoch, + long timestamp, + byte[] previousHash, + byte[] hash, + Map signatures ) { Objects.requireNonNull(hash, "Hash cannot be null"); - this.version = version; this.networkId = networkId; this.rootChainRoundNumber = rootChainRoundNumber; this.epoch = epoch; @@ -46,21 +43,21 @@ public class UnicitySeal { this.previousHash = previousHash; this.hash = hash; this.signatures = signatures == null - ? null - : signatures.entrySet().stream() - .map(entry -> Map.entry( - entry.getKey(), - Arrays.copyOf(entry.getValue(), entry.getValue().length) - ) - ) - .collect( - Collectors.toMap( - Map.Entry::getKey, - Map.Entry::getValue, - (e1, e2) -> e1, - LinkedHashMap::new - ) - ); + ? null + : signatures.entrySet().stream() + .map(entry -> Map.entry( + entry.getKey(), + Arrays.copyOf(entry.getValue(), entry.getValue().length) + ) + ) + .collect( + Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (e1, e2) -> e1, + LinkedHashMap::new + ) + ); } /** @@ -71,24 +68,18 @@ public class UnicitySeal { */ public UnicitySeal withSignatures(Map signatures) { return new UnicitySeal( - this.version, - this.networkId, - this.rootChainRoundNumber, - this.epoch, - this.timestamp, - this.previousHash, - this.hash, - signatures + this.networkId, + this.rootChainRoundNumber, + this.epoch, + this.timestamp, + this.previousHash, + this.hash, + signatures ); } - /** - * Get the version. - * - * @return version - */ public int getVersion() { - return this.version; + return UnicitySeal.VERSION; } /** @@ -134,7 +125,7 @@ public long getTimestamp() { */ public byte[] getPreviousHash() { return this.previousHash != null ? Arrays.copyOf(this.previousHash, this.previousHash.length) - : null; + : null; } /** @@ -153,21 +144,21 @@ public byte[] getHash() { */ public Map getSignatures() { return this.signatures == null - ? null - : this.signatures.entrySet().stream() - .map(entry -> Map.entry( - entry.getKey(), - Arrays.copyOf(entry.getValue(), entry.getValue().length) - ) - ) - .collect( - Collectors.toMap( - Map.Entry::getKey, - Map.Entry::getValue, - (e1, e2) -> e1, - LinkedHashMap::new - ) - ); + ? null + : this.signatures.entrySet().stream() + .map(entry -> Map.entry( + entry.getKey(), + Arrays.copyOf(entry.getValue(), entry.getValue().length) + ) + ) + .collect( + Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (e1, e2) -> e1, + LinkedHashMap::new + ) + ); } /** @@ -178,24 +169,31 @@ 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(), - CborDeserializer.decodeUnsignedInteger(data.get(4)).asLong(), - CborDeserializer.decodeNullable(data.get(5), CborDeserializer::decodeByteString), - CborDeserializer.decodeByteString(data.get(6)), - CborDeserializer.decodeMap(data.get(7)).stream() - .collect( - Collectors.toMap( - entry -> CborDeserializer.decodeTextString(entry.getKey()), - entry -> CborDeserializer.decodeByteString(entry.getValue() + CborDeserializer.decodeUnsignedInteger(data.get(1)).asShort(), + CborDeserializer.decodeUnsignedInteger(data.get(2)).asLong(), + CborDeserializer.decodeUnsignedInteger(data.get(3)).asLong(), + CborDeserializer.decodeUnsignedInteger(data.get(4)).asLong(), + CborDeserializer.decodeNullable(data.get(5), CborDeserializer::decodeByteString), + CborDeserializer.decodeByteString(data.get(6)), + CborDeserializer.decodeMap(data.get(7)).stream() + .collect( + Collectors.toMap( + entry -> CborDeserializer.decodeTextString(entry.getKey()), + entry -> CborDeserializer.decodeByteString(entry.getValue() + ) + ) ) - ) - ) ); } @@ -206,30 +204,30 @@ public static UnicitySeal fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeTag( - 1001, - CborSerializer.encodeArray( - CborSerializer.encodeUnsignedInteger(this.version), - CborSerializer.encodeUnsignedInteger(this.networkId), - CborSerializer.encodeUnsignedInteger(this.rootChainRoundNumber), - CborSerializer.encodeUnsignedInteger(this.epoch), - CborSerializer.encodeUnsignedInteger(this.timestamp), - CborSerializer.encodeOptional(this.previousHash, CborSerializer::encodeByteString), - CborSerializer.encodeByteString(this.hash), - CborSerializer.encodeOptional( - this.signatures, - (signatures) -> CborSerializer.encodeMap( - new CborMap( - signatures.entrySet().stream() - .map(entry -> new CborMap.Entry( - CborSerializer.encodeTextString(entry.getKey()), - CborSerializer.encodeByteString(entry.getValue()) - ) + UnicitySeal.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(UnicitySeal.VERSION), + CborSerializer.encodeUnsignedInteger(this.networkId), + CborSerializer.encodeUnsignedInteger(this.rootChainRoundNumber), + CborSerializer.encodeUnsignedInteger(this.epoch), + CborSerializer.encodeUnsignedInteger(this.timestamp), + CborSerializer.encodeOptional(this.previousHash, CborSerializer::encodeByteString), + CborSerializer.encodeByteString(this.hash), + CborSerializer.encodeOptional( + this.signatures, + (signatures) -> CborSerializer.encodeMap( + new CborMap( + signatures.entrySet().stream() + .map(entry -> new CborMap.Entry( + CborSerializer.encodeTextString(entry.getKey()), + CborSerializer.encodeByteString(entry.getValue()) + ) + ) + .collect(Collectors.toSet()) + ) ) - .collect(Collectors.toSet()) ) - ) ) - ) ); } @@ -240,14 +238,13 @@ public byte[] toCbor() { */ public byte[] toCborWithoutSignatures() { return new UnicitySeal( - this.version, - this.networkId, - this.rootChainRoundNumber, - this.epoch, - this.timestamp, - this.previousHash, - this.hash, - null + this.networkId, + this.rootChainRoundNumber, + this.epoch, + this.timestamp, + this.previousHash, + this.hash, + null ).toCbor(); } @@ -257,38 +254,37 @@ public boolean equals(Object o) { return false; } UnicitySeal that = (UnicitySeal) o; - return Objects.equals(this.version, that.version) && 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) - && Objects.deepEquals(this.hash, that.hash) && Objects.equals(this.signatures, - that.signatures); + 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) + && Objects.deepEquals(this.hash, that.hash) && Objects.equals(this.signatures, + that.signatures); } @Override public int hashCode() { - return Objects.hash(this.version, this.networkId, this.rootChainRoundNumber, this.epoch, - this.timestamp, - Arrays.hashCode(this.previousHash), Arrays.hashCode(this.hash), this.signatures); + return Objects.hash(UnicitySeal.VERSION, this.networkId, this.rootChainRoundNumber, this.epoch, + this.timestamp, + Arrays.hashCode(this.previousHash), Arrays.hashCode(this.hash), this.signatures); } @Override public String toString() { return String.format( - "UnicitySeal{version=%s, networkId=%s, rootChainRoundNumber=%s, epoch=%s, timestamp=%s, " - + "previousHash=%s, hash=%s, signatures=%s", - this.version, - this.networkId, - this.rootChainRoundNumber, - this.epoch, - this.timestamp, - this.previousHash != null ? HexConverter.encode(this.previousHash) : null, - HexConverter.encode(this.hash), - this.signatures.entrySet() - .stream() - .map(entry -> String.format("%s: %s", entry.getKey(), - HexConverter.encode(entry.getValue()))) - .collect(Collectors.toList()) + "UnicitySeal{networkId=%s, rootChainRoundNumber=%s, epoch=%s, timestamp=%s, " + + "previousHash=%s, hash=%s, signatures=%s", + this.networkId, + this.rootChainRoundNumber, + this.epoch, + this.timestamp, + this.previousHash != null ? HexConverter.encode(this.previousHash) : null, + HexConverter.encode(this.hash), + this.signatures.entrySet() + .stream() + .map(entry -> String.format("%s: %s", entry.getKey(), + HexConverter.encode(entry.getValue()))) + .collect(Collectors.toList()) ); } } 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 5143c2c..5dc3b4a 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 + 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,15 +64,22 @@ 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) - .collect(Collectors.toList()) + CborDeserializer.decodeUnsignedInteger(data.get(1)).asInt(), + CborDeserializer.decodeArray(data.get(2)).stream() + .map(HashStep::fromCbor) + .collect(Collectors.toList()) ); } @@ -83,14 +90,14 @@ public static UnicityTreeCertificate fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeTag( - 1014, - CborSerializer.encodeArray( - CborSerializer.encodeUnsignedInteger(this.version), - CborSerializer.encodeUnsignedInteger(this.partitionIdentifier), - CborSerializer.encodeArray(this.steps.stream() - .map(HashStep::toCbor) - .toArray(byte[][]::new)) - )); + UnicityTreeCertificate.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(UnicityTreeCertificate.VERSION), + CborSerializer.encodeUnsignedInteger(this.partitionIdentifier), + CborSerializer.encodeArray(this.steps.stream() + .map(HashStep::toCbor) + .toArray(byte[][]::new)) + )); } @Override @@ -99,20 +106,19 @@ public boolean equals(Object o) { return false; } UnicityTreeCertificate that = (UnicityTreeCertificate) o; - return Objects.equals(this.version, that.version) && Objects.equals( - this.partitionIdentifier, that.partitionIdentifier) && Objects.equals(this.steps, - that.steps); + 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); } /** @@ -158,8 +164,8 @@ public static HashStep fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); return new HashStep( - CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(), - CborDeserializer.decodeByteString(data.get(1)) + CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(), + CborDeserializer.decodeByteString(data.get(1)) ); } @@ -170,8 +176,8 @@ public static HashStep fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeArray( - CborSerializer.encodeUnsignedInteger(this.key), - CborSerializer.encodeByteString(this.hash) + CborSerializer.encodeUnsignedInteger(this.key), + CborSerializer.encodeByteString(this.hash) ); } @@ -182,7 +188,7 @@ public boolean equals(Object o) { } HashStep hashStep = (HashStep) o; return Objects.equals(this.key, hashStep.key) && Objects.deepEquals(this.hash, - hashStep.hash); + hashStep.hash); } @Override @@ -193,7 +199,7 @@ public int hashCode() { @Override public String toString() { return String.format("UnicityTreeCertificate.HashStep{key=%s, hash=%s", - this.key, HexConverter.encode(this.hash)); + this.key, HexConverter.encode(this.hash)); } } } 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 26d3018..4b8daad 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,20 +1,22 @@ 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; + /** * Verifies unicity certificate within an inclusion proof. */ public class UnicityCertificateVerification { - private UnicityCertificateVerification() {} + private UnicityCertificateVerification() { + } /** * Runs unicity certificate verification rules against the provided inclusion proof. @@ -24,23 +26,17 @@ private UnicityCertificateVerification() {} * @return verification result aggregating rule outcomes */ 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()); + InclusionProof inclusionProof) { + ArrayList> results = new ArrayList<>(); + VerificationResult result = UnicitySealHashMatchesWithRootHashRule.verify(inclusionProof.getUnicityCertificate()); results.add(result); if (result.getStatus() != VerificationStatus.OK) { return UnicityCertificateVerificationResult.fail(results); } result = UnicitySealQuorumSignaturesVerificationRule.verify(trustBase, - inclusionProof.getUnicityCertificate() - .getUnicitySeal()); + inclusionProof.getUnicityCertificate() + .getUnicitySeal()); results.add(result); if (result.getStatus() != VerificationStatus.OK) { return UnicityCertificateVerificationResult.fail(results); diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/verification/UnicityCertificateVerificationResult.java b/src/main/java/org/unicitylabs/sdk/api/bft/verification/UnicityCertificateVerificationResult.java index 918c5ea..df1a38a 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/verification/UnicityCertificateVerificationResult.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/verification/UnicityCertificateVerificationResult.java @@ -1,16 +1,17 @@ package org.unicitylabs.sdk.api.bft.verification; -import java.util.List; import org.unicitylabs.sdk.util.verification.VerificationResult; import org.unicitylabs.sdk.util.verification.VerificationStatus; +import java.util.List; + /** * Verification result type for unicity certificate verification. */ public class UnicityCertificateVerificationResult extends VerificationResult { private UnicityCertificateVerificationResult(VerificationStatus status, - List> results) { + List> results) { super("UnicityCertificateVerification", status, "", 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 index 1cec9b3..e69de29 100644 --- 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 @@ -1,34 +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 { - - private InputRecordCurrentHashVerificationRule() { - } - - /** - * Verify that inclusion proof merkle root matches current hash in input record. - * - * @param inclusionProof inclusion proof to verify - * - * @return verification result - */ - 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/api/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java b/src/main/java/org/unicitylabs/sdk/api/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java index d2639c4..4731f23 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java @@ -1,9 +1,6 @@ package org.unicitylabs.sdk.api.bft.verification.rule; import com.google.common.primitives.UnsignedBytes; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; import org.unicitylabs.sdk.api.bft.UnicityCertificate; import org.unicitylabs.sdk.api.bft.UnicityTreeCertificate; import org.unicitylabs.sdk.crypto.hash.DataHash; @@ -13,12 +10,17 @@ import org.unicitylabs.sdk.util.verification.VerificationResult; import org.unicitylabs.sdk.util.verification.VerificationStatus; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + /** * Rule to verify that the UnicitySeal hash matches the root hash of the UnicityTreeCertificate. */ public class UnicitySealHashMatchesWithRootHashRule { - private UnicitySealHashMatchesWithRootHashRule() {} + private UnicitySealHashMatchesWithRootHashRule() { + } /** * Verifies that the unicity seal hash matches the recomputed root hash of the unicity tree. @@ -27,54 +29,54 @@ private UnicitySealHashMatchesWithRootHashRule() {} * @return verification result with {@link VerificationStatus#OK} on match, otherwise fail */ public static VerificationResult verify( - UnicityCertificate unicityCertificate) { + UnicityCertificate unicityCertificate) { DataHash shardTreeCertificateRootHash = UnicityCertificate - .calculateShardTreeCertificateRootHash( - unicityCertificate.getInputRecord(), - unicityCertificate.getTechnicalRecordHash(), - unicityCertificate.getShardConfigurationHash(), - unicityCertificate.getShardTreeCertificate() - ); + .calculateShardTreeCertificateRootHash( + unicityCertificate.getInputRecord(), + unicityCertificate.getTechnicalRecordHash(), + unicityCertificate.getShardConfigurationHash(), + unicityCertificate.getShardTreeCertificate() + ); UnicityTreeCertificate unicityTreeCertificate = unicityCertificate.getUnicityTreeCertificate(); byte[] key = ByteBuffer.allocate(4) - .order(ByteOrder.BIG_ENDIAN) - .putInt(unicityTreeCertificate.getPartitionIdentifier()) - .array(); + .order(ByteOrder.BIG_ENDIAN) + .putInt(unicityTreeCertificate.getPartitionIdentifier()) + .array(); DataHash result = 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(); for (UnicityTreeCertificate.HashStep step : unicityTreeCertificate.getSteps()) { byte[] stepKey = ByteBuffer.allocate(4) - .order(ByteOrder.BIG_ENDIAN) - .putInt(step.getKey()) - .array(); + .order(ByteOrder.BIG_ENDIAN) + .putInt(step.getKey()) + .array(); DataHasher hasher = new DataHasher(HashAlgorithm.SHA256) - .update(CborSerializer.encodeByteString(new byte[]{(byte) 0x00})) // NODE - .update(CborSerializer.encodeByteString(stepKey)); + .update(CborSerializer.encodeByteString(new byte[]{(byte) 0x00})) // NODE + .update(CborSerializer.encodeByteString(stepKey)); if (UnsignedBytes.lexicographicalComparator().compare(key, stepKey) > 0) { hasher - .update(CborSerializer.encodeByteString(step.getHash())) - .update(CborSerializer.encodeByteString(result.getData())); + .update(CborSerializer.encodeByteString(step.getHash())) + .update(CborSerializer.encodeByteString(result.getData())); } else { hasher - .update(CborSerializer.encodeByteString(result.getData())) - .update(CborSerializer.encodeByteString(step.getHash())); + .update(CborSerializer.encodeByteString(result.getData())) + .update(CborSerializer.encodeByteString(step.getHash())); } result = hasher.digest(); @@ -84,10 +86,10 @@ public static VerificationResult verify( if (!Arrays.equals(unicitySealHash, result.getData())) { return new VerificationResult<>("UnicitySealHashMatchesWithRootHashRule", - VerificationStatus.FAIL); + VerificationStatus.FAIL); } return new VerificationResult<>("UnicitySealHashMatchesWithRootHashRule", - VerificationStatus.OK); + VerificationStatus.OK); } } diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java b/src/main/java/org/unicitylabs/sdk/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java index ba7947f..4bedac5 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java @@ -1,9 +1,5 @@ package org.unicitylabs.sdk.api.bft.verification.rule; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.api.bft.RootTrustBase.NodeInfo; import org.unicitylabs.sdk.api.bft.UnicitySeal; @@ -14,12 +10,18 @@ import org.unicitylabs.sdk.util.verification.VerificationResult; import org.unicitylabs.sdk.util.verification.VerificationStatus; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + /** * Rule to verify that the UnicitySeal contains valid quorum signatures. */ public class UnicitySealQuorumSignaturesVerificationRule { - private UnicitySealQuorumSignaturesVerificationRule() {} + private UnicitySealQuorumSignaturesVerificationRule() { + } /** * Verifies unicity seal signatures and checks that the quorum threshold is reached. @@ -29,21 +31,21 @@ private UnicitySealQuorumSignaturesVerificationRule() {} * @return verification result with per-signature details */ public static VerificationResult verify(RootTrustBase trustBase, - UnicitySeal unicitySeal) { + UnicitySeal unicitySeal) { List> results = new ArrayList<>(); DataHash hash = new DataHasher(HashAlgorithm.SHA256) - .update(unicitySeal.toCborWithoutSignatures()) - .digest(); + .update(unicitySeal.toCborWithoutSignatures()) + .digest(); int successful = 0; for (Map.Entry entry : unicitySeal.getSignatures().entrySet()) { String nodeId = entry.getKey(); byte[] signature = entry.getValue(); VerificationResult result = UnicitySealQuorumSignaturesVerificationRule.verifySignature( - trustBase, - nodeId, - signature, - hash.getData() + trustBase, + nodeId, + signature, + hash.getData() ); results.add(result); @@ -54,55 +56,55 @@ public static VerificationResult verify(RootTrustBase trustB if (successful >= trustBase.getQuorumThreshold()) { return new VerificationResult<>( - "UnicitySealQuorumSignaturesVerificationRule", - VerificationStatus.OK, - "Unicity quorum signatures verification threshold reached", - results + "UnicitySealQuorumSignaturesVerificationRule", + VerificationStatus.OK, + "Unicity quorum signatures verification threshold reached", + results ); } return new VerificationResult<>( - "UnicitySealQuorumSignaturesVerificationRule", - VerificationStatus.FAIL, - "Unicity quorum treshold was not reached", - results + "UnicitySealQuorumSignaturesVerificationRule", + VerificationStatus.FAIL, + "Unicity quorum treshold was not reached", + results ); } private static VerificationResult verifySignature( - RootTrustBase trustBase, - String nodeId, - byte[] signature, - byte[] hash + RootTrustBase trustBase, + String nodeId, + byte[] signature, + byte[] hash ) { NodeInfo node = trustBase.getRootNodes().stream() - .filter(n -> n.getNodeId().equals(nodeId)) - .findFirst() - .orElse(null); + .filter(n -> n.getNodeId().equals(nodeId)) + .findFirst() + .orElse(null); if (node == null) { return new VerificationResult<>( - String.format("SignatureVerificationRule[%s]", nodeId), - VerificationStatus.FAIL, - "No root node defined" + String.format("SignatureVerificationRule[%s]", nodeId), + VerificationStatus.FAIL, + "No root node defined" ); } if (!SigningService.verifyWithPublicKey( - hash, - Arrays.copyOf(signature, signature.length - 1), - node.getSigningKey() + hash, + Arrays.copyOf(signature, signature.length - 1), + node.getSigningKey() )) { return new VerificationResult<>( - String.format("SignatureVerificationRule[%s]", nodeId), - VerificationStatus.FAIL, - "Signature verification failed" + String.format("SignatureVerificationRule[%s]", nodeId), + VerificationStatus.FAIL, + "Signature verification failed" ); } return new VerificationResult<>( - String.format("SignatureVerificationRule[%s]", nodeId), - VerificationStatus.OK + String.format("SignatureVerificationRule[%s]", nodeId), + VerificationStatus.OK ); } diff --git a/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcError.java b/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcError.java index b1e4c6d..d35d327 100644 --- a/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcError.java +++ b/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcError.java @@ -13,8 +13,8 @@ public class JsonRpcError { @JsonCreator JsonRpcError( - @JsonProperty("code") int code, - @JsonProperty("message") String message + @JsonProperty("code") int code, + @JsonProperty("message") String message ) { this.code = code; this.message = message; diff --git a/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcHttpTransport.java b/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcHttpTransport.java index e61cd98..879da98 100644 --- a/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcHttpTransport.java +++ b/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcHttpTransport.java @@ -1,19 +1,13 @@ package org.unicitylabs.sdk.api.jsonrpc; +import okhttp3.*; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; + import java.io.IOException; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.unicitylabs.sdk.serializer.UnicityObjectMapper; /** * JSON-RPC HTTP service. @@ -59,27 +53,27 @@ public CompletableFuture request(String method, Object params, Class r * @return future with result */ public CompletableFuture request( - String method, - Object params, - Class resultType, - Map> headers + String method, + Object params, + Class resultType, + Map> headers ) { CompletableFuture future = new CompletableFuture<>(); try { Request.Builder requestBuilder = new Request.Builder() - .url(this.url) - .post( - RequestBody.create( - UnicityObjectMapper.JSON.writeValueAsString( - new JsonRpcRequest(method, params) - ), - JsonRpcHttpTransport.MEDIA_TYPE_JSON) - ); + .url(this.url) + .post( + RequestBody.create( + UnicityObjectMapper.JSON.writeValueAsString( + new JsonRpcRequest(method, params) + ), + JsonRpcHttpTransport.MEDIA_TYPE_JSON) + ); headers.forEach((header, values) -> - values.forEach(value -> - requestBuilder.addHeader(header, value))); + values.forEach(value -> + requestBuilder.addHeader(header, value))); Request request = requestBuilder.build(); @@ -99,17 +93,17 @@ public void onResponse(Call call, Response response) { } JsonRpcResponse data = UnicityObjectMapper.JSON.readValue( - body != null ? body.string() : "", - UnicityObjectMapper.JSON.getTypeFactory() - .constructParametricType(JsonRpcResponse.class, resultType) + body != null ? body.string() : "", + UnicityObjectMapper.JSON.getTypeFactory() + .constructParametricType(JsonRpcResponse.class, resultType) ); if (data.getError() != null) { future.completeExceptionally( - new JsonRpcNetworkException( - data.getError().getCode(), - data.getError().getMessage() - ) + new JsonRpcNetworkException( + data.getError().getCode(), + data.getError().getMessage() + ) ); return; } @@ -117,7 +111,7 @@ public void onResponse(Call call, Response response) { future.complete(data.getResult()); } catch (Exception e) { future.completeExceptionally( - new RuntimeException("Failed to parse JSON-RPC response", e)); + new RuntimeException("Failed to parse JSON-RPC response", e)); } } }); diff --git a/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcRequest.java b/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcRequest.java index 705765d..5ec8598 100644 --- a/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcRequest.java +++ b/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcRequest.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.UUID; /** @@ -33,9 +34,9 @@ public JsonRpcRequest(String method, Object params) { */ @JsonCreator public JsonRpcRequest( - @JsonProperty("id") UUID id, - @JsonProperty("method") String method, - @JsonProperty("params") Object params + @JsonProperty("id") UUID id, + @JsonProperty("method") String method, + @JsonProperty("params") Object params ) { this.id = id; this.method = method; diff --git a/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcResponse.java b/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcResponse.java index 8888252..88a1f9c 100644 --- a/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcResponse.java +++ b/src/main/java/org/unicitylabs/sdk/api/jsonrpc/JsonRpcResponse.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; + import java.util.Objects; import java.util.UUID; @@ -19,10 +20,10 @@ public class JsonRpcResponse { @JsonCreator JsonRpcResponse( - @JsonProperty("jsonrpc") String version, - @JsonProperty("result") T result, - @JsonProperty("error") JsonRpcError error, - @JsonProperty("id") UUID id + @JsonProperty("jsonrpc") String version, + @JsonProperty("result") T result, + @JsonProperty("error") JsonRpcError error, + @JsonProperty("id") UUID id ) { if (!"2.0".equals(version)) { throw new IllegalArgumentException("Invalid JSON-RPC version: " + version); @@ -77,8 +78,8 @@ public boolean equals(Object o) { } JsonRpcResponse that = (JsonRpcResponse) o; return Objects.equals(this.version, that.version) && Objects.equals(this.result, - that.result) && Objects.equals(this.error, that.error) && Objects.equals(this.id, - that.id); + that.result) && Objects.equals(this.error, that.error) && Objects.equals(this.id, + that.id); } @Override diff --git a/src/main/java/org/unicitylabs/sdk/crypto/MintSigningService.java b/src/main/java/org/unicitylabs/sdk/crypto/MintSigningService.java index f498f2b..ccdd3b5 100644 --- a/src/main/java/org/unicitylabs/sdk/crypto/MintSigningService.java +++ b/src/main/java/org/unicitylabs/sdk/crypto/MintSigningService.java @@ -1,6 +1,5 @@ package org.unicitylabs.sdk.crypto; -import java.util.Objects; import org.unicitylabs.sdk.crypto.hash.DataHasher; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; @@ -8,15 +7,18 @@ import org.unicitylabs.sdk.transaction.TokenId; import org.unicitylabs.sdk.util.HexConverter; +import java.util.Objects; + /** * Factory for the deterministic signing key used by mint transactions. */ public class MintSigningService { private static final byte[] MINTER_SECRET = HexConverter.decode( - "495f414d5f554e4956455253414c5f4d494e5445525f464f525f"); + "495f414d5f554e4956455253414c5f4d494e5445525f464f525f"); - private MintSigningService() {} + private MintSigningService() { + } /** * Create a signing service for the provided token id. @@ -29,12 +31,12 @@ public static SigningService create(TokenId tokenId) { Objects.requireNonNull(tokenId, "Token ID cannot be null"); return new SigningService( - new DataHasher(HashAlgorithm.SHA256) - .update(CborSerializer.encodeArray( - CborSerializer.encodeByteString(MintSigningService.MINTER_SECRET), - tokenId.toCbor())) - .digest() - .getData() + new DataHasher(HashAlgorithm.SHA256) + .update(CborSerializer.encodeArray( + CborSerializer.encodeByteString(MintSigningService.MINTER_SECRET), + tokenId.toCbor())) + .digest() + .getData() ); } diff --git a/src/main/java/org/unicitylabs/sdk/crypto/hash/DataHash.java b/src/main/java/org/unicitylabs/sdk/crypto/hash/DataHash.java index fbe1492..7b521cb 100644 --- a/src/main/java/org/unicitylabs/sdk/crypto/hash/DataHash.java +++ b/src/main/java/org/unicitylabs/sdk/crypto/hash/DataHash.java @@ -1,12 +1,13 @@ package org.unicitylabs.sdk.crypto.hash; -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.HexConverter; +import java.util.Arrays; +import java.util.Objects; + /** * DataHash represents a hash of data using a specific hash algorithm. */ diff --git a/src/main/java/org/unicitylabs/sdk/crypto/secp256k1/Signature.java b/src/main/java/org/unicitylabs/sdk/crypto/secp256k1/Signature.java index eaf61d5..05ad847 100644 --- a/src/main/java/org/unicitylabs/sdk/crypto/secp256k1/Signature.java +++ b/src/main/java/org/unicitylabs/sdk/crypto/secp256k1/Signature.java @@ -1,11 +1,12 @@ package org.unicitylabs.sdk.crypto.secp256k1; -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.HexConverter; +import java.util.Arrays; +import java.util.Objects; + /** * Signature implementation for signing service, this contains public key recovery byte as well. */ @@ -101,6 +102,6 @@ public int hashCode() { @Override public String toString() { return String.format("Signature{bytes=%s, recovery=%s}", HexConverter.encode(this.bytes), - this.recovery); + this.recovery); } } diff --git a/src/main/java/org/unicitylabs/sdk/crypto/secp256k1/SigningService.java b/src/main/java/org/unicitylabs/sdk/crypto/secp256k1/SigningService.java index fce64ab..855b32d 100644 --- a/src/main/java/org/unicitylabs/sdk/crypto/secp256k1/SigningService.java +++ b/src/main/java/org/unicitylabs/sdk/crypto/secp256k1/SigningService.java @@ -1,8 +1,5 @@ package org.unicitylabs.sdk.crypto.secp256k1; -import java.math.BigInteger; -import java.security.SecureRandom; -import java.util.Arrays; import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; @@ -15,6 +12,10 @@ import org.bouncycastle.math.ec.ECPoint; import org.unicitylabs.sdk.crypto.hash.DataHash; +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Arrays; + /** * Default signing service. */ @@ -23,10 +24,10 @@ public class SigningService { private static final String CURVE_NAME = "secp256k1"; private static final ECParameterSpec EC_SPEC = ECNamedCurveTable.getParameterSpec(CURVE_NAME); private static final ECDomainParameters EC_DOMAIN_PARAMETERS = new ECDomainParameters( - EC_SPEC.getCurve(), - EC_SPEC.getG(), - EC_SPEC.getN(), - EC_SPEC.getH() + EC_SPEC.getCurve(), + EC_SPEC.getG(), + EC_SPEC.getN(), + EC_SPEC.getH() ); private final ECPrivateKeyParameters privateKey; @@ -44,7 +45,7 @@ public SigningService(byte[] privateKey) { BigInteger privateKeyAsBigInt = new BigInteger(1, privateKey); if (privateKeyAsBigInt.compareTo(BigInteger.ONE) < 0 - || privateKeyAsBigInt.compareTo(EC_SPEC.getN()) >= 0) { + || privateKeyAsBigInt.compareTo(EC_SPEC.getN()) >= 0) { throw new IllegalArgumentException("Invalid private key: must be in range [1, N)"); } @@ -52,8 +53,8 @@ public SigningService(byte[] privateKey) { ECPoint q = EC_SPEC.getG().multiply(privateKeyAsBigInt); this.publicKey = q.getEncoded(true); // compressed format this.privateKey = new ECPrivateKeyParameters( - privateKeyAsBigInt, - EC_DOMAIN_PARAMETERS + privateKeyAsBigInt, + EC_DOMAIN_PARAMETERS ); } @@ -204,10 +205,10 @@ private byte[] toFixedLength(BigInteger value, int length) { * Recover public key from signature for a specific recovery ID. */ private static ECPoint recoverFromSignature( - int recId, - BigInteger r, - BigInteger s, - byte[] message + int recId, + BigInteger r, + BigInteger s, + byte[] message ) { BigInteger n = EC_DOMAIN_PARAMETERS.getN(); BigInteger x = r; @@ -235,8 +236,8 @@ private static ECPoint recoverFromSignature( ECPoint point1 = y.multiply(s); ECPoint point2 = EC_DOMAIN_PARAMETERS.getG().multiply(e); return point1 - .subtract(point2) - .multiply(r.modInverse(n)); + .subtract(point2) + .multiply(r.modInverse(n)); } diff --git a/src/main/java/org/unicitylabs/sdk/payment/PaymentData.java b/src/main/java/org/unicitylabs/sdk/payment/PaymentData.java index 5012d8b..850abda 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/PaymentData.java +++ b/src/main/java/org/unicitylabs/sdk/payment/PaymentData.java @@ -1,8 +1,9 @@ package org.unicitylabs.sdk.payment; -import java.util.Set; import org.unicitylabs.sdk.payment.asset.Asset; +import java.util.Set; + /** * Represents payment payload data. */ diff --git a/src/main/java/org/unicitylabs/sdk/payment/SplitReason.java b/src/main/java/org/unicitylabs/sdk/payment/SplitReason.java index de5ea84..aa7760e 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/SplitReason.java +++ b/src/main/java/org/unicitylabs/sdk/payment/SplitReason.java @@ -1,12 +1,13 @@ package org.unicitylabs.sdk.payment; -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.transaction.Token; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + /** * The reason for token splitting represented by an input token and inclusion proofs. */ @@ -16,8 +17,8 @@ public final class SplitReason { private final List proofs; private SplitReason( - Token token, - List proofs + Token token, + List proofs ) { this.token = token; this.proofs = List.copyOf(proofs); @@ -71,8 +72,8 @@ public static SplitReason fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); return new SplitReason( - Token.fromCbor(data.get(0)), - CborDeserializer.decodeArray(data.get(1)).stream().map(SplitReasonProof::fromCbor).collect(Collectors.toList()) + Token.fromCbor(data.get(0)), + CborDeserializer.decodeArray(data.get(1)).stream().map(SplitReasonProof::fromCbor).collect(Collectors.toList()) ); } @@ -83,8 +84,8 @@ public static SplitReason fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeArray( - this.token.toCbor(), - CborSerializer.encodeArray(this.proofs.stream().map(SplitReasonProof::toCbor).toArray(byte[][]::new)) + this.token.toCbor(), + CborSerializer.encodeArray(this.proofs.stream().map(SplitReasonProof::toCbor).toArray(byte[][]::new)) ); } } diff --git a/src/main/java/org/unicitylabs/sdk/payment/SplitReasonProof.java b/src/main/java/org/unicitylabs/sdk/payment/SplitReasonProof.java index 72ce197..ae3c2c7 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/SplitReasonProof.java +++ b/src/main/java/org/unicitylabs/sdk/payment/SplitReasonProof.java @@ -1,11 +1,12 @@ package org.unicitylabs.sdk.payment; -import java.util.List; -import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; -import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTreePath; import org.unicitylabs.sdk.payment.asset.AssetId; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import org.unicitylabs.sdk.smt.plain.SparseMerkleTreePath; +import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTreePath; + +import java.util.List; /** * Proof material for one split reason entry. @@ -16,9 +17,9 @@ public class SplitReasonProof { private final SparseMerkleSumTreePath assetTreePath; private SplitReasonProof( - AssetId assetId, - SparseMerkleTreePath aggregationPath, - SparseMerkleSumTreePath assetTreePath + AssetId assetId, + SparseMerkleTreePath aggregationPath, + SparseMerkleSumTreePath assetTreePath ) { this.assetId = assetId; this.aggregationPath = aggregationPath; @@ -62,9 +63,9 @@ public SparseMerkleSumTreePath getAssetTreePath() { * @return split reason proof */ public static SplitReasonProof create( - AssetId assetId, - SparseMerkleTreePath aggregationPath, - SparseMerkleSumTreePath assetTreePath + AssetId assetId, + SparseMerkleTreePath aggregationPath, + SparseMerkleSumTreePath assetTreePath ) { return new SplitReasonProof(assetId, aggregationPath, assetTreePath); } @@ -80,9 +81,9 @@ public static SplitReasonProof fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); return new SplitReasonProof( - AssetId.fromCbor(data.get(0)), - SparseMerkleTreePath.fromCbor(data.get(1)), - SparseMerkleSumTreePath.fromCbor(data.get(2)) + AssetId.fromCbor(data.get(0)), + SparseMerkleTreePath.fromCbor(data.get(1)), + SparseMerkleSumTreePath.fromCbor(data.get(2)) ); } @@ -93,9 +94,9 @@ public static SplitReasonProof fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeArray( - this.assetId.toCbor(), - this.aggregationPath.toCbor(), - this.assetTreePath.toCbor() + this.assetId.toCbor(), + this.aggregationPath.toCbor(), + this.assetTreePath.toCbor() ); } } diff --git a/src/main/java/org/unicitylabs/sdk/payment/SplitResult.java b/src/main/java/org/unicitylabs/sdk/payment/SplitResult.java index 77c73e7..fa9bfb4 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/SplitResult.java +++ b/src/main/java/org/unicitylabs/sdk/payment/SplitResult.java @@ -1,11 +1,12 @@ package org.unicitylabs.sdk.payment; +import org.unicitylabs.sdk.transaction.TokenId; +import org.unicitylabs.sdk.transaction.TransferTransaction; + import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; -import org.unicitylabs.sdk.transaction.TokenId; -import org.unicitylabs.sdk.transaction.TransferTransaction; /** * Result of token split generation containing burn transaction and per-token proofs. @@ -18,10 +19,10 @@ public class SplitResult { SplitResult(TransferTransaction burnTransaction, Map> proofs) { this.burnTransaction = burnTransaction; this.proofs = Map.copyOf( - proofs.entrySet().stream() - .collect( - Collectors.toMap(Entry::getKey, value -> List.copyOf(value.getValue())) - ) + proofs.entrySet().stream() + .collect( + Collectors.toMap(Entry::getKey, value -> List.copyOf(value.getValue())) + ) ); } diff --git a/src/main/java/org/unicitylabs/sdk/payment/TokenSplit.java b/src/main/java/org/unicitylabs/sdk/payment/TokenSplit.java index fe2fa41..377ebc0 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/TokenSplit.java +++ b/src/main/java/org/unicitylabs/sdk/payment/TokenSplit.java @@ -1,39 +1,31 @@ package org.unicitylabs.sdk.payment; -import java.math.BigInteger; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.hash.DataHash; 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.mtree.plain.SparseMerkleTree; -import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreeRootNode; -import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTree; -import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTreeRootNode; import org.unicitylabs.sdk.payment.asset.Asset; import org.unicitylabs.sdk.payment.asset.AssetId; import org.unicitylabs.sdk.predicate.Predicate; import org.unicitylabs.sdk.predicate.builtin.BurnPredicate; import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; -import org.unicitylabs.sdk.transaction.Address; -import org.unicitylabs.sdk.transaction.Token; -import org.unicitylabs.sdk.transaction.TokenId; -import org.unicitylabs.sdk.transaction.Transaction; -import org.unicitylabs.sdk.transaction.TransferTransaction; +import org.unicitylabs.sdk.smt.BranchExistsException; +import org.unicitylabs.sdk.smt.LeafOutOfBoundsException; +import org.unicitylabs.sdk.smt.MerkleTreePathVerificationResult; +import org.unicitylabs.sdk.smt.plain.SparseMerkleTree; +import org.unicitylabs.sdk.smt.plain.SparseMerkleTreeRootNode; +import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTree; +import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTreeRootNode; +import org.unicitylabs.sdk.transaction.*; import org.unicitylabs.sdk.util.verification.VerificationResult; import org.unicitylabs.sdk.util.verification.VerificationStatus; +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; + /** * Utilities for creating and verifying token split proofs. */ @@ -41,7 +33,8 @@ public class TokenSplit { private static final SecureRandom RANDOM = new SecureRandom(); - private TokenSplit() {} + private TokenSplit() { + } /** * Create split proofs and burn transaction for provided target token distributions. @@ -57,10 +50,10 @@ private TokenSplit() {} * @throws BranchExistsException if duplicate branches are inserted into a merkle tree */ public static SplitResult split( - Token token, - Predicate ownerPredicate, - PaymentDataDeserializer paymentDataDeserializer, - Map> splitTokens + Token token, + Predicate ownerPredicate, + PaymentDataDeserializer paymentDataDeserializer, + Map> splitTokens ) throws LeafOutOfBoundsException, BranchExistsException { Objects.requireNonNull(token, "Token cannot be null"); Objects.requireNonNull(ownerPredicate, "Owner predicate cannot be null"); @@ -75,25 +68,25 @@ public static SplitResult split( Objects.requireNonNull(asset, "Split token asset cannot be null"); SparseMerkleSumTree tree = trees.computeIfAbsent(asset.getId(), - v -> new SparseMerkleSumTree(HashAlgorithm.SHA256)); + v -> new SparseMerkleSumTree(HashAlgorithm.SHA256)); tree.addLeaf( - entry.getKey().toBitString().toBigInteger(), - new SparseMerkleSumTree.LeafValue(asset.getId().getBytes(), asset.getValue()) + entry.getKey().toBitString().toBigInteger(), + new SparseMerkleSumTree.LeafValue(asset.getId().getBytes(), asset.getValue()) ); } } PaymentData paymentData = paymentDataDeserializer.decode(token.getGenesis().getData()); Map assets = paymentData.getAssets().stream() - .collect(Collectors.toMap( - Asset::getId, - asset -> asset, - (a, b) -> { - throw new IllegalArgumentException( - "Payment data contains multiple assets with the same id: " + a.getId()); - } - ) - ); + .collect(Collectors.toMap( + Asset::getId, + asset -> asset, + (a, b) -> { + throw new IllegalArgumentException( + "Payment data contains multiple assets with the same id: " + a.getId()); + } + ) + ); if (trees.size() != assets.size()) { throw new IllegalArgumentException("Token and split tokens asset counts differ."); @@ -110,12 +103,12 @@ public static SplitResult split( SparseMerkleSumTreeRootNode root = entry.getValue().calculateRoot(); if (!root.getValue().equals(tokenAsset.getValue())) { throw new IllegalArgumentException( - String.format( - "Token contained %s %s assets, but tree has %s", - tokenAsset.getValue(), - tokenAsset.getId(), - root.getValue() - ) + String.format( + "Token contained %s %s assets, but tree has %s", + tokenAsset.getValue(), + tokenAsset.getId(), + root.getValue() + ) ); } @@ -129,25 +122,25 @@ public static SplitResult split( RANDOM.nextBytes(x); TransferTransaction burnTransaction = TransferTransaction.create( - token, - ownerPredicate, - Address.fromPredicate(burnPredicate), - x, - CborSerializer.encodeNull() + token, + ownerPredicate, + Address.fromPredicate(burnPredicate), + x, + CborSerializer.encodeNull() ); HashMap> proofs = new HashMap>(); for (Entry> entry : splitTokens.entrySet()) { proofs.put( - entry.getKey(), - List.copyOf( - entry.getValue().stream().map(asset -> SplitReasonProof.create( - asset.getId(), - aggregationRoot.getPath(asset.getId().toBitString().toBigInteger()), - assetTreeRoots.get(asset.getId()).getPath(entry.getKey().toBitString().toBigInteger()) - ) - ).collect(Collectors.toList()) - ) + entry.getKey(), + List.copyOf( + entry.getValue().stream().map(asset -> SplitReasonProof.create( + asset.getId(), + aggregationRoot.getPath(asset.getId().toBitString().toBigInteger()), + assetTreeRoots.get(asset.getId()).getPath(entry.getKey().toBitString().toBigInteger()) + ) + ).collect(Collectors.toList()) + ) ); } @@ -165,10 +158,10 @@ public static SplitResult split( * @return verification result */ public static VerificationResult verify( - Token token, - SplitPaymentDataDeserializer paymentDataDeserializer, - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier + Token token, + SplitPaymentDataDeserializer paymentDataDeserializer, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier ) { Objects.requireNonNull(token, "Token cannot be null"); Objects.requireNonNull(paymentDataDeserializer, "Payment data deserializer cannot be null"); @@ -179,36 +172,36 @@ public static VerificationResult verify( if (data.getAssets() == null) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - "Assets data is missing." + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + "Assets data is missing." ); } if (data.getReason() == null) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - "Reason is missing." + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + "Reason is missing." ); } VerificationResult verificationResult = data.getReason().getToken() - .verify(trustBase, predicateVerifier); + .verify(trustBase, predicateVerifier); if (verificationResult.getStatus() != VerificationStatus.OK) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - "Burn token verification failed.", - verificationResult + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + "Burn token verification failed.", + verificationResult ); } if (data.getAssets().size() != data.getReason().getProofs().size()) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - "Total amount of assets differ in token and proofs." + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + "Total amount of assets differ in token and proofs." ); } @@ -216,18 +209,18 @@ public static VerificationResult verify( for (Asset asset : data.getAssets()) { if (asset == null) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - "Asset data is missing." + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + "Asset data is missing." ); } AssetId assetId = asset.getId(); if (assets.putIfAbsent(assetId, asset) != null) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - String.format("Duplicate asset id %s found in asset data.", assetId) + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + String.format("Duplicate asset id %s found in asset data.", assetId) ); } } @@ -236,41 +229,41 @@ public static VerificationResult verify( DataHash root = data.getReason().getProofs().get(0).getAggregationPath().getRootHash(); for (SplitReasonProof proof : data.getReason().getProofs()) { MerkleTreePathVerificationResult aggregationPathResult = proof.getAggregationPath() - .verify(proof.getAssetId().toBitString().toBigInteger()); + .verify(proof.getAssetId().toBitString().toBigInteger()); if (!aggregationPathResult.isSuccessful()) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - String.format("Aggregation path verification failed for asset: %s", proof.getAssetId()) + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + String.format("Aggregation path verification failed for asset: %s", proof.getAssetId()) ); } MerkleTreePathVerificationResult assetTreePathResult = proof.getAssetTreePath() - .verify(token.getId().toBitString().toBigInteger()); + .verify(token.getId().toBitString().toBigInteger()); if (!assetTreePathResult.isSuccessful()) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - String.format("Asset tree path verification failed for token: %s", token.getId()) + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + String.format("Asset tree path verification failed for token: %s", token.getId()) ); } if (!proof.getAggregationPath().getRootHash().equals(root)) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - "Current proof is not derived from the same asset tree as other proofs." + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + "Current proof is not derived from the same asset tree as other proofs." ); } if (!Arrays.equals( - proof.getAssetTreePath().getRootHash().getImprint(), - proof.getAggregationPath().getSteps().get(0).getData().orElse(null) + proof.getAssetTreePath().getRootHash().getImprint(), + proof.getAggregationPath().getSteps().get(0).getData().orElse(null) )) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - "Asset tree root does not match aggregation path leaf." + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + "Asset tree root does not match aggregation path leaf." ); } @@ -278,9 +271,9 @@ public static VerificationResult verify( if (asset == null) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - String.format("Asset id %s not found in asset data.", proof.getAssetId()) + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + String.format("Asset id %s not found in asset data.", proof.getAssetId()) ); } @@ -288,25 +281,25 @@ public static VerificationResult verify( if (!proof.getAssetTreePath().getSteps().get(0).getValue().equals(amount)) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - String.format("Asset amount for asset id %s does not match asset tree leaf.", proof.getAssetId()) + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + String.format("Asset amount for asset id %s does not match asset tree leaf.", proof.getAssetId()) ); } if (!burnTokenLastTransaction.getRecipient() - .equals(Address.fromPredicate(BurnPredicate.create(proof.getAggregationPath().getRootHash().getImprint())))) { + .equals(Address.fromPredicate(BurnPredicate.create(proof.getAggregationPath().getRootHash().getImprint())))) { return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.FAIL, - "Aggregation path root does not match burn predicate." + "TokenSplitReasonVerificationRule", + VerificationStatus.FAIL, + "Aggregation path root does not match burn predicate." ); } } return new VerificationResult<>( - "TokenSplitReasonVerificationRule", - VerificationStatus.OK + "TokenSplitReasonVerificationRule", + VerificationStatus.OK ); } diff --git a/src/main/java/org/unicitylabs/sdk/payment/asset/Asset.java b/src/main/java/org/unicitylabs/sdk/payment/asset/Asset.java index b20bce6..e9744cb 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/asset/Asset.java +++ b/src/main/java/org/unicitylabs/sdk/payment/asset/Asset.java @@ -1,12 +1,13 @@ package org.unicitylabs.sdk.payment.asset; -import java.math.BigInteger; -import java.util.List; -import java.util.Objects; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.BigIntegerConverter; +import java.math.BigInteger; +import java.util.List; +import java.util.Objects; + /** * Represents an asset with an ID and a value. */ @@ -58,8 +59,8 @@ public static Asset fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); return new Asset( - AssetId.fromCbor(data.get(0)), - BigIntegerConverter.decode(CborDeserializer.decodeByteString(data.get(1))) + AssetId.fromCbor(data.get(0)), + BigIntegerConverter.decode(CborDeserializer.decodeByteString(data.get(1))) ); } @@ -70,8 +71,8 @@ public static Asset fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeArray( - this.id.toCbor(), - CborSerializer.encodeByteString(BigIntegerConverter.encode(this.value)) + this.id.toCbor(), + CborSerializer.encodeByteString(BigIntegerConverter.encode(this.value)) ); } diff --git a/src/main/java/org/unicitylabs/sdk/payment/asset/AssetId.java b/src/main/java/org/unicitylabs/sdk/payment/asset/AssetId.java index 4625845..febf8f1 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/asset/AssetId.java +++ b/src/main/java/org/unicitylabs/sdk/payment/asset/AssetId.java @@ -1,12 +1,13 @@ package org.unicitylabs.sdk.payment.asset; -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.util.Arrays; +import java.util.Objects; + /** * Unique identifier of an asset. */ @@ -50,7 +51,7 @@ public static AssetId fromCbor(byte[] bytes) { * @return bit string */ public BitString toBitString() { - return new BitString(this.bytes); + return BitString.fromBytes(this.bytes); } /** diff --git a/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java index 1807ca3..2f6ee17 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java @@ -1,16 +1,19 @@ 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; + /** * Generic predicate representation that stores engine, code, and parameters as encoded bytes. */ public class EncodedPredicate implements Predicate { + public static final long CBOR_TAG = 39032; private final PredicateEngine engine; private final byte[] code; @@ -34,14 +37,18 @@ public PredicateEngine getEngine() { * @return decoded encoded predicate */ public static EncodedPredicate fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(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()); + CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt()); return new EncodedPredicate( - engine, - CborDeserializer.decodeByteString(data.get(1)), - CborDeserializer.decodeByteString(data.get(2)) + engine, + CborDeserializer.decodeByteString(data.get(1)), + CborDeserializer.decodeByteString(data.get(2)) ); } @@ -52,11 +59,8 @@ public static EncodedPredicate fromCbor(byte[] bytes) { * @return encoded predicate containing engine, code, and parameters */ public static EncodedPredicate fromPredicate(Predicate predicate) { - return new EncodedPredicate( - predicate.getEngine(), - predicate.encodeCode(), - predicate.encodeParameters() - ); + return new EncodedPredicate(predicate.getEngine(), predicate.encodeCode(), + predicate.encodeParameters()); } @Override @@ -75,10 +79,13 @@ public byte[] encodeParameters() { * @return CBOR-encoded predicate bytes */ public byte[] toCbor() { - return CborSerializer.encodeArray( - CborSerializer.encodeUnsignedInteger(this.engine.getId()), - CborSerializer.encodeByteString(this.code), - CborSerializer.encodeByteString(this.parameters) + return CborSerializer.encodeTag( + EncodedPredicate.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(this.engine.getId()), + CborSerializer.encodeByteString(this.code), + CborSerializer.encodeByteString(this.parameters) + ) ); } @@ -89,7 +96,7 @@ public boolean equals(Object o) { } EncodedPredicate that = (EncodedPredicate) o; return this.engine == that.engine && Arrays.equals(this.code, that.code) && Arrays.equals( - this.parameters, that.parameters); + this.parameters, that.parameters); } @Override @@ -100,10 +107,10 @@ public int hashCode() { @Override public String toString() { return String.format( - "EncodedPredicate{engine=%s, code=%s, parameters=%s}", - this.engine, - HexConverter.encode(this.code), - HexConverter.encode(this.parameters) + "EncodedPredicate{engine=%s, code=%s, parameters=%s}", + this.engine, + HexConverter.encode(this.code), + HexConverter.encode(this.parameters) ); } } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java b/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java index 91facb2..3e85ed9 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java @@ -40,7 +40,7 @@ default boolean isEqualTo(Predicate other) { } return this.getEngine() == other.getEngine() - && Arrays.equals(this.encodeCode(), other.encodeCode()) - && Arrays.equals(this.encodeParameters(), other.encodeParameters()); + && Arrays.equals(this.encodeCode(), other.encodeCode()) + && Arrays.equals(this.encodeParameters(), other.encodeParameters()); } } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/builtin/BurnPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/builtin/BurnPredicate.java index b2c88d3..ff27ee3 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/builtin/BurnPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/builtin/BurnPredicate.java @@ -1,11 +1,12 @@ package org.unicitylabs.sdk.predicate.builtin; -import java.util.Arrays; -import java.util.Objects; import org.unicitylabs.sdk.predicate.Predicate; import org.unicitylabs.sdk.predicate.PredicateEngine; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import java.util.Arrays; +import java.util.Objects; + /** * Built-in predicate representing a burn operation. */ @@ -61,7 +62,7 @@ public static BurnPredicate fromPredicate(Predicate predicate) { } BuiltInPredicateType type = BuiltInPredicateType.fromId( - CborDeserializer.decodeUnsignedInteger(predicate.encodeCode()).asInt()); + CborDeserializer.decodeUnsignedInteger(predicate.encodeCode()).asInt()); if (type != BuiltInPredicateType.BURN) { throw new IllegalArgumentException("Predicate type must be BURN."); } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/builtin/DefaultBuiltInPredicateVerifier.java b/src/main/java/org/unicitylabs/sdk/predicate/builtin/DefaultBuiltInPredicateVerifier.java index 07e0bce..ce47c1b 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/builtin/DefaultBuiltInPredicateVerifier.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/builtin/DefaultBuiltInPredicateVerifier.java @@ -1,8 +1,5 @@ package org.unicitylabs.sdk.predicate.builtin; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.predicate.Predicate; @@ -15,6 +12,10 @@ import org.unicitylabs.sdk.util.verification.VerificationResult; import org.unicitylabs.sdk.util.verification.VerificationStatus; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * Default {@link PredicateVerifier} implementation for built-in predicates. */ @@ -30,7 +31,7 @@ public class DefaultBuiltInPredicateVerifier implements PredicateVerifier { * @throws IllegalArgumentException if multiple verifiers are provided for the same type */ public DefaultBuiltInPredicateVerifier( - List verifiers) { + List verifiers) { Map result = new HashMap<>(); for (BuiltInPredicateVerifier verifier : verifiers) { if (result.containsKey(verifier.getType())) { @@ -57,18 +58,18 @@ public PredicateEngine getPredicateEngine() { */ public static DefaultBuiltInPredicateVerifier create(PredicateVerifierService service, RootTrustBase trustBase) { return new DefaultBuiltInPredicateVerifier( - List.of( - new PayToPublicKeyPredicateVerifier() - ) + List.of( + new PayToPublicKeyPredicateVerifier() + ) ); } @Override public VerificationResult verify(Predicate predicate, - DataHash sourceStateHash, - DataHash transactionHash, byte[] unlockScript) { + DataHash sourceStateHash, + DataHash transactionHash, byte[] unlockScript) { BuiltInPredicateType type = BuiltInPredicateType.fromId( - CborDeserializer.decodeUnsignedInteger(predicate.encodeCode()).asInt()); + CborDeserializer.decodeUnsignedInteger(predicate.encodeCode()).asInt()); BuiltInPredicateVerifier verifier = this.verifiers.get(type); if (verifier == null) { diff --git a/src/main/java/org/unicitylabs/sdk/predicate/builtin/PayToPublicKeyPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/builtin/PayToPublicKeyPredicate.java index df43f36..b9cbd13 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/builtin/PayToPublicKeyPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/builtin/PayToPublicKeyPredicate.java @@ -1,12 +1,13 @@ package org.unicitylabs.sdk.predicate.builtin; -import java.util.Arrays; -import java.util.Objects; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; import org.unicitylabs.sdk.predicate.Predicate; import org.unicitylabs.sdk.predicate.PredicateEngine; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import java.util.Arrays; +import java.util.Objects; + /** * Built-in predicate that locks an output to a secp256k1 public key. */ @@ -61,7 +62,7 @@ public static PayToPublicKeyPredicate fromPredicate(Predicate predicate) { } BuiltInPredicateType type = BuiltInPredicateType.fromId( - CborDeserializer.decodeUnsignedInteger(predicate.encodeCode()).asInt()); + CborDeserializer.decodeUnsignedInteger(predicate.encodeCode()).asInt()); if (type != BuiltInPredicateType.PAY_TO_PUBLIC_KEY) { throw new IllegalArgumentException("Predicate type must be PAY_TO_PUBLIC_KEY."); } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/builtin/PayToPublicKeyPredicateUnlockScript.java b/src/main/java/org/unicitylabs/sdk/predicate/builtin/PayToPublicKeyPredicateUnlockScript.java index e50843b..e7f22fa 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/builtin/PayToPublicKeyPredicateUnlockScript.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/builtin/PayToPublicKeyPredicateUnlockScript.java @@ -37,17 +37,17 @@ public Signature getSignature() { * @return created unlock script */ public static PayToPublicKeyPredicateUnlockScript create( - Transaction transaction, - SigningService signingService + Transaction transaction, + SigningService signingService ) { DataHash hash = new DataHasher(HashAlgorithm.SHA256) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(transaction.getSourceStateHash().getData()), - CborSerializer.encodeByteString(transaction.calculateTransactionHash().getData()) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(transaction.getSourceStateHash().getData()), + CborSerializer.encodeByteString(transaction.calculateTransactionHash().getData()) + ) ) - ) - .digest(); + .digest(); return new PayToPublicKeyPredicateUnlockScript(signingService.sign(hash)); } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/builtin/verification/BuiltInPredicateVerifier.java b/src/main/java/org/unicitylabs/sdk/predicate/builtin/verification/BuiltInPredicateVerifier.java index 82e595d..f481fb8 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/builtin/verification/BuiltInPredicateVerifier.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/builtin/verification/BuiltInPredicateVerifier.java @@ -28,5 +28,5 @@ public interface BuiltInPredicateVerifier { * @return verification result with status and optional diagnostics */ VerificationResult verify(Predicate predicate, DataHash sourceStateHash, - DataHash transactionHash, byte[] unlockScript); + DataHash transactionHash, byte[] unlockScript); } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.java b/src/main/java/org/unicitylabs/sdk/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.java index bca8e6e..d13a7eb 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.java @@ -20,7 +20,8 @@ public class PayToPublicKeyPredicateVerifier implements BuiltInPredicateVerifier /** * Creates a verifier instance for pay-to-public-key predicates. */ - public PayToPublicKeyPredicateVerifier() {} + public PayToPublicKeyPredicateVerifier() { + } @Override public BuiltInPredicateType getType() { @@ -30,26 +31,26 @@ public BuiltInPredicateType getType() { @Override public VerificationResult verify(Predicate encodedPredicate, - DataHash sourceStateHash, - DataHash transactionHash, byte[] unlockScript) { + DataHash sourceStateHash, + DataHash transactionHash, byte[] unlockScript) { PayToPublicKeyPredicate predicate = PayToPublicKeyPredicate.fromPredicate(encodedPredicate); boolean result = SigningService.verifyWithPublicKey( - new DataHasher(HashAlgorithm.SHA256) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(sourceStateHash.getData()), - CborSerializer.encodeByteString(transactionHash.getData()) - ) - ) - .digest(), - Signature.decode(unlockScript).getBytes(), - predicate.getPublicKey() + new DataHasher(HashAlgorithm.SHA256) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(sourceStateHash.getData()), + CborSerializer.encodeByteString(transactionHash.getData()) + ) + ) + .digest(), + Signature.decode(unlockScript).getBytes(), + predicate.getPublicKey() ); if (!result) { return new VerificationResult<>("PayToPublicKeyPredicateVerifier", VerificationStatus.FAIL, - "Signature verification failed."); + "Signature verification failed."); } return new VerificationResult<>("PayToPublicKeyPredicateVerifier", VerificationStatus.OK); diff --git a/src/main/java/org/unicitylabs/sdk/predicate/verification/PredicateVerifier.java b/src/main/java/org/unicitylabs/sdk/predicate/verification/PredicateVerifier.java index 9998bee..b9f506f 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/verification/PredicateVerifier.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/verification/PredicateVerifier.java @@ -28,5 +28,5 @@ public interface PredicateVerifier { * @return verification result with status and diagnostics */ VerificationResult verify(Predicate predicate, DataHash sourceStateHash, - DataHash transactionHash, byte[] unlockScript); + DataHash transactionHash, byte[] unlockScript); } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/verification/PredicateVerifierService.java b/src/main/java/org/unicitylabs/sdk/predicate/verification/PredicateVerifierService.java index ac03726..5695437 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/verification/PredicateVerifierService.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/verification/PredicateVerifierService.java @@ -1,7 +1,5 @@ package org.unicitylabs.sdk.predicate.verification; -import java.util.HashMap; -import java.util.Map; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.predicate.Predicate; @@ -10,6 +8,9 @@ import org.unicitylabs.sdk.util.verification.VerificationResult; import org.unicitylabs.sdk.util.verification.VerificationStatus; +import java.util.HashMap; +import java.util.Map; + /** * Service registry that routes predicate verification to engine-specific verifiers. */ @@ -44,7 +45,7 @@ public static PredicateVerifierService create(RootTrustBase trustBase) { public PredicateVerifierService addVerifier(PredicateVerifier verifier) { if (this.verifiers.containsKey(verifier.getPredicateEngine())) { throw new RuntimeException("Predicate verifier already registered for predicate engine: " - + verifier.getPredicateEngine()); + + verifier.getPredicateEngine()); } this.verifiers.put(verifier.getPredicateEngine(), verifier); @@ -63,11 +64,11 @@ public PredicateVerifierService addVerifier(PredicateVerifier verifier) { * @throws IllegalArgumentException if no verifier is registered for the predicate engine */ public VerificationResult verify(Predicate predicate, - DataHash sourceStateHash, DataHash transactionHash, byte[] unlockScript) { + DataHash sourceStateHash, DataHash transactionHash, byte[] unlockScript) { PredicateVerifier verifier = this.verifiers.get(predicate.getEngine()); if (verifier == null) { throw new IllegalArgumentException( - "No verifier registered for predicate engine: " + predicate.getEngine()); + "No verifier registered for predicate engine: " + predicate.getEngine()); } return verifier.verify(predicate, sourceStateHash, transactionHash, unlockScript); diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializer.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializer.java index 2519c57..7712ebd 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializer.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializer.java @@ -1,15 +1,11 @@ package org.unicitylabs.sdk.serializer.cbor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; import org.unicitylabs.sdk.serializer.cbor.CborSerializer.CborMap; import org.unicitylabs.sdk.serializer.cbor.CborSerializer.CborMap.Entry; +import java.util.*; +import java.util.function.Function; + /** * CBOR deserialization utilities. */ @@ -68,7 +64,7 @@ public static byte[] decodeByteString(byte[] data) { public static String decodeTextString(byte[] data) { CborReader reader = new CborReader(data); return new String( - reader.read((int) reader.readLength(CborMajorType.TEXT_STRING))); + reader.read((int) reader.readLength(CborMajorType.TEXT_STRING))); } /** @@ -177,7 +173,7 @@ public long readLength(CborMajorType majorType) { } byte additionalInformation = (byte) (initialByte - & CborDeserializer.ADDITIONAL_INFORMATION_MASK); + & CborDeserializer.ADDITIONAL_INFORMATION_MASK); if (Byte.compareUnsigned(additionalInformation, (byte) 24) < 0) { return additionalInformation; } @@ -212,7 +208,7 @@ public byte[] readRawCbor() { } CborMajorType majorType = CborMajorType.fromType( - this.data[this.position] & CborDeserializer.MAJOR_TYPE_MASK); + this.data[this.position] & CborDeserializer.MAJOR_TYPE_MASK); int position = this.position; int length = (int) this.readLength(majorType); switch (majorType) { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborSerializer.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborSerializer.java index f3c5cc3..9c52220 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborSerializer.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborSerializer.java @@ -3,11 +3,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.function.Function; /** @@ -48,7 +44,7 @@ public static byte[] encodeUnsignedInteger(long input) { byte[] result = new byte[1 + bytes.length]; System.arraycopy(bytes, 0, result, 1, bytes.length); result[0] = (byte) (CborMajorType.UNSIGNED_INTEGER.getType() - | CborSerializer.getAdditionalInformationBits(bytes.length)); + | CborSerializer.getAdditionalInformationBits(bytes.length)); return result; } @@ -155,7 +151,7 @@ public static byte[] encodeTag(long tag, byte[] input) { byte[] bytes = CborSerializer.getUnsignedLongAsPaddedBytes(tag); byte[] result = new byte[1 + bytes.length + input.length]; result[0] = (byte) (CborMajorType.TAG.getType() - | CborSerializer.getAdditionalInformationBits(bytes.length)); + | CborSerializer.getAdditionalInformationBits(bytes.length)); System.arraycopy(bytes, 0, result, 1, bytes.length); System.arraycopy(input, 0, result, 1 + bytes.length, input.length); @@ -193,7 +189,7 @@ private static byte[] encodeRawArray(byte[] input, int length, CborMajorType typ byte[] lengthBytes = CborSerializer.getUnsignedLongAsPaddedBytes(length); byte[] result = new byte[1 + lengthBytes.length + input.length]; result[0] = (byte) (type.getType() - | CborSerializer.getAdditionalInformationBits(lengthBytes.length)); + | CborSerializer.getAdditionalInformationBits(lengthBytes.length)); System.arraycopy(lengthBytes, 0, result, 1, lengthBytes.length); System.arraycopy(input, 0, result, 1 + lengthBytes.length, input.length); @@ -211,8 +207,8 @@ private static byte[] getUnsignedLongAsPaddedBytes(long input) { } ByteBuffer buffer = ByteBuffer - .allocate((int) Math.pow(2, (int) Math.ceil(Math.log(length) / Math.log(2)))) - .order(ByteOrder.BIG_ENDIAN); + .allocate((int) Math.pow(2, (int) Math.ceil(Math.log(length) / Math.log(2)))) + .order(ByteOrder.BIG_ENDIAN); if (length <= 1) { buffer.put((byte) input); } else if (length <= 2) { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/ByteArrayJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/ByteArrayJson.java index 88ed416..76f8e82 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/ByteArrayJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/ByteArrayJson.java @@ -8,9 +8,10 @@ import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import java.io.IOException; import org.unicitylabs.sdk.util.HexConverter; +import java.io.IOException; + /** * Byte array serializer and deserializer implementation. */ @@ -41,7 +42,7 @@ public Serializer() { */ @Override public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) - throws IOException { + throws IOException { gen.writeString(HexConverter.encode(value)); } } @@ -70,7 +71,7 @@ public Deserializer() { public byte[] deserialize(JsonParser p, DeserializationContext ctx) throws IOException { if (p.getCurrentToken() != JsonToken.VALUE_STRING) { throw MismatchedInputException.from(p, byte[].class, - "Expected hex string value"); + "Expected hex string value"); } try { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/LongAsStringSerializer.java b/src/main/java/org/unicitylabs/sdk/serializer/json/LongAsStringSerializer.java index 8163313..6969399 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/LongAsStringSerializer.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/LongAsStringSerializer.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; + import java.io.IOException; /** @@ -19,7 +20,7 @@ public LongAsStringSerializer() { @Override public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) - throws IOException { + throws IOException { gen.writeString(value.toString()); } } 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 94% rename from src/main/java/org/unicitylabs/sdk/mtree/CommonPath.java rename to src/main/java/org/unicitylabs/sdk/smt/CommonPath.java index 177438b..6a4f1a6 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; @@ -61,7 +61,7 @@ public static CommonPath create(BigInteger path1, BigInteger path2) { int length = 0; while (Objects.equals(path1.and(mask), path2.and(mask)) && path.compareTo(path1) < 0 - && path.compareTo(path2) < 0) { + && path.compareTo(path2) < 0) { mask = mask.shiftLeft(1); length += 1; path = mask.or(mask.subtract(BigInteger.ONE).and(path1)); 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 95% rename from src/main/java/org/unicitylabs/sdk/mtree/MerkleTreePathVerificationResult.java rename to src/main/java/org/unicitylabs/sdk/smt/MerkleTreePathVerificationResult.java index 2613d0a..982fb5a 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; @@ -51,7 +51,7 @@ public boolean isSuccessful() { @Override public String toString() { return String.format("MerkleTreePathVerificationResult{pathValid=%b, pathIncluded=%b}", - pathValid, pathIncluded); + pathValid, pathIncluded); } @Override 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..3a887f5 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/Branch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/Branch.java @@ -1,8 +1,9 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; -import java.math.BigInteger; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; +import java.math.BigInteger; + /** * Sparse merkle tree branch structure. */ 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 82% 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..cf7792c 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/FinalizedLeafBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/FinalizedLeafBranch.java @@ -1,14 +1,15 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; -import java.math.BigInteger; -import java.util.Arrays; -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.CborSerializer; import org.unicitylabs.sdk.util.BigIntegerConverter; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; + /** * Finalized leaf branch in a sparse merkle tree. */ @@ -33,18 +34,18 @@ private FinalizedLeafBranch(BigInteger path, byte[] value, DataHash hash) { * @return finalized leaf branch */ public static FinalizedLeafBranch create( - BigInteger path, - byte[] value, - HashAlgorithm hashAlgorithm + BigInteger path, + byte[] value, + HashAlgorithm hashAlgorithm ) { DataHash hash = new DataHasher(hashAlgorithm) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(BigIntegerConverter.encode(path)), - CborSerializer.encodeByteString(value) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigIntegerConverter.encode(path)), + CborSerializer.encodeByteString(value) + ) ) - ) - .digest(); + .digest(); return new FinalizedLeafBranch(path, value, hash); } 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 61% 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..7badfc9 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/FinalizedNodeBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/FinalizedNodeBranch.java @@ -1,13 +1,14 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; -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.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.BigIntegerConverter; +import java.math.BigInteger; +import java.util.Objects; + /** * Finalized node branch in a sparse merkle tree. */ @@ -19,10 +20,10 @@ class FinalizedNodeBranch implements NodeBranch, FinalizedBranch { private final DataHash hash; private FinalizedNodeBranch( - BigInteger path, - FinalizedBranch left, - FinalizedBranch right, - DataHash hash + BigInteger path, + FinalizedBranch left, + FinalizedBranch right, + DataHash hash ) { this.path = path; this.left = left; @@ -40,30 +41,30 @@ private FinalizedNodeBranch( * @return finalized node branch */ public static FinalizedNodeBranch create( - BigInteger path, - FinalizedBranch left, - FinalizedBranch right, - HashAlgorithm hashAlgorithm + BigInteger path, + FinalizedBranch left, + FinalizedBranch right, + HashAlgorithm hashAlgorithm ) { DataHash hash = new DataHasher(hashAlgorithm) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(BigIntegerConverter.encode(path)), - CborSerializer.encodeOptional( - left == null - ? null - : left.getHash().getData(), - CborSerializer::encodeByteString - ), - CborSerializer.encodeOptional( - right == null - ? null - : right.getHash().getData(), - CborSerializer::encodeByteString - ) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigIntegerConverter.encode(path)), + CborSerializer.encodeOptional( + left == null + ? null + : left.getHash().getData(), + CborSerializer::encodeByteString + ), + CborSerializer.encodeOptional( + right == null + ? null + : right.getHash().getData(), + CborSerializer::encodeByteString + ) + ) ) - ) - .digest(); + .digest(); return new FinalizedNodeBranch(path, left, right, hash); } @@ -100,7 +101,7 @@ public boolean equals(Object o) { } FinalizedNodeBranch that = (FinalizedNodeBranch) o; return Objects.equals(this.path, that.path) && Objects.equals(this.left, that.left) - && Objects.equals(this.right, that.right); + && Objects.equals(this.right, that.right); } @Override 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..286a13f 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/PendingLeafBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/PendingLeafBranch.java @@ -1,9 +1,10 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; + +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import java.math.BigInteger; import java.util.Arrays; import java.util.Objects; -import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; /** * Pending leaf branch in a sparse merkle tree. 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 84% 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..8b39cc0 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/PendingNodeBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/PendingNodeBranch.java @@ -1,8 +1,9 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; + +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import java.math.BigInteger; import java.util.Objects; -import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; /** * Pending node branch in a sparse merkle tree. @@ -44,10 +45,10 @@ public Branch getRight() { @Override public FinalizedNodeBranch finalize(HashAlgorithm hashAlgorithm) { return FinalizedNodeBranch.create( - this.path, - this.left.finalize(hashAlgorithm), - this.right.finalize(hashAlgorithm), - hashAlgorithm + this.path, + this.left.finalize(hashAlgorithm), + this.right.finalize(hashAlgorithm), + hashAlgorithm ); } @@ -58,7 +59,7 @@ public boolean equals(Object o) { } PendingNodeBranch that = (PendingNodeBranch) o; return Objects.equals(this.path, that.path) && Objects.equals(this.left, that.left) - && Objects.equals(this.right, that.right); + && Objects.equals(this.right, that.right); } @Override 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 72% 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..ce5109a 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,12 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; + +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 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; /** * Sparse Merkle tree implementation. @@ -36,7 +37,7 @@ public SparseMerkleTree(HashAlgorithm hashAlgorithm) { * @throws IllegalArgumentException if path is less than 1 */ public synchronized void addLeaf(BigInteger path, byte[] data) - throws BranchExistsException, LeafOutOfBoundsException { + throws BranchExistsException, LeafOutOfBoundsException { if (path.compareTo(BigInteger.ONE) < 0) { throw new IllegalArgumentException("Path must be greater than 0"); } @@ -44,8 +45,8 @@ public synchronized void addLeaf(BigInteger path, byte[] data) boolean isRight = path.testBit(0); Branch branch = isRight ? this.right : this.left; Branch result = branch != null - ? SparseMerkleTree.buildTree(branch, path, Arrays.copyOf(data, data.length)) - : new PendingLeafBranch(path, Arrays.copyOf(data, data.length)); + ? SparseMerkleTree.buildTree(branch, path, Arrays.copyOf(data, data.length)) + : new PendingLeafBranch(path, Arrays.copyOf(data, data.length)); if (isRight) { this.right = result; @@ -69,7 +70,7 @@ public synchronized SparseMerkleTreeRootNode calculateRoot() { } private static Branch buildTree(Branch branch, BigInteger remainingPath, byte[] value) - throws BranchExistsException, LeafOutOfBoundsException { + throws BranchExistsException, LeafOutOfBoundsException { CommonPath commonPath = CommonPath.create(remainingPath, branch.getPath()); boolean isRight = remainingPath.shiftRight(commonPath.getLength()).testBit(0); @@ -85,11 +86,11 @@ private static Branch buildTree(Branch branch, BigInteger remainingPath, byte[] LeafBranch leafBranch = (LeafBranch) branch; LeafBranch oldBranch = new PendingLeafBranch( - branch.getPath().shiftRight(commonPath.getLength()), leafBranch.getValue()); + branch.getPath().shiftRight(commonPath.getLength()), leafBranch.getValue()); LeafBranch newBranch = new PendingLeafBranch(remainingPath.shiftRight(commonPath.getLength()), - value); + value); return new PendingNodeBranch(commonPath.getPath(), isRight ? oldBranch : newBranch, - isRight ? newBranch : oldBranch); + isRight ? newBranch : oldBranch); } NodeBranch nodeBranch = (NodeBranch) branch; @@ -97,23 +98,23 @@ private static Branch buildTree(Branch branch, BigInteger remainingPath, byte[] // if node branch is split in the middle if (commonPath.getPath().compareTo(branch.getPath()) < 0) { LeafBranch newBranch = new PendingLeafBranch(remainingPath.shiftRight(commonPath.getLength()), - value); + value); NodeBranch oldBranch = new PendingNodeBranch( - branch.getPath().shiftRight(commonPath.getLength()), nodeBranch.getLeft(), - nodeBranch.getRight()); + branch.getPath().shiftRight(commonPath.getLength()), nodeBranch.getLeft(), + nodeBranch.getRight()); return new PendingNodeBranch(commonPath.getPath(), isRight ? oldBranch : newBranch, - isRight ? newBranch : oldBranch); + isRight ? newBranch : oldBranch); } if (isRight) { return new PendingNodeBranch(nodeBranch.getPath(), nodeBranch.getLeft(), - SparseMerkleTree.buildTree(nodeBranch.getRight(), - remainingPath.shiftRight(commonPath.getLength()), value)); + SparseMerkleTree.buildTree(nodeBranch.getRight(), + remainingPath.shiftRight(commonPath.getLength()), value)); } return new PendingNodeBranch(nodeBranch.getPath(), - SparseMerkleTree.buildTree(nodeBranch.getLeft(), - remainingPath.shiftRight(commonPath.getLength()), value), nodeBranch.getRight()); + SparseMerkleTree.buildTree(nodeBranch.getLeft(), + remainingPath.shiftRight(commonPath.getLength()), value), nodeBranch.getRight()); } } 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 69% 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 a3df4cb..339ed76 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreePath.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePath.java @@ -1,16 +1,17 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; -import java.math.BigInteger; -import java.util.List; -import java.util.Objects; -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.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import org.unicitylabs.sdk.smt.MerkleTreePathVerificationResult; import org.unicitylabs.sdk.util.BigIntegerConverter; +import java.math.BigInteger; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + /** * Sparse merkle tree path for selected path. */ @@ -61,16 +62,16 @@ public MerkleTreePathVerificationResult verify(BigInteger stateId) { BigInteger currentPath = step.getPath(); if (step.getPath().compareTo(BigInteger.ONE) > 0) { DataHash hash = new DataHasher(this.rootHash.getAlgorithm()) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(BigIntegerConverter.encode(step.getPath())), - CborSerializer.encodeOptional( - step.getData().orElse(null), - CborSerializer::encodeByteString - ) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigIntegerConverter.encode(step.getPath())), + CborSerializer.encodeOptional( + step.getData().orElse(null), + CborSerializer::encodeByteString + ) + ) ) - ) - .digest(); + .digest(); currentData = hash.getData(); } else { @@ -87,14 +88,14 @@ public MerkleTreePathVerificationResult verify(BigInteger stateId) { byte[] right = isRight ? currentData : step.getData().orElse(null); DataHash hash = new DataHasher(this.rootHash.getAlgorithm()) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(BigIntegerConverter.encode(step.getPath())), - CborSerializer.encodeOptional(left, CborSerializer::encodeByteString), - CborSerializer.encodeOptional(right, CborSerializer::encodeByteString) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigIntegerConverter.encode(step.getPath())), + CborSerializer.encodeOptional(left, CborSerializer::encodeByteString), + CborSerializer.encodeOptional(right, CborSerializer::encodeByteString) + ) ) - ) - .digest(); + .digest(); currentData = hash.getData(); @@ -103,12 +104,12 @@ public MerkleTreePathVerificationResult verify(BigInteger stateId) { return new MerkleTreePathVerificationResult(false, false); } currentPath = currentPath.shiftLeft(length) - .or(step.getPath().and(BigInteger.ONE.shiftLeft(length).subtract(BigInteger.ONE))); + .or(step.getPath().and(BigInteger.ONE.shiftLeft(length).subtract(BigInteger.ONE))); previousStep = step; } boolean pathValid = currentData != null - && this.rootHash.equals(new DataHash(this.rootHash.getAlgorithm(), currentData)); + && this.rootHash.equals(new DataHash(this.rootHash.getAlgorithm(), currentData)); boolean pathIncluded = currentPath.compareTo(stateId) == 0; return new MerkleTreePathVerificationResult(pathValid, pathIncluded); @@ -124,10 +125,10 @@ public static SparseMerkleTreePath fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); return new SparseMerkleTreePath( - DataHash.fromCbor(data.get(0)), - CborDeserializer.decodeArray(data.get(1)).stream() - .map(SparseMerkleTreePathStep::fromCbor) - .collect(Collectors.toList()) + DataHash.fromCbor(data.get(0)), + CborDeserializer.decodeArray(data.get(1)).stream() + .map(SparseMerkleTreePathStep::fromCbor) + .collect(Collectors.toList()) ); } @@ -138,12 +139,12 @@ public static SparseMerkleTreePath fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeArray( - this.rootHash.toCbor(), - CborSerializer.encodeArray( - this.steps.stream() - .map(SparseMerkleTreePathStep::toCbor) - .toArray(byte[][]::new) - ) + this.rootHash.toCbor(), + CborSerializer.encodeArray( + this.steps.stream() + .map(SparseMerkleTreePathStep::toCbor) + .toArray(byte[][]::new) + ) ); } 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 78% 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 fcccc81..e86f23b 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreePathStep.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePathStep.java @@ -1,14 +1,15 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; + +import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import org.unicitylabs.sdk.util.BigIntegerConverter; +import org.unicitylabs.sdk.util.HexConverter; import java.math.BigInteger; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; -import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; -import org.unicitylabs.sdk.serializer.cbor.CborSerializer; -import org.unicitylabs.sdk.util.BigIntegerConverter; -import org.unicitylabs.sdk.util.HexConverter; /** * Sparse Merkle tree path step. @@ -25,8 +26,8 @@ public class SparseMerkleTreePathStep { * @param data step data */ public SparseMerkleTreePathStep( - BigInteger path, - byte[] data + BigInteger path, + byte[] data ) { Objects.requireNonNull(path, "path cannot be null"); if (path.compareTo(BigInteger.ZERO) < 0) { @@ -65,8 +66,8 @@ public static SparseMerkleTreePathStep fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); return new SparseMerkleTreePathStep( - BigIntegerConverter.decode(CborDeserializer.decodeByteString(data.get(0))), - CborDeserializer.decodeNullable(data.get(1), CborDeserializer::decodeByteString) + BigIntegerConverter.decode(CborDeserializer.decodeByteString(data.get(0))), + CborDeserializer.decodeNullable(data.get(1), CborDeserializer::decodeByteString) ); } @@ -77,8 +78,8 @@ public static SparseMerkleTreePathStep fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeArray( - CborSerializer.encodeByteString(BigIntegerConverter.encode(this.path)), - CborSerializer.encodeOptional(this.data, CborSerializer::encodeByteString) + CborSerializer.encodeByteString(BigIntegerConverter.encode(this.path)), + CborSerializer.encodeOptional(this.data, CborSerializer::encodeByteString) ); } @@ -99,9 +100,9 @@ public int hashCode() { @Override public String toString() { return String.format( - "MerkleTreePathStep{path=%s, data=%s}", - this.path.toString(2), - this.data == null ? "null" : HexConverter.encode(this.data) + "MerkleTreePathStep{path=%s, data=%s}", + this.path.toString(2), + this.data == null ? "null" : HexConverter.encode(this.data) ); } } 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 62% 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..e1dbece 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. @@ -20,12 +21,12 @@ private SparseMerkleTreeRootNode(FinalizedNodeBranch root) { } static SparseMerkleTreeRootNode create( - FinalizedBranch left, - FinalizedBranch right, - HashAlgorithm hashAlgorithm + FinalizedBranch left, + FinalizedBranch right, + HashAlgorithm hashAlgorithm ) { return new SparseMerkleTreeRootNode( - FinalizedNodeBranch.create(BigInteger.ONE, left, right, hashAlgorithm) + FinalizedNodeBranch.create(BigInteger.ONE, left, right, hashAlgorithm) ); } @@ -46,8 +47,8 @@ public DataHash getRootHash() { */ public SparseMerkleTreePath getPath(BigInteger path) { return new SparseMerkleTreePath( - this.root.getHash(), - SparseMerkleTreeRootNode.generatePath(path, this.root) + this.root.getHash(), + SparseMerkleTreeRootNode.generatePath(path, this.root) ); } @@ -66,8 +67,8 @@ public int hashCode() { } private static List generatePath( - BigInteger remainingPath, - FinalizedBranch parent + BigInteger remainingPath, + FinalizedBranch parent ) { if (parent instanceof LeafBranch) { LeafBranch leaf = (LeafBranch) parent; @@ -79,20 +80,20 @@ private static List generatePath( remainingPath = remainingPath.shiftRight(commonPath.getLength()); if (commonPath.getPath().compareTo(parent.getPath()) != 0 - || remainingPath.compareTo(BigInteger.ONE) == 0) { + || remainingPath.compareTo(BigInteger.ONE) == 0) { return List.of( - new SparseMerkleTreePathStep( - BigInteger.ZERO, - node.getLeft() == null - ? null - : node.getLeft().getHash().getData() - ), - new SparseMerkleTreePathStep( - node.getPath(), - node.getRight() == null - ? null - : node.getRight().getHash().getData() - ) + new SparseMerkleTreePathStep( + BigInteger.ZERO, + node.getLeft() == null + ? null + : node.getLeft().getHash().getData() + ), + new SparseMerkleTreePathStep( + node.getPath(), + node.getRight() == null + ? null + : node.getRight().getHash().getData() + ) ); } @@ -101,22 +102,22 @@ private static List generatePath( FinalizedBranch siblingBranch = isRight ? node.getLeft() : node.getRight(); SparseMerkleTreePathStep step = new SparseMerkleTreePathStep( - node.getPath(), - siblingBranch == null ? null : siblingBranch.getHash().getData() + node.getPath(), + siblingBranch == null ? null : siblingBranch.getHash().getData() ); if (branch == null) { return List.of( - new SparseMerkleTreePathStep( - isRight ? BigInteger.ONE : BigInteger.ZERO, - null - ), - step + new SparseMerkleTreePathStep( + isRight ? BigInteger.ONE : BigInteger.ZERO, + null + ), + step ); } List list = new ArrayList<>( - SparseMerkleTreeRootNode.generatePath(remainingPath, branch) + SparseMerkleTreeRootNode.generatePath(remainingPath, branch) ); list.add(step); 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..6e282c5 --- /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..3e3d095 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/smt/radix/FinalizedNodeBranch.java @@ -0,0 +1,86 @@ +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.LongConverter; + +import java.math.BigInteger; + +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..3b4c9d1 --- /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..99205df --- /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..ad1c518 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/Branch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/Branch.java @@ -1,8 +1,9 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; -import java.math.BigInteger; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; +import java.math.BigInteger; + /** * Branch in a sparse merkle sum tree. */ 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..2163141 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/FinalizedBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/FinalizedBranch.java @@ -1,8 +1,9 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; -import java.math.BigInteger; import org.unicitylabs.sdk.crypto.hash.DataHash; +import java.math.BigInteger; + /** * Finalized branch in sparse merkle sum tree. */ 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 76% 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..29a43a0 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/FinalizedLeafBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/FinalizedLeafBranch.java @@ -1,14 +1,15 @@ -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.serializer.cbor.CborSerializer; +import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTree.LeafValue; import org.unicitylabs.sdk.util.BigIntegerConverter; +import java.math.BigInteger; +import java.util.Objects; + /** * Finalized leaf branch in a sparse merkle sum tree. */ @@ -33,19 +34,19 @@ private FinalizedLeafBranch(BigInteger path, LeafValue value, DataHash hash) { * @return finalized leaf branch */ public static FinalizedLeafBranch create( - BigInteger path, - LeafValue value, - HashAlgorithm hashAlgorithm + BigInteger path, + LeafValue value, + HashAlgorithm hashAlgorithm ) { DataHash hash = new DataHasher(hashAlgorithm) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(BigIntegerConverter.encode(path)), - CborSerializer.encodeByteString(value.getValue()), - CborSerializer.encodeByteString(BigIntegerConverter.encode(value.getCounter())) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigIntegerConverter.encode(path)), + CborSerializer.encodeByteString(value.getValue()), + CborSerializer.encodeByteString(BigIntegerConverter.encode(value.getCounter())) + ) ) - ) - .digest(); + .digest(); return new FinalizedLeafBranch(path, value, hash); } 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 71% 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..58b308e 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/FinalizedNodeBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/FinalizedNodeBranch.java @@ -1,13 +1,14 @@ -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.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.BigIntegerConverter; +import java.math.BigInteger; +import java.util.Objects; + /** * Finalized node branch in a sparse merkle sum tree. */ @@ -20,11 +21,11 @@ class FinalizedNodeBranch implements NodeBranch, FinalizedBranch { private final DataHash hash; private FinalizedNodeBranch( - BigInteger path, - FinalizedBranch left, - FinalizedBranch right, - BigInteger counter, - DataHash hash + BigInteger path, + FinalizedBranch left, + FinalizedBranch right, + BigInteger counter, + DataHash hash ) { this.path = path; this.left = left; @@ -43,10 +44,10 @@ private FinalizedNodeBranch( * @return finalized node branch */ public static FinalizedNodeBranch create( - BigInteger path, - FinalizedBranch left, - FinalizedBranch right, - HashAlgorithm hashAlgorithm + BigInteger path, + FinalizedBranch left, + FinalizedBranch right, + HashAlgorithm hashAlgorithm ) { byte[] leftHash = left == null ? null : left.getHash().getData(); byte[] rightHash = right == null ? null : right.getHash().getData(); @@ -54,16 +55,16 @@ public static FinalizedNodeBranch create( BigInteger rightCounter = right == null ? BigInteger.ZERO : right.getCounter(); DataHash hash = new DataHasher(hashAlgorithm) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(BigIntegerConverter.encode(path)), - CborSerializer.encodeOptional(leftHash, CborSerializer::encodeByteString), - CborSerializer.encodeByteString(BigIntegerConverter.encode(leftCounter)), - CborSerializer.encodeOptional(rightHash, CborSerializer::encodeByteString), - CborSerializer.encodeByteString(BigIntegerConverter.encode(rightCounter)) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigIntegerConverter.encode(path)), + CborSerializer.encodeOptional(leftHash, CborSerializer::encodeByteString), + CborSerializer.encodeByteString(BigIntegerConverter.encode(leftCounter)), + CborSerializer.encodeOptional(rightHash, CborSerializer::encodeByteString), + CborSerializer.encodeByteString(BigIntegerConverter.encode(rightCounter)) + ) ) - ) - .digest(); + .digest(); BigInteger counter = leftCounter.add(rightCounter); @@ -107,7 +108,7 @@ public boolean equals(Object o) { } FinalizedNodeBranch that = (FinalizedNodeBranch) o; return Objects.equals(this.path, that.path) && Objects.equals(this.left, that.left) - && Objects.equals(this.right, that.right); + && Objects.equals(this.right, that.right); } @Override 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..f4f10c4 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,10 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; + +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; +import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTree.LeafValue; import java.math.BigInteger; import java.util.Objects; -import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.unicitylabs.sdk.mtree.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 89% 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..891dca6 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/PendingNodeBranch.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/PendingNodeBranch.java @@ -1,8 +1,9 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; + +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import java.math.BigInteger; import java.util.Objects; -import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; /** * Pending node branch in a sparse merkle sum tree. @@ -44,7 +45,7 @@ public Branch getRight() { @Override public FinalizedNodeBranch finalize(HashAlgorithm hashAlgorithm) { return FinalizedNodeBranch.create(this.path, this.left.finalize(hashAlgorithm), - this.right.finalize(hashAlgorithm), hashAlgorithm); + this.right.finalize(hashAlgorithm), hashAlgorithm); } @Override @@ -54,7 +55,7 @@ public boolean equals(Object o) { } PendingNodeBranch that = (PendingNodeBranch) o; return Objects.equals(this.path, that.path) && Objects.equals(this.left, that.left) - && Objects.equals(this.right, that.right); + && Objects.equals(this.right, that.right); } @Override 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 81% 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..467fb3a 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,13 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; + +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 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; /** * Sparse Merkle Sum Tree implementation. @@ -38,7 +39,7 @@ public SparseMerkleSumTree(HashAlgorithm hashAlgorithm) { * @throws NullPointerException if the path or value is null */ public synchronized void addLeaf(BigInteger path, LeafValue value) - throws BranchExistsException, LeafOutOfBoundsException { + throws BranchExistsException, LeafOutOfBoundsException { Objects.requireNonNull(path, "Path cannot be null"); Objects.requireNonNull(value, "Value cannot be null"); @@ -53,8 +54,8 @@ public synchronized void addLeaf(BigInteger path, LeafValue value) boolean isRight = path.testBit(0); Branch branch = isRight ? this.right : this.left; Branch result = branch != null - ? SparseMerkleSumTree.buildTree(branch, path, value) - : new PendingLeafBranch(path, value); + ? SparseMerkleSumTree.buildTree(branch, path, value) + : new PendingLeafBranch(path, value); if (isRight) { this.right = result; @@ -78,7 +79,7 @@ public synchronized SparseMerkleSumTreeRootNode calculateRoot() { } private static Branch buildTree(Branch branch, BigInteger remainingPath, LeafValue value) - throws BranchExistsException, LeafOutOfBoundsException { + throws BranchExistsException, LeafOutOfBoundsException { CommonPath commonPath = CommonPath.create(remainingPath, branch.getPath()); boolean isRight = remainingPath.shiftRight(commonPath.getLength()).testBit(0); @@ -94,11 +95,11 @@ private static Branch buildTree(Branch branch, BigInteger remainingPath, LeafVal LeafBranch leafBranch = (LeafBranch) branch; LeafBranch oldBranch = new PendingLeafBranch( - branch.getPath().shiftRight(commonPath.getLength()), leafBranch.getValue()); + branch.getPath().shiftRight(commonPath.getLength()), leafBranch.getValue()); LeafBranch newBranch = new PendingLeafBranch(remainingPath.shiftRight(commonPath.getLength()), - value); + value); return new PendingNodeBranch(commonPath.getPath(), isRight ? oldBranch : newBranch, - isRight ? newBranch : oldBranch); + isRight ? newBranch : oldBranch); } NodeBranch nodeBranch = (NodeBranch) branch; @@ -106,23 +107,23 @@ private static Branch buildTree(Branch branch, BigInteger remainingPath, LeafVal // if node branch is split in the middle if (commonPath.getPath().compareTo(branch.getPath()) < 0) { LeafBranch newBranch = new PendingLeafBranch(remainingPath.shiftRight(commonPath.getLength()), - value); + value); NodeBranch oldBranch = new PendingNodeBranch( - branch.getPath().shiftRight(commonPath.getLength()), nodeBranch.getLeft(), - nodeBranch.getRight()); + branch.getPath().shiftRight(commonPath.getLength()), nodeBranch.getLeft(), + nodeBranch.getRight()); return new PendingNodeBranch(commonPath.getPath(), isRight ? oldBranch : newBranch, - isRight ? newBranch : oldBranch); + isRight ? newBranch : oldBranch); } if (isRight) { return new PendingNodeBranch(nodeBranch.getPath(), nodeBranch.getLeft(), - SparseMerkleSumTree.buildTree(nodeBranch.getRight(), - remainingPath.shiftRight(commonPath.getLength()), value)); + SparseMerkleSumTree.buildTree(nodeBranch.getRight(), + remainingPath.shiftRight(commonPath.getLength()), value)); } return new PendingNodeBranch(nodeBranch.getPath(), - SparseMerkleSumTree.buildTree(nodeBranch.getLeft(), - remainingPath.shiftRight(commonPath.getLength()), value), nodeBranch.getRight()); + SparseMerkleSumTree.buildTree(nodeBranch.getLeft(), + remainingPath.shiftRight(commonPath.getLength()), value), nodeBranch.getRight()); } /** 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 67% 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 79a1347..1adf448 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreePath.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePath.java @@ -1,16 +1,17 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; -import java.math.BigInteger; -import java.util.List; -import java.util.Objects; -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.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import org.unicitylabs.sdk.smt.MerkleTreePathVerificationResult; import org.unicitylabs.sdk.util.BigIntegerConverter; +import java.math.BigInteger; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + /** * Path in a sparse merkle sum tree. */ @@ -20,8 +21,8 @@ public class SparseMerkleSumTreePath { private final List steps; SparseMerkleSumTreePath( - DataHash rootHash, - List steps + DataHash rootHash, + List steps ) { Objects.requireNonNull(rootHash, "root cannot be null"); Objects.requireNonNull(steps, "steps cannot be null"); @@ -55,7 +56,7 @@ public List getSteps() { * @return result of the verification */ public MerkleTreePathVerificationResult verify(BigInteger stateId) { - if (this.steps.size() == 0) { + if (this.steps.isEmpty()) { return new MerkleTreePathVerificationResult(false, false); } @@ -65,17 +66,17 @@ public MerkleTreePathVerificationResult verify(BigInteger stateId) { BigInteger currentSum = step.getValue(); if (step.getPath().compareTo(BigInteger.ONE) > 0) { DataHash hash = new DataHasher(this.rootHash.getAlgorithm()) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(BigIntegerConverter.encode(step.getPath())), - CborSerializer.encodeOptional( - step.getData().orElse(null), - CborSerializer::encodeByteString - ), - CborSerializer.encodeByteString(BigIntegerConverter.encode(step.getValue())) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigIntegerConverter.encode(step.getPath())), + CborSerializer.encodeOptional( + step.getData().orElse(null), + CborSerializer::encodeByteString + ), + CborSerializer.encodeByteString(BigIntegerConverter.encode(step.getValue())) + ) ) - ) - .digest(); + .digest(); currentData = hash.getData(); } else { @@ -94,16 +95,16 @@ public MerkleTreePathVerificationResult verify(BigInteger stateId) { BigInteger rightCounter = isRight ? currentSum : step.getValue(); DataHash hash = new DataHasher(this.rootHash.getAlgorithm()) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(BigIntegerConverter.encode(step.getPath())), - CborSerializer.encodeOptional(leftHash, CborSerializer::encodeByteString), - CborSerializer.encodeByteString(BigIntegerConverter.encode(leftCounter)), - CborSerializer.encodeOptional(rightHash, CborSerializer::encodeByteString), - CborSerializer.encodeByteString(BigIntegerConverter.encode(rightCounter)) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigIntegerConverter.encode(step.getPath())), + CborSerializer.encodeOptional(leftHash, CborSerializer::encodeByteString), + CborSerializer.encodeByteString(BigIntegerConverter.encode(leftCounter)), + CborSerializer.encodeOptional(rightHash, CborSerializer::encodeByteString), + CborSerializer.encodeByteString(BigIntegerConverter.encode(rightCounter)) + ) ) - ) - .digest(); + .digest(); currentData = hash.getData(); @@ -112,13 +113,13 @@ public MerkleTreePathVerificationResult verify(BigInteger stateId) { return new MerkleTreePathVerificationResult(false, false); } currentPath = currentPath.shiftLeft(length) - .or(step.getPath().and(BigInteger.ONE.shiftLeft(length).subtract(BigInteger.ONE))); + .or(step.getPath().and(BigInteger.ONE.shiftLeft(length).subtract(BigInteger.ONE))); currentSum = currentSum.add(step.getValue()); previousStep = step; } boolean pathValid = currentData != null - && this.rootHash.equals(new DataHash(this.rootHash.getAlgorithm(), currentData)); + && this.rootHash.equals(new DataHash(this.rootHash.getAlgorithm(), currentData)); boolean pathIncluded = currentPath.compareTo(stateId) == 0; return new MerkleTreePathVerificationResult(pathValid, pathIncluded); @@ -134,10 +135,10 @@ public static SparseMerkleSumTreePath fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); return new SparseMerkleSumTreePath( - DataHash.fromCbor(data.get(0)), - CborDeserializer.decodeArray(data.get(1)).stream() - .map(SparseMerkleSumTreePathStep::fromCbor) - .collect(Collectors.toList()) + DataHash.fromCbor(data.get(0)), + CborDeserializer.decodeArray(data.get(1)).stream() + .map(SparseMerkleSumTreePathStep::fromCbor) + .collect(Collectors.toList()) ); } @@ -148,12 +149,12 @@ public static SparseMerkleSumTreePath fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeArray( - this.rootHash.toCbor(), - CborSerializer.encodeArray( - this.steps.stream() - .map(SparseMerkleSumTreePathStep::toCbor) - .toArray(byte[][]::new) - ) + this.rootHash.toCbor(), + CborSerializer.encodeArray( + this.steps.stream() + .map(SparseMerkleSumTreePathStep::toCbor) + .toArray(byte[][]::new) + ) ); } @@ -185,8 +186,8 @@ public static class Root { private final BigInteger counter; Root( - DataHash hash, - BigInteger counter + DataHash hash, + BigInteger counter ) { this.hash = Objects.requireNonNull(hash, "hash cannot be null"); this.counter = Objects.requireNonNull(counter, "counter cannot be null"); @@ -220,8 +221,8 @@ public static Root fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); return new Root( - DataHash.fromCbor(data.get(0)), - BigIntegerConverter.decode(CborDeserializer.decodeByteString(data.get(1))) + DataHash.fromCbor(data.get(0)), + BigIntegerConverter.decode(CborDeserializer.decodeByteString(data.get(1))) ); } @@ -232,8 +233,8 @@ public static Root fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeArray( - this.hash.toCbor(), - CborSerializer.encodeByteString(BigIntegerConverter.encode(this.counter)) + this.hash.toCbor(), + CborSerializer.encodeByteString(BigIntegerConverter.encode(this.counter)) ); } 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 72% 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 4e94ffb..06dc725 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreePathStep.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePathStep.java @@ -1,14 +1,15 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; + +import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import org.unicitylabs.sdk.util.BigIntegerConverter; +import org.unicitylabs.sdk.util.HexConverter; import java.math.BigInteger; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; -import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; -import org.unicitylabs.sdk.serializer.cbor.CborSerializer; -import org.unicitylabs.sdk.util.BigIntegerConverter; -import org.unicitylabs.sdk.util.HexConverter; /** * Step in a sparse merkle sum tree path. @@ -20,9 +21,9 @@ public class SparseMerkleSumTreePathStep { private final BigInteger value; SparseMerkleSumTreePathStep( - BigInteger path, - byte[] data, - BigInteger value + BigInteger path, + byte[] data, + BigInteger value ) { Objects.requireNonNull(path, "path cannot be null"); Objects.requireNonNull(value, "value cannot be null"); @@ -69,9 +70,9 @@ public static SparseMerkleSumTreePathStep fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); return new SparseMerkleSumTreePathStep( - BigIntegerConverter.decode(CborDeserializer.decodeByteString(data.get(0))), - CborDeserializer.decodeNullable(data.get(1), CborDeserializer::decodeByteString), - BigIntegerConverter.decode(CborDeserializer.decodeByteString(data.get(2))) + BigIntegerConverter.decode(CborDeserializer.decodeByteString(data.get(0))), + CborDeserializer.decodeNullable(data.get(1), CborDeserializer::decodeByteString), + BigIntegerConverter.decode(CborDeserializer.decodeByteString(data.get(2))) ); } @@ -82,9 +83,9 @@ public static SparseMerkleSumTreePathStep fromCbor(byte[] bytes) { */ public byte[] toCbor() { return CborSerializer.encodeArray( - CborSerializer.encodeByteString(BigIntegerConverter.encode(this.path)), - CborSerializer.encodeOptional(this.data, CborSerializer::encodeByteString), - CborSerializer.encodeByteString(BigIntegerConverter.encode(this.value)) + CborSerializer.encodeByteString(BigIntegerConverter.encode(this.path)), + CborSerializer.encodeOptional(this.data, CborSerializer::encodeByteString), + CborSerializer.encodeByteString(BigIntegerConverter.encode(this.value)) ); } @@ -95,7 +96,7 @@ public boolean equals(Object o) { } SparseMerkleSumTreePathStep that = (SparseMerkleSumTreePathStep) o; return Objects.equals(this.path, that.path) && Arrays.equals(this.data, that.data) - && Objects.equals(this.value, that.value); + && Objects.equals(this.value, that.value); } @Override @@ -106,9 +107,9 @@ public int hashCode() { @Override public String toString() { return String.format("MerkleTreePathStep{path=%s, data=%s, value=%s}", - this.path.toString(2), - this.data == null ? null : HexConverter.encode(this.data), - this.value + this.path.toString(2), + this.data == null ? null : HexConverter.encode(this.data), + this.value ); } } 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 55% 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..9e7d710 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreeRootNode.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreeRootNode.java @@ -1,12 +1,13 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; + +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 Sum Tree root node. @@ -20,12 +21,12 @@ private SparseMerkleSumTreeRootNode(FinalizedNodeBranch root) { } static SparseMerkleSumTreeRootNode create( - FinalizedBranch left, - FinalizedBranch right, - HashAlgorithm hashAlgorithm + FinalizedBranch left, + FinalizedBranch right, + HashAlgorithm hashAlgorithm ) { return new SparseMerkleSumTreeRootNode( - FinalizedNodeBranch.create(BigInteger.ONE, left, right, hashAlgorithm) + FinalizedNodeBranch.create(BigInteger.ONE, left, right, hashAlgorithm) ); } @@ -55,8 +56,8 @@ public BigInteger getValue() { */ public SparseMerkleSumTreePath getPath(BigInteger path) { return new SparseMerkleSumTreePath( - this.root.getHash(), - SparseMerkleSumTreeRootNode.generatePath(path, this.root) + this.root.getHash(), + SparseMerkleSumTreeRootNode.generatePath(path, this.root) ); } @@ -75,15 +76,15 @@ public int hashCode() { } private static List generatePath( - BigInteger remainingPath, - FinalizedBranch parent + BigInteger remainingPath, + FinalizedBranch parent ) { if (parent instanceof LeafBranch) { LeafBranch leaf = (LeafBranch) parent; return List.of(new SparseMerkleSumTreePathStep( - leaf.getPath(), - leaf.getValue().getValue(), - leaf.getValue().getCounter() + leaf.getPath(), + leaf.getValue().getValue(), + leaf.getValue().getCounter() )); } @@ -92,26 +93,26 @@ private static List generatePath( remainingPath = remainingPath.shiftRight(commonPath.getLength()); if (commonPath.getPath().compareTo(parent.getPath()) != 0 - || remainingPath.compareTo(BigInteger.ONE) == 0) { + || remainingPath.compareTo(BigInteger.ONE) == 0) { return List.of( - new SparseMerkleSumTreePathStep( - BigInteger.ZERO, - node.getLeft() == null - ? null - : node.getLeft().getHash().getData(), - node.getLeft() == null - ? BigInteger.ZERO - : node.getLeft().getCounter() - ), - new SparseMerkleSumTreePathStep( - node.getPath(), - node.getRight() == null - ? null - : node.getRight().getHash().getData(), - node.getRight() == null - ? BigInteger.ZERO - : node.getRight().getCounter() - ) + new SparseMerkleSumTreePathStep( + BigInteger.ZERO, + node.getLeft() == null + ? null + : node.getLeft().getHash().getData(), + node.getLeft() == null + ? BigInteger.ZERO + : node.getLeft().getCounter() + ), + new SparseMerkleSumTreePathStep( + node.getPath(), + node.getRight() == null + ? null + : node.getRight().getHash().getData(), + node.getRight() == null + ? BigInteger.ZERO + : node.getRight().getCounter() + ) ); } @@ -120,24 +121,24 @@ private static List generatePath( FinalizedBranch siblingBranch = isRight ? node.getLeft() : node.getRight(); SparseMerkleSumTreePathStep step = new SparseMerkleSumTreePathStep( - node.getPath(), - siblingBranch == null ? null : siblingBranch.getHash().getData(), - siblingBranch == null ? BigInteger.ZERO : siblingBranch.getCounter() + node.getPath(), + siblingBranch == null ? null : siblingBranch.getHash().getData(), + siblingBranch == null ? BigInteger.ZERO : siblingBranch.getCounter() ); if (branch == null) { return List.of( - new SparseMerkleSumTreePathStep( - isRight ? BigInteger.ONE : BigInteger.ZERO, - null, - BigInteger.ZERO - ), - step + new SparseMerkleSumTreePathStep( + isRight ? BigInteger.ONE : BigInteger.ZERO, + null, + BigInteger.ZERO + ), + step ); } List list = new ArrayList<>( - SparseMerkleSumTreeRootNode.generatePath(remainingPath, branch) + SparseMerkleSumTreeRootNode.generatePath(remainingPath, branch) ); list.add(step); diff --git a/src/main/java/org/unicitylabs/sdk/transaction/Address.java b/src/main/java/org/unicitylabs/sdk/transaction/Address.java index b344895..c25c724 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/Address.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/Address.java @@ -1,6 +1,5 @@ package org.unicitylabs.sdk.transaction; -import java.util.Arrays; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.DataHasher; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; @@ -10,6 +9,8 @@ import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.HexConverter; +import java.util.Arrays; + /** * Transaction address. */ @@ -65,7 +66,7 @@ public static Address fromCbor(byte[] bytes) { */ public static Address fromPredicate(Predicate predicate) { DataHash hash = new DataHasher(HashAlgorithm.SHA256).update( - EncodedPredicate.fromPredicate(predicate).toCbor()).digest(); + EncodedPredicate.fromPredicate(predicate).toCbor()).digest(); return new Address(hash.getData()); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/CertifiedMintTransaction.java b/src/main/java/org/unicitylabs/sdk/transaction/CertifiedMintTransaction.java index a8546e9..f9ff5e7 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/CertifiedMintTransaction.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/CertifiedMintTransaction.java @@ -1,6 +1,5 @@ package org.unicitylabs.sdk.transaction; -import java.util.List; import org.unicitylabs.sdk.api.InclusionProof; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.hash.DataHash; @@ -13,6 +12,8 @@ import org.unicitylabs.sdk.util.verification.VerificationException; import org.unicitylabs.sdk.util.verification.VerificationResult; +import java.util.List; + /** * Mint transaction bundled with an inclusion proof. */ @@ -87,7 +88,7 @@ public InclusionProof getInclusionProof() { public static CertifiedMintTransaction fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); return new CertifiedMintTransaction(MintTransaction.fromCbor(data.get(0)), - InclusionProof.fromCbor(data.get(1))); + InclusionProof.fromCbor(data.get(1))); } /** @@ -101,13 +102,13 @@ public static CertifiedMintTransaction fromCbor(byte[] bytes) { * @throws VerificationException if inclusion proof verification fails */ public static CertifiedMintTransaction fromTransaction(RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, MintTransaction transaction, - InclusionProof inclusionProof) { + PredicateVerifierService predicateVerifier, MintTransaction transaction, + InclusionProof inclusionProof) { VerificationResult result = InclusionProofVerificationRule.verify( - trustBase, - predicateVerifier, - inclusionProof, - transaction + trustBase, + predicateVerifier, + inclusionProof, + transaction ); if (result.getStatus() != InclusionProofVerificationStatus.OK) { throw new VerificationException("Inclusion proof verification failed", result); @@ -134,6 +135,6 @@ public byte[] toCbor() { @Override public String toString() { return String.format("CertifiedMintTransaction{transaction=%s, inclusionProof=%s}", - this.transaction, this.inclusionProof); + this.transaction, this.inclusionProof); } } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/CertifiedTransferTransaction.java b/src/main/java/org/unicitylabs/sdk/transaction/CertifiedTransferTransaction.java index c386ac3..a74d318 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/CertifiedTransferTransaction.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/CertifiedTransferTransaction.java @@ -1,6 +1,5 @@ package org.unicitylabs.sdk.transaction; -import java.util.List; import org.unicitylabs.sdk.api.InclusionProof; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.hash.DataHash; @@ -13,6 +12,8 @@ import org.unicitylabs.sdk.util.verification.VerificationException; import org.unicitylabs.sdk.util.verification.VerificationResult; +import java.util.List; + /** * Transfer transaction with a verified inclusion proof. */ @@ -22,8 +23,8 @@ public class CertifiedTransferTransaction implements Transaction { private final InclusionProof inclusionProof; private CertifiedTransferTransaction( - TransferTransaction transaction, - InclusionProof inclusionProof + TransferTransaction transaction, + InclusionProof inclusionProof ) { this.transaction = transaction; this.inclusionProof = inclusionProof; @@ -99,7 +100,7 @@ public static CertifiedTransferTransaction fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); return new CertifiedTransferTransaction(TransferTransaction.fromCbor(data.get(0)), - InclusionProof.fromCbor(data.get(1))); + InclusionProof.fromCbor(data.get(1))); } /** @@ -118,13 +119,13 @@ public static CertifiedTransferTransaction fromCbor(byte[] bytes) { * @throws VerificationException if inclusion proof verification fails */ public static CertifiedTransferTransaction fromTransaction(RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, TransferTransaction transaction, - InclusionProof inclusionProof) { + PredicateVerifierService predicateVerifier, TransferTransaction transaction, + InclusionProof inclusionProof) { VerificationResult result = InclusionProofVerificationRule.verify( - trustBase, - predicateVerifier, - inclusionProof, - transaction + trustBase, + predicateVerifier, + inclusionProof, + transaction ); if (result.getStatus() != InclusionProofVerificationStatus.OK) { throw new VerificationException("Inclusion proof verification failed", result); @@ -166,6 +167,6 @@ public byte[] toCbor() { @Override public String toString() { return String.format("CertifiedTransferTransaction{transaction=%s, inclusionProof=%s}", - this.transaction, this.inclusionProof); + this.transaction, this.inclusionProof); } } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java b/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java index 2c50b34..2cd8675 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java @@ -1,8 +1,5 @@ package org.unicitylabs.sdk.transaction; -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,9 +11,14 @@ 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; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + /** * Represents a Mint Transaction. @@ -25,6 +27,8 @@ * it to an initial owner. */ public class MintTransaction implements Transaction { + public static final long CBOR_TAG = 39041; + private static final int VERSION = 1; private final MintTransactionState sourceStateHash; private final Predicate lockScript; @@ -34,12 +38,12 @@ public class MintTransaction implements Transaction { private final byte[] data; private MintTransaction( - MintTransactionState sourceStateHash, - Predicate lockScript, - Address recipient, - TokenId tokenId, - TokenType tokenType, - byte[] data + MintTransactionState sourceStateHash, + Predicate lockScript, + Address recipient, + TokenId tokenId, + TokenType tokenType, + byte[] data ) { this.sourceStateHash = sourceStateHash; this.lockScript = lockScript; @@ -49,6 +53,10 @@ private MintTransaction( this.data = data; } + public int getVersion() { + return MintTransaction.VERSION; + } + /** * Retrieves the state hash of the source state. @@ -116,10 +124,10 @@ public byte[] getNonce() { * @return mint transaction */ public static MintTransaction create( - Address recipient, - TokenId tokenId, - TokenType tokenType, - byte[] data + Address recipient, + TokenId tokenId, + TokenType tokenType, + byte[] data ) { Objects.requireNonNull(recipient, "Recipient cannot be null"); Objects.requireNonNull(tokenId, "Token ID cannot be null"); @@ -128,12 +136,12 @@ public static MintTransaction create( SigningService signingService = MintSigningService.create(tokenId); return new MintTransaction( - MintTransactionState.create(tokenId), - PayToPublicKeyPredicate.fromSigningService(signingService), - recipient, - tokenId, - tokenType, - Arrays.copyOf(data, data.length) + MintTransactionState.create(tokenId), + PayToPublicKeyPredicate.fromSigningService(signingService), + recipient, + tokenId, + tokenType, + Arrays.copyOf(data, data.length) ); } @@ -145,14 +153,23 @@ public static MintTransaction create( * @return mint transaction */ public static MintTransaction fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); - List aux = CborDeserializer.decodeArray(data.get(2)); + 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(0)), - TokenId.fromCbor(data.get(1)), - TokenType.fromCbor(aux.get(0)), - CborDeserializer.decodeByteString(aux.get(1)) + Address.fromCbor(data.get(1)), + TokenId.fromCbor(data.get(2)), + TokenType.fromCbor(aux.get(0)), + CborDeserializer.decodeByteString(aux.get(1)) ); } @@ -164,13 +181,13 @@ public static MintTransaction fromCbor(byte[] bytes) { @Override public DataHash calculateStateHash() { return new DataHasher(HashAlgorithm.SHA256) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(this.sourceStateHash.getImprint()), - CborSerializer.encodeByteString(this.getNonce()) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(this.sourceStateHash.getImprint()), + CborSerializer.encodeByteString(this.getNonce()) + ) ) - ) - .digest(); + .digest(); } /** @@ -190,11 +207,15 @@ public DataHash calculateTransactionHash() { */ @Override public byte[] toCbor() { - return CborSerializer.encodeArray( - this.recipient.toCbor(), - this.tokenId.toCbor(), - CborSerializer.encodeArray(this.tokenType.toCbor(), - CborSerializer.encodeByteString(this.data)) + 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)) + ) ); } @@ -208,19 +229,19 @@ public byte[] toCbor() { * @return certified mint transaction */ public CertifiedMintTransaction toCertifiedTransaction( - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - InclusionProof inclusionProof + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + InclusionProof inclusionProof ) { return CertifiedMintTransaction.fromTransaction(trustBase, predicateVerifier, this, - inclusionProof); + 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)); + "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/MintTransactionState.java b/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionState.java index b2147b5..8626f17 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionState.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionState.java @@ -1,19 +1,20 @@ package org.unicitylabs.sdk.transaction; -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.CborSerializer; import org.unicitylabs.sdk.util.HexConverter; +import java.util.Objects; + /** * Represents the state of a mint transaction. */ public class MintTransactionState extends DataHash { private static final byte[] MINT_SUFFIX = HexConverter.decode( - "9e82002c144d7c5796c50f6db50a0c7bbd7f717ae3af6c6c71a3e9eba3022730"); + "9e82002c144d7c5796c50f6db50a0c7bbd7f717ae3af6c6c71a3e9eba3022730"); private MintTransactionState(DataHash hash) { super(hash.getAlgorithm(), hash.getData()); @@ -29,14 +30,14 @@ public static MintTransactionState create(TokenId tokenId) { Objects.requireNonNull(tokenId, "Token ID cannot be null"); return new MintTransactionState( - new DataHasher(HashAlgorithm.SHA256) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(tokenId.getBytes()), - CborSerializer.encodeByteString(MintTransactionState.MINT_SUFFIX) - ) - ) - .digest() + new DataHasher(HashAlgorithm.SHA256) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(tokenId.getBytes()), + CborSerializer.encodeByteString(MintTransactionState.MINT_SUFFIX) + ) + ) + .digest() ); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/Token.java b/src/main/java/org/unicitylabs/sdk/transaction/Token.java index ee1d8ee..5bb8326 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,10 +11,16 @@ 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; + /** * Immutable token aggregate containing the certified genesis mint transaction and transfer history. */ -public final class Token { +public class Token { + public static final long CBOR_TAG = 39040; + private static final int VERSION = 1; private final CertifiedMintTransaction genesis; private final List transactions; @@ -30,6 +34,10 @@ private Token(CertifiedMintTransaction genesis) { this(genesis, List.of()); } + public int getVersion() { + return Token.VERSION; + } + /** * Returns the token identifier. * @@ -86,13 +94,22 @@ public List getTransactions() { * @return decoded token */ 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)), - transactions.stream().map(CertifiedTransferTransaction::fromCbor) - .collect(Collectors.toList()) + CertifiedMintTransaction.fromCbor(data.get(1)), + transactions.stream().map(CertifiedTransferTransaction::fromCbor) + .collect(Collectors.toList()) ); } @@ -106,7 +123,7 @@ public static Token fromCbor(byte[] bytes) { * @throws VerificationException if genesis verification fails */ public static Token mint(RootTrustBase trustBase, PredicateVerifierService predicateVerifier, - CertifiedMintTransaction genesis) { + CertifiedMintTransaction genesis) { Token token = new Token(genesis); VerificationResult result = token.verify(trustBase, predicateVerifier); if (result.getStatus() != VerificationStatus.OK) { @@ -126,12 +143,12 @@ public static Token mint(RootTrustBase trustBase, PredicateVerifierService predi * @throws VerificationException if transfer verification fails */ public Token transfer(RootTrustBase trustBase, PredicateVerifierService predicateVerifier, - CertifiedTransferTransaction transaction) { + CertifiedTransferTransaction transaction) { VerificationResult result = CertifiedTransferTransactionVerificationRule.verify( - trustBase, - predicateVerifier, - this.getLatestTransaction(), - transaction + trustBase, + predicateVerifier, + this.getLatestTransaction(), + transaction ); if (result.getStatus() != VerificationStatus.OK) { throw new VerificationException("Invalid token transfer transaction", result); @@ -150,34 +167,34 @@ public Token transfer(RootTrustBase trustBase, PredicateVerifierService predicat * @return verification result with nested per-step verification details */ public VerificationResult verify(RootTrustBase trustBase, - PredicateVerifierService predicateVerifier) { + PredicateVerifierService predicateVerifier) { List> results = new ArrayList<>(); VerificationResult result = CertifiedMintTransactionVerificationRule.verify(trustBase, - predicateVerifier, this.genesis); + predicateVerifier, this.genesis); results.add(result); if (result.getStatus() != VerificationStatus.OK) { return new VerificationResult<>("TokenVerification", VerificationStatus.FAIL, - "Genesis verification failed", results); + "Genesis verification failed", results); } List> transferResults = new ArrayList<>(); for (int i = 0; i < this.transactions.size(); i++) { CertifiedTransferTransaction transaction = this.transactions.get(i); result = CertifiedTransferTransactionVerificationRule.verify(trustBase, predicateVerifier, - i == 0 ? this.genesis : this.transactions.get(i - 1), transaction); + i == 0 ? this.genesis : this.transactions.get(i - 1), transaction); transferResults.add(result); if (result.getStatus() != VerificationStatus.OK) { results.add( - new VerificationResult<>("TokenTransferVerification", VerificationStatus.FAIL, "", - transferResults) + new VerificationResult<>("TokenTransferVerification", VerificationStatus.FAIL, "", + transferResults) ); return new VerificationResult<>("TokenVerification", VerificationStatus.FAIL, - String.format("Transaction[%s] verification failed", i), results); + String.format("Transaction[%s] verification failed", i), results); } } results.add(new VerificationResult<>("TokenTransferVerification", VerificationStatus.OK, "", - transferResults)); + transferResults)); return new VerificationResult<>("TokenVerification", VerificationStatus.OK, "", results); } @@ -188,10 +205,14 @@ public VerificationResult verify(RootTrustBase trustBase, * @return CBOR-encoded token bytes */ 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 e64f434..fd44370 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/TokenId.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/TokenId.java @@ -1,17 +1,19 @@ 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; + + /** * Globally unique identifier of a token. */ -public final class TokenId { +public class TokenId { private static final SecureRandom RANDOM = new SecureRandom(); private final byte[] bytes; @@ -73,7 +75,7 @@ public byte[] toCbor() { * @return bit string */ 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 cafdd9e..b845cdd 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/TokenType.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/TokenType.java @@ -1,17 +1,18 @@ 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; + /** * Type identifier of a token. */ -public final class TokenType { +public class TokenType { private static final SecureRandom RANDOM = new SecureRandom(); private final byte[] bytes; @@ -73,7 +74,7 @@ public byte[] toCbor() { * @return bit string */ 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 83754ee..2f798c7 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,13 +9,19 @@ 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; + /** * Transfer transaction that moves token ownership from a source state to a recipient. */ 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; @@ -26,19 +30,23 @@ public class TransferTransaction implements Transaction { private final byte[] data; private TransferTransaction( - DataHash sourceStateHash, - Predicate lockScript, - Address recipient, - byte[] x, - byte[] data + DataHash sourceStateHash, + Predicate lockScript, + Address recipient, + byte[] nonce, + byte[] data ) { this.sourceStateHash = sourceStateHash; this.lockScript = lockScript; this.recipient = recipient; - this.nonce = x; + this.nonce = nonce; this.data = data; } + public int getVersion() { + return TransferTransaction.VERSION; + } + @Override public byte[] getData() { @@ -77,18 +85,18 @@ public byte[] getNonce() { * @throws RuntimeException if the owner predicate does not match the latest recipient */ public static TransferTransaction create(Token token, Predicate owner, Address recipient, - byte[] x, byte[] data) { + byte[] x, byte[] data) { Transaction transaction = token.getLatestTransaction(); if (!transaction.getRecipient().equals(Address.fromPredicate(owner))) { throw new RuntimeException("Predicate does not match pay to script hash."); } return new TransferTransaction( - transaction.calculateStateHash(), - owner, - recipient, - x, - data + transaction.calculateStateHash(), + owner, + recipient, + x, + data ); } @@ -99,50 +107,63 @@ public static TransferTransaction create(Token token, Predicate owner, Address r * @return decoded transfer transaction */ 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)) ); } @Override public DataHash calculateStateHash() { return new DataHasher(HashAlgorithm.SHA256) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(this.sourceStateHash.getImprint()), - CborSerializer.encodeByteString(this.nonce) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(this.sourceStateHash.getImprint()), + CborSerializer.encodeByteString(this.nonce) + ) ) - ) - .digest(); + .digest(); } @Override public DataHash calculateTransactionHash() { return new DataHasher(HashAlgorithm.SHA256) - .update( - CborSerializer.encodeArray( - this.recipient.toCbor(), - CborSerializer.encodeByteString(this.nonce), - CborSerializer.encodeByteString(this.data) + .update( + CborSerializer.encodeArray( + this.recipient.toCbor(), + CborSerializer.encodeByteString(this.nonce), + CborSerializer.encodeByteString(this.data) + ) ) - ) - .digest(); + .digest(); } @Override public byte[] toCbor() { - return CborSerializer.encodeArray( - CborSerializer.encodeByteString(this.sourceStateHash.getData()), - EncodedPredicate.fromPredicate(this.lockScript).toCbor(), - this.recipient.toCbor(), - CborSerializer.encodeByteString(this.nonce), - 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.nonce), + CborSerializer.encodeByteString(this.data) + ) ); } @@ -155,19 +176,19 @@ public byte[] toCbor() { * @return certified transfer transaction */ public CertifiedTransferTransaction toCertifiedTransaction( - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - InclusionProof inclusionProof + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + InclusionProof inclusionProof ) { return CertifiedTransferTransaction.fromTransaction(trustBase, predicateVerifier, this, - inclusionProof); + inclusionProof); } @Override public String toString() { return String.format( - "TransferTransaction{sourceStateHash=%s, lockScript=%s, recipient=%s, x=%s, data=%s}", - this.sourceStateHash, this.lockScript, this.recipient, HexConverter.encode(this.nonce), - HexConverter.encode(this.data)); + "TransferTransaction{sourceStateHash=%s, lockScript=%s, recipient=%s, nonce=%s, data=%s}", + this.sourceStateHash, this.lockScript, this.recipient, HexConverter.encode(this.nonce), + HexConverter.encode(this.data)); } } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedMintTransactionVerificationRule.java b/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedMintTransactionVerificationRule.java index f85a454..19748f8 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedMintTransactionVerificationRule.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedMintTransactionVerificationRule.java @@ -1,7 +1,5 @@ package org.unicitylabs.sdk.transaction.verification; -import java.util.ArrayList; -import java.util.Arrays; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.MintSigningService; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; @@ -12,6 +10,9 @@ import org.unicitylabs.sdk.util.verification.VerificationResult; import org.unicitylabs.sdk.util.verification.VerificationStatus; +import java.util.ArrayList; +import java.util.Arrays; + /** * Verification rule set for certified mint transactions. * @@ -33,35 +34,35 @@ private CertifiedMintTransactionVerificationRule() { * @return verification result with child results for each validation step */ public static VerificationResult verify(RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, CertifiedMintTransaction transaction) { + PredicateVerifierService predicateVerifier, CertifiedMintTransaction transaction) { ArrayList> results = new ArrayList>(); SigningService signingService = MintSigningService.create(transaction.getTokenId()); VerificationResult result = Arrays.equals( - EncodedPredicate.fromPredicate(PayToPublicKeyPredicate.fromSigningService(signingService)) - .toCbor(), - transaction.getInclusionProof() - .getCertificationData() - .map(c -> EncodedPredicate.fromPredicate(c.getLockScript()).toCbor()) - .orElse(null)) - ? new VerificationResult<>("IsLockScriptValidVerificationRule", VerificationStatus.OK) - : new VerificationResult<>("IsLockScriptValidVerificationRule", VerificationStatus.FAIL); + EncodedPredicate.fromPredicate(PayToPublicKeyPredicate.fromSigningService(signingService)) + .toCbor(), + transaction.getInclusionProof() + .getCertificationData() + .map(c -> EncodedPredicate.fromPredicate(c.getLockScript()).toCbor()) + .orElse(null)) + ? new VerificationResult<>("IsLockScriptValidVerificationRule", VerificationStatus.OK) + : new VerificationResult<>("IsLockScriptValidVerificationRule", VerificationStatus.FAIL); results.add(result); if (result.getStatus() != VerificationStatus.OK) { return new VerificationResult<>("CertifiedMintTransactionVerificationRule", - VerificationStatus.FAIL, "Invalid lock script", results); + VerificationStatus.FAIL, "Invalid lock script", results); } result = InclusionProofVerificationRule.verify(trustBase, predicateVerifier, - transaction.getInclusionProof(), transaction); + transaction.getInclusionProof(), transaction); results.add(result); if (result.getStatus() != InclusionProofVerificationStatus.OK) { return new VerificationResult<>("CertifiedMintTransactionVerificationRule", - VerificationStatus.FAIL, "Inclusion proof verification failed", results); + VerificationStatus.FAIL, "Inclusion proof verification failed", results); } return new VerificationResult<>("CertifiedMintTransactionVerificationRule", - VerificationStatus.OK, "", results); + VerificationStatus.OK, "", results); } } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedTransferTransactionVerificationRule.java b/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedTransferTransactionVerificationRule.java index 9a107fa..a15adff 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedTransferTransactionVerificationRule.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedTransferTransactionVerificationRule.java @@ -1,6 +1,5 @@ package org.unicitylabs.sdk.transaction.verification; -import java.util.ArrayList; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; import org.unicitylabs.sdk.transaction.Address; @@ -9,6 +8,8 @@ import org.unicitylabs.sdk.util.verification.VerificationResult; import org.unicitylabs.sdk.util.verification.VerificationStatus; +import java.util.ArrayList; + /** * Verification rule set for certified transfer transactions. * @@ -31,43 +32,43 @@ private CertifiedTransferTransactionVerificationRule() { * @return verification result with child results for each validation step */ public static VerificationResult verify( - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - Transaction latestTransaction, - CertifiedTransferTransaction transaction) { + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + Transaction latestTransaction, + CertifiedTransferTransaction transaction) { ArrayList> results = new ArrayList>(); VerificationResult result = InclusionProofVerificationRule.verify(trustBase, - predicateVerifier, transaction.getInclusionProof(), transaction); + predicateVerifier, transaction.getInclusionProof(), transaction); results.add(result); if (result.getStatus() != InclusionProofVerificationStatus.OK) { return new VerificationResult<>("CertifiedTransferTransactionVerificationRule", - VerificationStatus.FAIL, "Inclusion proof verification failed", results); + VerificationStatus.FAIL, "Inclusion proof verification failed", results); } Address payToScriptHash = Address.fromPredicate(transaction.getLockScript()); result = new VerificationResult<>("RecipientVerificationRule", - latestTransaction.getRecipient().equals(payToScriptHash) ? VerificationStatus.OK - : VerificationStatus.FAIL); + latestTransaction.getRecipient().equals(payToScriptHash) ? VerificationStatus.OK + : VerificationStatus.FAIL); results.add(result); if (result.getStatus() != VerificationStatus.OK) { return new VerificationResult<>("CertifiedTransferTransactionVerificationRule", - VerificationStatus.FAIL, - "Transaction owner does not match the previous transaction recipient", results); + VerificationStatus.FAIL, + "Transaction owner does not match the previous transaction recipient", results); } result = new VerificationResult<>("SourceStateHashVerificationRule", - latestTransaction.calculateStateHash().equals(transaction.getSourceStateHash()) - ? VerificationStatus.OK : VerificationStatus.FAIL); + latestTransaction.calculateStateHash().equals(transaction.getSourceStateHash()) + ? VerificationStatus.OK : VerificationStatus.FAIL); results.add(result); if (result.getStatus() != VerificationStatus.OK) { return new VerificationResult<>("CertifiedTransferTransactionVerificationRule", - VerificationStatus.FAIL, - "Source state hash does not match the previous transaction state hash", results); + VerificationStatus.FAIL, + "Source state hash does not match the previous transaction state hash", results); } return new VerificationResult<>("CertifiedTransferTransactionVerificationRule", - VerificationStatus.OK, "", results); + VerificationStatus.OK, "", results); } } 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 a8ef5e2..bda3207 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/verification/InclusionProofVerificationRule.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/verification/InclusionProofVerificationRule.java @@ -1,20 +1,17 @@ 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; import org.unicitylabs.sdk.util.verification.VerificationStatus; - /** * This class provides the functionality to verify an inclusion proof against a given trust base * and transaction. It ensures that the inclusion proof is valid, authentic, and corresponds to @@ -30,9 +27,6 @@ */ public class InclusionProofVerificationRule { - private InclusionProofVerificationRule() { - } - /** * Verifies the provided inclusion proof against the specified trust base and transaction. * @@ -44,60 +38,57 @@ private InclusionProofVerificationRule() { * @return a {@code VerificationResult} object containing the {@code InclusionProofVerificationStatus} * and additional details about the verification outcome */ - 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); + public static VerificationResult verify(RootTrustBase trustBase, + 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 20178bd..5d3c7ca 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/verification/InclusionProofVerificationStatus.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/verification/InclusionProofVerificationStatus.java @@ -6,8 +6,6 @@ public enum InclusionProofVerificationStatus { /** The provided trust base is invalid or cannot be used for verification. */ INVALID_TRUSTBASE, - /** Leaf value in the proof does not match the expected transaction. */ - LEAF_VALUE_MISMATCH, /** Certification data required for verification is missing. */ MISSING_CERTIFICATION_DATA, /** Transaction hash does not match the value referenced by the proof. */ @@ -16,6 +14,8 @@ public enum InclusionProofVerificationStatus { NOT_AUTHENTICATED, /** Proof path is not included in the committed tree state. */ PATH_NOT_INCLUDED, + + INCLUSION_CERTIFICATE_MISSING, /** Proof path structure or hashes are invalid. */ PATH_INVALID, /** Inclusion proof verification succeeded. */ diff --git a/src/main/java/org/unicitylabs/sdk/util/BitString.java b/src/main/java/org/unicitylabs/sdk/util/BitString.java index eb286fa..98e7bf7 100644 --- a/src/main/java/org/unicitylabs/sdk/util/BitString.java +++ b/src/main/java/org/unicitylabs/sdk/util/BitString.java @@ -1,22 +1,17 @@ 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); @@ -24,13 +19,73 @@ public BitString(byte[] data) { } /** - * 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. + * 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. * * @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); } /** @@ -40,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/main/java/org/unicitylabs/sdk/util/verification/VerificationResult.java b/src/main/java/org/unicitylabs/sdk/util/verification/VerificationResult.java index 9801716..40f1d64 100644 --- a/src/main/java/org/unicitylabs/sdk/util/verification/VerificationResult.java +++ b/src/main/java/org/unicitylabs/sdk/util/verification/VerificationResult.java @@ -23,9 +23,9 @@ public class VerificationResult { * @param message descriptive message */ public VerificationResult( - String rule, - S status, - String message + String rule, + S status, + String message ) { this(rule, status, message, List.of()); } @@ -37,8 +37,8 @@ public VerificationResult( * @param status verification status */ public VerificationResult( - String rule, - S status + String rule, + S status ) { this(rule, status, "", List.of()); } @@ -52,10 +52,10 @@ public VerificationResult( * @param results nested verification results */ public VerificationResult( - String rule, - S status, - String message, - VerificationResult... results + String rule, + S status, + String message, + VerificationResult... results ) { this(rule, status, message, List.of(results)); } @@ -69,10 +69,10 @@ public VerificationResult( * @param results nested verification results */ public VerificationResult( - String rule, - S status, - String message, - List> results + String rule, + S status, + String message, + List> results ) { Objects.requireNonNull(rule, "Rule cannot be null"); Objects.requireNonNull(status, "Status cannot be null"); @@ -125,11 +125,11 @@ public List> getResults() { @Override public String toString() { return String.format( - "VerificationResult{rule=%s, status=%s, message=%s, results=%s}", - this.rule, - this.status, - this.message, - this.results + "VerificationResult{rule=%s, status=%s, message=%s, results=%s}", + this.rule, + this.status, + this.message, + this.results ); } } diff --git a/src/test/java/org/unicitylabs/sdk/AndroidCompatibilityTest.java b/src/test/java/org/unicitylabs/sdk/AndroidCompatibilityTest.java index ef4f247..a8002ef 100644 --- a/src/test/java/org/unicitylabs/sdk/AndroidCompatibilityTest.java +++ b/src/test/java/org/unicitylabs/sdk/AndroidCompatibilityTest.java @@ -1,14 +1,14 @@ package org.unicitylabs.sdk; +import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.DataHasher; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; import org.unicitylabs.sdk.transaction.TokenType; +import java.nio.charset.StandardCharsets; + import static org.junit.jupiter.api.Assertions.*; /** @@ -16,26 +16,26 @@ * These tests ensure we don't use any Java APIs that are not available on Android. */ public class AndroidCompatibilityTest { - - @Test - void testCoreSDKFeaturesWorkOnAndroid() throws Exception { - // Test 1: Hashing (uses Bouncy Castle, not Java crypto) - byte[] data = "test data".getBytes(StandardCharsets.UTF_8); - DataHash hash = new DataHasher(HashAlgorithm.SHA256).update(data).digest(); - assertNotNull(hash); - assertEquals(HashAlgorithm.SHA256, hash.getAlgorithm()); - - // Test 2: Signing Service (uses Bouncy Castle) - SigningService signingService = SigningService.generate(); - assertNotNull(signingService.getPublicKey()); - - // Test 3: Token IDs and Types + + @Test + void testCoreSDKFeaturesWorkOnAndroid() throws Exception { + // Test 1: Hashing (uses Bouncy Castle, not Java crypto) + byte[] data = "test data".getBytes(StandardCharsets.UTF_8); + DataHash hash = new DataHasher(HashAlgorithm.SHA256).update(data).digest(); + assertNotNull(hash); + assertEquals(HashAlgorithm.SHA256, hash.getAlgorithm()); + + // Test 2: Signing Service (uses Bouncy Castle) + SigningService signingService = SigningService.generate(); + assertNotNull(signingService.getPublicKey()); + + // Test 3: Token IDs and Types // TokenId tokenId = TokenId.create(new byte[32]); - TokenType tokenType = new TokenType(new byte[32]); + TokenType tokenType = new TokenType(new byte[32]); // assertNotNull(tokenId); - assertNotNull(tokenType); - - // Test 4: Predicates + assertNotNull(tokenType); + + // Test 4: Predicates // var predicate = MaskedPredicate.create( // tokenId, // tokenType, @@ -44,25 +44,25 @@ void testCoreSDKFeaturesWorkOnAndroid() throws Exception { // nonce // ).get(); // assertNotNull(predicate); - - // Test 5: Addresses + + // Test 5: Addresses // var address = DirectAddress.create(predicate.getReference()).get(); // assertNotNull(address.toString()); - - // Test 6: Verify we're not using Java 11+ specific APIs - // This is enforced by Animal Sniffer during build - } - - @Test - void testNoJava11SpecificAPIs() { - // This test documents that we avoid Java 11+ specific APIs: - // - No java.net.http.HttpClient (using OkHttp instead) - // - No var keyword in public APIs - // - No List.of(), Map.of(), Set.of() (using traditional constructors) - // - No Files.readString/writeString - // - Target Java 11 instead of Java 8 (Android 12+ supports Java 11) - - // Animal Sniffer plugin verifies this at build time - assertTrue(true, "Animal Sniffer validates Android compatibility"); - } + + // Test 6: Verify we're not using Java 11+ specific APIs + // This is enforced by Animal Sniffer during build + } + + @Test + void testNoJava11SpecificAPIs() { + // This test documents that we avoid Java 11+ specific APIs: + // - No java.net.http.HttpClient (using OkHttp instead) + // - No var keyword in public APIs + // - No List.of(), Map.of(), Set.of() (using traditional constructors) + // - No Files.readString/writeString + // - Target Java 11 instead of Java 8 (Android 12+ supports Java 11) + + // Animal Sniffer plugin verifies this at build time + assertTrue(true, "Animal Sniffer validates Android compatibility"); + } } \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/MockAggregatorServer.java b/src/test/java/org/unicitylabs/sdk/MockAggregatorServer.java index 83dbefe..b81d366 100644 --- a/src/test/java/org/unicitylabs/sdk/MockAggregatorServer.java +++ b/src/test/java/org/unicitylabs/sdk/MockAggregatorServer.java @@ -1,157 +1,157 @@ package org.unicitylabs.sdk; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.JsonNode; import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.util.Set; import java.util.HashSet; +import java.util.Set; import java.util.UUID; public class MockAggregatorServer { - - private final MockWebServer server; - private final ObjectMapper objectMapper; - private final Set protectedMethods; - private volatile boolean simulateRateLimit = false; - private volatile int rateLimitRetryAfter = 0; - private volatile String expectedApiKey = null; - - public MockAggregatorServer() { - this.server = new MockWebServer(); - this.objectMapper = new ObjectMapper(); - this.protectedMethods = new HashSet<>(); - this.protectedMethods.add("certification_request"); - - server.setDispatcher(new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest request) { - return handleRequest(request); - } - }); - } - - public void start() throws IOException { - server.start(); - } - - public void shutdown() throws IOException { - server.shutdown(); - } - - public String getUrl() { - return server.url("/").toString(); - } - - public RecordedRequest takeRequest() throws InterruptedException { - return server.takeRequest(); - } - - public void simulateRateLimitForNextRequest(int retryAfterSeconds) { - this.simulateRateLimit = true; - this.rateLimitRetryAfter = retryAfterSeconds; - } - - public void setExpectedApiKey(String apiKey) { - this.expectedApiKey = apiKey; - } - - private MockResponse handleRequest(RecordedRequest request) { + + private final MockWebServer server; + private final ObjectMapper objectMapper; + private final Set protectedMethods; + private volatile boolean simulateRateLimit = false; + private volatile int rateLimitRetryAfter = 0; + private volatile String expectedApiKey = null; + + public MockAggregatorServer() { + this.server = new MockWebServer(); + this.objectMapper = new ObjectMapper(); + this.protectedMethods = new HashSet<>(); + this.protectedMethods.add("certification_request"); + + server.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + return handleRequest(request); + } + }); + } + + public void start() throws IOException { + server.start(); + } + + public void shutdown() throws IOException { + server.shutdown(); + } + + public String getUrl() { + return server.url("/").toString(); + } + + public RecordedRequest takeRequest() throws InterruptedException { + return server.takeRequest(); + } + + public void simulateRateLimitForNextRequest(int retryAfterSeconds) { + this.simulateRateLimit = true; + this.rateLimitRetryAfter = retryAfterSeconds; + } + + public void setExpectedApiKey(String apiKey) { + this.expectedApiKey = apiKey; + } + + private MockResponse handleRequest(RecordedRequest request) { + try { + if (simulateRateLimit) { try { - if (simulateRateLimit) { - try { - return new MockResponse() - .setResponseCode(429) - .setHeader("Retry-After", String.valueOf(rateLimitRetryAfter)) - .setBody("Too Many Requests"); - } finally { - // Reset for next request - simulateRateLimit = false; - rateLimitRetryAfter = 0; - } - } - - String method = extractJsonRpcMethod(request); - - if (protectedMethods.contains(method) && expectedApiKey != null && !hasValidApiKey(request)) { - return new MockResponse() - .setResponseCode(401) - .setHeader("WWW-Authenticate", "Bearer") - .setBody("Unauthorized"); - } - - return generateSuccessResponse(method); - - } catch (Exception e) { - return new MockResponse() - .setResponseCode(400) - .setBody("Bad Request"); + return new MockResponse() + .setResponseCode(429) + .setHeader("Retry-After", String.valueOf(rateLimitRetryAfter)) + .setBody("Too Many Requests"); + } finally { + // Reset for next request + simulateRateLimit = false; + rateLimitRetryAfter = 0; } + } + + String method = extractJsonRpcMethod(request); + + if (protectedMethods.contains(method) && expectedApiKey != null && !hasValidApiKey(request)) { + return new MockResponse() + .setResponseCode(401) + .setHeader("WWW-Authenticate", "Bearer") + .setBody("Unauthorized"); + } + + return generateSuccessResponse(method); + + } catch (Exception e) { + return new MockResponse() + .setResponseCode(400) + .setBody("Bad Request"); } + } - private boolean hasValidApiKey(RecordedRequest request) { - String authHeader = request.getHeader("Authorization"); - if (authHeader != null && authHeader.startsWith("Bearer ")) { - String providedKey = authHeader.substring(7); - return expectedApiKey.equals(providedKey); - } - return false; + private boolean hasValidApiKey(RecordedRequest request) { + String authHeader = request.getHeader("Authorization"); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + String providedKey = authHeader.substring(7); + return expectedApiKey.equals(providedKey); } + return false; + } - private @Nullable String extractJsonRpcMethod(RecordedRequest request) throws JsonProcessingException { - if (!"POST".equals(request.getMethod())) { - return null; - } - JsonNode jsonRequest = objectMapper.readTree(request.getBody().readUtf8()); - return jsonRequest.has("method") ? jsonRequest.get("method").asText() : null; + private @Nullable String extractJsonRpcMethod(RecordedRequest request) throws JsonProcessingException { + if (!"POST".equals(request.getMethod())) { + return null; } + JsonNode jsonRequest = objectMapper.readTree(request.getBody().readUtf8()); + return jsonRequest.has("method") ? jsonRequest.get("method").asText() : null; + } - private MockResponse generateSuccessResponse(String method) { - String responseBody; - String id = UUID.randomUUID().toString(); - - switch (method != null ? method : "") { - case "certification_request": - responseBody = String.format( - "{\n" + - " \"jsonrpc\": \"2.0\",\n" + - " \"result\": {\n" + - " \"status\": \"SUCCESS\"\n" + - " },\n" + - " \"id\": \"%s\"\n" + - "}", id); - break; - - case "get_block_height": - responseBody = String.format( - "{\n" + - " \"jsonrpc\": \"2.0\",\n" + - " \"result\": {\n" + - " \"blockNumber\": \"67890\"\n" + - " },\n" + - " \"id\": \"%s\"\n" + - "}", id); - break; - - default: - responseBody = String.format( - "{\n" + - " \"jsonrpc\": \"2.0\",\n" + - " \"result\": \"OK\",\n" + - " \"id\": \"%s\"\n" + - "}", id); - break; - } - - return new MockResponse() + private MockResponse generateSuccessResponse(String method) { + String responseBody; + String id = UUID.randomUUID().toString(); + + switch (method != null ? method : "") { + case "certification_request": + responseBody = String.format( + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"result\": {\n" + + " \"status\": \"SUCCESS\"\n" + + " },\n" + + " \"id\": \"%s\"\n" + + "}", id); + break; + + case "get_block_height": + responseBody = String.format( + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"result\": {\n" + + " \"blockNumber\": \"67890\"\n" + + " },\n" + + " \"id\": \"%s\"\n" + + "}", id); + break; + + default: + responseBody = String.format( + "{\n" + + " \"jsonrpc\": \"2.0\",\n" + + " \"result\": \"OK\",\n" + + " \"id\": \"%s\"\n" + + "}", id); + break; + } + + return new MockResponse() .setResponseCode(200) .setHeader("Content-Type", "application/json") .setBody(responseBody); - } + } } \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java index e61da37..d5bd967 100644 --- a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java +++ b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java @@ -1,25 +1,20 @@ 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; @@ -53,8 +48,8 @@ public static TestAggregatorClient create() { */ public static TestAggregatorClient create(byte[] privateKey) { return new TestAggregatorClient( - new SparseMerkleTree(HashAlgorithm.SHA256), - new SigningService(privateKey) + new SparseMerkleTree(HashAlgorithm.SHA256), + new SigningService(privateKey) ); } @@ -65,10 +60,10 @@ public CompletableFuture submitCertificationRequest(Certi StateId stateId = StateId.fromCertificationData(certificationData); VerificationResult result = this.predicateVerifier.verify( - certificationData.getLockScript(), - certificationData.getSourceStateHash(), - certificationData.getTransactionHash(), - certificationData.getUnlockScript() + certificationData.getLockScript(), + certificationData.getSourceStateHash(), + certificationData.getTransactionHash(), + certificationData.getUnlockScript() ); if (result.getStatus() != VerificationStatus.OK) { @@ -76,8 +71,8 @@ public CompletableFuture submitCertificationRequest(Certi } if (!this.requests.containsKey(stateId)) { - DataHash leafValue = certificationData.calculateLeafValue(); - this.sparseMerkleTree.addLeaf(stateId.toBitString().toBigInteger(), leafValue.getImprint()); + DataHash leafValue = certificationData.getTransactionHash(); + this.sparseMerkleTree.addLeaf(stateId.getData(), leafValue.getData()); this.requests.put(stateId, certificationData); } @@ -89,15 +84,21 @@ public CompletableFuture submitCertificationRequest(Certi @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); - SparseMerkleTreeRootNode root = this.sparseMerkleTree.calculateRoot(); - return CompletableFuture.completedFuture( - InclusionProofFixture.create( - root.getPath(stateId.toBitString().toBigInteger()), - certificationData, - root.getRootHash(), - this.signingService - ) + + return CompletableFuture.completedFuture( + InclusionProofFixture.createResponse( + certificationData, + InclusionCertificate.create(root, stateId.getData()), + root.getHash(), + this.signingService + ) ); } diff --git a/src/test/java/org/unicitylabs/sdk/TestApiKeyIntegration.java b/src/test/java/org/unicitylabs/sdk/TestApiKeyIntegration.java index a9056d2..8e0df16 100644 --- a/src/test/java/org/unicitylabs/sdk/TestApiKeyIntegration.java +++ b/src/test/java/org/unicitylabs/sdk/TestApiKeyIntegration.java @@ -1,26 +1,10 @@ package org.unicitylabs.sdk; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import okhttp3.mockwebserver.RecordedRequest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -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.JsonRpcAggregatorClient; -import org.unicitylabs.sdk.crypto.hash.DataHash; -import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; +import org.unicitylabs.sdk.api.*; import org.unicitylabs.sdk.api.jsonrpc.JsonRpcNetworkException; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; import org.unicitylabs.sdk.predicate.builtin.PayToPublicKeyPredicate; @@ -30,6 +14,12 @@ import org.unicitylabs.sdk.transaction.TokenType; import org.unicitylabs.sdk.util.HexConverter; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; + public class TestApiKeyIntegration { private static final String TEST_API_KEY = "test-api-key-12345"; @@ -47,11 +37,11 @@ void setUp() throws Exception { mockServer.start(); clientWithApiKey = new JsonRpcAggregatorClient( - mockServer.getUrl(), TEST_API_KEY); + mockServer.getUrl(), TEST_API_KEY); clientWithoutApiKey = new JsonRpcAggregatorClient(mockServer.getUrl()); SigningService signingService = new SigningService( - HexConverter.decode("0000000000000000000000000000000000000000000000000000000000000001")); + HexConverter.decode("0000000000000000000000000000000000000000000000000000000000000001")); MintTransaction transaction = MintTransaction.create( Address.fromPredicate(PayToPublicKeyPredicate.fromSigningService(signingService)), @@ -70,7 +60,7 @@ void tearDown() throws Exception { @Test public void testSubmitCommitmentWithApiKey() throws Exception { CompletableFuture future = clientWithApiKey.submitCertificationRequest( - certificationData + certificationData ); CertificationResponse response = future.get(5, TimeUnit.SECONDS); @@ -83,7 +73,7 @@ public void testSubmitCommitmentWithApiKey() throws Exception { @Test public void testSubmitCommitmentWithoutApiKeyThrowsUnauthorized() throws Exception { CompletableFuture future = clientWithoutApiKey.submitCertificationRequest( - certificationData + certificationData ); try { @@ -104,7 +94,7 @@ public void testSubmitCommitmentWithWrongApiKeyThrowsUnauthorized() throws Excep mockServer.setExpectedApiKey("different-api-key"); CompletableFuture future = clientWithApiKey.submitCertificationRequest( - certificationData + certificationData ); try { @@ -125,7 +115,7 @@ public void testRateLimitExceeded() { mockServer.simulateRateLimitForNextRequest(30); CompletableFuture future = clientWithApiKey.submitCertificationRequest( - certificationData + certificationData ); try { @@ -135,7 +125,7 @@ public void testRateLimitExceeded() { assertInstanceOf(ExecutionException.class, e); assertInstanceOf(JsonRpcNetworkException.class, e.getCause()); assertTrue(e.getCause().getMessage().contains("Network error [429] occurred: Too Many Requests"), - e.getCause().getMessage()); + e.getCause().getMessage()); } } diff --git a/src/test/java/org/unicitylabs/sdk/api/InclusionProofFixture.java b/src/test/java/org/unicitylabs/sdk/api/InclusionProofFixture.java index 6183626..38c5ce3 100644 --- a/src/test/java/org/unicitylabs/sdk/api/InclusionProofFixture.java +++ b/src/test/java/org/unicitylabs/sdk/api/InclusionProofFixture.java @@ -3,17 +3,16 @@ 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) { - return new InclusionProofResponse( - 1L, - new InclusionProof( - path, - certificationData, - UnicityCertificateUtils.generateCertificate(signingService, root) - ) - ); - } + public static InclusionProofResponse createResponse(CertificationData certificationData, InclusionCertificate inclusionCertificate, DataHash root, SigningService signingService) { + return new InclusionProofResponse( + 1L, + new InclusionProof( + 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..22a68b7 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; @@ -29,186 +27,163 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class InclusionProofTest { - MintTransaction transaction; - PredicateVerifierService predicateVerifier; - StateId stateId; - SparseMerkleTreePath merkleTreePath; - CertificationData certificationData; - RootTrustBase trustBase; - UnicityCertificate unicityCertificate; - - @BeforeAll - public void createMerkleTreePath() throws Exception { - SigningService signingService = new SigningService( - HexConverter.decode("0000000000000000000000000000000000000000000000000000000000000001")); - - - transaction = MintTransaction.create( - Address.fromPredicate(PayToPublicKeyPredicate.fromSigningService(signingService)), - TokenId.generate(), - TokenType.generate(), - new byte[32] - ); - - certificationData = CertificationData.fromMintTransaction(transaction); - stateId = StateId.fromCertificationData(certificationData); - - SparseMerkleTree smt = new SparseMerkleTree(HashAlgorithm.SHA256); - smt.addLeaf(stateId.toBitString().toBigInteger(), certificationData.calculateLeafValue().getImprint()); - - merkleTreePath = smt.calculateRoot().getPath(stateId.toBitString().toBigInteger()); - SigningService ucSigningService = new SigningService(SigningService.generatePrivateKey()); - trustBase = RootTrustBaseUtils.generateRootTrustBase(ucSigningService.getPublicKey()); - unicityCertificate = UnicityCertificateUtils.generateCertificate(ucSigningService, - merkleTreePath.getRootHash()); - predicateVerifier = PredicateVerifierService.create(trustBase); - } - - @Test - public void testCborSerialization() { - InclusionProof inclusionProof = new InclusionProof( - merkleTreePath, - certificationData, - unicityCertificate - ); - - Assertions.assertEquals(inclusionProof, InclusionProof.fromCbor(inclusionProof.toCbor())); - } - - @Test - public void testStructure() { - Assertions.assertThrows(NullPointerException.class, - () -> new InclusionProof( - null, - this.certificationData, - this.unicityCertificate - ) - ); - Assertions.assertThrows(NullPointerException.class, - () -> new InclusionProof( - this.merkleTreePath, - this.certificationData, - null - ) - ); - Assertions.assertInstanceOf(InclusionProof.class, - new InclusionProof( - this.merkleTreePath, - this.certificationData, - this.unicityCertificate - ) - ); - Assertions.assertInstanceOf(InclusionProof.class, - new InclusionProof( - this.merkleTreePath, - null, - this.unicityCertificate - ) - ); - } - - @Test - public void testItVerifies() { - InclusionProof inclusionProof = new InclusionProof( - this.merkleTreePath, - this.certificationData, - this.unicityCertificate - ); - Assertions.assertEquals( - InclusionProofVerificationStatus.OK, - InclusionProofVerificationRule.verify( - this.trustBase, - this.predicateVerifier, - inclusionProof, - this.transaction - ).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, - new CertificationData( - this.certificationData.getLockScript(), - this.certificationData.getSourceStateHash(), - DataHash.fromImprint( - HexConverter.decode("00000000000000000000000000000000000000000000000000000000000000000001") - ), - this.certificationData.getUnlockScript() - ), - this.unicityCertificate - ); - - Assertions.assertEquals( - InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH, - InclusionProofVerificationRule.verify( - this.trustBase, - this.predicateVerifier, - invalidTransactionHashInclusionProof, - this.transaction - ).getStatus() - ); - } - - @Test - public void testItNotAuthenticated() { - InclusionProof invalidInclusionProof = new InclusionProof( - this.merkleTreePath, - new CertificationData( - this.certificationData.getLockScript(), - this.certificationData.getSourceStateHash(), - this.certificationData.getTransactionHash(), - PayToPublicKeyPredicateUnlockScript.create( - this.transaction, - new SigningService(SigningService.generatePrivateKey()) - ).encode() - ), - this.unicityCertificate - ); - - Assertions.assertEquals( - InclusionProofVerificationStatus.NOT_AUTHENTICATED, - InclusionProofVerificationRule.verify( - this.trustBase, - this.predicateVerifier, - invalidInclusionProof, - this.transaction - ).getStatus() - ); - } - - @Test - public void testVerificationFailsWithInvalidTrustbase() { - InclusionProof inclusionProof = new InclusionProof( - this.merkleTreePath, - this.certificationData, - this.unicityCertificate - ); - - Assertions.assertEquals( - InclusionProofVerificationStatus.INVALID_TRUSTBASE, - InclusionProofVerificationRule.verify( - RootTrustBaseUtils.generateRootTrustBase( - HexConverter.decode("020000000000000000000000000000000000000000000000000000000000000001") - ), - this.predicateVerifier, - inclusionProof, - this.transaction - ).getStatus() - ); - } + MintTransaction transaction; + PredicateVerifierService predicateVerifier; + StateId stateId; + InclusionCertificate inclusionCertificate; + CertificationData certificationData; + RootTrustBase trustBase; + UnicityCertificate unicityCertificate; + + @BeforeAll + public void createMerkleTreePath() throws Exception { + SigningService signingService = new SigningService( + HexConverter.decode("0000000000000000000000000000000000000000000000000000000000000001")); + + + transaction = MintTransaction.create( + Address.fromPredicate(PayToPublicKeyPredicate.fromSigningService(signingService)), + TokenId.generate(), + TokenType.generate(), + new byte[32] + ); + + certificationData = CertificationData.fromMintTransaction(transaction); + stateId = StateId.fromCertificationData(certificationData); + + SparseMerkleTree smt = new SparseMerkleTree(HashAlgorithm.SHA256); + smt.addLeaf(stateId.getData(), certificationData.getTransactionHash().getData()); + + 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, root.getHash()); + predicateVerifier = PredicateVerifierService.create(trustBase); + } + + @Test + public void testCborSerialization() { + InclusionProof inclusionProof = new InclusionProof( + certificationData, + inclusionCertificate, + unicityCertificate + ); + + Assertions.assertEquals(inclusionProof, InclusionProof.fromCbor(inclusionProof.toCbor())); + } + + @Test + public void testStructure() { + Assertions.assertThrows(NullPointerException.class, + () -> new InclusionProof( + this.certificationData, + this.inclusionCertificate, + null + ) + ); + Assertions.assertInstanceOf(InclusionProof.class, + new InclusionProof( + this.certificationData, + this.inclusionCertificate, + this.unicityCertificate + ) + ); + Assertions.assertInstanceOf(InclusionProof.class, + new InclusionProof( + null, + this.inclusionCertificate, + this.unicityCertificate + ) + ); + } + + @Test + public void testItVerifies() { + InclusionProof inclusionProof = new InclusionProof( + this.certificationData, + this.inclusionCertificate, + this.unicityCertificate + ); + Assertions.assertEquals( + InclusionProofVerificationStatus.OK, + InclusionProofVerificationRule.verify( + this.trustBase, + this.predicateVerifier, + inclusionProof, + this.transaction + ).getStatus() + ); + + InclusionProof invalidTransactionHashInclusionProof = new InclusionProof( + new CertificationData( + this.certificationData.getLockScript(), + this.certificationData.getSourceStateHash(), + DataHash.fromImprint( + HexConverter.decode("00000000000000000000000000000000000000000000000000000000000000000001") + ), + this.certificationData.getUnlockScript() + ), + this.inclusionCertificate, + this.unicityCertificate + ); + + Assertions.assertEquals( + InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH, + InclusionProofVerificationRule.verify( + this.trustBase, + this.predicateVerifier, + invalidTransactionHashInclusionProof, + this.transaction + ).getStatus() + ); + } + + @Test + public void testItNotAuthenticated() { + InclusionProof invalidInclusionProof = new InclusionProof( + new CertificationData( + this.certificationData.getLockScript(), + this.certificationData.getSourceStateHash(), + this.certificationData.getTransactionHash(), + PayToPublicKeyPredicateUnlockScript.create( + this.transaction, + new SigningService(SigningService.generatePrivateKey()) + ).encode() + ), + this.inclusionCertificate, + this.unicityCertificate + ); + + Assertions.assertEquals( + InclusionProofVerificationStatus.NOT_AUTHENTICATED, + InclusionProofVerificationRule.verify( + this.trustBase, + this.predicateVerifier, + invalidInclusionProof, + this.transaction + ).getStatus() + ); + } + + @Test + public void testVerificationFailsWithInvalidTrustbase() { + InclusionProof inclusionProof = new InclusionProof( + this.certificationData, + this.inclusionCertificate, + this.unicityCertificate + ); + + Assertions.assertEquals( + InclusionProofVerificationStatus.INVALID_TRUSTBASE, + InclusionProofVerificationRule.verify( + RootTrustBaseUtils.generateRootTrustBase( + HexConverter.decode("020000000000000000000000000000000000000000000000000000000000000001") + ), + this.predicateVerifier, + inclusionProof, + this.transaction + ).getStatus() + ); + } } diff --git a/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseTest.java b/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseTest.java index 8fedf12..8487d82 100644 --- a/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseTest.java +++ b/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseTest.java @@ -2,14 +2,13 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.unicitylabs.sdk.api.bft.RootTrustBase; public class RootTrustBaseTest { @Test public void testRootTrustBaseDeserializationFromJson() { RootTrustBase trustBase = RootTrustBase.fromJson( - "{\"version\":1,\"networkId\":3,\"epoch\":1,\"epochStartRound\":1,\"rootNodes\":[{\"nodeId\":\"16Uiu2HAm3PaA9z8jonZzfvuT1WJgTxCpbFkV4Wq4PSSBk7VctkmG\",\"sigKey\":\"0x03982564bf661da9c048397114fab9dcfbfedb0ad7c1b1c83e13c0f9fa633f7aa6\",\"stake\":1},{\"nodeId\":\"16Uiu2HAm8918Ds2nPiVLXg55kypyoXoiweokpQxtnguZjgxz3pNE\",\"sigKey\":\"0x039a2f7f41c5583d339f31490757152b947ccb19944634a40a16a762a32a4855d4\",\"stake\":1},{\"nodeId\":\"16Uiu2HAmEEEGyvYZno7hm2Gs8FwfPejWdpvWC3HLivQD5hXFbNUh\",\"sigKey\":\"0x038cabc84fa86076277879554c277f9a0a19955fa4c3b37871fca81d5f709777f1\",\"stake\":1},{\"nodeId\":\"16Uiu2HAmNwgru7QSsVRacGqXdtfeaf1oqtznvXA6rSzKU1822kuW\",\"sigKey\":\"0x03044c0309fd0a713440da958f8c510a40a4347aa82622481655d162c227d771e3\",\"stake\":1}],\"quorumThreshold\":3,\"stateHash\":\"\",\"changeRecordHash\":\"\",\"previousEntryHash\":\"\",\"signatures\":{\"16Uiu2HAm3PaA9z8jonZzfvuT1WJgTxCpbFkV4Wq4PSSBk7VctkmG\":\"0xfe672d56ddd60e4b028b52999b4e43bcbdac9413d9e8da6f969d46c249da8f492cd719017510af8b199b94c7605b79707da56950a4888320f8cf7e07329e92da01\",\"16Uiu2HAm8918Ds2nPiVLXg55kypyoXoiweokpQxtnguZjgxz3pNE\":\"0x8d1b178f6617a6aff9e9d4a71febac6837bd2a5088f3e3b81c766065e6c7a7ad718d1e0a1c7f7e12954514e663b888337cbaa6e7c8bd5e721f4ae5520ca6f09e00\",\"16Uiu2HAmEEEGyvYZno7hm2Gs8FwfPejWdpvWC3HLivQD5hXFbNUh\":\"0x28ef1e0279fb2962149011177c173aabb7e1fad102f07c898b9de4fe71b424390dd0cbc59f75453a7573c4853218eab800431e42fd0d4a6000ef73d50170d03101\",\"16Uiu2HAmNwgru7QSsVRacGqXdtfeaf1oqtznvXA6rSzKU1822kuW\":\"0xf563d04beb3eb5bd5967cb53e6cc1d1b331cd37c03d7a34ba7f11bc0d2c4994818168e1f5caf88956b34384dd3d3685c432d7a487b5c0bee2da012fd70891bab01\"}}" + "{\"version\":1,\"networkId\":3,\"epoch\":1,\"epochStartRound\":1,\"rootNodes\":[{\"nodeId\":\"16Uiu2HAm3PaA9z8jonZzfvuT1WJgTxCpbFkV4Wq4PSSBk7VctkmG\",\"sigKey\":\"0x03982564bf661da9c048397114fab9dcfbfedb0ad7c1b1c83e13c0f9fa633f7aa6\",\"stake\":1},{\"nodeId\":\"16Uiu2HAm8918Ds2nPiVLXg55kypyoXoiweokpQxtnguZjgxz3pNE\",\"sigKey\":\"0x039a2f7f41c5583d339f31490757152b947ccb19944634a40a16a762a32a4855d4\",\"stake\":1},{\"nodeId\":\"16Uiu2HAmEEEGyvYZno7hm2Gs8FwfPejWdpvWC3HLivQD5hXFbNUh\",\"sigKey\":\"0x038cabc84fa86076277879554c277f9a0a19955fa4c3b37871fca81d5f709777f1\",\"stake\":1},{\"nodeId\":\"16Uiu2HAmNwgru7QSsVRacGqXdtfeaf1oqtznvXA6rSzKU1822kuW\",\"sigKey\":\"0x03044c0309fd0a713440da958f8c510a40a4347aa82622481655d162c227d771e3\",\"stake\":1}],\"quorumThreshold\":3,\"stateHash\":\"\",\"changeRecordHash\":\"\",\"previousEntryHash\":\"\",\"signatures\":{\"16Uiu2HAm3PaA9z8jonZzfvuT1WJgTxCpbFkV4Wq4PSSBk7VctkmG\":\"0xfe672d56ddd60e4b028b52999b4e43bcbdac9413d9e8da6f969d46c249da8f492cd719017510af8b199b94c7605b79707da56950a4888320f8cf7e07329e92da01\",\"16Uiu2HAm8918Ds2nPiVLXg55kypyoXoiweokpQxtnguZjgxz3pNE\":\"0x8d1b178f6617a6aff9e9d4a71febac6837bd2a5088f3e3b81c766065e6c7a7ad718d1e0a1c7f7e12954514e663b888337cbaa6e7c8bd5e721f4ae5520ca6f09e00\",\"16Uiu2HAmEEEGyvYZno7hm2Gs8FwfPejWdpvWC3HLivQD5hXFbNUh\":\"0x28ef1e0279fb2962149011177c173aabb7e1fad102f07c898b9de4fe71b424390dd0cbc59f75453a7573c4853218eab800431e42fd0d4a6000ef73d50170d03101\",\"16Uiu2HAmNwgru7QSsVRacGqXdtfeaf1oqtznvXA6rSzKU1822kuW\":\"0xf563d04beb3eb5bd5967cb53e6cc1d1b331cd37c03d7a34ba7f11bc0d2c4994818168e1f5caf88956b34384dd3d3685c432d7a487b5c0bee2da012fd70891bab01\"}}" ); Assertions.assertEquals(1, trustBase.getVersion()); diff --git a/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseUtils.java b/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseUtils.java index ea27a4f..3ad587a 100644 --- a/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseUtils.java +++ b/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseUtils.java @@ -2,27 +2,26 @@ import java.util.Map; import java.util.Set; -import org.unicitylabs.sdk.api.bft.RootTrustBase; public class RootTrustBaseUtils { public static RootTrustBase generateRootTrustBase(byte[] publicKey) { return new RootTrustBase( - 0, - 0, - 0, - 0, - Set.of( - new RootTrustBase.NodeInfo( - "NODE", - publicKey, - 1 - ) - ), - 1, - new byte[0], - new byte[0], - null, - Map.of() + 0, + 0, + 0, + 0, + Set.of( + new RootTrustBase.NodeInfo( + "NODE", + publicKey, + 1 + ) + ), + 1, + new byte[0], + new byte[0], + null, + Map.of() ); } } 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..dd5d36d 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,12 +9,12 @@ public class UnicityCertificateTest { @Test public void testUnicityCertificateDeserializationFromCbor() { byte[] data = HexConverter.decode( - "d903ef8701d903f08a0118f70058220000ea2fc549e86b1f8666bd7a6d5dd0721b446856ad80c67ea958c4045dfb0b627c58220000ea2fc549e86b1f8666bd7a6d5dd0721b446856ad80c67ea958c4045dfb0b627c401a68d27af6f600f6582024bf5304778618efe3303e1ac36a5c23b7d9d64bbd2c2ae92ea1488ad59e9cfa58206a362e77353752942e9c8101511861ee7fac83ba0873ee88f34416535ae17c5c82418080d903f68301078182055820b206f60d312bf4422815c861e9330ddc3a29b0c744864d7bea41f94d1d34c669d903e988010319073b001a68d27af958204b3f7f036452271a1245ca79f6c2e35adc0b192e6eeb001dff35d70d1be3b2d55820380451b471673eb68cf750ceedb09755c08e3b0b7dea32d0101465970ca3a661a4783531365569753248416d33506141397a386a6f6e5a7a6676755431574a675478437062466b5634577134505353426b375663746b6d475841c5103160b79e7691cec53448949bbd93a870a38c89ad4c929aa1c8e77f2a1d9053d3f8f04e96bcb38ba234bf3dee52baad4b1df94be0b8a5e569918fb94e18ec00783531365569753248416d383931384473326e5069564c586735356b7970796f586f6977656f6b705178746e67755a6a67787a33704e455841fa3736ebdc729b31d6e3c964787dfcefcbd39286a1619665eac1313e6d35049b4385156f652ad9ac87a769d3bf7b8cc819bb5a6f838e595d2e2092df2592849400783531365569753248416d454545477976595a6e6f37686d3247733846776650656a57647076574333484c6976514435685846624e55685841e49ea9e0ca1bb21a37a203fef7bc64efc660ff208f4e572ad3d58ae1d5f1e86e7af1c030c66b3325f99886b1a1d0836487c16112c43c07248b490121d473287701783531365569753248416d4e776772753751537356526163477158647466656166316f71747a6e7658413672537a4b55313832326b75575841ecb36e24df58876277643117344d5329fc1bb22b51e9e6835ac587c894db06a11110e966fd97acd91659c778494d9b4dcdc1bd22ffb47a90e3bec38db8a7310f01"); + "d998598701d9985a8a010000f65820747276e851904305a7457d31edd9d374cdedbc7f72c4f5674da410cddb23b6c14a00000000000000000000004a00000000000000000000004a000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058200000000000000000000000000000000000000000000000000000000000000000d9985b8301418080d9985c83010080d9985d880100000000f65820bd991b4e822f177252fa79aebdec0a24546519b813fdd54e6e48fc67d12c454da1644e4f44455841a3ed5d3e5ea04d39297d5db9cc69bbd97eabf2351e6f96660b8fc3e8d106daf06e0f6336c0862a8068ebdd326474b5a0f52f7942ccf010219a619c650ea4a2a301"); UnicityCertificate unicityCertificate = UnicityCertificate.fromCbor(data); Assertions.assertEquals( - "d903ef8701d903f08a0118f70058220000ea2fc549e86b1f8666bd7a6d5dd0721b446856ad80c67ea958c4045dfb0b627c58220000ea2fc549e86b1f8666bd7a6d5dd0721b446856ad80c67ea958c4045dfb0b627c401a68d27af6f600f6582024bf5304778618efe3303e1ac36a5c23b7d9d64bbd2c2ae92ea1488ad59e9cfa58206a362e77353752942e9c8101511861ee7fac83ba0873ee88f34416535ae17c5c82418080d903f68301078182055820b206f60d312bf4422815c861e9330ddc3a29b0c744864d7bea41f94d1d34c669d903e988010319073b001a68d27af958204b3f7f036452271a1245ca79f6c2e35adc0b192e6eeb001dff35d70d1be3b2d55820380451b471673eb68cf750ceedb09755c08e3b0b7dea32d0101465970ca3a661a4783531365569753248416d33506141397a386a6f6e5a7a6676755431574a675478437062466b5634577134505353426b375663746b6d475841c5103160b79e7691cec53448949bbd93a870a38c89ad4c929aa1c8e77f2a1d9053d3f8f04e96bcb38ba234bf3dee52baad4b1df94be0b8a5e569918fb94e18ec00783531365569753248416d383931384473326e5069564c586735356b7970796f586f6977656f6b705178746e67755a6a67787a33704e455841fa3736ebdc729b31d6e3c964787dfcefcbd39286a1619665eac1313e6d35049b4385156f652ad9ac87a769d3bf7b8cc819bb5a6f838e595d2e2092df2592849400783531365569753248416d454545477976595a6e6f37686d3247733846776650656a57647076574333484c6976514435685846624e55685841e49ea9e0ca1bb21a37a203fef7bc64efc660ff208f4e572ad3d58ae1d5f1e86e7af1c030c66b3325f99886b1a1d0836487c16112c43c07248b490121d473287701783531365569753248416d4e776772753751537356526163477158647466656166316f71747a6e7658413672537a4b55313832326b75575841ecb36e24df58876277643117344d5329fc1bb22b51e9e6835ac587c894db06a11110e966fd97acd91659c778494d9b4dcdc1bd22ffb47a90e3bec38db8a7310f01", - HexConverter.encode(unicityCertificate.toCbor()) + "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/common/CommonTestFlow.java b/src/test/java/org/unicitylabs/sdk/common/CommonTestFlow.java index 8f4dd74..32b5e15 100644 --- a/src/test/java/org/unicitylabs/sdk/common/CommonTestFlow.java +++ b/src/test/java/org/unicitylabs/sdk/common/CommonTestFlow.java @@ -31,31 +31,31 @@ public abstract class CommonTestFlow { @Test public void testTransferFlow() throws Exception { Token aliceToken = TokenUtils.mintToken( - this.client, - this.trustBase, - this.predicateVerifier, - Address.fromPredicate(PayToPublicKeyPredicate.create(ALICE_SIGNING_SERVICE.getPublicKey())) + this.client, + this.trustBase, + this.predicateVerifier, + Address.fromPredicate(PayToPublicKeyPredicate.create(ALICE_SIGNING_SERVICE.getPublicKey())) ); Token bobToken = TokenUtils.transferToken( - this.client, - this.trustBase, - this.predicateVerifier, - aliceToken.toCbor(), - Address.fromPredicate(PayToPublicKeyPredicate.create(BOB_SIGNING_SERVICE.getPublicKey())), - ALICE_SIGNING_SERVICE + this.client, + this.trustBase, + this.predicateVerifier, + aliceToken.toCbor(), + Address.fromPredicate(PayToPublicKeyPredicate.create(BOB_SIGNING_SERVICE.getPublicKey())), + ALICE_SIGNING_SERVICE ); Token carolToken = TokenUtils.transferToken( - this.client, - this.trustBase, - this.predicateVerifier, - bobToken.toCbor(), - Address.fromPredicate(PayToPublicKeyPredicate.create(CAROL_SIGNING_SERVICE.getPublicKey())), - BOB_SIGNING_SERVICE + this.client, + this.trustBase, + this.predicateVerifier, + bobToken.toCbor(), + Address.fromPredicate(PayToPublicKeyPredicate.create(CAROL_SIGNING_SERVICE.getPublicKey())), + BOB_SIGNING_SERVICE ); Assertions.assertEquals(VerificationStatus.OK, - carolToken.verify(this.trustBase, this.predicateVerifier).getStatus()); + carolToken.verify(this.trustBase, this.predicateVerifier).getStatus()); } } \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/crypto/secp256k1/SignatureRecoveryTest.java b/src/test/java/org/unicitylabs/sdk/crypto/secp256k1/SignatureRecoveryTest.java index d5e8130..99dacf6 100644 --- a/src/test/java/org/unicitylabs/sdk/crypto/secp256k1/SignatureRecoveryTest.java +++ b/src/test/java/org/unicitylabs/sdk/crypto/secp256k1/SignatureRecoveryTest.java @@ -1,87 +1,88 @@ package org.unicitylabs.sdk.crypto.secp256k1; +import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.crypto.hash.DataHash; -import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import org.unicitylabs.sdk.crypto.hash.DataHasher; +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import org.unicitylabs.sdk.util.HexConverter; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test signature recovery functionality */ public class SignatureRecoveryTest { - - @Test - void testSignatureRecoveryId() { - // Create a signing service with a known private key - byte[] privateKey = HexConverter.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); - SigningService signingService = new SigningService(privateKey); - - // Create test data and hash it - byte[] testData = "Hello, Unicity!".getBytes(); - DataHasher hasher = new DataHasher(HashAlgorithm.SHA256); - hasher.update(testData); - DataHash hash = hasher.digest(); - - // Sign the hash - Signature signature = signingService.sign(hash); - - // Verify recovery ID is 0 or 1 - assertTrue(signature.getRecovery() == 0 || signature.getRecovery() == 1, - "Recovery ID should be 0 or 1, got: " + signature.getRecovery()); - - // Verify signature with known public key - byte[] publicKey = signingService.getPublicKey(); - assertTrue(SigningService.verifyWithPublicKey(hash, signature.getBytes(), publicKey)); - } - - @Test - void testPublicKeyRecovery() { - // Create a signing service with a known private key - byte[] privateKey = HexConverter.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); - SigningService signingService = new SigningService(privateKey); - byte[] expectedPublicKey = signingService.getPublicKey(); - - // Create test data and hash it - byte[] testData = "Test public key recovery".getBytes(); - DataHasher hasher = new DataHasher(HashAlgorithm.SHA256); - hasher.update(testData); - DataHash hash = hasher.digest(); - - // Sign the hash - Signature signature = signingService.sign(hash); - - // Verify signature using recovered public key - assertTrue(SigningService.verifySignatureWithRecoveredPublicKey(hash, signature), - "Signature verification with recovered public key should succeed"); - } - - @Test - void testSignatureFormatCompliance() { - // Test with the exact values from TypeScript test - String transactionHashHex = "0000d6035b65700f0af73cc62a580eb833c20f40aaee460087f5fb43ebb3c047f1d4"; - String signatureHex = "301c7f19d5e0a7e350012ab7bbaf26a0152a751eec06d18563f96bcf06d2380e7de7ce6cebb8c11479d1bd9c463c3ba47396b5f815c552b344d430b0d011a2e701"; - String expectedPublicKeyHex = "02bf8d9e7687f66c7fce1e98edbc05566f7db740030722cf6cf62aca035c5035ea"; - - // Parse the signature - byte[] sigBytes = HexConverter.decode(signatureHex); - assertEquals(65, sigBytes.length, "Signature should be 65 bytes"); - - // Extract components - int recoveryId = sigBytes[64] & 0xFF; - - // Create signature object - byte[] sigOnly = new byte[64]; - System.arraycopy(sigBytes, 0, sigOnly, 0, 64); - Signature signature = new Signature(sigOnly, recoveryId); - - // Parse hash - DataHash transactionHash = DataHash.fromImprint(HexConverter.decode(transactionHashHex)); - - // Verify using recovered public key - assertTrue(SigningService.verifySignatureWithRecoveredPublicKey(transactionHash, signature), - "Should verify with recovered public key"); - } + + @Test + void testSignatureRecoveryId() { + // Create a signing service with a known private key + byte[] privateKey = HexConverter.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); + SigningService signingService = new SigningService(privateKey); + + // Create test data and hash it + byte[] testData = "Hello, Unicity!".getBytes(); + DataHasher hasher = new DataHasher(HashAlgorithm.SHA256); + hasher.update(testData); + DataHash hash = hasher.digest(); + + // Sign the hash + Signature signature = signingService.sign(hash); + + // Verify recovery ID is 0 or 1 + assertTrue(signature.getRecovery() == 0 || signature.getRecovery() == 1, + "Recovery ID should be 0 or 1, got: " + signature.getRecovery()); + + // Verify signature with known public key + byte[] publicKey = signingService.getPublicKey(); + assertTrue(SigningService.verifyWithPublicKey(hash, signature.getBytes(), publicKey)); + } + + @Test + void testPublicKeyRecovery() { + // Create a signing service with a known private key + byte[] privateKey = HexConverter.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); + SigningService signingService = new SigningService(privateKey); + byte[] expectedPublicKey = signingService.getPublicKey(); + + // Create test data and hash it + byte[] testData = "Test public key recovery".getBytes(); + DataHasher hasher = new DataHasher(HashAlgorithm.SHA256); + hasher.update(testData); + DataHash hash = hasher.digest(); + + // Sign the hash + Signature signature = signingService.sign(hash); + + // Verify signature using recovered public key + assertTrue(SigningService.verifySignatureWithRecoveredPublicKey(hash, signature), + "Signature verification with recovered public key should succeed"); + } + + @Test + void testSignatureFormatCompliance() { + // Test with the exact values from TypeScript test + String transactionHashHex = "0000d6035b65700f0af73cc62a580eb833c20f40aaee460087f5fb43ebb3c047f1d4"; + String signatureHex = "301c7f19d5e0a7e350012ab7bbaf26a0152a751eec06d18563f96bcf06d2380e7de7ce6cebb8c11479d1bd9c463c3ba47396b5f815c552b344d430b0d011a2e701"; + String expectedPublicKeyHex = "02bf8d9e7687f66c7fce1e98edbc05566f7db740030722cf6cf62aca035c5035ea"; + + // Parse the signature + byte[] sigBytes = HexConverter.decode(signatureHex); + assertEquals(65, sigBytes.length, "Signature should be 65 bytes"); + + // Extract components + int recoveryId = sigBytes[64] & 0xFF; + + // Create signature object + byte[] sigOnly = new byte[64]; + System.arraycopy(sigBytes, 0, sigOnly, 0, 64); + Signature signature = new Signature(sigOnly, recoveryId); + + // Parse hash + DataHash transactionHash = DataHash.fromImprint(HexConverter.decode(transactionHashHex)); + + // Verify using recovered public key + assertTrue(SigningService.verifySignatureWithRecoveredPublicKey(transactionHash, signature), + "Should verify with recovered public key"); + } } \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/crypto/secp256k1/SigningServiceTest.java b/src/test/java/org/unicitylabs/sdk/crypto/secp256k1/SigningServiceTest.java index 97ca24d..8ed558f 100644 --- a/src/test/java/org/unicitylabs/sdk/crypto/secp256k1/SigningServiceTest.java +++ b/src/test/java/org/unicitylabs/sdk/crypto/secp256k1/SigningServiceTest.java @@ -1,79 +1,79 @@ package org.unicitylabs.sdk.crypto.secp256k1; +import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; public class SigningServiceTest { - - @Test - public void testGeneratePrivateKey() { - byte[] privateKey = SigningService.generatePrivateKey(); - - assertNotNull(privateKey); - assertEquals(32, privateKey.length); - - // Test that we can create a signing service with it - SigningService service = new SigningService(privateKey); - assertNotNull(service.getPublicKey()); - assertEquals(33, service.getPublicKey().length); // Compressed public key - } - - @Test - public void testSignAndVerify() { - byte[] privateKey = SigningService.generatePrivateKey(); - SigningService service = new SigningService(privateKey); - - // Create a test hash - DataHash hash = new DataHash(HashAlgorithm.SHA256, new byte[32]); - - // Sign the hash - Signature signature = service.sign(hash); - - assertNotNull(signature); - assertEquals(64, signature.getBytes().length); - - // Verify the signature - boolean isValid = service.verify(hash, signature); - - assertTrue(isValid); - } - - @Test - public void testVerifyWithPublicKey() { - byte[] privateKey = SigningService.generatePrivateKey(); - SigningService service = new SigningService(privateKey); - byte[] publicKey = service.getPublicKey(); - - // Create a test hash - DataHash hash = new DataHash(HashAlgorithm.SHA256, new byte[32]); - - // Sign the hash - Signature signature = service.sign(hash); - - // Verify with public key - boolean isValid = SigningService.verifyWithPublicKey(hash, signature.getBytes(), publicKey); - - assertTrue(isValid); - } - - @Test - public void testInvalidSignature() { - byte[] privateKey = SigningService.generatePrivateKey(); - SigningService service = new SigningService(privateKey); - - // Create a test hash - DataHash hash = new DataHash(HashAlgorithm.SHA256, new byte[32]); - - // Create an invalid signature - byte[] invalidSig = new byte[64]; - Signature signature = new Signature(invalidSig, 0); - - // Verify the signature - boolean isValid = service.verify(hash, signature); - - assertFalse(isValid); - } + + @Test + public void testGeneratePrivateKey() { + byte[] privateKey = SigningService.generatePrivateKey(); + + assertNotNull(privateKey); + assertEquals(32, privateKey.length); + + // Test that we can create a signing service with it + SigningService service = new SigningService(privateKey); + assertNotNull(service.getPublicKey()); + assertEquals(33, service.getPublicKey().length); // Compressed public key + } + + @Test + public void testSignAndVerify() { + byte[] privateKey = SigningService.generatePrivateKey(); + SigningService service = new SigningService(privateKey); + + // Create a test hash + DataHash hash = new DataHash(HashAlgorithm.SHA256, new byte[32]); + + // Sign the hash + Signature signature = service.sign(hash); + + assertNotNull(signature); + assertEquals(64, signature.getBytes().length); + + // Verify the signature + boolean isValid = service.verify(hash, signature); + + assertTrue(isValid); + } + + @Test + public void testVerifyWithPublicKey() { + byte[] privateKey = SigningService.generatePrivateKey(); + SigningService service = new SigningService(privateKey); + byte[] publicKey = service.getPublicKey(); + + // Create a test hash + DataHash hash = new DataHash(HashAlgorithm.SHA256, new byte[32]); + + // Sign the hash + Signature signature = service.sign(hash); + + // Verify with public key + boolean isValid = SigningService.verifyWithPublicKey(hash, signature.getBytes(), publicKey); + + assertTrue(isValid); + } + + @Test + public void testInvalidSignature() { + byte[] privateKey = SigningService.generatePrivateKey(); + SigningService service = new SigningService(privateKey); + + // Create a test hash + DataHash hash = new DataHash(HashAlgorithm.SHA256, new byte[32]); + + // Create an invalid signature + byte[] invalidSig = new byte[64]; + Signature signature = new Signature(invalidSig, 0); + + // Verify the signature + boolean isValid = service.verify(hash, signature); + + assertFalse(isValid); + } } \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java b/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java index 5c95d1f..bafab7e 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java @@ -1,18 +1,18 @@ package org.unicitylabs.sdk.e2e; -import java.io.IOException; -import java.io.InputStream; - -import org.unicitylabs.sdk.StateTransitionClient; -import org.unicitylabs.sdk.api.JsonRpcAggregatorClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.unicitylabs.sdk.StateTransitionClient; +import org.unicitylabs.sdk.api.JsonRpcAggregatorClient; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.common.CommonTestFlow; import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; +import java.io.IOException; +import java.io.InputStream; + import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/org/unicitylabs/sdk/functional/payment/SplitBuilderTest.java b/src/test/java/org/unicitylabs/sdk/functional/payment/SplitBuilderTest.java index bf4cfed..c88ac3f 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/payment/SplitBuilderTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/payment/SplitBuilderTest.java @@ -1,16 +1,5 @@ package org.unicitylabs.sdk.functional.payment; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.util.AbstractSet; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -20,15 +9,7 @@ import org.unicitylabs.sdk.api.bft.RootTrustBase; 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.mtree.sum.SparseMerkleSumTree; -import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTreeRootNode; -import org.unicitylabs.sdk.payment.SplitPaymentData; -import org.unicitylabs.sdk.payment.SplitReason; -import org.unicitylabs.sdk.payment.SplitReasonProof; -import org.unicitylabs.sdk.payment.SplitResult; -import org.unicitylabs.sdk.payment.TokenSplit; +import org.unicitylabs.sdk.payment.*; import org.unicitylabs.sdk.payment.asset.Asset; import org.unicitylabs.sdk.payment.asset.AssetId; import org.unicitylabs.sdk.predicate.builtin.PayToPublicKeyPredicate; @@ -36,6 +17,10 @@ import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import org.unicitylabs.sdk.smt.plain.SparseMerkleTree; +import org.unicitylabs.sdk.smt.plain.SparseMerkleTreeRootNode; +import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTree; +import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTreeRootNode; import org.unicitylabs.sdk.transaction.Address; import org.unicitylabs.sdk.transaction.Token; import org.unicitylabs.sdk.transaction.TokenId; @@ -43,6 +28,12 @@ import org.unicitylabs.sdk.util.verification.VerificationStatus; import org.unicitylabs.sdk.utils.TokenUtils; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; + /** * Functional tests for minting and splitting tokens with proof verification. */ @@ -71,11 +62,11 @@ public void setupFixture() throws Exception { this.asset2 = new Asset(new AssetId("ASSET_2".getBytes(StandardCharsets.UTF_8)), BigInteger.valueOf(500)); this.splitToken = createSplitToken( - this.client, - signingService, - ownerPredicate, - Set.of(this.asset1, this.asset2), - Set.of(this.asset1, this.asset2) + this.client, + signingService, + ownerPredicate, + Set.of(this.asset1, this.asset2), + Set.of(this.asset1, this.asset2) ); } @@ -93,71 +84,71 @@ public void verifyTokenSplitIsSuccessful() throws Exception { TestPaymentData paymentData = new TestPaymentData(assets); Token token = TokenUtils.mintToken( - this.client, - this.trustBase, - this.predicateVerifier, - Address.fromPredicate(predicate), - paymentData.encode() + this.client, + this.trustBase, + this.predicateVerifier, + Address.fromPredicate(predicate), + paymentData.encode() ); IllegalArgumentException exception = Assertions.assertThrows( - IllegalArgumentException.class, - () -> TokenSplit.split( - token, - predicate, - TestPaymentData::decode, - Map.of(TokenId.generate(), Set.of(this.asset1)) - ) + IllegalArgumentException.class, + () -> TokenSplit.split( + token, + predicate, + TestPaymentData::decode, + Map.of(TokenId.generate(), Set.of(this.asset1)) + ) ); Assertions.assertEquals("Token and split tokens asset counts differ.", exception.getMessage()); exception = Assertions.assertThrows( - IllegalArgumentException.class, - () -> TokenSplit.split( - token, - predicate, - TestPaymentData::decode, - Map.of( - TokenId.generate(), - Set.of(this.asset1, new Asset(this.asset2.getId(), BigInteger.valueOf(400))) + IllegalArgumentException.class, + () -> TokenSplit.split( + token, + predicate, + TestPaymentData::decode, + Map.of( + TokenId.generate(), + Set.of(this.asset1, new Asset(this.asset2.getId(), BigInteger.valueOf(400))) + ) ) - ) ); Assertions.assertEquals("Token contained 500 AssetId{bytes=41535345545f32} assets, but tree has 400", - exception.getMessage()); + exception.getMessage()); exception = Assertions.assertThrows( - IllegalArgumentException.class, - () -> TokenSplit.split( - token, - predicate, - TestPaymentData::decode, - Map.of( - TokenId.generate(), - Set.of(this.asset1, new Asset(this.asset2.getId(), BigInteger.valueOf(1500))) + IllegalArgumentException.class, + () -> TokenSplit.split( + token, + predicate, + TestPaymentData::decode, + Map.of( + TokenId.generate(), + Set.of(this.asset1, new Asset(this.asset2.getId(), BigInteger.valueOf(1500))) + ) ) - ) ); Assertions.assertEquals("Token contained 500 AssetId{bytes=41535345545f32} assets, but tree has 1500", - exception.getMessage()); + exception.getMessage()); Map> splitTokens = Map.of( - TokenId.generate(), Set.of(this.asset1), - TokenId.generate(), Set.of(this.asset2) + TokenId.generate(), Set.of(this.asset1), + TokenId.generate(), Set.of(this.asset2) ); SplitResult result = TokenSplit.split(token, predicate, TestPaymentData::decode, splitTokens); Token burnToken = TokenUtils.transferToken( - this.client, - this.trustBase, - this.predicateVerifier, - token, - result.getBurnTransaction(), - PayToPublicKeyPredicateUnlockScript.create(result.getBurnTransaction(), signingService) + this.client, + this.trustBase, + this.predicateVerifier, + token, + result.getBurnTransaction(), + PayToPublicKeyPredicateUnlockScript.create(result.getBurnTransaction(), signingService) ); for (Entry> entry : splitTokens.entrySet()) { @@ -165,63 +156,63 @@ public void verifyTokenSplitIsSuccessful() throws Exception { Assertions.assertNotNull(proofs); Token splitToken = TokenUtils.mintToken( - this.client, - this.trustBase, - this.predicateVerifier, - entry.getKey(), - Address.fromPredicate(predicate), - new TestSplitPaymentData( - entry.getValue(), - SplitReason.create( - burnToken, - proofs - ) - ).encode() + this.client, + this.trustBase, + this.predicateVerifier, + entry.getKey(), + Address.fromPredicate(predicate), + new TestSplitPaymentData( + entry.getValue(), + SplitReason.create( + burnToken, + proofs + ) + ).encode() ); Assertions.assertEquals( - VerificationStatus.OK, - splitToken.verify(this.trustBase, this.predicateVerifier).getStatus() + VerificationStatus.OK, + splitToken.verify(this.trustBase, this.predicateVerifier).getStatus() ); Assertions.assertEquals(VerificationStatus.OK, - TokenSplit.verify( - Token.fromCbor(splitToken.toCbor()), - TestSplitPaymentData::decode, - this.trustBase, - this.predicateVerifier - ).getStatus()); + TokenSplit.verify( + Token.fromCbor(splitToken.toCbor()), + TestSplitPaymentData::decode, + this.trustBase, + this.predicateVerifier + ).getStatus()); } } @Test public void verifyFailsWhenTokenIsNull() { assertNpe("Token cannot be null", - () -> TokenSplit.verify(null, TestSplitPaymentData::decode, this.trustBase, this.predicateVerifier)); + () -> TokenSplit.verify(null, TestSplitPaymentData::decode, this.trustBase, this.predicateVerifier)); } @Test public void verifyFailsWhenDeserializerIsNull() { assertNpe("Payment data deserializer cannot be null", - () -> TokenSplit.verify(this.splitToken, null, this.trustBase, this.predicateVerifier)); + () -> TokenSplit.verify(this.splitToken, null, this.trustBase, this.predicateVerifier)); } @Test public void verifyFailsWhenTrustBaseIsNull() { assertNpe("Trust base cannot be null", - () -> TokenSplit.verify(this.splitToken, TestSplitPaymentData::decode, null, this.predicateVerifier)); + () -> TokenSplit.verify(this.splitToken, TestSplitPaymentData::decode, null, this.predicateVerifier)); } @Test public void verifyFailsWhenPredicateVerifierIsNull() { assertNpe("Predicate verifier cannot be null", - () -> TokenSplit.verify(this.splitToken, TestSplitPaymentData::decode, this.trustBase, null)); + () -> TokenSplit.verify(this.splitToken, TestSplitPaymentData::decode, this.trustBase, null)); } @Test public void verifyFailsWhenAssetsAreMissing() { VerificationResult result = verifyWithData( - this.splitToken, - new TestSplitPaymentData(null, TestSplitPaymentData.decode(this.splitToken.getGenesis().getData()).getReason()) + this.splitToken, + new TestSplitPaymentData(null, TestSplitPaymentData.decode(this.splitToken.getGenesis().getData()).getReason()) ); assertFailWithMessage(result, "Assets data is missing."); @@ -230,8 +221,8 @@ public void verifyFailsWhenAssetsAreMissing() { @Test public void verifyFailsWhenReasonIsMissing() { VerificationResult result = verifyWithData( - this.splitToken, - new TestSplitPaymentData(Set.of(this.asset1), null) + this.splitToken, + new TestSplitPaymentData(Set.of(this.asset1), null) ); assertFailWithMessage(result, "Reason is missing."); @@ -241,20 +232,24 @@ public void verifyFailsWhenReasonIsMissing() { public void verifyFailsWhenBurnTokenVerificationFails() { List payloadData = CborDeserializer.decodeArray(this.splitToken.getGenesis().getData()); List reasonData = CborDeserializer.decodeArray(payloadData.get(1)); - List reasonTokenData = CborDeserializer.decodeArray(reasonData.get(0)); - List transactions = CborDeserializer.decodeArray(reasonTokenData.get(1)); + + CborDeserializer.CborTag reasonTokenTag = CborDeserializer.decodeTag(reasonData.get(0)); + List reasonTokenData = CborDeserializer.decodeArray(reasonTokenTag.getData()); + List transactions = CborDeserializer.decodeArray(reasonTokenData.get(2)); List certifiedTransfer = CborDeserializer.decodeArray(transactions.get(0)); - List transfer = CborDeserializer.decodeArray(certifiedTransfer.get(0)); + + CborDeserializer.CborTag transferTag = CborDeserializer.decodeTag(certifiedTransfer.get(0)); + List transfer = CborDeserializer.decodeArray(transferTag.getData()); // Corrupt burn transaction recipient address so burn token verification fails. byte[] invalidRecipient = new byte[32]; invalidRecipient[0] = 1; - transfer.set(2, Address.fromBytes(invalidRecipient).toCbor()); + transfer.set(3, Address.fromBytes(invalidRecipient).toCbor()); - certifiedTransfer.set(0, encodeArray(transfer)); + certifiedTransfer.set(0, CborSerializer.encodeTag(transferTag.getTag(), encodeArray(transfer))); transactions.set(0, encodeArray(certifiedTransfer)); - reasonTokenData.set(1, encodeArray(transactions)); - reasonData.set(0, encodeArray(reasonTokenData)); + reasonTokenData.set(2, encodeArray(transactions)); + reasonData.set(0, CborSerializer.encodeTag(reasonTokenTag.getTag(), encodeArray(reasonTokenData))); payloadData.set(1, encodeArray(reasonData)); byte[] payload = encodeArray(payloadData); @@ -266,9 +261,9 @@ public void verifyFailsWhenBurnTokenVerificationFails() { @Test public void verifyFailsWhenAssetAndProofCountsDiffer() { VerificationResult result = verifyWithData( - this.splitToken, - new TestSplitPaymentData(Set.of(this.asset1), - TestSplitPaymentData.decode(this.splitToken.getGenesis().getData()).getReason()) + this.splitToken, + new TestSplitPaymentData(Set.of(this.asset1), + TestSplitPaymentData.decode(this.splitToken.getGenesis().getData()).getReason()) ); assertFailWithMessage(result, "Total amount of assets differ in token and proofs."); @@ -278,9 +273,9 @@ public void verifyFailsWhenAssetAndProofCountsDiffer() { public void verifyFailsWhenAssetEntryIsNull() { Set invalidAssets = new NonUniqueAssetSet(Arrays.asList(null, this.asset1)); VerificationResult result = verifyWithData( - this.splitToken, - new TestSplitPaymentData(invalidAssets, - TestSplitPaymentData.decode(this.splitToken.getGenesis().getData()).getReason()) + this.splitToken, + new TestSplitPaymentData(invalidAssets, + TestSplitPaymentData.decode(this.splitToken.getGenesis().getData()).getReason()) ); assertFailWithMessage(result, "Asset data is missing."); @@ -292,13 +287,13 @@ public void verifyFailsWhenAssetIdsAreDuplicated() { Set duplicatedAssets = new NonUniqueAssetSet(List.of(this.asset1, duplicate)); VerificationResult result = verifyWithData( - this.splitToken, - new TestSplitPaymentData(duplicatedAssets, - TestSplitPaymentData.decode(this.splitToken.getGenesis().getData()).getReason()) + this.splitToken, + new TestSplitPaymentData(duplicatedAssets, + TestSplitPaymentData.decode(this.splitToken.getGenesis().getData()).getReason()) ); assertFailWithMessage(result, - String.format("Duplicate asset id %s found in asset data.", this.asset1.getId())); + String.format("Duplicate asset id %s found in asset data.", this.asset1.getId())); } @Test @@ -310,23 +305,23 @@ public void verifyFailsWhenAggregationPathVerificationFails() throws Exception { SparseMerkleTreeRootNode aggregationRoot = new SparseMerkleTree(HashAlgorithm.SHA256).calculateRoot(); proofs.set( - 0, - SplitReasonProof.create( - proof.getAssetId(), - aggregationRoot.getPath(proof.getAssetId().toBitString().toBigInteger()), - proof.getAssetTreePath() - ) + 0, + SplitReasonProof.create( + proof.getAssetId(), + aggregationRoot.getPath(proof.getAssetId().toBitString().toBigInteger()), + proof.getAssetTreePath() + ) ); byte[] payload = new TestSplitPaymentData( - splitPaymentData.getAssets(), - SplitReason.create(splitReason.getToken(), proofs) + splitPaymentData.getAssets(), + SplitReason.create(splitReason.getToken(), proofs) ).encode(); VerificationResult result = verifyWithPayload(this.splitToken, payload); assertFailWithMessage(result, - String.format("Aggregation path verification failed for asset: %s", proof.getAssetId())); + String.format("Aggregation path verification failed for asset: %s", proof.getAssetId())); } @Test @@ -339,21 +334,21 @@ public void verifyFailsWhenAssetTreePathVerificationFails() throws Exception { SparseMerkleSumTreeRootNode assetTreeRoot = new SparseMerkleSumTree(HashAlgorithm.SHA256).calculateRoot(); SplitReasonProof mutated = SplitReasonProof.create( - proof.getAssetId(), - proof.getAggregationPath(), - assetTreeRoot.getPath(this.splitToken.getId().toBitString().toBigInteger()) + proof.getAssetId(), + proof.getAggregationPath(), + assetTreeRoot.getPath(this.splitToken.getId().toBitString().toBigInteger()) ); proofs.set(0, mutated); byte[] payload = new TestSplitPaymentData( - splitPaymentData.getAssets(), - SplitReason.create(splitReason.getToken(), proofs) + splitPaymentData.getAssets(), + SplitReason.create(splitReason.getToken(), proofs) ).encode(); VerificationResult result = verifyWithPayload(this.splitToken, payload); assertFailWithMessage(result, - String.format("Asset tree path verification failed for token: %s", this.splitToken.getId())); + String.format("Asset tree path verification failed for token: %s", this.splitToken.getId())); } @Test @@ -365,21 +360,21 @@ public void verifyFailsWhenProofsUseDifferentAssetTrees() throws Exception { SparseMerkleTree aggregationTree = new SparseMerkleTree(HashAlgorithm.SHA256); aggregationTree.addLeaf( - secondProof.getAssetId().toBitString().toBigInteger(), - secondProof.getAssetTreePath().getRootHash().getImprint() + secondProof.getAssetId().toBitString().toBigInteger(), + secondProof.getAssetTreePath().getRootHash().getImprint() ); SparseMerkleTreeRootNode otherAggregationRoot = aggregationTree.calculateRoot(); SplitReasonProof mutated = SplitReasonProof.create( - secondProof.getAssetId(), - otherAggregationRoot.getPath(secondProof.getAssetId().toBitString().toBigInteger()), - secondProof.getAssetTreePath() + secondProof.getAssetId(), + otherAggregationRoot.getPath(secondProof.getAssetId().toBitString().toBigInteger()), + secondProof.getAssetTreePath() ); proofs.set(1, mutated); byte[] payload = new TestSplitPaymentData( - splitPaymentData.getAssets(), - SplitReason.create(splitReason.getToken(), proofs) + splitPaymentData.getAssets(), + SplitReason.create(splitReason.getToken(), proofs) ).encode(); VerificationResult result = verifyWithPayload(this.splitToken, payload); @@ -395,23 +390,23 @@ public void verifyFailsWhenAssetTreeRootDoesNotMatchAggregationLeaf() throws Exc SparseMerkleSumTree assetTree = new SparseMerkleSumTree(HashAlgorithm.SHA256); assetTree.addLeaf( - this.splitToken.getId().toBitString().toBigInteger(), - new SparseMerkleSumTree.LeafValue( - proof.getAssetId().getBytes(), - proof.getAssetTreePath().getSteps().get(0).getValue().add(BigInteger.ONE) - ) + this.splitToken.getId().toBitString().toBigInteger(), + new SparseMerkleSumTree.LeafValue( + proof.getAssetId().getBytes(), + proof.getAssetTreePath().getSteps().get(0).getValue().add(BigInteger.ONE) + ) ); SplitReasonProof mutated = SplitReasonProof.create( - proof.getAssetId(), - proof.getAggregationPath(), - assetTree.calculateRoot().getPath(this.splitToken.getId().toBitString().toBigInteger()) + proof.getAssetId(), + proof.getAggregationPath(), + assetTree.calculateRoot().getPath(this.splitToken.getId().toBitString().toBigInteger()) ); proofs.set(0, mutated); byte[] payload = new TestSplitPaymentData( - splitPaymentData.getAssets(), - SplitReason.create(splitReason.getToken(), proofs) + splitPaymentData.getAssets(), + SplitReason.create(splitReason.getToken(), proofs) ).encode(); VerificationResult result = verifyWithPayload(this.splitToken, payload); @@ -424,16 +419,16 @@ public void verifyFailsWhenProofAssetIdIsMissingFromAssetData() { SplitReason splitReason = splitPaymentData.getReason(); List proofs = List.of(splitReason.getProofs().get(0)); Set assets = splitPaymentData.getAssets().stream() - .filter(asset -> !asset.getId().equals(proofs.get(0).getAssetId())) - .collect(Collectors.toSet()); + .filter(asset -> !asset.getId().equals(proofs.get(0).getAssetId())) + .collect(Collectors.toSet()); byte[] payload = new TestSplitPaymentData( - assets, - SplitReason.create(splitReason.getToken(), proofs) + assets, + SplitReason.create(splitReason.getToken(), proofs) ).encode(); VerificationResult result = verifyWithPayload(this.splitToken, payload); assertFailWithMessage(result, - String.format("Asset id %s not found in asset data.", proofs.get(0).getAssetId())); + String.format("Asset id %s not found in asset data.", proofs.get(0).getAssetId())); } @Test @@ -449,7 +444,7 @@ public void verifyFailsWhenAssetAmountDoesNotMatchLeafAmount() { VerificationResult result = verifyWithPayload(this.splitToken, payload); assertFailWithMessage(result, - String.format("Asset amount for asset id %s does not match asset tree leaf.", asset.getId())); + String.format("Asset amount for asset id %s does not match asset tree leaf.", asset.getId())); } @Test @@ -461,21 +456,21 @@ public void verifyFailsWhenAggregationRootDoesNotMatchBurnPredicate() throws Exc SparseMerkleTree aggregationTree = new SparseMerkleTree(HashAlgorithm.SHA256); aggregationTree.addLeaf( - proof.getAssetId().toBitString().toBigInteger(), - proof.getAssetTreePath().getRootHash().getImprint() + proof.getAssetId().toBitString().toBigInteger(), + proof.getAssetTreePath().getRootHash().getImprint() ); SparseMerkleTreeRootNode aggregationRoot = aggregationTree.calculateRoot(); SplitReasonProof mutated = SplitReasonProof.create( - proof.getAssetId(), - aggregationRoot.getPath(proof.getAssetId().toBitString().toBigInteger()), - proof.getAssetTreePath() + proof.getAssetId(), + aggregationRoot.getPath(proof.getAssetId().toBitString().toBigInteger()), + proof.getAssetTreePath() ); proofs.set(0, mutated); byte[] payload = new TestSplitPaymentData( - splitPaymentData.getAssets(), - SplitReason.create(splitReason.getToken(), proofs) + splitPaymentData.getAssets(), + SplitReason.create(splitReason.getToken(), proofs) ).encode(); VerificationResult result = verifyWithPayload(this.splitToken, payload); @@ -483,65 +478,65 @@ public void verifyFailsWhenAggregationRootDoesNotMatchBurnPredicate() throws Exc } private Token createSplitToken( - StateTransitionClient client, - SigningService signingService, - PayToPublicKeyPredicate ownerPredicate, - Set sourceAssets, - Set outputAssets + StateTransitionClient client, + SigningService signingService, + PayToPublicKeyPredicate ownerPredicate, + Set sourceAssets, + Set outputAssets ) throws Exception { Token sourceToken = TokenUtils.mintToken( - client, - this.trustBase, - this.predicateVerifier, - Address.fromPredicate(ownerPredicate), - new TestPaymentData(sourceAssets).encode() + client, + this.trustBase, + this.predicateVerifier, + Address.fromPredicate(ownerPredicate), + new TestPaymentData(sourceAssets).encode() ); TokenId outputTokenId = TokenId.generate(); SplitResult split = TokenSplit.split( - sourceToken, - ownerPredicate, - TestPaymentData::decode, - Map.of(outputTokenId, outputAssets) + sourceToken, + ownerPredicate, + TestPaymentData::decode, + Map.of(outputTokenId, outputAssets) ); Token burnToken = TokenUtils.transferToken( - client, - this.trustBase, - this.predicateVerifier, - sourceToken, - split.getBurnTransaction(), - PayToPublicKeyPredicateUnlockScript.create(split.getBurnTransaction(), signingService) + client, + this.trustBase, + this.predicateVerifier, + sourceToken, + split.getBurnTransaction(), + PayToPublicKeyPredicateUnlockScript.create(split.getBurnTransaction(), signingService) ); return TokenUtils.mintToken( - client, - this.trustBase, - this.predicateVerifier, - outputTokenId, - Address.fromPredicate(ownerPredicate), - new TestSplitPaymentData( - outputAssets, - SplitReason.create(burnToken, split.getProofs().get(outputTokenId)) - ).encode() + client, + this.trustBase, + this.predicateVerifier, + outputTokenId, + Address.fromPredicate(ownerPredicate), + new TestSplitPaymentData( + outputAssets, + SplitReason.create(burnToken, split.getProofs().get(outputTokenId)) + ).encode() ); } private VerificationResult verify(Token token) { return TokenSplit.verify( - Token.fromCbor(token.toCbor()), - TestSplitPaymentData::decode, - this.trustBase, - this.predicateVerifier + Token.fromCbor(token.toCbor()), + TestSplitPaymentData::decode, + this.trustBase, + this.predicateVerifier ); } private VerificationResult verifyWithData(Token token, SplitPaymentData paymentData) { return TokenSplit.verify( - Token.fromCbor(token.toCbor()), - ignored -> paymentData, - this.trustBase, - this.predicateVerifier + Token.fromCbor(token.toCbor()), + ignored -> paymentData, + this.trustBase, + this.predicateVerifier ); } @@ -550,17 +545,21 @@ private VerificationResult verifyWithPayload(Token token, by } private Token withPayload(Token token, byte[] payload) { - List tokenData = CborDeserializer.decodeArray(token.toCbor()); - List genesis = CborDeserializer.decodeArray(tokenData.get(0)); - List mint = CborDeserializer.decodeArray(genesis.get(0)); - List aux = CborDeserializer.decodeArray(mint.get(2)); + CborDeserializer.CborTag tokenTag = CborDeserializer.decodeTag(token.toCbor()); + List tokenData = CborDeserializer.decodeArray(tokenTag.getData()); + + List certifiedGenesis = CborDeserializer.decodeArray(tokenData.get(1)); + + CborDeserializer.CborTag mintTag = CborDeserializer.decodeTag(certifiedGenesis.get(0)); + List mint = CborDeserializer.decodeArray(mintTag.getData()); + List aux = CborDeserializer.decodeArray(mint.get(3)); aux.set(1, CborSerializer.encodeByteString(payload)); - mint.set(2, encodeArray(aux)); - genesis.set(0, encodeArray(mint)); - tokenData.set(0, encodeArray(genesis)); + mint.set(3, encodeArray(aux)); + certifiedGenesis.set(0, CborSerializer.encodeTag(mintTag.getTag(), encodeArray(mint))); + tokenData.set(1, encodeArray(certifiedGenesis)); - return Token.fromCbor(encodeArray(tokenData)); + return Token.fromCbor(CborSerializer.encodeTag(tokenTag.getTag(), encodeArray(tokenData))); } private void assertFailWithMessage(VerificationResult result, String message) { diff --git a/src/test/java/org/unicitylabs/sdk/functional/payment/TestPaymentData.java b/src/test/java/org/unicitylabs/sdk/functional/payment/TestPaymentData.java index 73e7167..2e670a7 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/payment/TestPaymentData.java +++ b/src/test/java/org/unicitylabs/sdk/functional/payment/TestPaymentData.java @@ -1,12 +1,13 @@ package org.unicitylabs.sdk.functional.payment; -import java.util.Set; -import java.util.stream.Collectors; import org.unicitylabs.sdk.payment.PaymentData; import org.unicitylabs.sdk.payment.asset.Asset; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import java.util.Set; +import java.util.stream.Collectors; + public class TestPaymentData implements PaymentData { private final Set assets; @@ -22,8 +23,8 @@ public Set getAssets() { public static TestPaymentData decode(byte[] bytes) { Set assets = CborDeserializer.decodeArray(bytes).stream() - .map(Asset::fromCbor) - .collect(Collectors.toSet()); + .map(Asset::fromCbor) + .collect(Collectors.toSet()); return new TestPaymentData(assets); } @@ -31,9 +32,9 @@ public static TestPaymentData decode(byte[] bytes) { @Override public byte[] encode() { return CborSerializer.encodeArray( - this.assets.stream() - .map(Asset::toCbor) - .toArray(byte[][]::new) + this.assets.stream() + .map(Asset::toCbor) + .toArray(byte[][]::new) ); } } \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/functional/payment/TestSplitPaymentData.java b/src/test/java/org/unicitylabs/sdk/functional/payment/TestSplitPaymentData.java index bbf5062..3e27450 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/payment/TestSplitPaymentData.java +++ b/src/test/java/org/unicitylabs/sdk/functional/payment/TestSplitPaymentData.java @@ -1,14 +1,15 @@ package org.unicitylabs.sdk.functional.payment; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; import org.unicitylabs.sdk.payment.SplitPaymentData; import org.unicitylabs.sdk.payment.SplitReason; import org.unicitylabs.sdk.payment.asset.Asset; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + /** * Test implementation of split payment payload used by functional tests. */ @@ -58,10 +59,10 @@ public static TestSplitPaymentData decode(byte[] bytes) { List data = CborDeserializer.decodeArray(bytes); Set assets = CborDeserializer.decodeNullable( - data.get(0), - result -> CborDeserializer.decodeArray(result).stream() - .map(asset -> CborDeserializer.decodeNullable(asset, Asset::fromCbor)) - .collect(Collectors.toSet()) + data.get(0), + result -> CborDeserializer.decodeArray(result).stream() + .map(asset -> CborDeserializer.decodeNullable(asset, Asset::fromCbor)) + .collect(Collectors.toSet()) ); SplitReason reason = CborDeserializer.decodeNullable(data.get(1), SplitReason::fromCbor); @@ -77,13 +78,13 @@ public static TestSplitPaymentData decode(byte[] bytes) { @Override public byte[] encode() { return CborSerializer.encodeArray( - CborSerializer.encodeOptional( - this.assets, - assets -> CborSerializer.encodeArray( - assets.stream().map(asset -> CborSerializer.encodeOptional(asset, Asset::toCbor)).toArray(byte[][]::new) - ) - ), - CborSerializer.encodeOptional(this.reason, SplitReason::toCbor) + CborSerializer.encodeOptional( + this.assets, + assets -> CborSerializer.encodeArray( + assets.stream().map(asset -> CborSerializer.encodeOptional(asset, Asset::toCbor)).toArray(byte[][]::new) + ) + ), + CborSerializer.encodeOptional(this.reason, SplitReason::toCbor) ); } diff --git a/src/test/java/org/unicitylabs/sdk/hash/DataHashTest.java b/src/test/java/org/unicitylabs/sdk/hash/DataHashTest.java index 9e6cffb..31769ae 100644 --- a/src/test/java/org/unicitylabs/sdk/hash/DataHashTest.java +++ b/src/test/java/org/unicitylabs/sdk/hash/DataHashTest.java @@ -4,8 +4,6 @@ import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; -import org.unicitylabs.sdk.serializer.cbor.CborSerializer; -import org.unicitylabs.sdk.serializer.json.JsonSerializationException; import org.unicitylabs.sdk.util.HexConverter; public class DataHashTest { @@ -13,10 +11,10 @@ public class DataHashTest { @Test public void testInvalidDataHashArguments() { NullPointerException exception = Assertions.assertThrows(NullPointerException.class, - () -> new DataHash(null, new byte[32])); + () -> new DataHash(null, new byte[32])); Assertions.assertEquals("algorithm cannot be null", exception.getMessage()); exception = Assertions.assertThrows(NullPointerException.class, - () -> new DataHash(HashAlgorithm.SHA256, null)); + () -> new DataHash(HashAlgorithm.SHA256, null)); Assertions.assertEquals("data cannot be null", exception.getMessage()); } diff --git a/src/test/java/org/unicitylabs/sdk/hash/DataHasherTest.java b/src/test/java/org/unicitylabs/sdk/hash/DataHasherTest.java index abcad60..e77bcd9 100644 --- a/src/test/java/org/unicitylabs/sdk/hash/DataHasherTest.java +++ b/src/test/java/org/unicitylabs/sdk/hash/DataHasherTest.java @@ -7,7 +7,8 @@ import java.nio.charset.StandardCharsets; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class DataHasherTest { @@ -21,17 +22,17 @@ public void testSha256WithUpdate() { assertEquals(HashAlgorithm.SHA256, hash.getAlgorithm()); assertArrayEquals( - new byte[]{ - (byte) 0x2c, (byte) 0xf2, (byte) 0x4d, (byte) 0xba, (byte) 0x5f, (byte) 0xb0, - (byte) 0xa3, (byte) 0x0e, - (byte) 0x26, (byte) 0xe8, (byte) 0x3b, (byte) 0x2a, (byte) 0xc5, (byte) 0xb9, - (byte) 0xe2, (byte) 0x9e, - (byte) 0x1b, (byte) 0x16, (byte) 0x1e, (byte) 0x5c, (byte) 0x1f, (byte) 0xa7, - (byte) 0x42, (byte) 0x5e, - (byte) 0x73, (byte) 0x04, (byte) 0x33, (byte) 0x62, (byte) 0x93, (byte) 0x8b, - (byte) 0x98, (byte) 0x24 - }, - hash.getData() + new byte[]{ + (byte) 0x2c, (byte) 0xf2, (byte) 0x4d, (byte) 0xba, (byte) 0x5f, (byte) 0xb0, + (byte) 0xa3, (byte) 0x0e, + (byte) 0x26, (byte) 0xe8, (byte) 0x3b, (byte) 0x2a, (byte) 0xc5, (byte) 0xb9, + (byte) 0xe2, (byte) 0x9e, + (byte) 0x1b, (byte) 0x16, (byte) 0x1e, (byte) 0x5c, (byte) 0x1f, (byte) 0xa7, + (byte) 0x42, (byte) 0x5e, + (byte) 0x73, (byte) 0x04, (byte) 0x33, (byte) 0x62, (byte) 0x93, (byte) 0x8b, + (byte) 0x98, (byte) 0x24 + }, + hash.getData() ); } @@ -46,17 +47,17 @@ public void testMultipleUpdates() { // Should produce same hash as "hello" assertArrayEquals( - new byte[]{ - (byte) 0x2c, (byte) 0xf2, (byte) 0x4d, (byte) 0xba, (byte) 0x5f, (byte) 0xb0, - (byte) 0xa3, (byte) 0x0e, - (byte) 0x26, (byte) 0xe8, (byte) 0x3b, (byte) 0x2a, (byte) 0xc5, (byte) 0xb9, - (byte) 0xe2, (byte) 0x9e, - (byte) 0x1b, (byte) 0x16, (byte) 0x1e, (byte) 0x5c, (byte) 0x1f, (byte) 0xa7, - (byte) 0x42, (byte) 0x5e, - (byte) 0x73, (byte) 0x04, (byte) 0x33, (byte) 0x62, (byte) 0x93, (byte) 0x8b, - (byte) 0x98, (byte) 0x24 - }, - hash.getData() + new byte[]{ + (byte) 0x2c, (byte) 0xf2, (byte) 0x4d, (byte) 0xba, (byte) 0x5f, (byte) 0xb0, + (byte) 0xa3, (byte) 0x0e, + (byte) 0x26, (byte) 0xe8, (byte) 0x3b, (byte) 0x2a, (byte) 0xc5, (byte) 0xb9, + (byte) 0xe2, (byte) 0x9e, + (byte) 0x1b, (byte) 0x16, (byte) 0x1e, (byte) 0x5c, (byte) 0x1f, (byte) 0xa7, + (byte) 0x42, (byte) 0x5e, + (byte) 0x73, (byte) 0x04, (byte) 0x33, (byte) 0x62, (byte) 0x93, (byte) 0x8b, + (byte) 0x98, (byte) 0x24 + }, + hash.getData() ); } } \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/jsonrpc/JsonRpcRequestTest.java b/src/test/java/org/unicitylabs/sdk/jsonrpc/JsonRpcRequestTest.java index 48ce9ea..41b5771 100644 --- a/src/test/java/org/unicitylabs/sdk/jsonrpc/JsonRpcRequestTest.java +++ b/src/test/java/org/unicitylabs/sdk/jsonrpc/JsonRpcRequestTest.java @@ -1,25 +1,26 @@ package org.unicitylabs.sdk.jsonrpc; import com.fasterxml.jackson.core.JsonProcessingException; -import java.util.List; -import java.util.UUID; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.api.jsonrpc.JsonRpcRequest; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import java.util.List; +import java.util.UUID; + public class JsonRpcRequestTest { @Test public void testJsonSerialization() throws JsonProcessingException { JsonRpcRequest request = new JsonRpcRequest( - UUID.fromString("42b9d83f-f984-4c81-9a53-fa1bdbd30b99"), - "testMethod", - List.of("param1", "param2") + UUID.fromString("42b9d83f-f984-4c81-9a53-fa1bdbd30b99"), + "testMethod", + List.of("param1", "param2") ); Assertions.assertEquals( - "{\"id\":\"42b9d83f-f984-4c81-9a53-fa1bdbd30b99\",\"method\":\"testMethod\",\"params\":[\"param1\",\"param2\"],\"jsonrpc\":\"2.0\"}", - UnicityObjectMapper.JSON.writeValueAsString(request)); + "{\"id\":\"42b9d83f-f984-4c81-9a53-fa1bdbd30b99\",\"method\":\"testMethod\",\"params\":[\"param1\",\"param2\"],\"jsonrpc\":\"2.0\"}", + UnicityObjectMapper.JSON.writeValueAsString(request)); } } diff --git a/src/test/java/org/unicitylabs/sdk/jsonrpc/JsonRpcResponseTest.java b/src/test/java/org/unicitylabs/sdk/jsonrpc/JsonRpcResponseTest.java index 6999726..c2af3ea 100644 --- a/src/test/java/org/unicitylabs/sdk/jsonrpc/JsonRpcResponseTest.java +++ b/src/test/java/org/unicitylabs/sdk/jsonrpc/JsonRpcResponseTest.java @@ -12,9 +12,9 @@ public class JsonRpcResponseTest { @Test public void testJsonSerialization() throws JsonProcessingException { JsonRpcResponse data = UnicityObjectMapper.JSON.readValue( - "{\"jsonrpc\":\"2.0\",\"result\":{\"blockNumber\":\"846973\"},\"id\":\"60ce8f4d-4c78-4690-a330-a92d3cf497a9\"}", - UnicityObjectMapper.JSON.getTypeFactory() - .constructParametricType(JsonRpcResponse.class, BlockHeightResponse.class)); + "{\"jsonrpc\":\"2.0\",\"result\":{\"blockNumber\":\"846973\"},\"id\":\"60ce8f4d-4c78-4690-a330-a92d3cf497a9\"}", + UnicityObjectMapper.JSON.getTypeFactory() + .constructParametricType(JsonRpcResponse.class, BlockHeightResponse.class)); Assertions.assertEquals("60ce8f4d-4c78-4690-a330-a92d3cf497a9", data.getId().toString()); Assertions.assertEquals("2.0", data.getVersion()); diff --git a/src/test/java/org/unicitylabs/sdk/mtree/plain/MerkleTreePathTest.java b/src/test/java/org/unicitylabs/sdk/mtree/plain/MerkleTreePathTest.java deleted file mode 100644 index 652f2f2..0000000 --- a/src/test/java/org/unicitylabs/sdk/mtree/plain/MerkleTreePathTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.unicitylabs.sdk.mtree.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.serializer.cbor.CborSerializer; -import org.unicitylabs.sdk.util.HexConverter; -import java.math.BigInteger; -import java.util.List; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class MerkleTreePathTest { - - @Test - public void testConstructorThrowsOnNullArguments() { - Exception exception = assertThrows(NullPointerException.class, - () -> new SparseMerkleTreePath(null, null) - ); - assertEquals("rootHash cannot be null", exception.getMessage()); - exception = assertThrows(NullPointerException.class, - () -> new SparseMerkleTreePath(new DataHash(HashAlgorithm.SHA256, new byte[32]), null) - ); - assertEquals("steps cannot be null", exception.getMessage()); - } - - @Test - public void testShouldVerifyInclusionProof() { - SparseMerkleTreePath path = new SparseMerkleTreePath( - DataHash.fromImprint( - HexConverter.decode( - "0000e9748bbd0c45fc357ffe7c221c7db1ef02f589680d8b0a370b48a669435bde13" - ) - ), - List.of( - new SparseMerkleTreePathStep( - BigInteger.valueOf(69), - HexConverter.decode("76616c756535") - ), - new SparseMerkleTreePathStep( - BigInteger.valueOf(4), - HexConverter.decode( - "8471f8ea3c9a0e50627df4c72d9bd5affbdc12050ee7f4250974ed64949f3b0f" - ) - ), - new SparseMerkleTreePathStep( - BigInteger.valueOf(1), - HexConverter.decode( - "66507538ce0fae31018cfc7b01841b5308e7e44306445710acee947ec4a4b2cd" - ) - ) - ) - ); - - Assertions.assertEquals(new MerkleTreePathVerificationResult(true, true), - path.verify(BigInteger.valueOf(0b100010100))); - Assertions.assertEquals(new MerkleTreePathVerificationResult(true, false), - path.verify(BigInteger.valueOf(0b111))); - } - - @Test - public void testEmptyPathVerification() throws JsonProcessingException { - byte[] cbor = CborSerializer.encodeArray( - DataHash.fromImprint( - HexConverter.decode("00001e54402898172f2948615fb17627733abbd120a85381c624ad060d28321be672") - ).toCbor(), - CborSerializer.encodeArray( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(HexConverter.decode("01")), - CborSerializer.encodeNull() - ), - CborSerializer.encodeArray( - CborSerializer.encodeByteString(HexConverter.decode("01")), - CborSerializer.encodeNull() - ) - ) - ); - - SparseMerkleTreePath path = SparseMerkleTreePath.fromCbor(cbor); - - MerkleTreePathVerificationResult result = path.verify(BigInteger.valueOf(101)); - Assertions.assertTrue(result.isPathValid()); - Assertions.assertFalse(result.isPathIncluded()); - } -} diff --git a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java b/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java deleted file mode 100644 index bb70203..0000000 --- a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java +++ /dev/null @@ -1,182 +0,0 @@ -package org.unicitylabs.sdk.mtree.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.util.HexConverter; - -public class SparseMerkleTreeTest { - - private final SparseMerkleTreeRootNode root = SparseMerkleTreeRootNode.create( - new PendingNodeBranch( - BigInteger.valueOf(0b10), - new PendingNodeBranch( - BigInteger.valueOf(0b10), - new PendingNodeBranch( - BigInteger.valueOf(0b100), - new PendingLeafBranch( - BigInteger.valueOf(0b10000), - HexConverter.decode("76616c75653030303030303030") - ), - new PendingNodeBranch( - BigInteger.valueOf(0b1001), - new PendingLeafBranch( - BigInteger.valueOf(0b10), - HexConverter.decode("76616c75653030303130303030") - ), - new PendingLeafBranch( - BigInteger.valueOf(0b11), - HexConverter.decode("76616c75653030303130303030") - ) - ) - ), - new PendingLeafBranch( - BigInteger.valueOf(0b11), - HexConverter.decode("76616c7565313030") - ) - ), - new PendingLeafBranch( - BigInteger.valueOf(0b1000101), - HexConverter.decode("76616c756530303031303130") - ) - ).finalize(HashAlgorithm.SHA256), - new PendingNodeBranch( - BigInteger.valueOf(0b11), - new PendingNodeBranch( - BigInteger.valueOf(0b1010), - new PendingLeafBranch( - BigInteger.valueOf(0b11110), - HexConverter.decode("76616c75653131313030313031") - ), - new PendingLeafBranch( - BigInteger.valueOf(0b1101), - HexConverter.decode("76616c756531303130313031") - ) - ), - new PendingNodeBranch( - BigInteger.valueOf(0b11), - new PendingLeafBranch( - BigInteger.valueOf(0b10), - HexConverter.decode("76616c7565303131") - ), - new PendingLeafBranch( - BigInteger.valueOf(0b1111011), - HexConverter.decode("76616c75653131313031313131") - ) - ) - ).finalize(HashAlgorithm.SHA256), - HashAlgorithm.SHA256 - ); - - @Test - public void treeShouldBeHalfCalculated() throws Exception { - SparseMerkleTree smt = new SparseMerkleTree(HashAlgorithm.SHA256); - - smt.addLeaf(BigInteger.valueOf(0b10), new byte[]{1, 2, 3}); - smt.calculateRoot(); - smt.addLeaf(BigInteger.valueOf(0b11), new byte[]{1, 2, 3, 4}); - - FinalizedLeafBranch left = new PendingLeafBranch(BigInteger.valueOf(2), - new byte[]{1, 2, 3}).finalize(HashAlgorithm.SHA256); - PendingLeafBranch right = new PendingLeafBranch(BigInteger.valueOf(3), new byte[]{1, 2, 3, 4}); - - Field leftField = SparseMerkleTree.class.getDeclaredField("left"); - leftField.setAccessible(true); - Field rightField = SparseMerkleTree.class.getDeclaredField("right"); - rightField.setAccessible(true); - - Assertions.assertEquals(left, leftField.get(smt)); - Assertions.assertEquals(right, rightField.get(smt)); - } - - @Test - public void shouldVerifyTheTree() throws Exception { - SparseMerkleTree smt = new SparseMerkleTree(HashAlgorithm.SHA256); - Map leaves = Map.ofEntries( - Map.entry(0b110010000, "value00010000"), - Map.entry(0b100000000, "value00000000"), - Map.entry(0b100010000, "value00010000"), - Map.entry(0b111100101, "value11100101"), - Map.entry(0b1100, "value100"), - Map.entry(0b1011, "value011"), - Map.entry(0b111101111, "value11101111"), - Map.entry(0b10001010, "value0001010"), - Map.entry(0b11010101, "value1010101") - ); - for (Map.Entry leaf : leaves.entrySet()) { - smt.addLeaf(BigInteger.valueOf(leaf.getKey()), - leaf.getValue().getBytes(StandardCharsets.UTF_8)); - } - - Assertions.assertThrows(BranchExistsException.class, () -> - smt.addLeaf(BigInteger.valueOf(0b10000000), "OnPath".getBytes(StandardCharsets.UTF_8)) - ); - - Assertions.assertThrows(LeafOutOfBoundsException.class, () -> - smt.addLeaf(BigInteger.valueOf(0b1000000000), - "ThroughLeaf".getBytes(StandardCharsets.UTF_8)) - ); - - Assertions.assertEquals(smt.calculateRoot(), this.root); - } - - @Test - public void shouldGetWorkingPath() throws Exception { - SparseMerkleTree smt = new SparseMerkleTree(HashAlgorithm.SHA256); - Map leaves = Map.ofEntries( - Map.entry(0b110010000, "value00010000"), - Map.entry(0b100000000, "value00000000"), - Map.entry(0b100010000, "value00010000"), - Map.entry(0b111100101, "value11100101"), - Map.entry(0b1100, "value100"), - Map.entry(0b1011, "value011"), - Map.entry(0b111101111, "value11101111"), - Map.entry(0b10001010, "value0001010"), - Map.entry(0b11010101, "value1010101") - ); - for (Map.Entry leaf : leaves.entrySet()) { - smt.addLeaf(BigInteger.valueOf(leaf.getKey()), - leaf.getValue().getBytes(StandardCharsets.UTF_8)); - } - SparseMerkleTreeRootNode root = smt.calculateRoot(); - - SparseMerkleTreePath path = root.getPath(BigInteger.valueOf(0b11010)); - MerkleTreePathVerificationResult result = path.verify(BigInteger.valueOf(0b11010)); - Assertions.assertFalse(result.isPathIncluded()); - Assertions.assertTrue(result.isPathValid()); - Assertions.assertFalse(result.isSuccessful()); - - path = root.getPath(BigInteger.valueOf(0b110010000)); - result = path.verify(BigInteger.valueOf(0b110010000)); - Assertions.assertTrue(result.isPathIncluded()); - Assertions.assertTrue(result.isPathValid()); - Assertions.assertTrue(result.isSuccessful()); - - path = root.getPath(BigInteger.valueOf(0b110010000)); - result = path.verify(BigInteger.valueOf(0b11010)); - Assertions.assertFalse(result.isPathIncluded()); - Assertions.assertTrue(result.isPathValid()); - Assertions.assertFalse(result.isSuccessful()); - - path = root.getPath(BigInteger.valueOf(0b111100101)); - result = path.verify(BigInteger.valueOf(0b111100101)); - Assertions.assertTrue(result.isPathIncluded()); - Assertions.assertTrue(result.isPathValid()); - Assertions.assertTrue(result.isSuccessful()); - - SparseMerkleTree emptyTree = new SparseMerkleTree(HashAlgorithm.SHA256); - SparseMerkleTreeRootNode emptyRoot = emptyTree.calculateRoot(); - path = emptyRoot.getPath(BigInteger.valueOf(0b100)); - result = path.verify(BigInteger.valueOf(0b10)); - Assertions.assertFalse(result.isPathIncluded()); - Assertions.assertTrue(result.isPathValid()); - Assertions.assertFalse(result.isSuccessful()); - } -} diff --git a/src/test/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializerTest.java b/src/test/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializerTest.java index 3de6126..95f6d72 100644 --- a/src/test/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializerTest.java +++ b/src/test/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializerTest.java @@ -1,8 +1,5 @@ package org.unicitylabs.sdk.serializer.cbor; -import java.util.Iterator; -import java.util.List; -import java.util.Set; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer.CborTag; @@ -10,74 +7,78 @@ import org.unicitylabs.sdk.serializer.cbor.CborSerializer.CborMap.Entry; import org.unicitylabs.sdk.util.HexConverter; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + public class CborDeserializerTest { @Test void testReadUnsignedInteger() { Assertions.assertEquals( - 5, - CborDeserializer.decodeUnsignedInteger(HexConverter.decode("05")).asLong() + 5, + CborDeserializer.decodeUnsignedInteger(HexConverter.decode("05")).asLong() ); Assertions.assertEquals( - 100, - CborDeserializer.decodeUnsignedInteger(HexConverter.decode("1864")).asLong() + 100, + CborDeserializer.decodeUnsignedInteger(HexConverter.decode("1864")).asLong() ); Assertions.assertEquals( - 10000, - CborDeserializer.decodeUnsignedInteger(HexConverter.decode("192710")).asLong() + 10000, + CborDeserializer.decodeUnsignedInteger(HexConverter.decode("192710")).asLong() ); Assertions.assertEquals( - 66000, - CborDeserializer.decodeUnsignedInteger(HexConverter.decode("1a000101d0")).asLong() + 66000, + CborDeserializer.decodeUnsignedInteger(HexConverter.decode("1a000101d0")).asLong() ); Assertions.assertEquals( - 8147483647L, - CborDeserializer.decodeUnsignedInteger(HexConverter.decode("1b00000001e5a0bbff")).asLong() + 8147483647L, + CborDeserializer.decodeUnsignedInteger(HexConverter.decode("1b00000001e5a0bbff")).asLong() ); Assertions.assertEquals( - -5, - CborDeserializer.decodeUnsignedInteger(HexConverter.decode("1bfffffffffffffffb")).asLong() + -5, + CborDeserializer.decodeUnsignedInteger(HexConverter.decode("1bfffffffffffffffb")).asLong() ); } @Test void testReadByteString() { Assertions.assertArrayEquals( - new byte[5], - CborDeserializer.decodeByteString(HexConverter.decode("450000000000")) + new byte[5], + CborDeserializer.decodeByteString(HexConverter.decode("450000000000")) ); Assertions.assertArrayEquals( - new byte[25], - CborDeserializer.decodeByteString( - HexConverter.decode("581900000000000000000000000000000000000000000000000000")) + new byte[25], + CborDeserializer.decodeByteString( + HexConverter.decode("581900000000000000000000000000000000000000000000000000")) ); } @Test void testReadTextString() { Assertions.assertEquals( - "Hello, world!", - CborDeserializer.decodeTextString(HexConverter.decode("6d48656c6c6f2c20776f726c6421")) + "Hello, world!", + CborDeserializer.decodeTextString(HexConverter.decode("6d48656c6c6f2c20776f726c6421")) ); Assertions.assertEquals( - new String(new byte[25]), - CborDeserializer.decodeTextString( - HexConverter.decode("781900000000000000000000000000000000000000000000000000")) + new String(new byte[25]), + CborDeserializer.decodeTextString( + HexConverter.decode("781900000000000000000000000000000000000000000000000000")) ); } @Test void testReadArray() { List data = CborDeserializer.decodeArray( - HexConverter.decode( - "98196d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c6421") + HexConverter.decode( + "98196d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c6421") ); for (byte[] item : data) { @@ -88,49 +89,49 @@ void testReadArray() { @Test void testReadMap() { Set data = CborDeserializer.decodeMap( - HexConverter.decode( - "a4430000006d48656c6c6f2c20776f726c6421430000016d48656c6c6f2c20776f726c64216454657374f66d48656c6c6f2c20776f726c6421581900000000000000000000000000000000000000000000000000") + HexConverter.decode( + "a4430000006d48656c6c6f2c20776f726c6421430000016d48656c6c6f2c20776f726c64216454657374f66d48656c6c6f2c20776f726c6421581900000000000000000000000000000000000000000000000000") ); Iterator iterator = data.iterator(); Entry entry = iterator.next(); Assertions.assertArrayEquals( - CborSerializer.encodeByteString(HexConverter.decode("000000")), - entry.getKey() + CborSerializer.encodeByteString(HexConverter.decode("000000")), + entry.getKey() ); Assertions.assertArrayEquals( - CborSerializer.encodeTextString("Hello, world!"), - entry.getValue() + CborSerializer.encodeTextString("Hello, world!"), + entry.getValue() ); entry = iterator.next(); Assertions.assertArrayEquals( - CborSerializer.encodeByteString(HexConverter.decode("000001")), - entry.getKey() + CborSerializer.encodeByteString(HexConverter.decode("000001")), + entry.getKey() ); Assertions.assertArrayEquals( - CborSerializer.encodeTextString("Hello, world!"), - entry.getValue() + CborSerializer.encodeTextString("Hello, world!"), + entry.getValue() ); entry = iterator.next(); Assertions.assertArrayEquals( - CborSerializer.encodeTextString("Test"), - entry.getKey() + CborSerializer.encodeTextString("Test"), + entry.getKey() ); Assertions.assertArrayEquals( - CborSerializer.encodeNull(), - entry.getValue() + CborSerializer.encodeNull(), + entry.getValue() ); entry = iterator.next(); Assertions.assertArrayEquals( - CborSerializer.encodeTextString("Hello, world!"), - entry.getKey() + CborSerializer.encodeTextString("Hello, world!"), + entry.getKey() ); Assertions.assertArrayEquals( - CborSerializer.encodeByteString(new byte[25]), - entry.getValue() + CborSerializer.encodeByteString(new byte[25]), + entry.getValue() ); } @@ -144,26 +145,26 @@ void testReadBoolean() { @Test void testReadOptional() { Assertions.assertNull( - CborDeserializer.decodeNullable( - HexConverter.decode("f6"), - CborDeserializer::decodeUnsignedInteger - ) + CborDeserializer.decodeNullable( + HexConverter.decode("f6"), + CborDeserializer::decodeUnsignedInteger + ) ); } @Test void testEncodeTag() { CborTag tag = CborDeserializer.decodeTag( - HexConverter.decode("d4781a746167206e756d62657220736d616c6c6572207468616e203234") + HexConverter.decode("d4781a746167206e756d62657220736d616c6c6572207468616e203234") ); Assertions.assertEquals( - 20, - tag.getTag() + 20, + tag.getTag() ); Assertions.assertArrayEquals( - CborSerializer.encodeTextString("tag number smaller than 24"), - tag.getData() + CborSerializer.encodeTextString("tag number smaller than 24"), + tag.getData() ); } } diff --git a/src/test/java/org/unicitylabs/sdk/serializer/cbor/CborSerializerTest.java b/src/test/java/org/unicitylabs/sdk/serializer/cbor/CborSerializerTest.java index 78a84cc..0658267 100644 --- a/src/test/java/org/unicitylabs/sdk/serializer/cbor/CborSerializerTest.java +++ b/src/test/java/org/unicitylabs/sdk/serializer/cbor/CborSerializerTest.java @@ -1,100 +1,101 @@ package org.unicitylabs.sdk.serializer.cbor; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.serializer.cbor.CborSerializer.CborMap; import org.unicitylabs.sdk.util.HexConverter; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + public class CborSerializerTest { @Test void testCborMap() { // Check that key cannot be null on entry Assertions.assertThrows(NullPointerException.class, - () -> new CborMap.Entry(null, new byte[5])); + () -> new CborMap.Entry(null, new byte[5])); // Check that value cannot be null on entry Assertions.assertThrows(NullPointerException.class, - () -> new CborMap.Entry(new byte[5], null)); + () -> new CborMap.Entry(new byte[5], null)); // Do not allow null entries Assertions.assertThrows(NullPointerException.class, () -> new CborMap(null)); // Check if duplicate keys are detected Assertions.assertThrows(IllegalArgumentException.class, () -> Set.of( - new CborMap.Entry(new byte[5], new byte[5]), - new CborMap.Entry(new byte[5], new byte[5]) + new CborMap.Entry(new byte[5], new byte[5]), + new CborMap.Entry(new byte[5], new byte[5]) )); } @Test void testEncodeUnsignedInteger() { Assertions.assertArrayEquals( - HexConverter.decode("05"), - CborSerializer.encodeUnsignedInteger(5) + HexConverter.decode("05"), + CborSerializer.encodeUnsignedInteger(5) ); Assertions.assertArrayEquals( - HexConverter.decode("1864"), - CborSerializer.encodeUnsignedInteger(100) + HexConverter.decode("1864"), + CborSerializer.encodeUnsignedInteger(100) ); Assertions.assertArrayEquals( - HexConverter.decode("192710"), - CborSerializer.encodeUnsignedInteger(10000) + HexConverter.decode("192710"), + CborSerializer.encodeUnsignedInteger(10000) ); Assertions.assertArrayEquals( - HexConverter.decode("1a000101d0"), - CborSerializer.encodeUnsignedInteger(66000) + HexConverter.decode("1a000101d0"), + CborSerializer.encodeUnsignedInteger(66000) ); Assertions.assertArrayEquals( - HexConverter.decode("1b00000001e5a0bbff"), - CborSerializer.encodeUnsignedInteger(8147483647L) + HexConverter.decode("1b00000001e5a0bbff"), + CborSerializer.encodeUnsignedInteger(8147483647L) ); Assertions.assertArrayEquals( - HexConverter.decode("1bfffffffffffffffb"), - CborSerializer.encodeUnsignedInteger(-5) + HexConverter.decode("1bfffffffffffffffb"), + CborSerializer.encodeUnsignedInteger(-5) ); } @Test void testEncodeByteString() { Assertions.assertArrayEquals( - HexConverter.decode("450000000000"), - CborSerializer.encodeByteString(new byte[5]) + HexConverter.decode("450000000000"), + CborSerializer.encodeByteString(new byte[5]) ); Assertions.assertArrayEquals( - HexConverter.decode("581900000000000000000000000000000000000000000000000000"), - CborSerializer.encodeByteString(new byte[25]) + HexConverter.decode("581900000000000000000000000000000000000000000000000000"), + CborSerializer.encodeByteString(new byte[25]) ); } @Test void testEncodeTextString() { Assertions.assertArrayEquals( - HexConverter.decode("6d48656c6c6f2c20776f726c6421"), - CborSerializer.encodeTextString("Hello, world!") + HexConverter.decode("6d48656c6c6f2c20776f726c6421"), + CborSerializer.encodeTextString("Hello, world!") ); Assertions.assertArrayEquals( - HexConverter.decode("781900000000000000000000000000000000000000000000000000"), - CborSerializer.encodeTextString(new String(new byte[25])) + HexConverter.decode("781900000000000000000000000000000000000000000000000000"), + CborSerializer.encodeTextString(new String(new byte[25])) ); } @Test void testEncodeArray() { Assertions.assertArrayEquals( - HexConverter.decode( - "826d48656c6c6f2c20776f726c6421581900000000000000000000000000000000000000000000000000"), - CborSerializer.encodeArray( - CborSerializer.encodeTextString("Hello, world!"), - CborSerializer.encodeByteString(new byte[25]) - ) + HexConverter.decode( + "826d48656c6c6f2c20776f726c6421581900000000000000000000000000000000000000000000000000"), + CborSerializer.encodeArray( + CborSerializer.encodeTextString("Hello, world!"), + CborSerializer.encodeByteString(new byte[25]) + ) ); List list = new ArrayList<>(); @@ -103,73 +104,73 @@ void testEncodeArray() { } Assertions.assertArrayEquals( - HexConverter.decode( - "98196d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c6421"), - CborSerializer.encodeArray(list.toArray(byte[][]::new)) + HexConverter.decode( + "98196d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c6421"), + CborSerializer.encodeArray(list.toArray(byte[][]::new)) ); } @Test void testEncodeMap() { Assertions.assertArrayEquals( - HexConverter.decode( - "a4430000006d48656c6c6f2c20776f726c6421430000016d48656c6c6f2c20776f726c64216454657374f66d48656c6c6f2c20776f726c6421581900000000000000000000000000000000000000000000000000"), - CborSerializer.encodeMap( - new CborMap( - Set.of( - new CborMap.Entry( - CborSerializer.encodeByteString(HexConverter.decode("000001")), - CborSerializer.encodeTextString("Hello, world!") - ), - new CborMap.Entry( - CborSerializer.encodeByteString(HexConverter.decode("000000")), - CborSerializer.encodeTextString("Hello, world!") - ), - new CborMap.Entry( - CborSerializer.encodeTextString("Hello, world!"), - CborSerializer.encodeByteString(new byte[25]) - ), - new CborMap.Entry( - CborSerializer.encodeTextString("Test"), - CborSerializer.encodeNull() + HexConverter.decode( + "a4430000006d48656c6c6f2c20776f726c6421430000016d48656c6c6f2c20776f726c64216454657374f66d48656c6c6f2c20776f726c6421581900000000000000000000000000000000000000000000000000"), + CborSerializer.encodeMap( + new CborMap( + Set.of( + new CborMap.Entry( + CborSerializer.encodeByteString(HexConverter.decode("000001")), + CborSerializer.encodeTextString("Hello, world!") + ), + new CborMap.Entry( + CborSerializer.encodeByteString(HexConverter.decode("000000")), + CborSerializer.encodeTextString("Hello, world!") + ), + new CborMap.Entry( + CborSerializer.encodeTextString("Hello, world!"), + CborSerializer.encodeByteString(new byte[25]) + ), + new CborMap.Entry( + CborSerializer.encodeTextString("Test"), + CborSerializer.encodeNull() + ) + ) ) - ) ) - ) ); } @Test void testEncodeBoolean() { Assertions.assertArrayEquals( - HexConverter.decode("f5"), - CborSerializer.encodeBoolean(true) + HexConverter.decode("f5"), + CborSerializer.encodeBoolean(true) ); Assertions.assertArrayEquals( - HexConverter.decode("f4"), - CborSerializer.encodeBoolean(false) + HexConverter.decode("f4"), + CborSerializer.encodeBoolean(false) ); } @Test void testEncodeNull() { Assertions.assertArrayEquals( - HexConverter.decode("f6"), - CborSerializer.encodeNull() + HexConverter.decode("f6"), + CborSerializer.encodeNull() ); } @Test void testEncodeTag() { Assertions.assertArrayEquals( - HexConverter.decode("d4781a746167206e756d62657220736d616c6c6572207468616e203234"), - CborSerializer.encodeTag(20, CborSerializer.encodeTextString("tag number smaller than 24")) + HexConverter.decode("d4781a746167206e756d62657220736d616c6c6572207468616e203234"), + CborSerializer.encodeTag(20, CborSerializer.encodeTextString("tag number smaller than 24")) ); Assertions.assertArrayEquals( - HexConverter.decode("d874706c6172676520746167206e756d626572"), - CborSerializer.encodeTag(116, CborSerializer.encodeTextString("large tag number")) + HexConverter.decode("d874706c6172676520746167206e756d626572"), + CborSerializer.encodeTag(116, CborSerializer.encodeTextString("large tag number")) ); } diff --git a/src/test/java/org/unicitylabs/sdk/mtree/CommonPathTest.java b/src/test/java/org/unicitylabs/sdk/smt/CommonPathTest.java similarity index 63% rename from src/test/java/org/unicitylabs/sdk/mtree/CommonPathTest.java rename to src/test/java/org/unicitylabs/sdk/smt/CommonPathTest.java index 6535860..650352a 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; @@ -10,16 +10,16 @@ public class CommonPathTest { @Test public void shouldCalculateCommonPath() { Assertions.assertEquals(CommonPath.create( - BigInteger.valueOf(0b11), - BigInteger.valueOf(0b111101111) + BigInteger.valueOf(0b11), + BigInteger.valueOf(0b111101111) ), new CommonPath(BigInteger.valueOf(0b11), 1)); Assertions.assertEquals(CommonPath.create( - BigInteger.valueOf(0b111101111), - BigInteger.valueOf(0b11) + BigInteger.valueOf(0b111101111), + BigInteger.valueOf(0b11) ), new CommonPath(BigInteger.valueOf(0b11), 1)); Assertions.assertEquals(CommonPath.create( - BigInteger.valueOf(0b110010000), - BigInteger.valueOf(0b100010000) + BigInteger.valueOf(0b110010000), + BigInteger.valueOf(0b100010000) ), new CommonPath(BigInteger.valueOf(0b10010000), 7)); } } diff --git a/src/test/java/org/unicitylabs/sdk/smt/plain/MerkleTreePathTest.java b/src/test/java/org/unicitylabs/sdk/smt/plain/MerkleTreePathTest.java new file mode 100644 index 0000000..76ed7c8 --- /dev/null +++ b/src/test/java/org/unicitylabs/sdk/smt/plain/MerkleTreePathTest.java @@ -0,0 +1,90 @@ +package org.unicitylabs.sdk.smt.plain; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.unicitylabs.sdk.crypto.hash.DataHash; +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; +import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import org.unicitylabs.sdk.smt.MerkleTreePathVerificationResult; +import org.unicitylabs.sdk.util.HexConverter; + +import java.math.BigInteger; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class MerkleTreePathTest { + + @Test + public void testConstructorThrowsOnNullArguments() { + Exception exception = assertThrows(NullPointerException.class, + () -> new SparseMerkleTreePath(null, null) + ); + assertEquals("rootHash cannot be null", exception.getMessage()); + exception = assertThrows(NullPointerException.class, + () -> new SparseMerkleTreePath(new DataHash(HashAlgorithm.SHA256, new byte[32]), null) + ); + assertEquals("steps cannot be null", exception.getMessage()); + } + + @Test + public void testShouldVerifyInclusionProof() { + SparseMerkleTreePath path = new SparseMerkleTreePath( + DataHash.fromImprint( + HexConverter.decode( + "0000e9748bbd0c45fc357ffe7c221c7db1ef02f589680d8b0a370b48a669435bde13" + ) + ), + List.of( + new SparseMerkleTreePathStep( + BigInteger.valueOf(69), + HexConverter.decode("76616c756535") + ), + new SparseMerkleTreePathStep( + BigInteger.valueOf(4), + HexConverter.decode( + "8471f8ea3c9a0e50627df4c72d9bd5affbdc12050ee7f4250974ed64949f3b0f" + ) + ), + new SparseMerkleTreePathStep( + BigInteger.valueOf(1), + HexConverter.decode( + "66507538ce0fae31018cfc7b01841b5308e7e44306445710acee947ec4a4b2cd" + ) + ) + ) + ); + + Assertions.assertEquals(new MerkleTreePathVerificationResult(true, true), + path.verify(BigInteger.valueOf(0b100010100))); + Assertions.assertEquals(new MerkleTreePathVerificationResult(true, false), + path.verify(BigInteger.valueOf(0b111))); + } + + @Test + public void testEmptyPathVerification() throws JsonProcessingException { + byte[] cbor = CborSerializer.encodeArray( + DataHash.fromImprint( + HexConverter.decode("00001e54402898172f2948615fb17627733abbd120a85381c624ad060d28321be672") + ).toCbor(), + CborSerializer.encodeArray( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(HexConverter.decode("01")), + CborSerializer.encodeNull() + ), + CborSerializer.encodeArray( + CborSerializer.encodeByteString(HexConverter.decode("01")), + CborSerializer.encodeNull() + ) + ) + ); + + SparseMerkleTreePath path = SparseMerkleTreePath.fromCbor(cbor); + + MerkleTreePathVerificationResult result = path.verify(BigInteger.valueOf(101)); + Assertions.assertTrue(result.isPathValid()); + Assertions.assertFalse(result.isPathIncluded()); + } +} 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 54% 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..9d3e9f6 100644 --- a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreePathFixture.java +++ b/src/test/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePathFixture.java @@ -1,18 +1,19 @@ -package org.unicitylabs.sdk.mtree.plain; +package org.unicitylabs.sdk.smt.plain; -import java.util.List; import org.unicitylabs.sdk.crypto.hash.DataHasher; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; +import java.util.List; + public class SparseMerkleTreePathFixture { public static SparseMerkleTreePath create() { return new SparseMerkleTreePath( - new DataHasher(HashAlgorithm.SHA256) - .update(new byte[]{0}) - .update(new byte[]{0}) - .digest(), - List.of() + new DataHasher(HashAlgorithm.SHA256) + .update(new byte[]{0}) + .update(new byte[]{0}) + .digest(), + List.of() ); } diff --git a/src/test/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreeTest.java b/src/test/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreeTest.java new file mode 100644 index 0000000..3f02344 --- /dev/null +++ b/src/test/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreeTest.java @@ -0,0 +1,183 @@ +package org.unicitylabs.sdk.smt.plain; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; +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( + new PendingNodeBranch( + BigInteger.valueOf(0b10), + new PendingNodeBranch( + BigInteger.valueOf(0b10), + new PendingNodeBranch( + BigInteger.valueOf(0b100), + new PendingLeafBranch( + BigInteger.valueOf(0b10000), + HexConverter.decode("76616c75653030303030303030") + ), + new PendingNodeBranch( + BigInteger.valueOf(0b1001), + new PendingLeafBranch( + BigInteger.valueOf(0b10), + HexConverter.decode("76616c75653030303130303030") + ), + new PendingLeafBranch( + BigInteger.valueOf(0b11), + HexConverter.decode("76616c75653030303130303030") + ) + ) + ), + new PendingLeafBranch( + BigInteger.valueOf(0b11), + HexConverter.decode("76616c7565313030") + ) + ), + new PendingLeafBranch( + BigInteger.valueOf(0b1000101), + HexConverter.decode("76616c756530303031303130") + ) + ).finalize(HashAlgorithm.SHA256), + new PendingNodeBranch( + BigInteger.valueOf(0b11), + new PendingNodeBranch( + BigInteger.valueOf(0b1010), + new PendingLeafBranch( + BigInteger.valueOf(0b11110), + HexConverter.decode("76616c75653131313030313031") + ), + new PendingLeafBranch( + BigInteger.valueOf(0b1101), + HexConverter.decode("76616c756531303130313031") + ) + ), + new PendingNodeBranch( + BigInteger.valueOf(0b11), + new PendingLeafBranch( + BigInteger.valueOf(0b10), + HexConverter.decode("76616c7565303131") + ), + new PendingLeafBranch( + BigInteger.valueOf(0b1111011), + HexConverter.decode("76616c75653131313031313131") + ) + ) + ).finalize(HashAlgorithm.SHA256), + HashAlgorithm.SHA256 + ); + + @Test + public void treeShouldBeHalfCalculated() throws Exception { + SparseMerkleTree smt = new SparseMerkleTree(HashAlgorithm.SHA256); + + smt.addLeaf(BigInteger.valueOf(0b10), new byte[]{1, 2, 3}); + smt.calculateRoot(); + smt.addLeaf(BigInteger.valueOf(0b11), new byte[]{1, 2, 3, 4}); + + FinalizedLeafBranch left = new PendingLeafBranch(BigInteger.valueOf(2), + new byte[]{1, 2, 3}).finalize(HashAlgorithm.SHA256); + PendingLeafBranch right = new PendingLeafBranch(BigInteger.valueOf(3), new byte[]{1, 2, 3, 4}); + + Field leftField = SparseMerkleTree.class.getDeclaredField("left"); + leftField.setAccessible(true); + Field rightField = SparseMerkleTree.class.getDeclaredField("right"); + rightField.setAccessible(true); + + Assertions.assertEquals(left, leftField.get(smt)); + Assertions.assertEquals(right, rightField.get(smt)); + } + + @Test + public void shouldVerifyTheTree() throws Exception { + SparseMerkleTree smt = new SparseMerkleTree(HashAlgorithm.SHA256); + Map leaves = Map.ofEntries( + Map.entry(0b110010000, "value00010000"), + Map.entry(0b100000000, "value00000000"), + Map.entry(0b100010000, "value00010000"), + Map.entry(0b111100101, "value11100101"), + Map.entry(0b1100, "value100"), + Map.entry(0b1011, "value011"), + Map.entry(0b111101111, "value11101111"), + Map.entry(0b10001010, "value0001010"), + Map.entry(0b11010101, "value1010101") + ); + for (Map.Entry leaf : leaves.entrySet()) { + smt.addLeaf(BigInteger.valueOf(leaf.getKey()), + leaf.getValue().getBytes(StandardCharsets.UTF_8)); + } + + Assertions.assertThrows(BranchExistsException.class, () -> + smt.addLeaf(BigInteger.valueOf(0b10000000), "OnPath".getBytes(StandardCharsets.UTF_8)) + ); + + Assertions.assertThrows(LeafOutOfBoundsException.class, () -> + smt.addLeaf(BigInteger.valueOf(0b1000000000), + "ThroughLeaf".getBytes(StandardCharsets.UTF_8)) + ); + + Assertions.assertEquals(smt.calculateRoot(), this.root); + } + + @Test + public void shouldGetWorkingPath() throws Exception { + SparseMerkleTree smt = new SparseMerkleTree(HashAlgorithm.SHA256); + Map leaves = Map.ofEntries( + Map.entry(0b110010000, "value00010000"), + Map.entry(0b100000000, "value00000000"), + Map.entry(0b100010000, "value00010000"), + Map.entry(0b111100101, "value11100101"), + Map.entry(0b1100, "value100"), + Map.entry(0b1011, "value011"), + Map.entry(0b111101111, "value11101111"), + Map.entry(0b10001010, "value0001010"), + Map.entry(0b11010101, "value1010101") + ); + for (Map.Entry leaf : leaves.entrySet()) { + smt.addLeaf(BigInteger.valueOf(leaf.getKey()), + leaf.getValue().getBytes(StandardCharsets.UTF_8)); + } + SparseMerkleTreeRootNode root = smt.calculateRoot(); + + SparseMerkleTreePath path = root.getPath(BigInteger.valueOf(0b11010)); + MerkleTreePathVerificationResult result = path.verify(BigInteger.valueOf(0b11010)); + Assertions.assertFalse(result.isPathIncluded()); + Assertions.assertTrue(result.isPathValid()); + Assertions.assertFalse(result.isSuccessful()); + + path = root.getPath(BigInteger.valueOf(0b110010000)); + result = path.verify(BigInteger.valueOf(0b110010000)); + Assertions.assertTrue(result.isPathIncluded()); + Assertions.assertTrue(result.isPathValid()); + Assertions.assertTrue(result.isSuccessful()); + + path = root.getPath(BigInteger.valueOf(0b110010000)); + result = path.verify(BigInteger.valueOf(0b11010)); + Assertions.assertFalse(result.isPathIncluded()); + Assertions.assertTrue(result.isPathValid()); + Assertions.assertFalse(result.isSuccessful()); + + path = root.getPath(BigInteger.valueOf(0b111100101)); + result = path.verify(BigInteger.valueOf(0b111100101)); + Assertions.assertTrue(result.isPathIncluded()); + Assertions.assertTrue(result.isPathValid()); + Assertions.assertTrue(result.isSuccessful()); + + SparseMerkleTree emptyTree = new SparseMerkleTree(HashAlgorithm.SHA256); + SparseMerkleTreeRootNode emptyRoot = emptyTree.calculateRoot(); + path = emptyRoot.getPath(BigInteger.valueOf(0b100)); + result = path.verify(BigInteger.valueOf(0b10)); + Assertions.assertFalse(result.isPathIncluded()); + Assertions.assertTrue(result.isPathValid()); + Assertions.assertFalse(result.isSuccessful()); + } +} 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 79% 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..2dfc635 100644 --- a/src/test/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreeTest.java +++ b/src/test/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreeTest.java @@ -1,15 +1,16 @@ -package org.unicitylabs.sdk.mtree.sum; +package org.unicitylabs.sdk.smt.sum; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; 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 org.unicitylabs.sdk.util.HexConverter; + import java.math.BigInteger; import java.util.Map; import java.util.Map.Entry; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.unicitylabs.sdk.util.HexConverter; class SparseMerkleSumTreeTest { @@ -40,10 +41,10 @@ void shouldAgreeWithSpecExamples() throws Exception { @Test void shouldBuildTreeWithNumericValues() throws Exception { Map leaves = Map.of( - new BigInteger("1000", 2), new LeafValue("left-1".getBytes(), BigInteger.valueOf(10)), - new BigInteger("1001", 2), new LeafValue("right-1".getBytes(), BigInteger.valueOf(20)), - new BigInteger("1010", 2), new LeafValue("left-2".getBytes(), BigInteger.valueOf(30)), - new BigInteger("1011", 2), new LeafValue("right-2".getBytes(), BigInteger.valueOf(40)) + new BigInteger("1000", 2), new LeafValue("left-1".getBytes(), BigInteger.valueOf(10)), + new BigInteger("1001", 2), new LeafValue("right-1".getBytes(), BigInteger.valueOf(20)), + new BigInteger("1010", 2), new LeafValue("left-2".getBytes(), BigInteger.valueOf(30)), + new BigInteger("1011", 2), new LeafValue("right-2".getBytes(), BigInteger.valueOf(40)) ); SparseMerkleSumTree tree = new SparseMerkleSumTree(HashAlgorithm.SHA256); @@ -63,12 +64,12 @@ void shouldBuildTreeWithNumericValues() throws Exception { Assertions.assertEquals(root.getRootHash(), path.getRootHash()); Assertions.assertArrayEquals( - entry.getValue().getValue(), - path.getSteps().get(0).getData().orElse(null) + entry.getValue().getValue(), + path.getSteps().get(0).getData().orElse(null) ); Assertions.assertEquals( - entry.getValue().getCounter(), - path.getSteps().get(0).getValue() + entry.getValue().getCounter(), + path.getSteps().get(0).getValue() ); } @@ -81,9 +82,9 @@ void shouldBuildTreeWithNumericValues() throws Exception { void shouldThrowErrorOnNonPositivePathOrSum() { SparseMerkleSumTree tree = new SparseMerkleSumTree(HashAlgorithm.SHA256); Assertions.assertThrows(IllegalArgumentException.class, - () -> tree.addLeaf(BigInteger.valueOf(-1), - new LeafValue(new byte[32], BigInteger.valueOf(100)))); + () -> tree.addLeaf(BigInteger.valueOf(-1), + new LeafValue(new byte[32], BigInteger.valueOf(100)))); Assertions.assertThrows(IllegalArgumentException.class, - () -> tree.addLeaf(BigInteger.ONE, new LeafValue(new byte[32], BigInteger.valueOf(-1)))); + () -> tree.addLeaf(BigInteger.ONE, new LeafValue(new byte[32], BigInteger.valueOf(-1)))); } } diff --git a/src/test/java/org/unicitylabs/sdk/token/TokenIdTest.java b/src/test/java/org/unicitylabs/sdk/token/TokenIdTest.java index 8436b3a..9f3172d 100644 --- a/src/test/java/org/unicitylabs/sdk/token/TokenIdTest.java +++ b/src/test/java/org/unicitylabs/sdk/token/TokenIdTest.java @@ -1,19 +1,20 @@ package org.unicitylabs.sdk.token; -import java.math.BigInteger; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.transaction.TokenId; +import java.math.BigInteger; + class TokenIdTest { - @Test - void toBigInt() { - TokenId tokenId = new TokenId(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}); - Assertions.assertEquals( + @Test + void toBigInt() { + TokenId tokenId = new TokenId(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}); + Assertions.assertEquals( new BigInteger("116247956593636886635080929986192315456660021052790183176621769190627866451744"), tokenId.toBitString().toBigInteger() - ); - } + ); + } } diff --git a/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java b/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java index 559e7cc..c28c31d 100644 --- a/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java +++ b/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java @@ -1,6 +1,5 @@ package org.unicitylabs.sdk.utils; -import java.security.SecureRandom; import org.junit.jupiter.api.Assertions; import org.unicitylabs.sdk.StateTransitionClient; import org.unicitylabs.sdk.api.CertificationData; @@ -13,15 +12,12 @@ 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.transaction.Address; -import org.unicitylabs.sdk.transaction.MintTransaction; -import org.unicitylabs.sdk.transaction.Token; -import org.unicitylabs.sdk.transaction.TokenId; -import org.unicitylabs.sdk.transaction.TokenType; -import org.unicitylabs.sdk.transaction.TransferTransaction; +import org.unicitylabs.sdk.transaction.*; import org.unicitylabs.sdk.util.InclusionProofUtils; import org.unicitylabs.sdk.util.verification.VerificationStatus; +import java.security.SecureRandom; + /** * Test helpers for minting and transferring certified tokens. */ @@ -40,17 +36,17 @@ public class TokenUtils { * @throws Exception when request or verification fails */ public static Token mintToken( - StateTransitionClient client, - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - Address recipient + StateTransitionClient client, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + Address recipient ) throws Exception { return TokenUtils.mintToken( - client, - trustBase, - predicateVerifier, - recipient, - CborSerializer.encodeArray() + client, + trustBase, + predicateVerifier, + recipient, + CborSerializer.encodeArray() ); } @@ -68,19 +64,19 @@ public static Token mintToken( * @throws Exception when request or verification fails */ public static Token mintToken( - StateTransitionClient client, - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - Address recipient, - byte[] data - ) throws Exception { + StateTransitionClient client, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + Address recipient, + byte[] data + ) throws Exception { return TokenUtils.mintToken( - client, - trustBase, - predicateVerifier, - TokenId.generate(), - recipient, - data + client, + trustBase, + predicateVerifier, + TokenId.generate(), + recipient, + data ); } @@ -99,21 +95,21 @@ public static Token mintToken( * @throws Exception when request or verification fails */ public static Token mintToken( - StateTransitionClient client, - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - TokenId tokenId, - Address recipient, - byte[] data + StateTransitionClient client, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + TokenId tokenId, + Address recipient, + byte[] data ) throws Exception { return TokenUtils.mintToken( - client, - trustBase, - predicateVerifier, - tokenId, - TokenType.generate(), - recipient, - data + client, + trustBase, + predicateVerifier, + tokenId, + TokenType.generate(), + recipient, + data ); } @@ -133,19 +129,19 @@ public static Token mintToken( * @throws Exception when request or verification fails */ public static Token mintToken( - StateTransitionClient client, - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - TokenId tokenId, - TokenType tokenType, - Address recipient, - byte[] data + StateTransitionClient client, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + TokenId tokenId, + TokenType tokenType, + Address recipient, + byte[] data ) throws Exception { MintTransaction transaction = MintTransaction.create( - recipient, - tokenId, - tokenType, - data + recipient, + tokenId, + tokenType, + data ); CertificationData certificationData = CertificationData.fromMintTransaction(transaction); @@ -153,17 +149,17 @@ public static Token mintToken( CertificationResponse response = client.submitCertificationRequest(certificationData).get(); if (response.getStatus() != CertificationStatus.SUCCESS) { throw new RuntimeException( - String.format("Certification Request failed with status '%s'", response.getStatus())); + String.format("Certification Request failed with status '%s'", response.getStatus())); } return Token.mint( - trustBase, - predicateVerifier, - transaction.toCertifiedTransaction( trustBase, predicateVerifier, - InclusionProofUtils.waitInclusionProof(client, trustBase, predicateVerifier, transaction).get() - ) + transaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + InclusionProofUtils.waitInclusionProof(client, trustBase, predicateVerifier, transaction).get() + ) ); } @@ -183,12 +179,12 @@ public static Token mintToken( * @throws Exception when request or verification fails */ public static Token transferToken( - StateTransitionClient client, - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - byte[] tokenBytes, - Address recipient, - SigningService signingService + StateTransitionClient client, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + byte[] tokenBytes, + Address recipient, + SigningService signingService ) throws Exception { Token token = Token.fromCbor(tokenBytes); Assertions.assertEquals(VerificationStatus.OK, token.verify(trustBase, predicateVerifier).getStatus()); @@ -197,20 +193,20 @@ public static Token transferToken( new SecureRandom().nextBytes(x); TransferTransaction transaction = TransferTransaction.create( - token, - PayToPublicKeyPredicate.create(signingService.getPublicKey()), - recipient, - x, - CborSerializer.encodeArray() + token, + PayToPublicKeyPredicate.create(signingService.getPublicKey()), + recipient, + x, + CborSerializer.encodeArray() ); return TokenUtils.transferToken( - client, - trustBase, - predicateVerifier, - token, - transaction, - PayToPublicKeyPredicateUnlockScript.create(transaction, signingService) + client, + trustBase, + predicateVerifier, + token, + transaction, + PayToPublicKeyPredicateUnlockScript.create(transaction, signingService) ); } @@ -229,35 +225,35 @@ public static Token transferToken( * @throws Exception when request or verification fails */ public static Token transferToken( - StateTransitionClient client, - RootTrustBase trustBase, - PredicateVerifierService predicateVerifier, - Token token, - TransferTransaction transaction, - UnlockScript unlockScript + StateTransitionClient client, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + Token token, + TransferTransaction transaction, + UnlockScript unlockScript ) throws Exception { CertificationResponse response = client.submitCertificationRequest( - CertificationData.fromTransaction(transaction, unlockScript) + CertificationData.fromTransaction(transaction, unlockScript) ).get(); if (response.getStatus() != CertificationStatus.SUCCESS) { throw new RuntimeException( - String.format("Certification Request failed with status '%s'", response.getStatus())); + String.format("Certification Request failed with status '%s'", response.getStatus())); } return token.transfer( - trustBase, - predicateVerifier, - transaction.toCertifiedTransaction( trustBase, predicateVerifier, - InclusionProofUtils.waitInclusionProof( - client, - trustBase, - predicateVerifier, - transaction - ).get() - ) + transaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + InclusionProofUtils.waitInclusionProof( + client, + trustBase, + predicateVerifier, + transaction + ).get() + ) ); } diff --git a/src/test/resources/docker/aggregator/docker-compose.yml b/src/test/resources/docker/aggregator/docker-compose.yml index c071b84..8572d79 100644 --- a/src/test/resources/docker/aggregator/docker-compose.yml +++ b/src/test/resources/docker/aggregator/docker-compose.yml @@ -51,9 +51,9 @@ services: entrypoint: [ "mongosh", "--host", "mongo1:27017", "--file", "/mongo-init.js" ] # mongo default port 27017 aggregator-test: -# build: -# context: ${AGGREGATOR_HOME:-../../../../../aggregators_net} # path to aggregator dockerfile directory -# dockerfile: Dockerfile + # build: + # context: ${AGGREGATOR_HOME:-../../../../../aggregators_net} # path to aggregator dockerfile directory + # dockerfile: Dockerfile image: ghcr.io/unicitynetwork/aggregators_net:bbabb5f093e829fa789ed6e83f57af98df3f1752 container_name: aggregator-test # must be specified for Testcontainers docker compose API depends_on: diff --git a/src/test/resources/docker/aggregator/mongo-init.js b/src/test/resources/docker/aggregator/mongo-init.js index 8202b68..79a96b1 100644 --- a/src/test/resources/docker/aggregator/mongo-init.js +++ b/src/test/resources/docker/aggregator/mongo-init.js @@ -1,9 +1,9 @@ /* eslint-disable */ rs.initiate({ - _id: "rs0", - members: [ - { _id: 0, host: "mongo1:27017" }, - { _id: 1, host: "mongo2:27017" }, - { _id: 2, host: "mongo3:27017" } - ] + _id: "rs0", + members: [ + {_id: 0, host: "mongo1:27017"}, + {_id: 1, host: "mongo2:27017"}, + {_id: 2, host: "mongo3:27017"} + ] }); \ No newline at end of file