diff --git a/src/main/java/org/unicitylabs/sdk/api/CertificationData.java b/src/main/java/org/unicitylabs/sdk/api/CertificationData.java index 7ec2771..7f298bd 100644 --- a/src/main/java/org/unicitylabs/sdk/api/CertificationData.java +++ b/src/main/java/org/unicitylabs/sdk/api/CertificationData.java @@ -94,7 +94,7 @@ public static CertificationData fromCbor(byte[] bytes) { if (tag.getTag() != CertificationData.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData()); + List data = CborDeserializer.decodeArray(tag.getData(), 5); int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); if (version != CertificationData.VERSION) { @@ -137,7 +137,6 @@ public static CertificationData fromMintTransaction(MintTransaction transaction) * @return certification data */ public static CertificationData fromTransaction(Transaction transaction, UnlockScript unlockScript) { - Objects.requireNonNull(transaction, "transaction cannot be null"); Objects.requireNonNull(unlockScript, "unlockScript cannot be null"); return CertificationData.fromTransaction(transaction, unlockScript.encode()); diff --git a/src/main/java/org/unicitylabs/sdk/api/InclusionProof.java b/src/main/java/org/unicitylabs/sdk/api/InclusionProof.java index bab61ac..8944587 100644 --- a/src/main/java/org/unicitylabs/sdk/api/InclusionProof.java +++ b/src/main/java/org/unicitylabs/sdk/api/InclusionProof.java @@ -74,7 +74,7 @@ public static InclusionProof fromCbor(byte[] bytes) { if (tag.getTag() != InclusionProof.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData()); + List data = CborDeserializer.decodeArray(tag.getData(), 4); int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); if (version != InclusionProof.VERSION) { @@ -120,7 +120,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(InclusionProof.VERSION, this.inclusionCertificate, this.certificationData, this.unicityCertificate); + return Objects.hash(this.inclusionCertificate, this.certificationData, this.unicityCertificate); } @Override diff --git a/src/main/java/org/unicitylabs/sdk/api/InclusionProofResponse.java b/src/main/java/org/unicitylabs/sdk/api/InclusionProofResponse.java index 6f0a775..d53e22e 100644 --- a/src/main/java/org/unicitylabs/sdk/api/InclusionProofResponse.java +++ b/src/main/java/org/unicitylabs/sdk/api/InclusionProofResponse.java @@ -42,7 +42,7 @@ public InclusionProof getInclusionProof() { * @return inclusion proof response */ public static InclusionProofResponse fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); + List data = CborDeserializer.decodeArray(bytes, 2); return new InclusionProofResponse( CborDeserializer.decodeUnsignedInteger(data.get(0)).asLong(), InclusionProof.fromCbor(data.get(1)) diff --git a/src/main/java/org/unicitylabs/sdk/api/JsonRpcAggregatorClient.java b/src/main/java/org/unicitylabs/sdk/api/JsonRpcAggregatorClient.java index 1c47e0d..5f9f0db 100644 --- a/src/main/java/org/unicitylabs/sdk/api/JsonRpcAggregatorClient.java +++ b/src/main/java/org/unicitylabs/sdk/api/JsonRpcAggregatorClient.java @@ -3,6 +3,7 @@ import org.unicitylabs.sdk.api.jsonrpc.JsonRpcHttpTransport; import org.unicitylabs.sdk.util.HexConverter; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -14,6 +15,7 @@ * Default aggregator client. */ public class JsonRpcAggregatorClient implements AggregatorClient { + private static final String STATE_ID_HEADER = "X-State-ID"; private final JsonRpcHttpTransport transport; private final String apiKey; @@ -54,9 +56,11 @@ public CompletableFuture submitCertificationRequest( CertificationRequest request = CertificationRequest.create( 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> headers = new HashMap<>(); + headers.put(STATE_ID_HEADER, List.of(request.getStateId().toString())); + if (this.apiKey != null) { + headers.put(AUTHORIZATION, List.of(String.format("Bearer %s", this.apiKey))); + } return this.transport.request( "certification_request", 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 af1e455..d8a1a1e 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/InputRecord.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/InputRecord.java @@ -150,7 +150,7 @@ public static InputRecord fromCbor(byte[] bytes) { if (tag.getTag() != InputRecord.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData()); + List data = CborDeserializer.decodeArray(tag.getData(), 10); int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); if (version != InputRecord.VERSION) { @@ -212,7 +212,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(InputRecord.VERSION, this.roundNumber, this.epoch, + return Objects.hash(this.roundNumber, this.epoch, Arrays.hashCode(this.previousHash), Arrays.hashCode(this.hash), Arrays.hashCode(this.summaryValue), this.timestamp, Arrays.hashCode(this.blockHash), 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 babcc26..94227b3 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/ShardTreeCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/ShardTreeCertificate.java @@ -64,7 +64,7 @@ public static ShardTreeCertificate fromCbor(byte[] bytes) { if (tag.getTag() != ShardTreeCertificate.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData()); + List data = CborDeserializer.decodeArray(tag.getData(), 3); int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); if (version != ShardTreeCertificate.VERSION) { @@ -111,7 +111,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(ShardTreeCertificate.VERSION, this.shard, this.siblingHashList); + return Objects.hash(this.shard, this.siblingHashList); } @Override 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 09286d3..4900501 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/UnicityCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/UnicityCertificate.java @@ -173,7 +173,7 @@ public static UnicityCertificate fromCbor(byte[] bytes) { if (tag.getTag() != UnicityCertificate.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData()); + List data = CborDeserializer.decodeArray(tag.getData(), 7); int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); if (version != UnicityCertificate.VERSION) { @@ -226,7 +226,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(UnicityCertificate.VERSION, this.inputRecord, Arrays.hashCode(this.technicalRecordHash), + return Objects.hash(this.inputRecord, Arrays.hashCode(this.technicalRecordHash), Arrays.hashCode(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 c7ec830..3759ffe 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/UnicitySeal.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/UnicitySeal.java @@ -144,7 +144,7 @@ public static UnicitySeal fromCbor(byte[] bytes) { if (tag.getTag() != UnicitySeal.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData()); + List data = CborDeserializer.decodeArray(tag.getData(), 8); int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); if (version != UnicitySeal.VERSION) { @@ -235,7 +235,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(UnicitySeal.VERSION, this.networkId, this.rootChainRoundNumber, this.epoch, + return Objects.hash(this.networkId, this.rootChainRoundNumber, this.epoch, this.timestamp, Arrays.hashCode(this.previousHash), Arrays.hashCode(this.hash), this.signatures); } 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 a248f54..0f979a6 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/UnicityTreeCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/UnicityTreeCertificate.java @@ -68,7 +68,7 @@ public static UnicityTreeCertificate fromCbor(byte[] bytes) { if (tag.getTag() != UnicityTreeCertificate.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData()); + List data = CborDeserializer.decodeArray(tag.getData(), 3); int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); if (version != UnicityTreeCertificate.VERSION) { @@ -112,7 +112,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(UnicityTreeCertificate.VERSION, this.partitionIdentifier, this.steps); + return Objects.hash(this.partitionIdentifier, this.steps); } @Override @@ -160,7 +160,7 @@ public byte[] getHash() { * @return hash step */ public static HashStep fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); + List data = CborDeserializer.decodeArray(bytes, 2); return new HashStep( CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(), diff --git a/src/main/java/org/unicitylabs/sdk/payment/SplitAssetProof.java b/src/main/java/org/unicitylabs/sdk/payment/SplitAssetProof.java index d891d25..29654c2 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/SplitAssetProof.java +++ b/src/main/java/org/unicitylabs/sdk/payment/SplitAssetProof.java @@ -79,7 +79,7 @@ public static SplitAssetProof create( * @return split reason proof */ public static SplitAssetProof fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); + List data = CborDeserializer.decodeArray(bytes, 3); return new SplitAssetProof( AssetId.fromCbor(data.get(0)), diff --git a/src/main/java/org/unicitylabs/sdk/payment/SplitMintJustification.java b/src/main/java/org/unicitylabs/sdk/payment/SplitMintJustification.java index 7f64f21..6f68714 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/SplitMintJustification.java +++ b/src/main/java/org/unicitylabs/sdk/payment/SplitMintJustification.java @@ -77,7 +77,7 @@ public static SplitMintJustification fromCbor(byte[] bytes) { if (tag.getTag() != SplitMintJustification.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData()); + List data = CborDeserializer.decodeArray(tag.getData(), 2); return SplitMintJustification.create( Token.fromCbor(data.get(0)), CborDeserializer.decodeArray(data.get(1)).stream().map(SplitAssetProof::fromCbor).collect(Collectors.toSet()) 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 e9744cb..ee253dd 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/asset/Asset.java +++ b/src/main/java/org/unicitylabs/sdk/payment/asset/Asset.java @@ -56,7 +56,7 @@ public BigInteger getValue() { * @return asset */ public static Asset fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); + List data = CborDeserializer.decodeArray(bytes, 2); return new Asset( AssetId.fromCbor(data.get(0)), diff --git a/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java index 2f6ee17..ef35844 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java @@ -41,7 +41,7 @@ public static EncodedPredicate fromCbor(byte[] bytes) { if (tag.getTag() != EncodedPredicate.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData()); + List data = CborDeserializer.decodeArray(tag.getData(), 3); PredicateEngine engine = PredicateEngine.fromId( CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt()); 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 7712ebd..275ee9d 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializer.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializer.java @@ -85,6 +85,25 @@ public static List decodeArray(byte[] data) { return result; } + /** + * Read a fixed-size CBOR array from bytes. Throws when the encoded array length does not match + * the expected length. + * + * @param data bytes + * @param expectedLength expected number of array elements + * @return CBOR element array + * + * @throws CborSerializationException when the array length differs from {@code expectedLength} + */ + public static List decodeArray(byte[] data, long expectedLength) { + List result = decodeArray(data); + if (result.size() != expectedLength) { + throw new CborSerializationException( + String.format("Expected array of %d elements, got %d", expectedLength, result.size())); + } + return result; + } + /** * Read elements as raw CBOR element map from CBOR bytes. * diff --git a/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePath.java b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePath.java index a8ff834..1dcd88e 100644 --- a/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePath.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePath.java @@ -122,7 +122,7 @@ public MerkleTreePathVerificationResult verify(BigInteger stateId) { * @return path */ public static SparseMerkleTreePath fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); + List data = CborDeserializer.decodeArray(bytes, 2); return new SparseMerkleTreePath( DataHash.fromCbor(data.get(0)), diff --git a/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePathStep.java b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePathStep.java index e0fefb4..931c850 100644 --- a/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePathStep.java +++ b/src/main/java/org/unicitylabs/sdk/smt/plain/SparseMerkleTreePathStep.java @@ -63,7 +63,7 @@ public Optional getData() { * @return sparse Merkle tree path step */ public static SparseMerkleTreePathStep fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); + List data = CborDeserializer.decodeArray(bytes, 2); return new SparseMerkleTreePathStep( BigIntegerConverter.decode(CborDeserializer.decodeByteString(data.get(0))), diff --git a/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePath.java b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePath.java index 907d2cb..bdc0763 100644 --- a/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePath.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePath.java @@ -132,7 +132,7 @@ public MerkleTreePathVerificationResult verify(BigInteger stateId) { * @return path */ public static SparseMerkleSumTreePath fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); + List data = CborDeserializer.decodeArray(bytes, 2); return new SparseMerkleSumTreePath( DataHash.fromCbor(data.get(0)), @@ -218,7 +218,7 @@ public BigInteger getCounter() { * @return root */ public static Root fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); + List data = CborDeserializer.decodeArray(bytes, 2); return new Root( DataHash.fromCbor(data.get(0)), diff --git a/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePathStep.java b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePathStep.java index dc0168f..532fce8 100644 --- a/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePathStep.java +++ b/src/main/java/org/unicitylabs/sdk/smt/sum/SparseMerkleSumTreePathStep.java @@ -67,7 +67,7 @@ public BigInteger getValue() { * @return step */ public static SparseMerkleSumTreePathStep fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); + List data = CborDeserializer.decodeArray(bytes, 3); return new SparseMerkleSumTreePathStep( BigIntegerConverter.decode(CborDeserializer.decodeByteString(data.get(0))), diff --git a/src/main/java/org/unicitylabs/sdk/transaction/CertifiedMintTransaction.java b/src/main/java/org/unicitylabs/sdk/transaction/CertifiedMintTransaction.java index 7ea3b20..52fbb5f 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/CertifiedMintTransaction.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/CertifiedMintTransaction.java @@ -91,7 +91,7 @@ public InclusionProof getInclusionProof() { * @return decoded certified mint transaction */ public static CertifiedMintTransaction fromCbor(byte[] bytes) { - List data = CborDeserializer.decodeArray(bytes); + List data = CborDeserializer.decodeArray(bytes, 2); return new CertifiedMintTransaction(MintTransaction.fromCbor(data.get(0)), InclusionProof.fromCbor(data.get(1))); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/CertifiedTransferTransaction.java b/src/main/java/org/unicitylabs/sdk/transaction/CertifiedTransferTransaction.java index 1b9bb79..fd5ff1d 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/CertifiedTransferTransaction.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/CertifiedTransferTransaction.java @@ -74,7 +74,7 @@ public InclusionProof getInclusionProof() { * @return certified transfer transaction */ public static CertifiedTransferTransaction fromCbor(byte[] bytes, Token token) { - List data = CborDeserializer.decodeArray(bytes); + List data = CborDeserializer.decodeArray(bytes, 2); return new CertifiedTransferTransaction( TransferTransaction.fromCbor(data.get(0), token), diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java b/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java index aafac72..ddeb177 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java @@ -161,7 +161,7 @@ public static MintTransaction fromCbor(byte[] bytes) { if (tag.getTag() != MintTransaction.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData()); + List data = CborDeserializer.decodeArray(tag.getData(), 6); int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); if (version != MintTransaction.VERSION) { diff --git a/src/main/java/org/unicitylabs/sdk/transaction/Token.java b/src/main/java/org/unicitylabs/sdk/transaction/Token.java index 93f4d95..3f20f39 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/Token.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/Token.java @@ -98,7 +98,7 @@ public static Token fromCbor(byte[] bytes) { if (tag.getTag() != Token.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData()); + List data = CborDeserializer.decodeArray(tag.getData(), 3); int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); if (version != Token.VERSION) { diff --git a/src/main/java/org/unicitylabs/sdk/transaction/TransferTransaction.java b/src/main/java/org/unicitylabs/sdk/transaction/TransferTransaction.java index b22214d..deadca1 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/TransferTransaction.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/TransferTransaction.java @@ -108,7 +108,7 @@ public static TransferTransaction fromCbor(byte[] bytes, Token token) { if (tag.getTag() != TransferTransaction.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData()); + List data = CborDeserializer.decodeArray(tag.getData(), 4); int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); if (version != TransferTransaction.VERSION) { diff --git a/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedUnicityIdMintTransactionVerificationRule.java b/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedUnicityIdMintTransactionVerificationRule.java new file mode 100644 index 0000000..5064636 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedUnicityIdMintTransactionVerificationRule.java @@ -0,0 +1,60 @@ +package org.unicitylabs.sdk.transaction.verification; + +import org.unicitylabs.sdk.api.bft.RootTrustBase; +import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; +import org.unicitylabs.sdk.unicityid.CertifiedUnicityIdMintTransaction; +import org.unicitylabs.sdk.util.verification.VerificationResult; +import org.unicitylabs.sdk.util.verification.VerificationStatus; + +import java.util.ArrayList; +import java.util.List; + +/** + * Verification rule for the genesis (mint) of a unicity id token. Validates the inclusion proof of + * the certified mint transaction. + */ +public class CertifiedUnicityIdMintTransactionVerificationRule { + + private CertifiedUnicityIdMintTransactionVerificationRule() { + } + + /** + * Verify the certified unicity id mint transaction. + * + * @param trustBase root trust base + * @param predicateVerifier predicate verifier + * @param genesis certified unicity id mint transaction to verify + * + * @return verification result + */ + public static VerificationResult verify( + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + CertifiedUnicityIdMintTransaction genesis + ) { + List> results = new ArrayList<>(); + + VerificationResult result = InclusionProofVerificationRule.verify( + trustBase, + predicateVerifier, + genesis.getInclusionProof(), + genesis + ); + results.add(result); + if (result.getStatus() != InclusionProofVerificationStatus.OK) { + return new VerificationResult<>( + "CertifiedUnicityIdMintTransactionVerificationRule", + VerificationStatus.FAIL, + String.format("Inclusion proof verification failed: %s", result.getStatus()), + results + ); + } + + return new VerificationResult<>( + "CertifiedUnicityIdMintTransactionVerificationRule", + VerificationStatus.OK, + "", + results + ); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/unicityid/CertifiedUnicityIdMintTransaction.java b/src/main/java/org/unicitylabs/sdk/unicityid/CertifiedUnicityIdMintTransaction.java new file mode 100644 index 0000000..67381ac --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/unicityid/CertifiedUnicityIdMintTransaction.java @@ -0,0 +1,172 @@ +package org.unicitylabs.sdk.unicityid; + +import org.unicitylabs.sdk.api.InclusionProof; +import org.unicitylabs.sdk.api.bft.RootTrustBase; +import org.unicitylabs.sdk.crypto.hash.DataHash; +import org.unicitylabs.sdk.predicate.Predicate; +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.CborSerializer; +import org.unicitylabs.sdk.transaction.TokenId; +import org.unicitylabs.sdk.transaction.TokenType; +import org.unicitylabs.sdk.transaction.Transaction; +import org.unicitylabs.sdk.transaction.verification.InclusionProofVerificationRule; +import org.unicitylabs.sdk.transaction.verification.InclusionProofVerificationStatus; +import org.unicitylabs.sdk.util.verification.VerificationException; +import org.unicitylabs.sdk.util.verification.VerificationResult; + +import java.util.List; +import java.util.Optional; + +/** + * Unicity id mint transaction bundled with a verified inclusion proof. + */ +public final class CertifiedUnicityIdMintTransaction implements Transaction { + + private final UnicityIdMintTransaction transaction; + private final InclusionProof inclusionProof; + + private CertifiedUnicityIdMintTransaction(UnicityIdMintTransaction transaction, + InclusionProof inclusionProof) { + this.transaction = transaction; + this.inclusionProof = inclusionProof; + } + + @Override + public Optional getData() { + return this.transaction.getData(); + } + + @Override + public Predicate getLockScript() { + return this.transaction.getLockScript(); + } + + @Override + public Predicate getRecipient() { + return this.transaction.getRecipient(); + } + + @Override + public DataHash getSourceStateHash() { + return this.transaction.getSourceStateHash(); + } + + @Override + public byte[] getStateMask() { + return this.transaction.getStateMask(); + } + + /** + * Returns the token id derived from the unicity id. + * + * @return token id + */ + public TokenId getTokenId() { + return this.transaction.getTokenId(); + } + + /** + * Returns the token type. + * + * @return token type + */ + public TokenType getTokenType() { + return this.transaction.getTokenType(); + } + + /** + * Returns the target predicate. + * + * @return target predicate + */ + public PayToPublicKeyPredicate getTargetPredicate() { + return this.transaction.getTargetPredicate(); + } + + /** + * Returns the unicity id. + * + * @return unicity id + */ + public UnicityId getUnicityId() { + return this.transaction.getUnicityId(); + } + + /** + * Returns the inclusion proof certifying this transaction. + * + * @return inclusion proof + */ + public InclusionProof getInclusionProof() { + return this.inclusionProof; + } + + /** + * Deserializes a certified unicity id mint transaction from CBOR. + * + * @param bytes CBOR-encoded certified mint transaction + * + * @return decoded certified mint transaction + */ + public static CertifiedUnicityIdMintTransaction fromCbor(byte[] bytes) { + List data = CborDeserializer.decodeArray(bytes, 2); + return new CertifiedUnicityIdMintTransaction( + UnicityIdMintTransaction.fromCbor(data.get(0)), + InclusionProof.fromCbor(data.get(1)) + ); + } + + /** + * Creates a certified unicity id mint transaction after verifying its inclusion proof. + * + * @param trustBase trust base used to verify inclusion proof signatures + * @param predicateVerifier predicate verifier service + * @param transaction unicity id mint transaction to certify + * @param inclusionProof inclusion proof for the transaction + * + * @return certified mint transaction + * + * @throws VerificationException if inclusion proof verification fails + */ + public static CertifiedUnicityIdMintTransaction fromTransaction( + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + UnicityIdMintTransaction transaction, + InclusionProof inclusionProof + ) { + VerificationResult result = InclusionProofVerificationRule.verify( + trustBase, + predicateVerifier, + inclusionProof, + transaction + ); + if (result.getStatus() != InclusionProofVerificationStatus.OK) { + throw new VerificationException("Inclusion proof verification failed", result); + } + + return new CertifiedUnicityIdMintTransaction(transaction, inclusionProof); + } + + @Override + public DataHash calculateStateHash() { + return this.transaction.calculateStateHash(); + } + + @Override + public DataHash calculateTransactionHash() { + return this.transaction.calculateTransactionHash(); + } + + @Override + public byte[] toCbor() { + return CborSerializer.encodeArray(this.transaction.toCbor(), this.inclusionProof.toCbor()); + } + + @Override + public String toString() { + return String.format("CertifiedUnicityIdMintTransaction{transaction=%s, inclusionProof=%s}", + this.transaction, this.inclusionProof); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/unicityid/UnicityId.java b/src/main/java/org/unicitylabs/sdk/unicityid/UnicityId.java new file mode 100644 index 0000000..48bc1db --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/unicityid/UnicityId.java @@ -0,0 +1,127 @@ +package org.unicitylabs.sdk.unicityid; + +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.CborSerializer; +import org.unicitylabs.sdk.transaction.TokenId; + +import java.util.List; +import java.util.Objects; + +/** + * Human-readable identifier for a unicity token. The pair (domain, name) is hashed deterministically + * to derive the corresponding {@link TokenId}. + */ +public final class UnicityId { + + private final String name; + private final String domain; + + /** + * Create a unicity id with name only (no domain). + * + * @param name token name + */ + public UnicityId(String name) { + this(name, null); + } + + /** + * Create a unicity id. + * + * @param name token name + * @param domain optional domain; may be null + */ + public UnicityId(String name, String domain) { + this.name = Objects.requireNonNull(name, "name cannot be null"); + this.domain = domain; + } + + /** + * Get the token name. + * + * @return name + */ + public String getName() { + return this.name; + } + + /** + * Get the optional domain. + * + * @return domain, or null if not set + */ + public String getDomain() { + return this.domain; + } + + /** + * Deserialize a unicity id from CBOR bytes. + * + * @param bytes CBOR bytes + * + * @return unicity id + */ + public static UnicityId fromCbor(byte[] bytes) { + List data = CborDeserializer.decodeArray(bytes, 2); + return new UnicityId( + CborDeserializer.decodeTextString(data.get(0)), + CborDeserializer.decodeNullable(data.get(1), CborDeserializer::decodeTextString) + ); + } + + /** + * Serialize the unicity id to CBOR bytes. + * + * @return CBOR bytes + */ + public byte[] toCbor() { + return CborSerializer.encodeArray( + CborSerializer.encodeTextString(this.name), + CborSerializer.encodeNullable(this.domain, CborSerializer::encodeTextString) + ); + } + + /** + * Derive the token id from this unicity id by hashing the tagged ("NAMETAG_", domain, name) + * tuple with SHA-256. + * + * @return derived token id + */ + public TokenId toTokenId() { + DataHash hash = new DataHasher(HashAlgorithm.SHA256) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeTextString("NAMETAG_"), + CborSerializer.encodeNullable(this.domain, CborSerializer::encodeTextString), + CborSerializer.encodeTextString(this.name) + ) + ) + .digest(); + return new TokenId(hash.getData()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof UnicityId)) { + return false; + } + UnicityId that = (UnicityId) o; + return this.name.equals(that.name) && Objects.equals(this.domain, that.domain); + } + + @Override + public int hashCode() { + return Objects.hash(this.name, this.domain); + } + + @Override + public String toString() { + return "@" + (this.domain != null ? this.domain + "/" : "") + this.name; + } +} diff --git a/src/main/java/org/unicitylabs/sdk/unicityid/UnicityIdMintTransaction.java b/src/main/java/org/unicitylabs/sdk/unicityid/UnicityIdMintTransaction.java new file mode 100644 index 0000000..a056191 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/unicityid/UnicityIdMintTransaction.java @@ -0,0 +1,260 @@ +package org.unicitylabs.sdk.unicityid; + +import org.unicitylabs.sdk.api.InclusionProof; +import org.unicitylabs.sdk.api.bft.RootTrustBase; +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.predicate.EncodedPredicate; +import org.unicitylabs.sdk.predicate.Predicate; +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.transaction.MintTransactionState; +import org.unicitylabs.sdk.transaction.TokenId; +import org.unicitylabs.sdk.transaction.TokenType; +import org.unicitylabs.sdk.transaction.Transaction; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * Mint transaction that derives its token id from a {@link UnicityId}. The token's data field is + * the encoded target predicate. + */ +public final class UnicityIdMintTransaction implements Transaction { + public static final long CBOR_TAG = 39041; + private static final int VERSION = 1; + + private final MintTransactionState sourceStateHash; + private final PayToPublicKeyPredicate lockScript; + private final Predicate recipient; + private final TokenId tokenId; + private final TokenType tokenType; + private final PayToPublicKeyPredicate targetPredicate; + private final UnicityId unicityId; + + private UnicityIdMintTransaction( + MintTransactionState sourceStateHash, + PayToPublicKeyPredicate lockScript, + Predicate recipient, + TokenId tokenId, + TokenType tokenType, + PayToPublicKeyPredicate targetPredicate, + UnicityId unicityId + ) { + this.sourceStateHash = sourceStateHash; + this.lockScript = lockScript; + this.recipient = recipient; + this.tokenId = tokenId; + this.tokenType = tokenType; + this.targetPredicate = targetPredicate; + this.unicityId = unicityId; + } + + /** + * Get the version number. + * + * @return version + */ + public int getVersion() { + return UnicityIdMintTransaction.VERSION; + } + + @Override + public MintTransactionState getSourceStateHash() { + return this.sourceStateHash; + } + + @Override + public PayToPublicKeyPredicate getLockScript() { + return this.lockScript; + } + + @Override + public Predicate getRecipient() { + return this.recipient; + } + + /** + * Get the token id derived from the unicity id. + * + * @return token id + */ + public TokenId getTokenId() { + return this.tokenId; + } + + /** + * Get the token type. + * + * @return token type + */ + public TokenType getTokenType() { + return this.tokenType; + } + + /** + * Get the target predicate (the predicate the minted token is locked to). + * + * @return target predicate + */ + public PayToPublicKeyPredicate getTargetPredicate() { + return this.targetPredicate; + } + + /** + * Get the unicity id. + * + * @return unicity id + */ + public UnicityId getUnicityId() { + return this.unicityId; + } + + @Override + public Optional getData() { + return Optional.of(EncodedPredicate.fromPredicate(this.targetPredicate).toCbor()); + } + + @Override + public byte[] getStateMask() { + return this.tokenId.getBytes(); + } + + /** + * Create a unicity id mint transaction. The token id is derived from the unicity id; the lock + * script is supplied by the caller. + * + * @param lockScript lock script predicate (the predicate that must be unlocked to spend this + * transaction) + * @param recipient recipient predicate + * @param unicityId unicity id producing the token id + * @param tokenType token type identifier + * @param targetPredicate target predicate the minted token will be locked to + * + * @return mint transaction + */ + public static UnicityIdMintTransaction create( + PayToPublicKeyPredicate lockScript, + Predicate recipient, + UnicityId unicityId, + TokenType tokenType, + PayToPublicKeyPredicate targetPredicate + ) { + Objects.requireNonNull(lockScript, "lockScript cannot be null"); + Objects.requireNonNull(recipient, "recipient cannot be null"); + Objects.requireNonNull(unicityId, "unicityId cannot be null"); + Objects.requireNonNull(tokenType, "tokenType cannot be null"); + Objects.requireNonNull(targetPredicate, "targetPredicate cannot be null"); + + TokenId tokenId = unicityId.toTokenId(); + + return new UnicityIdMintTransaction( + MintTransactionState.create(tokenId), + lockScript, + recipient, + tokenId, + tokenType, + targetPredicate, + unicityId + ); + } + + /** + * Deserialize a unicity id mint transaction from CBOR bytes. + * + * @param bytes CBOR bytes + * + * @return mint transaction + * + * @throws CborSerializationException if the bytes do not carry the expected tag, version, or if + * the encoded token id does not match the unicity id + */ + public static UnicityIdMintTransaction fromCbor(byte[] bytes) { + CborDeserializer.CborTag tag = CborDeserializer.decodeTag(bytes); + if (tag.getTag() != UnicityIdMintTransaction.CBOR_TAG) { + throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); + } + List data = CborDeserializer.decodeArray(tag.getData(), 6); + + int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); + if (version != UnicityIdMintTransaction.VERSION) { + throw new CborSerializationException(String.format("Unsupported version: %s", version)); + } + + return UnicityIdMintTransaction.create( + PayToPublicKeyPredicate.fromPredicate( + EncodedPredicate.fromCbor(data.get(1)) + ), + EncodedPredicate.fromCbor(data.get(2)), + UnicityId.fromCbor(data.get(3)), + TokenType.fromCbor(data.get(4)), + PayToPublicKeyPredicate.fromPredicate( + EncodedPredicate.fromCbor(data.get(5)) + ) + ); + } + + @Override + public DataHash calculateStateHash() { + return new DataHasher(HashAlgorithm.SHA256) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(this.sourceStateHash.getImprint()), + CborSerializer.encodeByteString(this.getStateMask()) + ) + ) + .digest(); + } + + @Override + public DataHash calculateTransactionHash() { + return new DataHasher(HashAlgorithm.SHA256).update(this.toCbor()).digest(); + } + + @Override + public byte[] toCbor() { + return CborSerializer.encodeTag( + UnicityIdMintTransaction.CBOR_TAG, + CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(UnicityIdMintTransaction.VERSION), + EncodedPredicate.fromPredicate(this.lockScript).toCbor(), + EncodedPredicate.fromPredicate(this.recipient).toCbor(), + this.unicityId.toCbor(), + this.tokenType.toCbor(), + EncodedPredicate.fromPredicate(this.targetPredicate).toCbor() + ) + ); + } + + /** + * Build the certified version by attaching and verifying an inclusion proof. + * + * @param trustBase root trust base + * @param predicateVerifier predicate verifier + * @param inclusionProof inclusion proof + * + * @return certified mint transaction + */ + public CertifiedUnicityIdMintTransaction toCertifiedTransaction( + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + InclusionProof inclusionProof + ) { + return CertifiedUnicityIdMintTransaction.fromTransaction(trustBase, predicateVerifier, this, + inclusionProof); + } + + @Override + public String toString() { + return String.format( + "UnicityIdMintTransaction{lockScript=%s, recipient=%s, tokenId=%s, tokenType=%s, unicityId=%s, targetPredicate=%s}", + this.lockScript, this.recipient, this.tokenId, this.tokenType, this.unicityId, + this.targetPredicate + ); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/unicityid/UnicityIdToken.java b/src/main/java/org/unicitylabs/sdk/unicityid/UnicityIdToken.java new file mode 100644 index 0000000..b2a0fe2 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/unicityid/UnicityIdToken.java @@ -0,0 +1,141 @@ +package org.unicitylabs.sdk.unicityid; + +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.CborSerializer; +import org.unicitylabs.sdk.transaction.TokenId; +import org.unicitylabs.sdk.transaction.TokenType; +import org.unicitylabs.sdk.transaction.verification.CertifiedUnicityIdMintTransactionVerificationRule; +import org.unicitylabs.sdk.util.verification.VerificationException; +import org.unicitylabs.sdk.util.verification.VerificationResult; +import org.unicitylabs.sdk.util.verification.VerificationStatus; + +import java.util.ArrayList; +import java.util.List; + +/** + * Token whose genesis is a {@link CertifiedUnicityIdMintTransaction}. The token's identifier is + * deterministically derived from a {@link UnicityId}. + */ +public final class UnicityIdToken { + + private final CertifiedUnicityIdMintTransaction genesis; + + private UnicityIdToken(CertifiedUnicityIdMintTransaction genesis) { + this.genesis = genesis; + } + + /** + * Returns the certified genesis mint transaction. + * + * @return genesis transaction + */ + public CertifiedUnicityIdMintTransaction getGenesis() { + return this.genesis; + } + + /** + * Returns the token id. + * + * @return token id + */ + public TokenId getId() { + return this.genesis.getTokenId(); + } + + /** + * Returns the token type. + * + * @return token type + */ + public TokenType getType() { + return this.genesis.getTokenType(); + } + + /** + * Returns the unicity id used to derive this token's identifier. + * + * @return unicity id + */ + public UnicityId getUnicityId() { + return this.genesis.getUnicityId(); + } + + /** + * Deserialize a unicity id token from CBOR bytes. + * + * @param bytes CBOR bytes + * + * @return decoded token + */ + public static UnicityIdToken fromCbor(byte[] bytes) { + List data = CborDeserializer.decodeArray(bytes, 1); + return new UnicityIdToken(CertifiedUnicityIdMintTransaction.fromCbor(data.get(0))); + } + + /** + * Build a unicity id token from a certified genesis transaction and verify it. + * + * @param trustBase trust base used for certification verification + * @param predicateVerifier predicate verifier service + * @param genesis certified mint transaction + * + * @return verified token + * + * @throws VerificationException if genesis verification fails + */ + public static UnicityIdToken mint( + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + CertifiedUnicityIdMintTransaction genesis + ) { + UnicityIdToken token = new UnicityIdToken(genesis); + VerificationResult result = token.verify(trustBase, predicateVerifier); + if (result.getStatus() != VerificationStatus.OK) { + throw new VerificationException("Invalid token genesis", result); + } + + return token; + } + + /** + * Serialize this token to CBOR bytes. + * + * @return CBOR bytes + */ + public byte[] toCbor() { + return CborSerializer.encodeArray(this.genesis.toCbor()); + } + + /** + * Verify the token by validating its certified mint transaction. + * + * @param trustBase trust base used for certification verification + * @param predicateVerifier predicate verifier service + * + * @return verification result + */ + public VerificationResult verify( + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier + ) { + List> results = new ArrayList<>(); + VerificationResult result = CertifiedUnicityIdMintTransactionVerificationRule.verify( + trustBase, + predicateVerifier, + this.genesis + ); + results.add(result); + if (result.getStatus() != VerificationStatus.OK) { + return new VerificationResult<>("TokenVerification", VerificationStatus.FAIL, "", results); + } + + return new VerificationResult<>("TokenVerification", VerificationStatus.OK, "", results); + } + + @Override + public String toString() { + return String.format("UnicityIdToken{genesis=%s}", this.genesis); + } +} diff --git a/src/test/java/org/unicitylabs/sdk/common/CommonTestFlow.java b/src/test/java/org/unicitylabs/sdk/common/CommonTestFlow.java index 44b326d..528edb0 100644 --- a/src/test/java/org/unicitylabs/sdk/common/CommonTestFlow.java +++ b/src/test/java/org/unicitylabs/sdk/common/CommonTestFlow.java @@ -3,12 +3,21 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.StateTransitionClient; +import org.unicitylabs.sdk.api.CertificationData; +import org.unicitylabs.sdk.api.CertificationResponse; +import org.unicitylabs.sdk.api.CertificationStatus; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; 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.transaction.Token; +import org.unicitylabs.sdk.transaction.TokenType; import org.unicitylabs.sdk.transaction.verification.MintJustificationVerifierService; +import org.unicitylabs.sdk.unicityid.UnicityId; +import org.unicitylabs.sdk.unicityid.UnicityIdMintTransaction; +import org.unicitylabs.sdk.unicityid.UnicityIdToken; +import org.unicitylabs.sdk.util.InclusionProofUtils; import org.unicitylabs.sdk.util.verification.VerificationStatus; import org.unicitylabs.sdk.utils.TokenUtils; @@ -62,4 +71,65 @@ public void testTransferFlow() throws Exception { Assertions.assertEquals(VerificationStatus.OK, carolToken.verify(this.trustBase, this.predicateVerifier, this.mintJustificationVerifier).getStatus()); } + + /** + * Default successful flow: mint a unicity-id token and then mint a regular token whose recipient + * is the unicity-id token's target predicate. + */ + @Test + public void testUnicityIdMintFlow() throws Exception { + SigningService unicityIdSigningService = SigningService.generate(); + PayToPublicKeyPredicate targetPredicate = PayToPublicKeyPredicate.create( + ALICE_SIGNING_SERVICE.getPublicKey()); + + UnicityId unicityId = new UnicityId("testuser", "unicity-labs/test"); + UnicityIdMintTransaction unicityIdMintTransaction = UnicityIdMintTransaction.create( + PayToPublicKeyPredicate.fromSigningService(unicityIdSigningService), + targetPredicate, + unicityId, + TokenType.generate(), + targetPredicate + ); + + CertificationData unicityIdCertificationData = CertificationData.fromTransaction( + unicityIdMintTransaction, + PayToPublicKeyPredicateUnlockScript.create(unicityIdMintTransaction, unicityIdSigningService) + ); + + CertificationResponse unicityIdResponse = this.client + .submitCertificationRequest(unicityIdCertificationData).get(); + Assertions.assertEquals(CertificationStatus.SUCCESS, unicityIdResponse.getStatus()); + + UnicityIdToken aliceUnicityIdToken = UnicityIdToken.mint( + this.trustBase, + this.predicateVerifier, + unicityIdMintTransaction.toCertifiedTransaction( + this.trustBase, + this.predicateVerifier, + InclusionProofUtils.waitInclusionProof(this.client, this.trustBase, + this.predicateVerifier, unicityIdMintTransaction).get() + ) + ); + + Assertions.assertEquals(VerificationStatus.OK, + aliceUnicityIdToken.verify(this.trustBase, this.predicateVerifier).getStatus()); + + UnicityIdToken decodedUnicityIdToken = UnicityIdToken.fromCbor(aliceUnicityIdToken.toCbor()); + Assertions.assertArrayEquals(aliceUnicityIdToken.toCbor(), decodedUnicityIdToken.toCbor()); + Assertions.assertEquals(aliceUnicityIdToken.getId(), decodedUnicityIdToken.getId()); + Assertions.assertEquals(VerificationStatus.OK, + decodedUnicityIdToken.verify(this.trustBase, this.predicateVerifier).getStatus()); + + Token aliceToken = TokenUtils.mintToken( + this.client, + this.trustBase, + this.predicateVerifier, + this.mintJustificationVerifier, + aliceUnicityIdToken.getGenesis().getTargetPredicate() + ); + + Assertions.assertEquals(VerificationStatus.OK, + aliceToken.verify(this.trustBase, this.predicateVerifier, this.mintJustificationVerifier) + .getStatus()); + } } \ No newline at end of file