From 5fc94ebc060f04e0a399d0683529694d2ec866a4 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Sun, 14 Sep 2025 10:54:40 +0400 Subject: [PATCH 01/16] Add BFT unicity certificate deserializing --- .../org/unicitylabs/sdk/bft/InputRecord.java | 94 +++++++++++++++++++ .../sdk/bft/ShardTreeCertificate.java | 41 ++++++++ .../sdk/bft/UnicityCertificate.java | 78 +++++++++++++++ .../org/unicitylabs/sdk/bft/UnicitySeal.java | 89 ++++++++++++++++++ .../sdk/bft/UnicityTreeCertificate.java | 85 +++++++++++++++++ .../sdk/serializer/UnicityObjectMapper.java | 25 +++++ .../serializer/cbor/bft/InputRecordCbor.java | 85 +++++++++++++++++ .../cbor/bft/ShardTreeCertificateCbor.java | 72 ++++++++++++++ .../cbor/bft/UnicityCertificateCbor.java | 70 ++++++++++++++ .../serializer/cbor/bft/UnicitySealCbor.java | 78 +++++++++++++++ .../cbor/bft/UnicityTreeCertificateCbor.java | 70 ++++++++++++++ .../UnicityTreeCertificateHashStepCbor.java | 51 ++++++++++ .../sdk/transaction/MintReasonType.java | 7 -- .../sdk/bft/UnicityCertificateTest.java | 17 ++++ 14 files changed, 855 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/unicitylabs/sdk/bft/InputRecord.java create mode 100644 src/main/java/org/unicitylabs/sdk/bft/ShardTreeCertificate.java create mode 100644 src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java create mode 100644 src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java create mode 100644 src/main/java/org/unicitylabs/sdk/bft/UnicityTreeCertificate.java create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityCertificateCbor.java create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java delete mode 100644 src/main/java/org/unicitylabs/sdk/transaction/MintReasonType.java create mode 100644 src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java diff --git a/src/main/java/org/unicitylabs/sdk/bft/InputRecord.java b/src/main/java/org/unicitylabs/sdk/bft/InputRecord.java new file mode 100644 index 0000000..c5d85a8 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/bft/InputRecord.java @@ -0,0 +1,94 @@ +package org.unicitylabs.sdk.bft; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; +import org.unicitylabs.sdk.util.HexConverter; + +public class InputRecord { + + private final BigInteger version; + private final BigInteger roundNumber; + private final BigInteger epoch; + private final byte[] previousHash; + private final byte[] hash; + private final byte[] summaryValue; + private final BigInteger timestamp; + private final byte[] blockHash; + private final BigInteger sumOfEarnedFees; + private final byte[] executedTransactionsHash; + + public InputRecord( + BigInteger version, + BigInteger roundNumber, + BigInteger epoch, + byte[] previousHash, + byte[] hash, + byte[] summaryValue, + BigInteger timestamp, + byte[] blockHash, + BigInteger sumOfEarnedFees, + byte[] executedTransactionsHash + ) { + Objects.requireNonNull(version, "Version cannot be null"); + Objects.requireNonNull(roundNumber, "Round number cannot be null"); + Objects.requireNonNull(epoch, "Epoch cannot be null"); + Objects.requireNonNull(summaryValue, "Summary value cannot be null"); + Objects.requireNonNull(timestamp, "Timestamp cannot be null"); + Objects.requireNonNull(sumOfEarnedFees, "Sum of earned fees cannot be null"); + + this.version = version; + this.roundNumber = roundNumber; + this.epoch = epoch; + this.previousHash = previousHash; + this.hash = hash; + this.summaryValue = summaryValue; + this.timestamp = timestamp; + this.blockHash = blockHash; + this.sumOfEarnedFees = sumOfEarnedFees; + this.executedTransactionsHash = executedTransactionsHash; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof InputRecord)) { + 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); + } + + @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)); + } + + @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, + this.hash != null ? HexConverter.encode(this.hash) : null, + 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/bft/ShardTreeCertificate.java b/src/main/java/org/unicitylabs/sdk/bft/ShardTreeCertificate.java new file mode 100644 index 0000000..ce8687a --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/bft/ShardTreeCertificate.java @@ -0,0 +1,41 @@ +package org.unicitylabs.sdk.bft; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import org.unicitylabs.sdk.util.HexConverter; + +public class ShardTreeCertificate { + + private final byte[] shard; + private final List siblingHashList; + + public ShardTreeCertificate(byte[] shard, List siblingHashList) { + Objects.requireNonNull(shard, "Shard cannot be null"); + Objects.requireNonNull(siblingHashList, "Sibling hash list cannot be null"); + + this.shard = shard; + this.siblingHashList = siblingHashList; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ShardTreeCertificate)) { + return false; + } + ShardTreeCertificate that = (ShardTreeCertificate) o; + return Objects.deepEquals(this.shard, that.shard) && Objects.equals( + this.siblingHashList, that.siblingHashList); + } + + @Override + public int hashCode() { + return Objects.hash(Arrays.hashCode(this.shard), this.siblingHashList); + } + + @Override + public String toString() { + return String.format("ShardTreeCertificate{shard=%s, siblingHashList=%s}", + HexConverter.encode(this.shard), this.siblingHashList); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java b/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java new file mode 100644 index 0000000..411b0cf --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java @@ -0,0 +1,78 @@ +package org.unicitylabs.sdk.bft; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; +import org.unicitylabs.sdk.util.HexConverter; + +public class UnicityCertificate { + + private final BigInteger version; + private final InputRecord inputRecord; + private final byte[] technicalRecordHash; + private final byte[] shardConfigurationHash; + public final ShardTreeCertificate shardTreeCertificate; + public final UnicityTreeCertificate unicityTreeCertificate; + public final UnicitySeal unicitySeal; + + public UnicityCertificate( + BigInteger version, + InputRecord inputRecord, + byte[] technicalRecordHash, + byte[] shardConfigurationHash, + ShardTreeCertificate shardTreeCertificate, + UnicityTreeCertificate unicityTreeCertificate, + UnicitySeal unicitySeal + ) { + Objects.requireNonNull(version, "Version cannot be null"); + Objects.requireNonNull(inputRecord, "Input record cannot be null"); + Objects.requireNonNull(shardConfigurationHash, "Shard configuration hash cannot be null"); + Objects.requireNonNull(shardTreeCertificate, "Shard tree certificate cannot be null"); + Objects.requireNonNull(unicityTreeCertificate, "Unicity tree certificate cannot be null"); + Objects.requireNonNull(unicitySeal, "Unicity seal cannot be null"); + + this.version = version; + this.inputRecord = inputRecord; + this.technicalRecordHash = technicalRecordHash; + this.shardConfigurationHash = shardConfigurationHash; + this.shardTreeCertificate = shardTreeCertificate; + this.unicityTreeCertificate = unicityTreeCertificate; + this.unicitySeal = unicitySeal; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof UnicityCertificate)) { + 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); + } + + @Override + public int hashCode() { + return Objects.hash(this.version, this.inputRecord, Arrays.hashCode(this.technicalRecordHash), + Arrays.hashCode(this.shardConfigurationHash), this.shardTreeCertificate, + this.unicityTreeCertificate, this.unicitySeal); + } + + @Override + public String toString() { + return String.format("UnicityCertificate{version=%s, inputRecord=%s, technicalRecordHash=%s, " + + "shardConfigurationHash=%s, shardTreeCertificate=%s, unicityTreeCertificate=%s, " + + "unicitySeal=%s}", + this.version, + this.inputRecord, + this.technicalRecordHash != null ? HexConverter.encode(this.technicalRecordHash) : null, + HexConverter.encode(this.shardConfigurationHash), + this.shardTreeCertificate, + this.unicityTreeCertificate, + this.unicitySeal + ); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java b/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java new file mode 100644 index 0000000..9e7b1af --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java @@ -0,0 +1,89 @@ +package org.unicitylabs.sdk.bft; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.stream.Collectors; +import org.unicitylabs.sdk.util.HexConverter; + +public class UnicitySeal { + + private final BigInteger version; + private final BigInteger networkId; + private final BigInteger rootChainRoundNumber; + private final BigInteger epoch; + private final BigInteger timestamp; + private final byte[] previousHash; // nullable + private final byte[] hash; + private final Map signatures; + + public UnicitySeal( + BigInteger version, + BigInteger networkId, + BigInteger rootChainRoundNumber, + BigInteger epoch, + BigInteger timestamp, + byte[] previousHash, + byte[] hash, + Map signatures + ) { + Objects.requireNonNull(version, "Version cannot be null"); + Objects.requireNonNull(networkId, "Network ID cannot be null"); + Objects.requireNonNull(rootChainRoundNumber, "Root chain round number cannot be null"); + Objects.requireNonNull(epoch, "Epoch cannot be null"); + Objects.requireNonNull(timestamp, "Timestamp cannot be null"); + Objects.requireNonNull(hash, "Hash cannot be null"); + Objects.requireNonNull(signatures, "Signatures cannot be null"); + + this.version = version; + this.networkId = networkId; + this.rootChainRoundNumber = rootChainRoundNumber; + this.epoch = epoch; + this.timestamp = timestamp; + this.previousHash = previousHash; + this.hash = hash; + this.signatures = signatures; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof UnicitySeal)) { + 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); + } + + @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); + } + + @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()) + ); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/bft/UnicityTreeCertificate.java b/src/main/java/org/unicitylabs/sdk/bft/UnicityTreeCertificate.java new file mode 100644 index 0000000..798f93e --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/bft/UnicityTreeCertificate.java @@ -0,0 +1,85 @@ +package org.unicitylabs.sdk.bft; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import org.unicitylabs.sdk.util.HexConverter; + +public class UnicityTreeCertificate { + + private final BigInteger version; + private final BigInteger partitionIdentifier; + private final List steps; + + public UnicityTreeCertificate( + BigInteger version, + BigInteger partitionIdentifier, + List steps + ) { + Objects.requireNonNull(version, "Version cannot be null"); + Objects.requireNonNull(partitionIdentifier, "Partition identifier cannot be null"); + Objects.requireNonNull(steps, "Steps cannot be null"); + + this.version = version; + this.partitionIdentifier = partitionIdentifier; + this.steps = steps; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof UnicityTreeCertificate)) { + 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); + } + + @Override + public int hashCode() { + return Objects.hash(this.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); + } + + public static class HashStep { + + private final BigInteger key; + private final byte[] hash; + + public HashStep(BigInteger key, byte[] hash) { + Objects.requireNonNull(key, "Key cannot be null"); + Objects.requireNonNull(hash, "Hash cannot be null"); + + this.key = key; + this.hash = hash; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof HashStep)) { + return false; + } + HashStep hashStep = (HashStep) o; + return Objects.equals(this.key, hashStep.key) && Objects.deepEquals(this.hash, + hashStep.hash); + } + + @Override + public int hashCode() { + return Objects.hash(this.key, Arrays.hashCode(this.hash)); + } + + @Override + public String toString() { + return String.format("UnicityTreeCertificate.HashStep{key=%s, hash=%s", + this.key, HexConverter.encode(this.hash)); + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java index 4c4f0c9..d784f94 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java @@ -11,6 +11,11 @@ import org.unicitylabs.sdk.api.RequestId; import org.unicitylabs.sdk.api.SubmitCommitmentRequest; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; +import org.unicitylabs.sdk.bft.InputRecord; +import org.unicitylabs.sdk.bft.ShardTreeCertificate; +import org.unicitylabs.sdk.bft.UnicityCertificate; +import org.unicitylabs.sdk.bft.UnicitySeal; +import org.unicitylabs.sdk.bft.UnicityTreeCertificate; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.jsonrpc.JsonRpcError; import org.unicitylabs.sdk.jsonrpc.JsonRpcRequest; @@ -25,6 +30,12 @@ import org.unicitylabs.sdk.predicate.UnmaskedPredicate; import org.unicitylabs.sdk.serializer.cbor.address.AddressCbor; import org.unicitylabs.sdk.serializer.cbor.api.AuthenticatorCbor; +import org.unicitylabs.sdk.serializer.cbor.bft.InputRecordCbor; +import org.unicitylabs.sdk.serializer.cbor.bft.ShardTreeCertificateCbor; +import org.unicitylabs.sdk.serializer.cbor.bft.UnicityCertificateCbor; +import org.unicitylabs.sdk.serializer.cbor.bft.UnicitySealCbor; +import org.unicitylabs.sdk.serializer.cbor.bft.UnicityTreeCertificateCbor; +import org.unicitylabs.sdk.serializer.cbor.bft.UnicityTreeCertificateHashStepCbor; import org.unicitylabs.sdk.serializer.cbor.hash.DataHashCbor; import org.unicitylabs.sdk.serializer.cbor.mtree.plain.SparseMerkleTreePathCbor; import org.unicitylabs.sdk.serializer.cbor.mtree.plain.SparseMerkleTreePathStepBranchCbor; @@ -187,6 +198,20 @@ private static ObjectMapper createCborObjectMapper() { module.addSerializer(BurnPredicate.class, new BurnPredicateCbor.Serializer()); module.addDeserializer(BurnPredicate.class, new BurnPredicateCbor.Deserializer()); + // BFT - UnicityCertificate + module.addSerializer(UnicityCertificate.class, new UnicityCertificateCbor.Serializer()); + module.addDeserializer(UnicityCertificate.class, new UnicityCertificateCbor.Deserializer()); + module.addSerializer(InputRecord.class, new InputRecordCbor.Serializer()); + module.addDeserializer(InputRecord.class, new InputRecordCbor.Deserializer()); + module.addSerializer(ShardTreeCertificate.class, new ShardTreeCertificateCbor.Serializer()); + module.addDeserializer(ShardTreeCertificate.class, new ShardTreeCertificateCbor.Deserializer()); + module.addSerializer(UnicityTreeCertificate.class, new UnicityTreeCertificateCbor.Serializer()); + module.addDeserializer(UnicityTreeCertificate.class, new UnicityTreeCertificateCbor.Deserializer()); + module.addSerializer(UnicityTreeCertificate.HashStep.class, new UnicityTreeCertificateHashStepCbor.Serializer()); + module.addDeserializer(UnicityTreeCertificate.HashStep.class, new UnicityTreeCertificateHashStepCbor.Deserializer()); + module.addSerializer(UnicitySeal.class, new UnicitySealCbor.Serializer()); + module.addDeserializer(UnicitySeal.class, new UnicitySealCbor.Deserializer()); + ObjectMapper objectMapper = new CBORMapper(); objectMapper.registerModule(new Jdk8Module()); objectMapper.registerModule(module); diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java new file mode 100644 index 0000000..a01ba15 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java @@ -0,0 +1,85 @@ +package org.unicitylabs.sdk.serializer.cbor.bft; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.dataformat.cbor.CBORParser; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import org.unicitylabs.sdk.bft.InputRecord; +import org.unicitylabs.sdk.bft.UnicityCertificate; +import org.unicitylabs.sdk.util.HexConverter; + +public class InputRecordCbor { + + private InputRecordCbor() { + } + + public static class Serializer extends JsonSerializer { + + @Override + public void serialize(InputRecord value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + + gen.writeStartArray(value, 0); + gen.writeEndArray(); + } + } + + public static class Deserializer extends JsonDeserializer { + + @Override + public InputRecord deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + if (!p.isExpectedStartArrayToken()) { + throw MismatchedInputException.from(p, InputRecord.class, "Expected array value"); + } + p.nextToken(); + + BigInteger version = p.readValueAs(BigInteger.class); + + BigInteger roundNumber = p.readValueAs(BigInteger.class); + BigInteger epoch = p.readValueAs(BigInteger.class); + byte[] previousHash = p.readValueAs(byte[].class); + byte[] hash = p.readValueAs(byte[].class); + byte[] summaryValue = p.readValueAs(byte[].class); + BigInteger timestamp = p.readValueAs(BigInteger.class); + byte[] blockHash = p.readValueAs(byte[].class); + BigInteger sumOfEarnedFees = p.readValueAs(BigInteger.class); + byte[] executedTransactionsHash = p.readValueAs(byte[].class); + + + InputRecord result = new InputRecord( + version, + roundNumber, + epoch, + previousHash != null + ? HexConverter.decode(new String(previousHash, StandardCharsets.UTF_8)) + : null, + hash != null + ? HexConverter.decode(new String(hash, StandardCharsets.UTF_8)) + : null, + summaryValue, + timestamp, + blockHash, + sumOfEarnedFees, + executedTransactionsHash + ); + + if (p.nextToken() != JsonToken.END_ARRAY) { + throw MismatchedInputException.from(p, InputRecord.class, "Expected end of array"); + } + + return result; + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java new file mode 100644 index 0000000..53b2521 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java @@ -0,0 +1,72 @@ +package org.unicitylabs.sdk.serializer.cbor.bft; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.dataformat.cbor.CBORParser; +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import org.unicitylabs.sdk.bft.InputRecord; +import org.unicitylabs.sdk.bft.ShardTreeCertificate; +import org.unicitylabs.sdk.bft.UnicityCertificate; +import org.unicitylabs.sdk.bft.UnicitySeal; +import org.unicitylabs.sdk.bft.UnicityTreeCertificate; +import org.unicitylabs.sdk.transaction.Transaction; +import org.unicitylabs.sdk.transaction.TransferTransactionData; +import org.unicitylabs.sdk.util.HexConverter; + +public class ShardTreeCertificateCbor { + + private ShardTreeCertificateCbor() { + } + + public static class Serializer extends JsonSerializer { + + @Override + public void serialize(ShardTreeCertificate value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + + gen.writeStartArray(value, 0); + gen.writeEndArray(); + } + } + + public static class Deserializer extends JsonDeserializer { + + @Override + public ShardTreeCertificate deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + if (!p.isExpectedStartArrayToken()) { + throw MismatchedInputException.from(p, ShardTreeCertificate.class, "Expected array value"); + } + p.nextToken(); + byte[] shard = p.readValueAs(byte[].class); + if (p.nextToken() != JsonToken.START_ARRAY) { + throw MismatchedInputException.from(p, ShardTreeCertificate.class, "Expected sibling hash list"); + } + + List siblings = new ArrayList<>(); + while (p.nextToken() != JsonToken.END_ARRAY) { + siblings.add(p.readValueAs(byte[].class)); + } + + ShardTreeCertificate result = new ShardTreeCertificate(shard, siblings); + + if (p.nextToken() != JsonToken.END_ARRAY) { + throw MismatchedInputException.from(p, ShardTreeCertificate.class, "Expected end of array"); + } + + return result; + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityCertificateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityCertificateCbor.java new file mode 100644 index 0000000..31e4736 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityCertificateCbor.java @@ -0,0 +1,70 @@ +package org.unicitylabs.sdk.serializer.cbor.bft; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.dataformat.cbor.CBORParser; +import java.io.IOException; +import java.math.BigInteger; +import org.unicitylabs.sdk.api.Authenticator; +import org.unicitylabs.sdk.bft.InputRecord; +import org.unicitylabs.sdk.bft.ShardTreeCertificate; +import org.unicitylabs.sdk.bft.UnicityCertificate; +import org.unicitylabs.sdk.bft.UnicitySeal; +import org.unicitylabs.sdk.bft.UnicityTreeCertificate; +import org.unicitylabs.sdk.hash.DataHash; +import org.unicitylabs.sdk.signing.Signature; +import org.unicitylabs.sdk.util.HexConverter; + +public class UnicityCertificateCbor { + + private UnicityCertificateCbor() { + } + + public static class Serializer extends JsonSerializer { + + @Override + public void serialize(UnicityCertificate value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + + gen.writeStartArray(value, 0); + gen.writeEndArray(); + } + } + + public static class Deserializer extends JsonDeserializer { + + @Override + public UnicityCertificate deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + if (!p.isExpectedStartArrayToken()) { + throw MismatchedInputException.from(p, UnicityCertificate.class, "Expected array value"); + } + p.nextToken(); + + UnicityCertificate result = new UnicityCertificate( + p.readValueAs(BigInteger.class), + p.readValueAs(InputRecord.class), + p.readValueAs(byte[].class), + p.readValueAs(byte[].class), + p.readValueAs(ShardTreeCertificate.class), + p.readValueAs(UnicityTreeCertificate.class), + p.readValueAs(UnicitySeal.class) + ); + + if (p.nextToken() != JsonToken.END_ARRAY) { + throw MismatchedInputException.from(p, UnicityCertificate.class, "Expected end of array"); + } + + return result; + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java new file mode 100644 index 0000000..e334424 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java @@ -0,0 +1,78 @@ +package org.unicitylabs.sdk.serializer.cbor.bft; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.dataformat.cbor.CBORParser; +import java.io.IOException; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; +import org.unicitylabs.sdk.bft.UnicitySeal; + +public class UnicitySealCbor { + + private UnicitySealCbor() { + } + + public static class Serializer extends JsonSerializer { + + @Override + public void serialize(UnicitySeal value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + + gen.writeStartArray(value, 0); + gen.writeEndArray(); + } + } + + public static class Deserializer extends JsonDeserializer { + + @Override + public UnicitySeal deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + if (!p.isExpectedStartArrayToken()) { + throw MismatchedInputException.from(p, UnicitySeal.class, "Expected array value"); + } + p.nextToken(); + + BigInteger version = p.readValueAs(BigInteger.class); + BigInteger networkId = p.readValueAs(BigInteger.class); + BigInteger rootChainRoundNumber = p.readValueAs(BigInteger.class); + BigInteger epoch = p.readValueAs(BigInteger.class); + BigInteger timestamp = p.readValueAs(BigInteger.class); + byte[] previousHash = p.readValueAs(byte[].class); + byte[] hash = p.readValueAs(byte[].class); + + if (p.nextToken() != JsonToken.START_OBJECT) { + throw MismatchedInputException.from(p, UnicitySeal.class, "Expected map value"); + } + + Map signatures = new HashMap<>(); + while (p.nextToken() != JsonToken.END_OBJECT) { + String name = p.currentName(); + p.nextToken(); + signatures.put(name, p.readValueAs(byte[].class)); + } + + return new UnicitySeal( + version, + networkId, + rootChainRoundNumber, + epoch, + timestamp, + previousHash, + hash, + signatures + ); + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java new file mode 100644 index 0000000..e14e306 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java @@ -0,0 +1,70 @@ +package org.unicitylabs.sdk.serializer.cbor.bft; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.dataformat.cbor.CBORParser; +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import org.unicitylabs.sdk.bft.UnicityCertificate; +import org.unicitylabs.sdk.bft.UnicityTreeCertificate; +import org.unicitylabs.sdk.token.Token; + +public class UnicityTreeCertificateCbor { + + private UnicityTreeCertificateCbor() { + } + + public static class Serializer extends JsonSerializer { + + @Override + public void serialize(UnicityTreeCertificate value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + + gen.writeStartArray(value, 0); + gen.writeEndArray(); + } + } + + public static class Deserializer extends JsonDeserializer { + + @Override + public UnicityTreeCertificate deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + if (!p.isExpectedStartArrayToken()) { + throw MismatchedInputException.from(p, UnicityTreeCertificate.class, "Expected array value"); + } + p.nextToken(); + + BigInteger version = p.readValueAs(BigInteger.class); + BigInteger partitionIdentifier = p.readValueAs(BigInteger.class); + + if (p.nextToken() != JsonToken.START_ARRAY) { + throw MismatchedInputException.from(p, UnicityTreeCertificate.class, "Expected hash step list"); + } + + List steps = new ArrayList<>(); + while (p.nextToken() != JsonToken.END_ARRAY) { + steps.add(ctx.readValue(p, UnicityTreeCertificate.HashStep.class)); + } + + UnicityTreeCertificate result = new UnicityTreeCertificate(version, partitionIdentifier, steps); + + if (p.nextToken() != JsonToken.END_ARRAY) { + throw MismatchedInputException.from(p, UnicityTreeCertificate.HashStep.class, "Expected end of array"); + } + + return result; + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java new file mode 100644 index 0000000..1f54542 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java @@ -0,0 +1,51 @@ +package org.unicitylabs.sdk.serializer.cbor.bft; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.dataformat.cbor.CBORParser; +import java.io.IOException; +import java.math.BigInteger; +import java.util.List; +import org.unicitylabs.sdk.bft.UnicityTreeCertificate; + +public class UnicityTreeCertificateHashStepCbor { + + private UnicityTreeCertificateHashStepCbor() { + } + + public static class Serializer extends JsonSerializer { + + @Override + public void serialize(UnicityTreeCertificate.HashStep value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + + gen.writeStartArray(value, 0); + gen.writeEndArray(); + } + } + + public static class Deserializer extends JsonDeserializer { + + @Override + public UnicityTreeCertificate.HashStep deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + if (!p.isExpectedStartArrayToken()) { + throw MismatchedInputException.from(p, UnicityTreeCertificate.class, "Expected array value"); + } + p.nextToken(); + + return new UnicityTreeCertificate.HashStep( + p.readValueAs(BigInteger.class), + p.readValueAs(byte[].class) + ); + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintReasonType.java b/src/main/java/org/unicitylabs/sdk/transaction/MintReasonType.java deleted file mode 100644 index 801dd19..0000000 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintReasonType.java +++ /dev/null @@ -1,7 +0,0 @@ - -package org.unicitylabs.sdk.transaction; - -public enum MintReasonType { - GENESIS, - SPLIT -} diff --git a/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java b/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java new file mode 100644 index 0000000..b655489 --- /dev/null +++ b/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java @@ -0,0 +1,17 @@ +package org.unicitylabs.sdk.bft; + +import java.io.IOException; +import org.junit.jupiter.api.Test; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.util.HexConverter; + +public class UnicityCertificateTest { + @Test + public void testUnicityCertificateDeserializationFromCbor() throws IOException { + byte[] data = HexConverter.decode("d903ef8701d903f08a011a001ea47f005844303030306464313361666438613231333530336162663037313838353439333665333862393739633964396262393339323762333930393932346630373733313362313958443030303064643133616664386132313335303361626630373138383534393336653338623937396339643962623933393237623339303939323466303737333133623139401a68c413dff600f6582035ed27f40a24cdc321f92f3f59e265b817c986c98ff7d7e3e12367221b77735458207f58d8708258c4834849627c3110783be0c0ae9b344be807629e0b8e98e441cd82418080d903f683010780d903e98801031a009936f2001a68c413e15820c162885b569be72c83f14afc6e0e5533267022f337c1cac6f0028dbc77737fdd58209c08adef980baed2fe5444e116b777646b340fbb0a500168ac9477efc8a80386a1783531365569753248416d4562723766323557666d4a65457968713775444451646339536e656471667945585a70474e474e426177416e58410d6b0908ff4fa4c3b196c63c6420482d02e68e6ed3bdc4184d524833c50c4f0f58787dcb038b0a8d49b9a1bc260a5ceff4048a39e2984b9d4b349176d2039c0101"); + UnicityCertificate certificate = UnicityObjectMapper.CBOR.readValue(data, + UnicityCertificate.class); + System.out.println(certificate); + } + +} From ec55316c71b64b5f536801868b699d3284c8bc5e Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Fri, 19 Sep 2025 23:46:36 +0400 Subject: [PATCH 02/16] Change predicate system to use predicate engine --- .../sdk/StateTransitionClient.java | 17 ++- .../sum/SparseMerkleSumTreePathStep.java | 8 +- .../sdk/predicate/BurnPredicate.java | 79 ----------- .../sdk/predicate/EncodedPredicate.java | 55 ++++++++ .../unicitylabs/sdk/predicate/Predicate.java | 24 ++-- .../sdk/predicate/PredicateEngine.java | 5 + .../sdk/predicate/PredicateEngineService.java | 24 ++++ .../sdk/predicate/PredicateEngineType.java | 6 + ...Reference.java => PredicateReference.java} | 2 +- .../sdk/predicate/PredicateType.java | 8 -- .../sdk/predicate/SerializablePredicate.java | 9 ++ .../sdk/predicate/embedded/BurnPredicate.java | 101 ++++++++++++++ .../BurnPredicateReference.java | 7 +- .../{ => embedded}/DefaultPredicate.java | 100 ++++++++++---- .../embedded/EmbeddedPredicateEngine.java | 37 +++++ .../embedded/EmbeddedPredicateType.java | 32 +++++ .../{ => embedded}/MaskedPredicate.java | 18 ++- .../MaskedPredicateReference.java | 7 +- .../{ => embedded}/UnmaskedPredicate.java | 38 ++++-- .../UnmaskedPredicateReference.java | 7 +- .../sdk/serializer/UnicityObjectMapper.java | 27 ++-- ...SparseMerkleSumTreePathStepBranchCbor.java | 6 +- .../cbor/predicate/BurnPredicateCbor.java | 45 ++----- .../cbor/predicate/MaskedPredicateCbor.java | 57 ++++---- .../cbor/predicate/PredicateCbor.java | 72 +++++----- .../cbor/predicate/UnmaskedPredicateCbor.java | 60 ++++----- .../sdk/serializer/cbor/token/TokenCbor.java | 6 + .../serializer/cbor/token/TokenStateCbor.java | 2 +- .../split/SplitMintReasonCbor.java | 19 ++- ...SparseMerkleSumTreePathStepBranchJson.java | 8 +- .../json/predicate/BurnPredicateJson.java | 105 --------------- .../json/predicate/MaskedPredicateJson.java | 125 ----------------- .../json/predicate/PredicateJson.java | 72 +++++----- .../json/predicate/UnmaskedPredicateJson.java | 126 ------------------ .../sdk/serializer/json/token/TokenJson.java | 12 +- .../serializer/json/token/TokenStateJson.java | 7 +- .../split/SplitMintReasonJson.java | 13 +- .../split/SplitMintReasonProofJson.java | 2 +- .../java/org/unicitylabs/sdk/token/Token.java | 101 +++++--------- .../org/unicitylabs/sdk/token/TokenState.java | 35 ++--- .../sdk/transaction/MintReasonType.java | 5 + .../sdk/transaction/MintTransactionData.java | 8 +- .../transaction/MintTransactionReason.java | 1 + .../sdk/transaction/Transaction.java | 2 +- .../sdk/transaction/TransferCommitment.java | 7 +- .../transaction/TransferTransactionData.java | 6 +- .../transaction/split/SplitMintReason.java | 22 ++- .../transaction/split/TokenSplitBuilder.java | 15 ++- .../sdk/common/BaseEscrowSwapTest.java | 21 ++- .../sdk/common/split/BaseTokenSplitTest.java | 26 ++-- .../unicitylabs/sdk/e2e/CommonTestFlow.java | 90 +++++++++---- ...nedPredicateDoubleSpendPreventionTest.java | 12 +- .../sdk/mtree/plain/SparseMerkleTreeTest.java | 5 +- .../MaskedPredicateReferenceTest.java | 1 + .../org/unicitylabs/sdk/token/TokenTest.java | 8 +- .../sdk/transaction/CommitmentTest.java | 2 +- .../split/TokenSplitBuilderTest.java | 28 ++-- .../org/unicitylabs/sdk/utils/TokenUtils.java | 27 ++-- 58 files changed, 868 insertions(+), 902 deletions(-) delete mode 100644 src/main/java/org/unicitylabs/sdk/predicate/BurnPredicate.java create mode 100644 src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java create mode 100644 src/main/java/org/unicitylabs/sdk/predicate/PredicateEngine.java create mode 100644 src/main/java/org/unicitylabs/sdk/predicate/PredicateEngineService.java create mode 100644 src/main/java/org/unicitylabs/sdk/predicate/PredicateEngineType.java rename src/main/java/org/unicitylabs/sdk/predicate/{IPredicateReference.java => PredicateReference.java} (82%) delete mode 100644 src/main/java/org/unicitylabs/sdk/predicate/PredicateType.java create mode 100644 src/main/java/org/unicitylabs/sdk/predicate/SerializablePredicate.java create mode 100644 src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java rename src/main/java/org/unicitylabs/sdk/predicate/{ => embedded}/BurnPredicateReference.java (84%) rename src/main/java/org/unicitylabs/sdk/predicate/{ => embedded}/DefaultPredicate.java (56%) create mode 100644 src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateEngine.java create mode 100644 src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateType.java rename src/main/java/org/unicitylabs/sdk/predicate/{ => embedded}/MaskedPredicate.java (65%) rename src/main/java/org/unicitylabs/sdk/predicate/{ => embedded}/MaskedPredicateReference.java (88%) rename src/main/java/org/unicitylabs/sdk/predicate/{ => embedded}/UnmaskedPredicate.java (55%) rename src/main/java/org/unicitylabs/sdk/predicate/{ => embedded}/UnmaskedPredicateReference.java (88%) delete mode 100644 src/main/java/org/unicitylabs/sdk/serializer/json/predicate/BurnPredicateJson.java delete mode 100644 src/main/java/org/unicitylabs/sdk/serializer/json/predicate/MaskedPredicateJson.java delete mode 100644 src/main/java/org/unicitylabs/sdk/serializer/json/predicate/UnmaskedPredicateJson.java create mode 100644 src/main/java/org/unicitylabs/sdk/transaction/MintReasonType.java diff --git a/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java b/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java index 6c22764..0b8e3bb 100644 --- a/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java +++ b/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java @@ -4,6 +4,7 @@ import org.unicitylabs.sdk.api.IAggregatorClient; import org.unicitylabs.sdk.api.RequestId; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; +import org.unicitylabs.sdk.predicate.PredicateEngineService; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenState; import org.unicitylabs.sdk.transaction.Commitment; @@ -38,14 +39,17 @@ public > CompletableFuture submitCommitment( Token token, TransferCommitment commitment) { - if (!commitment.getTransactionData().getSourceState().getUnlockPredicate() - .isOwner(commitment.getAuthenticator().getPublicKey())) { + if ( + !PredicateEngineService.createPredicate( + commitment.getTransactionData().getSourceState().getPredicate() + ).isOwner(commitment.getAuthenticator().getPublicKey()) + ) { throw new IllegalArgumentException( "Ownership verification failed: Authenticator does not match source state predicate."); } return this.client.submitCommitment(commitment.getRequestId(), commitment.getTransactionData() - .calculateHash(token.getId(), token.getType()), commitment.getAuthenticator()); + .calculateHash(), commitment.getAuthenticator()); } public > Token finalizeTransaction( @@ -60,18 +64,17 @@ public > Token finalizeTransaction( Token token, TokenState state, Transaction transaction, - List> nametagTokens + List> nametags ) { Objects.requireNonNull(token, "Token is null"); - return token.update(state, transaction, nametagTokens); + return token.update(state, transaction, nametags); } public CompletableFuture getTokenStatus( Token> token, byte[] publicKey) { - RequestId requestId = RequestId.create(publicKey, - token.getState().calculateHash(token.getId(), token.getType())); + RequestId requestId = RequestId.create(publicKey, token.getState().calculateHash()); return this.client.getInclusionProof(requestId) .thenApply(inclusionProof -> inclusionProof.verify(requestId)); } diff --git a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreePathStep.java b/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreePathStep.java index 86b1572..6641e9d 100644 --- a/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreePathStep.java +++ b/src/main/java/org/unicitylabs/sdk/mtree/sum/SparseMerkleSumTreePathStep.java @@ -19,7 +19,7 @@ public class SparseMerkleSumTreePathStep { sibling, branch == null ? null - : new Branch(branch.getValue().getCounter(), branch.getValue().getValue()) + : new Branch(branch.getValue().getValue(), branch.getValue().getCounter()) ); } @@ -29,7 +29,7 @@ public class SparseMerkleSumTreePathStep { path, sibling, branch == null ? null - : new Branch(branch.getCounter(), branch.getChildrenHash().getImprint()) + : new Branch(branch.getChildrenHash().getImprint(), branch.getCounter()) ); } @@ -44,7 +44,7 @@ public class SparseMerkleSumTreePathStep { SparseMerkleSumTreePathStep(BigInteger path, FinalizedBranch sibling, Branch branch) { this( path, - sibling == null ? null : new Branch(sibling.getCounter(), sibling.getHash().getImprint()), + sibling == null ? null : new Branch(sibling.getHash().getImprint(), sibling.getCounter()), branch ); } @@ -95,7 +95,7 @@ public static class Branch { private final byte[] value; private final BigInteger counter; - public Branch(BigInteger counter, byte[] value) { + public Branch(byte[] value, BigInteger counter) { Objects.requireNonNull(counter, "counter cannot be null"); this.value = value == null ? null : Arrays.copyOf(value, value.length); diff --git a/src/main/java/org/unicitylabs/sdk/predicate/BurnPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/BurnPredicate.java deleted file mode 100644 index 416fa9a..0000000 --- a/src/main/java/org/unicitylabs/sdk/predicate/BurnPredicate.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.unicitylabs.sdk.predicate; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.node.ArrayNode; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import org.unicitylabs.sdk.hash.DataHash; -import org.unicitylabs.sdk.hash.DataHasher; -import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.serializer.UnicityObjectMapper; -import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; -import org.unicitylabs.sdk.token.Token; -import org.unicitylabs.sdk.token.TokenId; -import org.unicitylabs.sdk.token.TokenType; -import org.unicitylabs.sdk.transaction.Transaction; -import org.unicitylabs.sdk.transaction.TransferTransactionData; - -public class BurnPredicate implements Predicate { - private final DataHash burnReason; - private final byte[] nonce; - - public BurnPredicate(byte[] nonce, DataHash reason) { - Objects.requireNonNull(nonce, "Nonce cannot be null"); - Objects.requireNonNull(reason, "Burn reason cannot be null"); - - this.burnReason = reason; - this.nonce = Arrays.copyOf(nonce, nonce.length); - } - - public DataHash getReason() { - return this.burnReason; - } - - @Override - public String getType() { - return PredicateType.BURN.name(); - } - - // TODO: Do we need nonce for burn predicate? - @Override - public byte[] getNonce() { - return Arrays.copyOf(this.nonce, this.nonce.length); - } - - @Override - public boolean isOwner(byte[] publicKey) { - return false; - } - - @Override - public boolean verify( - List> transactions, - Token token - ) { - return false; - } - - @Override - public DataHash calculateHash(TokenId tokenId, TokenType tokenType) { - ArrayNode node = UnicityObjectMapper.CBOR.createArrayNode(); - node.addPOJO(this.getReference(tokenType).getHash()); - node.addPOJO(tokenId); - node.add(this.nonce); - - try { - return new DataHasher(HashAlgorithm.SHA256) - .update(UnicityObjectMapper.CBOR.writeValueAsBytes(node)) - .digest(); - } catch (JsonProcessingException e) { - throw new CborSerializationException(e); - } - } - - @Override - public BurnPredicateReference getReference(TokenType tokenType) { - return BurnPredicateReference.create(tokenType, this.burnReason); - } -} diff --git a/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java new file mode 100644 index 0000000..4ac9a76 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/predicate/EncodedPredicate.java @@ -0,0 +1,55 @@ +package org.unicitylabs.sdk.predicate; + +import java.util.Arrays; +import java.util.Objects; +import org.unicitylabs.sdk.util.HexConverter; + +public class EncodedPredicate implements SerializablePredicate { + private final PredicateEngineType engine; + private final byte[] code; + private final byte[] parameters; + + public EncodedPredicate(PredicateEngineType engine, byte[] code, byte[] parameters) { + Objects.requireNonNull(code, "Code must not be null"); + Objects.requireNonNull(parameters, "Parameters must not be null"); + + this.engine = engine; + this.code = Arrays.copyOf(code, code.length); + this.parameters = Arrays.copyOf(parameters, parameters.length); + } + + public PredicateEngineType getEngine() { + return this.engine; + } + + @Override + public byte[] encode() { + return Arrays.copyOf(this.code, this.code.length); + } + + @Override + public byte[] encodeParameters() { + return Arrays.copyOf(this.parameters, this.parameters.length); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof EncodedPredicate)) { + return false; + } + EncodedPredicate predicate = (EncodedPredicate) o; + return this.engine == predicate.engine && Objects.deepEquals(this.code, predicate.code) + && Objects.deepEquals(this.parameters, predicate.parameters); + } + + @Override + public int hashCode() { + return Objects.hash(this.engine, Arrays.hashCode(this.code), Arrays.hashCode(this.parameters)); + } + + @Override + public String toString() { + return String.format("Predicate{engine=%s, code=%s, parameters=%s}", this.engine, + HexConverter.encode(this.code), HexConverter.encode(this.parameters)); + } +} \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java b/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java index 1aacdc9..e70cbde 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java @@ -1,21 +1,17 @@ package org.unicitylabs.sdk.predicate; -import java.util.List; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.token.Token; -import org.unicitylabs.sdk.token.TokenId; -import org.unicitylabs.sdk.token.TokenType; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.transaction.TransferTransactionData; -public interface Predicate { - String getType(); - DataHash calculateHash(TokenId tokenId, TokenType tokenType); - IPredicateReference getReference(TokenType tokenType); - byte[] getNonce(); - boolean isOwner(byte[] publicKey); - boolean verify( - List> transactions, - Token token - ); -} \ No newline at end of file +public interface Predicate extends SerializablePredicate { + DataHash calculateHash(); + + PredicateReference getReference(); + + boolean isOwner(byte[] publicKey); + + boolean verify(Token token, Transaction transaction); +} + diff --git a/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngine.java b/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngine.java new file mode 100644 index 0000000..f700d55 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngine.java @@ -0,0 +1,5 @@ +package org.unicitylabs.sdk.predicate; + +public interface PredicateEngine { + Predicate create(SerializablePredicate predicate); +} diff --git a/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngineService.java b/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngineService.java new file mode 100644 index 0000000..00c32be --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngineService.java @@ -0,0 +1,24 @@ +package org.unicitylabs.sdk.predicate; + +import java.util.HashMap; +import org.unicitylabs.sdk.predicate.embedded.EmbeddedPredicateEngine; + +public class PredicateEngineService { + + private static final HashMap ENGINES = new HashMap<>() { + { + put(PredicateEngineType.EMBEDDED, new EmbeddedPredicateEngine()); + } + }; + + public static Predicate createPredicate(SerializablePredicate predicate) { + PredicateEngine engine = PredicateEngineService.ENGINES.get(predicate.getEngine()); + if (engine == null) { + throw new IllegalArgumentException("Unsupported predicate engine type: " + predicate.getEngine()); + } + + return engine.create(predicate); + } + + +} diff --git a/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngineType.java b/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngineType.java new file mode 100644 index 0000000..afef11a --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngineType.java @@ -0,0 +1,6 @@ + +package org.unicitylabs.sdk.predicate; + +public enum PredicateEngineType { + EMBEDDED, +} diff --git a/src/main/java/org/unicitylabs/sdk/predicate/IPredicateReference.java b/src/main/java/org/unicitylabs/sdk/predicate/PredicateReference.java similarity index 82% rename from src/main/java/org/unicitylabs/sdk/predicate/IPredicateReference.java rename to src/main/java/org/unicitylabs/sdk/predicate/PredicateReference.java index 01c35e6..6ede73e 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/IPredicateReference.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/PredicateReference.java @@ -3,7 +3,7 @@ import org.unicitylabs.sdk.address.Address; import org.unicitylabs.sdk.hash.DataHash; -public interface IPredicateReference { +public interface PredicateReference { DataHash getHash(); Address toAddress(); } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/PredicateType.java b/src/main/java/org/unicitylabs/sdk/predicate/PredicateType.java deleted file mode 100644 index 0be13ed..0000000 --- a/src/main/java/org/unicitylabs/sdk/predicate/PredicateType.java +++ /dev/null @@ -1,8 +0,0 @@ - -package org.unicitylabs.sdk.predicate; - -public enum PredicateType { - UNMASKED, - MASKED, - BURN -} diff --git a/src/main/java/org/unicitylabs/sdk/predicate/SerializablePredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/SerializablePredicate.java new file mode 100644 index 0000000..0fdc4b8 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/predicate/SerializablePredicate.java @@ -0,0 +1,9 @@ +package org.unicitylabs.sdk.predicate; + +public interface SerializablePredicate { + PredicateEngineType getEngine(); + + byte[] encode(); + + byte[] encodeParameters(); +} diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java new file mode 100644 index 0000000..52b89c5 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java @@ -0,0 +1,101 @@ +package org.unicitylabs.sdk.predicate.embedded; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ArrayNode; +import java.util.Objects; +import org.unicitylabs.sdk.hash.DataHash; +import org.unicitylabs.sdk.hash.DataHasher; +import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.predicate.Predicate; +import org.unicitylabs.sdk.predicate.PredicateEngineType; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; +import org.unicitylabs.sdk.token.Token; +import org.unicitylabs.sdk.token.TokenId; +import org.unicitylabs.sdk.token.TokenType; +import org.unicitylabs.sdk.transaction.InclusionProof; +import org.unicitylabs.sdk.transaction.Transaction; +import org.unicitylabs.sdk.transaction.TransferTransactionData; + +public class BurnPredicate implements Predicate { + + private final TokenId tokenId; + private final TokenType tokenType; + private final DataHash burnReason; + + public BurnPredicate(TokenId tokenId, TokenType tokenType, DataHash reason) { + Objects.requireNonNull(tokenId, "Token id cannot be null"); + Objects.requireNonNull(tokenType, "Token type cannot be null"); + Objects.requireNonNull(reason, "Burn reason cannot be null"); + + this.tokenId = tokenId; + this.tokenType = tokenType; + this.burnReason = reason; + } + + public TokenId getTokenId() { + return this.tokenId; + } + + public TokenType getTokenType() { + return this.tokenType; + } + + public DataHash getReason() { + return this.burnReason; + } + + @Override + public boolean isOwner(byte[] publicKey) { + return false; + } + + @Override + public boolean verify(Token token, Transaction transaction) { + return false; + } + + @Override + public DataHash calculateHash() { + ArrayNode node = UnicityObjectMapper.CBOR.createArrayNode(); + node.addPOJO(this.getReference().getHash()); + node.addPOJO(this.tokenId); + + try { + return new DataHasher(HashAlgorithm.SHA256) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(node)) + .digest(); + } catch (JsonProcessingException e) { + throw new CborSerializationException(e); + } + } + + @Override + public BurnPredicateReference getReference() { + return BurnPredicateReference.create(this.tokenType, this.burnReason); + } + + @Override + public PredicateEngineType getEngine() { + return PredicateEngineType.EMBEDDED; + } + + @Override + public byte[] encode() { + return EmbeddedPredicateType.BURN.getBytes(); + } + + @Override + public byte[] encodeParameters() { + try { + return UnicityObjectMapper.CBOR.writeValueAsBytes( + UnicityObjectMapper.CBOR.createArrayNode() + .addPOJO(this.tokenId) + .addPOJO(this.tokenType) + .addPOJO(this.burnReason) + ); + } catch (JsonProcessingException e) { + throw new CborSerializationException(e); + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/predicate/BurnPredicateReference.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicateReference.java similarity index 84% rename from src/main/java/org/unicitylabs/sdk/predicate/BurnPredicateReference.java rename to src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicateReference.java index 47e4975..d211310 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/BurnPredicateReference.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicateReference.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.predicate; +package org.unicitylabs.sdk.predicate.embedded; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -6,11 +6,12 @@ import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.predicate.PredicateReference; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.token.TokenType; -public class BurnPredicateReference implements IPredicateReference { +public class BurnPredicateReference implements PredicateReference { private final DataHash hash; @@ -24,7 +25,7 @@ public DataHash getHash() { public static BurnPredicateReference create(TokenType tokenType, DataHash burnReason) { ArrayNode node = UnicityObjectMapper.CBOR.createArrayNode(); - node.add(PredicateType.BURN.name()); + node.add(EmbeddedPredicateType.BURN.name()); node.addPOJO(tokenType); node.addPOJO(burnReason); diff --git a/src/main/java/org/unicitylabs/sdk/predicate/DefaultPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java similarity index 56% rename from src/main/java/org/unicitylabs/sdk/predicate/DefaultPredicate.java rename to src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java index 5ce857a..27ecb11 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/DefaultPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.predicate; +package org.unicitylabs.sdk.predicate.embedded; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -10,10 +10,16 @@ import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.predicate.EncodedPredicate; +import org.unicitylabs.sdk.predicate.Predicate; +import org.unicitylabs.sdk.predicate.PredicateEngineType; +import org.unicitylabs.sdk.predicate.PredicateReference; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenType; +import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.transaction.InclusionProofVerificationStatus; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.transaction.TransferTransactionData; @@ -24,33 +30,49 @@ */ public abstract class DefaultPredicate implements Predicate { - private final PredicateType type; + private final EmbeddedPredicateType type; + private final TokenId tokenId; + private final TokenType tokenType; private final byte[] publicKey; private final String signingAlgorithm; private final HashAlgorithm hashAlgorithm; private final byte[] nonce; protected DefaultPredicate( - PredicateType type, + EmbeddedPredicateType type, + TokenId tokenId, + TokenType tokenType, byte[] publicKey, String signingAlgorithm, HashAlgorithm hashAlgorithm, byte[] nonce) { Objects.requireNonNull(type, "Predicate type cannot be null"); + Objects.requireNonNull(tokenId, "TokenId cannot be null"); + Objects.requireNonNull(tokenType, "TokenType cannot be null"); Objects.requireNonNull(publicKey, "Public key cannot be null"); Objects.requireNonNull(signingAlgorithm, "Signing algorithm cannot be null"); Objects.requireNonNull(hashAlgorithm, "Hash algorithm cannot be null"); Objects.requireNonNull(nonce, "Nonce cannot be null"); this.type = type; + this.tokenId = tokenId; + this.tokenType = tokenType; this.publicKey = Arrays.copyOf(publicKey, publicKey.length); this.signingAlgorithm = signingAlgorithm; this.hashAlgorithm = hashAlgorithm; this.nonce = Arrays.copyOf(nonce, nonce.length); } - public String getType() { - return this.type.name(); + public EmbeddedPredicateType getType() { + return this.type; + } + + public TokenId getTokenId() { + return this.tokenId; + } + + public TokenType getTokenType() { + return this.tokenType; } public byte[] getPublicKey() { @@ -70,10 +92,10 @@ public byte[] getNonce() { } @Override - public DataHash calculateHash(TokenId tokenId, TokenType tokenType) { + public DataHash calculateHash() { ArrayNode node = UnicityObjectMapper.CBOR.createArrayNode(); - node.addPOJO(this.getReference(tokenType).getHash()); - node.addPOJO(tokenId); + node.addPOJO(this.getReference().getHash()); + node.addPOJO(this.tokenId); node.add(this.getNonce()); try { @@ -85,7 +107,7 @@ public DataHash calculateHash(TokenId tokenId, TokenType tokenType) { } } - public abstract IPredicateReference getReference(TokenType tokenType); + public abstract PredicateReference getReference(); @Override public boolean isOwner(byte[] publicKey) { @@ -93,16 +115,14 @@ public boolean isOwner(byte[] publicKey) { } @Override - public boolean verify( - List> transactions, - Token token - ) { - Transaction transaction = transactions.get(transactions.size() - 1); + public boolean verify(Token token, Transaction transaction) { + if (!this.tokenId.equals(token.getId()) || !this.tokenType.equals(token.getType())) { + return false; + } Authenticator authenticator = transaction.getInclusionProof().getAuthenticator().orElse(null); - DataHash transactionHash = transaction.getInclusionProof().getTransactionHash().orElse(null); - if (authenticator == null || transactionHash == null) { + if (authenticator == null) { return false; } @@ -110,22 +130,54 @@ public boolean verify( return false; } - if (!authenticator.verify(transaction.getData().calculateHash(token.getId(), token.getType()))) { + DataHash transactionHash = transaction.getData().calculateHash(); + if (!authenticator.verify(transactionHash)) { return false; } - RequestId requestId = RequestId.create(this.publicKey, - transaction.getData().getSourceState().calculateHash(token.getId(), token.getType())); + RequestId requestId = RequestId.create( + this.publicKey, + transaction.getData().getSourceState().calculateHash() + ); return transaction.getInclusionProof().verify(requestId) == InclusionProofVerificationStatus.OK; } + @Override + public PredicateEngineType getEngine() { + return PredicateEngineType.EMBEDDED; + } + + @Override + public byte[] encode() { + return this.type.getBytes(); + } + + @Override + public byte[] encodeParameters() { + try { + return UnicityObjectMapper.CBOR.writeValueAsBytes( + UnicityObjectMapper.CBOR.createArrayNode() + .addPOJO(this.tokenId) + .addPOJO(this.tokenType) + .add(this.publicKey) + .add(this.signingAlgorithm) + .addPOJO(this.hashAlgorithm) + .add(this.nonce) + ); + } catch (JsonProcessingException e) { + throw new CborSerializationException(e); + } + } + @Override public boolean equals(Object o) { if (!(o instanceof DefaultPredicate)) { return false; } DefaultPredicate that = (DefaultPredicate) o; - return type == that.type && Objects.deepEquals(this.publicKey, that.publicKey) + return this.type == that.type && Objects.equals(this.tokenId, that.tokenId) + && Objects.equals(this.tokenType, that.tokenType) + && Objects.deepEquals(this.publicKey, that.publicKey) && Objects.equals(this.signingAlgorithm, that.signingAlgorithm) && this.hashAlgorithm == that.hashAlgorithm && Arrays.equals(this.nonce, that.nonce); @@ -133,15 +185,17 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(this.type, Arrays.hashCode(this.publicKey), this.signingAlgorithm, - this.hashAlgorithm, Arrays.hashCode(nonce)); + return Objects.hash(this.type, this.tokenId, this.tokenType, Arrays.hashCode(this.publicKey), + this.signingAlgorithm, this.hashAlgorithm, Arrays.hashCode(nonce)); } @Override public String toString() { return String.format( - "DefaultPredicate{type=%s, publicKey=%s, algorithm=%s, hashAlgorithm=%s, nonce=%s}", + "DefaultPredicate{type=%s, tokenId=%s, tokenType=%s, publicKey=%s, algorithm=%s, hashAlgorithm=%s, nonce=%s}", this.type, + this.tokenId, + this.tokenType, HexConverter.encode(this.publicKey), this.signingAlgorithm, this.hashAlgorithm, diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateEngine.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateEngine.java new file mode 100644 index 0000000..47e1e56 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateEngine.java @@ -0,0 +1,37 @@ +package org.unicitylabs.sdk.predicate.embedded; + +import java.io.IOException; +import org.unicitylabs.sdk.predicate.Predicate; +import org.unicitylabs.sdk.predicate.PredicateEngine; +import org.unicitylabs.sdk.predicate.SerializablePredicate; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; + +public class EmbeddedPredicateEngine implements PredicateEngine { + + public Predicate create(SerializablePredicate predicate) { + try { + EmbeddedPredicateType type = EmbeddedPredicateType.fromBytes(predicate.encode()); + switch (type) { + case MASKED: + return UnicityObjectMapper.CBOR.readValue( + predicate.encodeParameters(), + MaskedPredicate.class + ); + case UNMASKED: + return UnicityObjectMapper.CBOR.readValue( + predicate.encodeParameters(), + UnmaskedPredicate.class + ); + case BURN: + return UnicityObjectMapper.CBOR.readValue( + predicate.encodeParameters(), + BurnPredicate.class + ); + default: + throw new IllegalArgumentException("Unknown predicate type: " + type); + } + } catch (IOException e) { + throw new RuntimeException("Failed to create predicate", e); + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateType.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateType.java new file mode 100644 index 0000000..2f40181 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateType.java @@ -0,0 +1,32 @@ + +package org.unicitylabs.sdk.predicate.embedded; + +import java.util.Arrays; +import org.unicitylabs.sdk.util.HexConverter; + +public enum EmbeddedPredicateType { + UNMASKED(new byte[] {0x0}), + MASKED(new byte[] {0x1}), + BURN(new byte[] {0x2}); + + private final byte[] bytes; + + EmbeddedPredicateType(byte[] bytes) { + this.bytes = bytes; + } + + public byte[] getBytes() { + return Arrays.copyOf(this.bytes, this.bytes.length); + } + + public static EmbeddedPredicateType fromBytes(byte[] bytes) { + for (EmbeddedPredicateType type : EmbeddedPredicateType.values()) { + if (Arrays.equals(bytes, type.getBytes())) { + return type; + } + } + + throw new RuntimeException("Invalid embedded predicate type"); + } + +} diff --git a/src/main/java/org/unicitylabs/sdk/predicate/MaskedPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/MaskedPredicate.java similarity index 65% rename from src/main/java/org/unicitylabs/sdk/predicate/MaskedPredicate.java rename to src/main/java/org/unicitylabs/sdk/predicate/embedded/MaskedPredicate.java index fc689a4..5f5f54f 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/MaskedPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/MaskedPredicate.java @@ -1,18 +1,22 @@ -package org.unicitylabs.sdk.predicate; +package org.unicitylabs.sdk.predicate.embedded; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.signing.SigningService; +import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenType; public class MaskedPredicate extends DefaultPredicate { - public MaskedPredicate( + TokenId tokenId, + TokenType tokenType, byte[] publicKey, String signingAlgorithm, HashAlgorithm hashAlgorithm, byte[] nonce) { super( - PredicateType.MASKED, + EmbeddedPredicateType.MASKED, + tokenId, + tokenType, publicKey, signingAlgorithm, hashAlgorithm, @@ -21,17 +25,19 @@ public MaskedPredicate( } public static MaskedPredicate create( + TokenId tokenId, + TokenType tokenType, SigningService signingService, HashAlgorithm hashAlgorithm, byte[] nonce) { - return new MaskedPredicate(signingService.getPublicKey(), signingService.getAlgorithm(), + return new MaskedPredicate(tokenId, tokenType, signingService.getPublicKey(), signingService.getAlgorithm(), hashAlgorithm, nonce); } @Override - public MaskedPredicateReference getReference(TokenType tokenType) { + public MaskedPredicateReference getReference() { return MaskedPredicateReference.create( - tokenType, + this.getTokenType(), this.getSigningAlgorithm(), this.getPublicKey(), this.getHashAlgorithm(), diff --git a/src/main/java/org/unicitylabs/sdk/predicate/MaskedPredicateReference.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/MaskedPredicateReference.java similarity index 88% rename from src/main/java/org/unicitylabs/sdk/predicate/MaskedPredicateReference.java rename to src/main/java/org/unicitylabs/sdk/predicate/embedded/MaskedPredicateReference.java index f2eb55c..27be0a9 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/MaskedPredicateReference.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/MaskedPredicateReference.java @@ -1,8 +1,9 @@ -package org.unicitylabs.sdk.predicate; +package org.unicitylabs.sdk.predicate.embedded; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; import org.unicitylabs.sdk.address.DirectAddress; +import org.unicitylabs.sdk.predicate.PredicateReference; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; @@ -11,7 +12,7 @@ import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.TokenType; -public class MaskedPredicateReference implements IPredicateReference { +public class MaskedPredicateReference implements PredicateReference { private final DataHash hash; @@ -26,7 +27,7 @@ public DataHash getHash() { public static MaskedPredicateReference create(TokenType tokenType, String signingAlgorithm, byte[] publicKey, HashAlgorithm hashAlgorithm, byte[] nonce) { ArrayNode node = UnicityObjectMapper.CBOR.createArrayNode(); - node.add(PredicateType.MASKED.name()); + node.add(EmbeddedPredicateType.MASKED.name()); node.addPOJO(tokenType); node.add(signingAlgorithm); node.addPOJO(hashAlgorithm); diff --git a/src/main/java/org/unicitylabs/sdk/predicate/UnmaskedPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java similarity index 55% rename from src/main/java/org/unicitylabs/sdk/predicate/UnmaskedPredicate.java rename to src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java index 2d2f182..b48ac8e 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/UnmaskedPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java @@ -1,27 +1,38 @@ -package org.unicitylabs.sdk.predicate; +package org.unicitylabs.sdk.predicate.embedded; -import java.util.List; +import com.fasterxml.jackson.core.JsonProcessingException; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.predicate.PredicateEngineType; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.signing.Signature; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.Token; +import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenType; +import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.transaction.TransferTransactionData; public class UnmaskedPredicate extends DefaultPredicate { public UnmaskedPredicate( + TokenId tokenId, + TokenType tokenType, byte[] publicKey, String signingAlgorithm, HashAlgorithm hashAlgorithm, - byte[] nonce) { - super(PredicateType.UNMASKED, publicKey, signingAlgorithm, hashAlgorithm, nonce); + byte[] nonce + ) { + super(EmbeddedPredicateType.UNMASKED, tokenId, tokenType, publicKey, signingAlgorithm, + hashAlgorithm, nonce); } public static UnmaskedPredicate create( + TokenId tokenId, + TokenType tokenType, SigningService signingService, HashAlgorithm hashAlgorithm, byte[] salt @@ -30,6 +41,8 @@ public static UnmaskedPredicate create( new DataHasher(HashAlgorithm.SHA256).update(salt).digest()); return new UnmaskedPredicate( + tokenId, + tokenType, signingService.getPublicKey(), signingService.getAlgorithm(), hashAlgorithm, @@ -37,16 +50,13 @@ public static UnmaskedPredicate create( } @Override - public boolean verify( - List> transactions, - Token token - ) { - return super.verify(transactions, token) && SigningService.verifyWithPublicKey( + public boolean verify(Token token, Transaction transaction) { + return super.verify(token, transaction) && SigningService.verifyWithPublicKey( new DataHasher(HashAlgorithm.SHA256) .update( - transactions.size() > 1 - ? transactions.get(transactions.size() - 2).getData().getSalt() - : token.getGenesis().getData().getSalt() + token.getTransactions().isEmpty() + ? token.getGenesis().getData().getSalt() + : token.getTransactions().getLast().getData().getSalt() ) .digest(), this.getNonce(), @@ -54,9 +64,9 @@ public boolean verify( ); } - public UnmaskedPredicateReference getReference(TokenType tokenType) { + public UnmaskedPredicateReference getReference() { return UnmaskedPredicateReference.create( - tokenType, + this.getTokenType(), this.getSigningAlgorithm(), this.getPublicKey(), this.getHashAlgorithm() diff --git a/src/main/java/org/unicitylabs/sdk/predicate/UnmaskedPredicateReference.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicateReference.java similarity index 88% rename from src/main/java/org/unicitylabs/sdk/predicate/UnmaskedPredicateReference.java rename to src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicateReference.java index ae85e9e..9aa0719 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/UnmaskedPredicateReference.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicateReference.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.predicate; +package org.unicitylabs.sdk.predicate.embedded; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -6,12 +6,13 @@ import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.predicate.PredicateReference; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.TokenType; -public class UnmaskedPredicateReference implements IPredicateReference { +public class UnmaskedPredicateReference implements PredicateReference { private final DataHash hash; @@ -26,7 +27,7 @@ public DataHash getHash() { public static UnmaskedPredicateReference create(TokenType tokenType, String signingAlgorithm, byte[] publicKey, HashAlgorithm hashAlgorithm) { ArrayNode node = UnicityObjectMapper.CBOR.createArrayNode(); - node.add(PredicateType.UNMASKED.name()); + node.add(EmbeddedPredicateType.UNMASKED.name()); node.addPOJO(tokenType); node.add(signingAlgorithm); node.addPOJO(hashAlgorithm); diff --git a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java index d784f94..0406f6f 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java @@ -24,10 +24,11 @@ import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePathStep; import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTreePath; import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTreePathStep; -import org.unicitylabs.sdk.predicate.BurnPredicate; -import org.unicitylabs.sdk.predicate.MaskedPredicate; +import org.unicitylabs.sdk.predicate.SerializablePredicate; +import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; import org.unicitylabs.sdk.predicate.Predicate; -import org.unicitylabs.sdk.predicate.UnmaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; import org.unicitylabs.sdk.serializer.cbor.address.AddressCbor; import org.unicitylabs.sdk.serializer.cbor.api.AuthenticatorCbor; import org.unicitylabs.sdk.serializer.cbor.bft.InputRecordCbor; @@ -76,10 +77,7 @@ import org.unicitylabs.sdk.serializer.json.mtree.sum.SparseMerkleSumTreePathJson; import org.unicitylabs.sdk.serializer.json.mtree.sum.SparseMerkleSumTreePathStepBranchJson; import org.unicitylabs.sdk.serializer.json.mtree.sum.SparseMerkleSumTreePathStepJson; -import org.unicitylabs.sdk.serializer.json.predicate.BurnPredicateJson; -import org.unicitylabs.sdk.serializer.json.predicate.MaskedPredicateJson; import org.unicitylabs.sdk.serializer.json.predicate.PredicateJson; -import org.unicitylabs.sdk.serializer.json.predicate.UnmaskedPredicateJson; import org.unicitylabs.sdk.serializer.json.token.fungible.TokenCoinDataJson; import org.unicitylabs.sdk.serializer.json.token.TokenIdJson; import org.unicitylabs.sdk.serializer.json.token.TokenJson; @@ -190,14 +188,14 @@ private static ObjectMapper createCborObjectMapper() { module.addSerializer(TokenState.class, new TokenStateCbor.Serializer()); module.addDeserializer(TokenState.class, new TokenStateCbor.Deserializer()); - module.addDeserializer(Predicate.class, new PredicateCbor.Deserializer()); - module.addSerializer(MaskedPredicate.class, new MaskedPredicateCbor.Serializer()); + module.addSerializer(SerializablePredicate.class, new PredicateCbor.Serializer()); + module.addDeserializer(SerializablePredicate.class, new PredicateCbor.Deserializer()); + module.addDeserializer(MaskedPredicate.class, new MaskedPredicateCbor.Deserializer()); - module.addSerializer(UnmaskedPredicate.class, new UnmaskedPredicateCbor.Serializer()); module.addDeserializer(UnmaskedPredicate.class, new UnmaskedPredicateCbor.Deserializer()); - module.addSerializer(BurnPredicate.class, new BurnPredicateCbor.Serializer()); module.addDeserializer(BurnPredicate.class, new BurnPredicateCbor.Deserializer()); + // BFT - UnicityCertificate module.addSerializer(UnicityCertificate.class, new UnicityCertificateCbor.Serializer()); module.addDeserializer(UnicityCertificate.class, new UnicityCertificateCbor.Deserializer()); @@ -284,13 +282,8 @@ private static ObjectMapper createJsonObjectMapper() { module.addSerializer(TokenState.class, new TokenStateJson.Serializer()); module.addDeserializer(TokenState.class, new TokenStateJson.Deserializer()); - module.addDeserializer(Predicate.class, new PredicateJson.Deserializer()); - module.addSerializer(MaskedPredicate.class, new MaskedPredicateJson.Serializer()); - module.addDeserializer(MaskedPredicate.class, new MaskedPredicateJson.Deserializer()); - module.addSerializer(UnmaskedPredicate.class, new UnmaskedPredicateJson.Serializer()); - module.addDeserializer(UnmaskedPredicate.class, new UnmaskedPredicateJson.Deserializer()); - module.addSerializer(BurnPredicate.class, new BurnPredicateJson.Serializer()); - module.addDeserializer(BurnPredicate.class, new BurnPredicateJson.Deserializer()); + module.addSerializer(SerializablePredicate.class, new PredicateJson.Serializer()); + module.addDeserializer(SerializablePredicate.class, new PredicateJson.Deserializer()); module.addSerializer(Transaction.class, new TransactionJson.Serializer()); module.addDeserializer(Transaction.class, new TransactionJson.Deserializer()); diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/mtree/sum/SparseMerkleSumTreePathStepBranchCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/mtree/sum/SparseMerkleSumTreePathStepBranchCbor.java index 7b38c0d..3a81394 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/mtree/sum/SparseMerkleSumTreePathStepBranchCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/mtree/sum/SparseMerkleSumTreePathStepBranchCbor.java @@ -28,8 +28,8 @@ public void serialize(Branch value, JsonGenerator gen, } gen.writeStartArray(value, 2); - gen.writeObject(BigIntegerConverter.encode(value.getCounter())); gen.writeObject(value.getValue()); + gen.writeObject(BigIntegerConverter.encode(value.getCounter())); gen.writeEndArray(); } } @@ -46,8 +46,8 @@ public Branch deserialize(JsonParser p, DeserializationContext ctx) p.nextToken(); Branch branch = new Branch( - BigIntegerConverter.decode(p.readValueAs(byte[].class)), - p.readValueAs(byte[].class) + p.readValueAs(byte[].class), + BigIntegerConverter.decode(p.readValueAs(byte[].class)) ); if (p.nextToken() != JsonToken.END_ARRAY) { throw MismatchedInputException.from(p, Branch.class, diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/BurnPredicateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/BurnPredicateCbor.java index 0620d9a..ba2ff63 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/BurnPredicateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/BurnPredicateCbor.java @@ -1,41 +1,21 @@ package org.unicitylabs.sdk.serializer.cbor.predicate; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import org.unicitylabs.sdk.hash.DataHash; -import org.unicitylabs.sdk.predicate.BurnPredicate; -import org.unicitylabs.sdk.predicate.PredicateType; import java.io.IOException; +import org.unicitylabs.sdk.hash.DataHash; +import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; +import org.unicitylabs.sdk.token.TokenId; +import org.unicitylabs.sdk.token.TokenType; -public class BurnPredicateCbor { +public class BurnPredicateCbor { private BurnPredicateCbor() { } - public static class Serializer extends JsonSerializer { - - @Override - public void serialize(BurnPredicate value, JsonGenerator gen, - SerializerProvider serializers) - throws IOException { - if (value == null) { - gen.writeNull(); - return; - } - - gen.writeStartArray(value, 3); - gen.writeObject(value.getType()); - gen.writeObject(value.getNonce()); - gen.writeObject(value.getReason()); - gen.writeEndArray(); - } - } - public static class Deserializer extends JsonDeserializer { @@ -45,14 +25,17 @@ public BurnPredicate deserialize(JsonParser p, DeserializationContext ctx) if (!p.isExpectedStartArrayToken()) { throw MismatchedInputException.from(p, BurnPredicate.class, "Expected array value"); } + p.nextToken(); + + TokenId tokenId = p.readValueAs(TokenId.class); + TokenType tokenType = p.readValueAs(TokenType.class); + DataHash reason = p.readValueAs(DataHash.class); - String type = p.readValueAs(String.class); - if (!PredicateType.BURN.name().equals(type)) { - throw MismatchedInputException.from(p, BurnPredicate.class, - "Expected predicate type to be " + PredicateType.BURN); + if (p.nextToken() != JsonToken.END_ARRAY) { + throw MismatchedInputException.from(p, BurnPredicate.class, "Expected end of array"); } - return new BurnPredicate(p.readValueAs(byte[].class), p.readValueAs(DataHash.class)); + return new BurnPredicate(tokenId, tokenType, reason); } } } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/MaskedPredicateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/MaskedPredicateCbor.java index 62fa372..cb5f851 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/MaskedPredicateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/MaskedPredicateCbor.java @@ -1,42 +1,23 @@ package org.unicitylabs.sdk.serializer.cbor.predicate; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.MaskedPredicate; -import org.unicitylabs.sdk.predicate.PredicateType; import java.io.IOException; +import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; +import org.unicitylabs.sdk.token.TokenId; +import org.unicitylabs.sdk.token.TokenType; + public class MaskedPredicateCbor { private MaskedPredicateCbor() { } - public static class Serializer extends JsonSerializer { - - @Override - public void serialize(MaskedPredicate value, JsonGenerator gen, SerializerProvider serializers) - throws IOException { - if (value == null) { - gen.writeNull(); - return; - } - - gen.writeStartArray(value, 5); - gen.writeObject(value.getType()); - gen.writeObject(value.getPublicKey()); - gen.writeObject(value.getSigningAlgorithm()); - gen.writeObject(value.getHashAlgorithm().getValue()); - gen.writeObject(value.getNonce()); - gen.writeEndArray(); - } - } - public static class Deserializer extends JsonDeserializer { @@ -46,18 +27,26 @@ public MaskedPredicate deserialize(JsonParser p, DeserializationContext ctx) if (!p.isExpectedStartArrayToken()) { throw MismatchedInputException.from(p, MaskedPredicate.class, "Expected array value"); } + p.nextToken(); + + TokenId tokenId = p.readValueAs(TokenId.class); + TokenType tokenType = p.readValueAs(TokenType.class); + byte[] publicKey = p.readValueAs(byte[].class); + String signingAlgorithm = p.readValueAs(String.class); + HashAlgorithm hashAlgorithm = p.readValueAs(HashAlgorithm.class); + byte[] nonce = p.readValueAs(byte[].class); - String type = p.readValueAs(String.class); - if (!PredicateType.MASKED.name().equals(type)) { - throw MismatchedInputException.from(p, MaskedPredicate.class, - "Expected predicate type to be " + PredicateType.MASKED); + if (p.nextToken() != JsonToken.END_ARRAY) { + throw MismatchedInputException.from(p, MaskedPredicate.class, "Expected end of array"); } return new MaskedPredicate( - p.readValueAs(byte[].class), - p.readValueAs(String.class), - p.readValueAs(HashAlgorithm.class), - p.readValueAs(byte[].class) + tokenId, + tokenType, + publicKey, + signingAlgorithm, + hashAlgorithm, + nonce ); } } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/PredicateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/PredicateCbor.java index 711834f..d4d70b8 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/PredicateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/PredicateCbor.java @@ -1,15 +1,22 @@ package org.unicitylabs.sdk.serializer.cbor.predicate; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import org.unicitylabs.sdk.predicate.BurnPredicate; -import org.unicitylabs.sdk.predicate.MaskedPredicate; +import org.unicitylabs.sdk.predicate.EncodedPredicate; +import org.unicitylabs.sdk.predicate.PredicateEngineType; +import org.unicitylabs.sdk.predicate.SerializablePredicate; +import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; import org.unicitylabs.sdk.predicate.Predicate; -import org.unicitylabs.sdk.predicate.PredicateType; -import org.unicitylabs.sdk.predicate.UnmaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.EmbeddedPredicateType; +import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; import java.io.IOException; public class PredicateCbor { @@ -17,44 +24,43 @@ public class PredicateCbor { private PredicateCbor() { } - public static class Deserializer extends JsonDeserializer { + public static class Serializer extends JsonSerializer { @Override - public Predicate deserialize(JsonParser p, DeserializationContext ctx) throws IOException { - if (!p.isExpectedStartArrayToken()) { - throw MismatchedInputException.from(p, Predicate.class, "Expected array value"); + public void serialize(SerializablePredicate value, JsonGenerator gen, + SerializerProvider serializers) + throws IOException { + if (value == null) { + gen.writeNull(); + return; } - JsonNode node = p.getCodec().readTree(p); - JsonNode typeNode = node.get(0); + gen.writeStartArray(value, 3); + gen.writeObject(value.getEngine().ordinal()); + gen.writeObject(value.encode()); + gen.writeObject(value.encodeParameters()); + gen.writeEndArray(); + } + } + + public static class Deserializer extends JsonDeserializer { - if (typeNode == null) { - throw MismatchedInputException.from(p, Predicate.class, "Missing predicate 'type' field"); + @Override + public SerializablePredicate deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + if (!p.isExpectedStartArrayToken()) { + throw MismatchedInputException.from(p, Predicate.class, "Expected array value"); } - switch (PredicateType.valueOf(typeNode.asText())) { - case MASKED: - { - JsonParser parser = node.traverse(p.getCodec()); - parser.nextToken(); - return ctx.readValue(parser, MaskedPredicate.class); - } - case UNMASKED: - { - JsonParser parser = node.traverse(p.getCodec()); - parser.nextToken(); - return ctx.readValue(parser, UnmaskedPredicate.class); - } - case BURN: { - JsonParser parser = node.traverse(p.getCodec()); - parser.nextToken(); - return ctx.readValue(parser, BurnPredicate.class); - } - default: - p.skipChildren(); + p.nextToken(); + PredicateEngineType engine = p.readValueAs(PredicateEngineType.class); + byte[] code = p.readValueAs(byte[].class); + byte[] parameters = p.readValueAs(byte[].class); + + if (p.nextToken() != JsonToken.END_ARRAY) { + throw MismatchedInputException.from(p, Predicate.class, "Expected end of array"); } - return null; + return new EncodedPredicate(engine, code, parameters); } } } \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/UnmaskedPredicateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/UnmaskedPredicateCbor.java index 8b96e43..9a510db 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/UnmaskedPredicateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/UnmaskedPredicateCbor.java @@ -1,40 +1,22 @@ package org.unicitylabs.sdk.serializer.cbor.predicate; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.PredicateType; -import org.unicitylabs.sdk.predicate.UnmaskedPredicate; import java.io.IOException; +import org.unicitylabs.sdk.hash.DataHash; +import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; +import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; +import org.unicitylabs.sdk.token.TokenId; +import org.unicitylabs.sdk.token.TokenType; -public class UnmaskedPredicateCbor { - private UnmaskedPredicateCbor() { - } - - public static class Serializer extends JsonSerializer { - @Override - public void serialize(UnmaskedPredicate value, JsonGenerator gen, - SerializerProvider serializers) - throws IOException { - if (value == null) { - gen.writeNull(); - return; - } +public class UnmaskedPredicateCbor { - gen.writeStartArray(value, 5); - gen.writeObject(value.getType()); - gen.writeObject(value.getPublicKey()); - gen.writeObject(value.getSigningAlgorithm()); - gen.writeObject(value.getHashAlgorithm().getValue()); - gen.writeObject(value.getNonce()); - gen.writeEndArray(); - } + private UnmaskedPredicateCbor() { } public static class Deserializer extends @@ -46,18 +28,26 @@ public UnmaskedPredicate deserialize(JsonParser p, DeserializationContext ctx) if (!p.isExpectedStartArrayToken()) { throw MismatchedInputException.from(p, UnmaskedPredicate.class, "Expected array value"); } + p.nextToken(); + + TokenId tokenId = p.readValueAs(TokenId.class); + TokenType tokenType = p.readValueAs(TokenType.class); + byte[] publicKey = p.readValueAs(byte[].class); + String signingAlgorithm = p.readValueAs(String.class); + HashAlgorithm hashAlgorithm = p.readValueAs(HashAlgorithm.class); + byte[] nonce = p.readValueAs(byte[].class); - String type = p.readValueAs(String.class); - if (!PredicateType.UNMASKED.name().equals(type)) { - throw MismatchedInputException.from(p, UnmaskedPredicate.class, - "Expected predicate type to be " + PredicateType.UNMASKED); + if (p.nextToken() != JsonToken.END_ARRAY) { + throw MismatchedInputException.from(p, UnmaskedPredicate.class, "Expected end of array"); } return new UnmaskedPredicate( - p.readValueAs(byte[].class), - p.readValueAs(String.class), - p.readValueAs(HashAlgorithm.class), - p.readValueAs(byte[].class) + tokenId, + tokenType, + publicKey, + signingAlgorithm, + hashAlgorithm, + nonce ); } } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/token/TokenCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/token/TokenCbor.java index 5e58d21..1f13c50 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/token/TokenCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/token/TokenCbor.java @@ -55,6 +55,12 @@ public Token> deserialize(JsonParser p, throw MismatchedInputException.from(p, Token.class, "Expected array value"); } + String version = p.readValueAs(String.class); + if (!Token.TOKEN_VERSION.equals(version)) { + throw MismatchedInputException.from(p, Token.class, + "Expected version to be " + Token.TOKEN_VERSION); + } + return new Token<>( p.readValueAs(TokenState.class), ctx.readValue(p, ctx.getTypeFactory().constructParametricType(Transaction.class, MintTransactionData.class)), diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/token/TokenStateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/token/TokenStateCbor.java index a3c26f8..750b792 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/token/TokenStateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/token/TokenStateCbor.java @@ -27,7 +27,7 @@ public void serialize(TokenState value, JsonGenerator gen, SerializerProvider se } gen.writeStartArray(value, 2); - gen.writeObject(value.getUnlockPredicate()); + gen.writeObject(value.getPredicate()); gen.writeObject(value.getData()); gen.writeEndArray(); } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/transaction/split/SplitMintReasonCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/transaction/split/SplitMintReasonCbor.java index 6305d73..0a5888c 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/transaction/split/SplitMintReasonCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/transaction/split/SplitMintReasonCbor.java @@ -8,17 +8,20 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import org.unicitylabs.sdk.token.Token; +import org.unicitylabs.sdk.transaction.MintReasonType; import org.unicitylabs.sdk.transaction.split.SplitMintReason; import org.unicitylabs.sdk.transaction.split.SplitMintReasonProof; import java.io.IOException; import java.util.List; public class SplitMintReasonCbor { + private SplitMintReasonCbor() { } public static class Serializer extends JsonSerializer { + public Serializer() { } @@ -30,7 +33,8 @@ public void serialize(SplitMintReason value, JsonGenerator gen, SerializerProvid return; } - gen.writeStartArray(value, 2); + gen.writeStartArray(value, 3); + gen.writeObject(value.getType()); gen.writeObject(value.getToken()); gen.writeObject(value.getProofs()); gen.writeEndArray(); @@ -43,14 +47,23 @@ public Deserializer() { } @Override - public SplitMintReason deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + public SplitMintReason deserialize(JsonParser p, DeserializationContext ctx) + throws IOException { if (!p.isExpectedStartArrayToken()) { throw MismatchedInputException.from(p, SplitMintReason.class, "Expected array value"); } + String type = p.readValueAs(String.class); + + if (!MintReasonType.TOKEN_SPLIT.name().equals(type)) { + throw MismatchedInputException.from(p, SplitMintReason.class, + "Expected type to be " + MintReasonType.TOKEN_SPLIT); + } + return new SplitMintReason( p.readValueAs(Token.class), - ctx.readValue(p, ctx.getTypeFactory().constructCollectionType(List.class, SplitMintReasonProof.class)) + ctx.readValue(p, + ctx.getTypeFactory().constructCollectionType(List.class, SplitMintReasonProof.class)) ); } } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/mtree/sum/SparseMerkleSumTreePathStepBranchJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/mtree/sum/SparseMerkleSumTreePathStepBranchJson.java index f636640..70503fb 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/mtree/sum/SparseMerkleSumTreePathStepBranchJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/mtree/sum/SparseMerkleSumTreePathStepBranchJson.java @@ -28,8 +28,8 @@ public void serialize(Branch value, JsonGenerator gen, } gen.writeStartArray(); - gen.writeObject(value.getCounter().toString()); gen.writeObject(value.getValue()); + gen.writeObject(value.getCounter().toString()); gen.writeEndArray(); } } @@ -46,9 +46,9 @@ public Branch deserialize(JsonParser p, DeserializationContext ctx) p.nextToken(); Branch branch = new Branch( - new BigInteger(p.readValueAs(String.class)), - p.readValueAs(byte[].class) - ); + p.readValueAs(byte[].class), + new BigInteger(p.readValueAs(String.class)) + ); if (p.nextToken() != JsonToken.END_ARRAY) { throw MismatchedInputException.from(p, Branch.class, "Expected end of array"); diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/BurnPredicateJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/BurnPredicateJson.java deleted file mode 100644 index 006759b..0000000 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/BurnPredicateJson.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.unicitylabs.sdk.serializer.json.predicate; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import org.unicitylabs.sdk.hash.DataHash; -import org.unicitylabs.sdk.predicate.BurnPredicate; -import org.unicitylabs.sdk.predicate.PredicateType; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - -public class BurnPredicateJson { - - private static final String TYPE_FIELD = "type"; - private static final String NONCE_FIELD = "nonce"; - private static final String REASON_FIELD = "reason"; - - private BurnPredicateJson() { - } - - public static class Serializer extends JsonSerializer { - - @Override - public void serialize(BurnPredicate value, JsonGenerator gen, - SerializerProvider serializers) - throws IOException { - if (value == null) { - gen.writeNull(); - return; - } - - gen.writeStartObject(); - gen.writeObjectField(TYPE_FIELD, value.getType()); - gen.writeObjectField(REASON_FIELD, value.getReason()); - gen.writeEndObject(); - } - } - - public static class Deserializer extends - JsonDeserializer { - - @Override - public BurnPredicate deserialize(JsonParser p, DeserializationContext ctx) - throws IOException { - PredicateType type; - byte[] nonce = null; - DataHash reason = null; - - Set fields = new HashSet<>(); - - if (!p.isExpectedStartObjectToken()) { - throw MismatchedInputException.from(p, BurnPredicate.class, "Expected object value"); - } - - while (p.nextToken() != JsonToken.END_OBJECT) { - String fieldName = p.currentName(); - - if (!fields.add(fieldName)) { - throw MismatchedInputException.from(p, BurnPredicate.class, - String.format("Duplicate field: %s", fieldName)); - } - - p.nextToken(); - try { - switch (fieldName) { - case TYPE_FIELD: - type = PredicateType.valueOf(p.readValueAs(String.class)); - if (type != PredicateType.BURN) { - throw MismatchedInputException.from(p, BurnPredicate.class, - String.format("Expected type to be %s, but got %s", PredicateType.MASKED, - type)); - } - break; - case NONCE_FIELD: - nonce = p.readValueAs(byte[].class); - break; - case REASON_FIELD: - reason = p.readValueAs(DataHash.class); - break; - default: - p.skipChildren(); - } - } catch (Exception e) { - throw MismatchedInputException.wrapWithPath(e, BurnPredicate.class, fieldName); - } - } - - Set missingFields = new HashSet<>( - Set.of(TYPE_FIELD, NONCE_FIELD, REASON_FIELD)); - missingFields.removeAll(fields); - if (!missingFields.isEmpty()) { - throw MismatchedInputException.from(p, BurnPredicate.class, - String.format("Missing required fields: %s", missingFields)); - } - - return new BurnPredicate(nonce, reason); - } - } -} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/MaskedPredicateJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/MaskedPredicateJson.java deleted file mode 100644 index 7779edd..0000000 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/MaskedPredicateJson.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.unicitylabs.sdk.serializer.json.predicate; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.MaskedPredicate; -import org.unicitylabs.sdk.predicate.PredicateType; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - -public class MaskedPredicateJson { - - private static final String TYPE_FIELD = "type"; - private static final String PUBLIC_KEY_FIELD = "publicKey"; - private static final String ALGORITHM_FIELD = "algorithm"; - private static final String HASH_ALGORITHM_FIELD = "hashAlgorithm"; - private static final String NONCE_FIELD = "nonce"; - - private MaskedPredicateJson() { - } - - public static class Serializer extends JsonSerializer { - - @Override - public void serialize(MaskedPredicate value, JsonGenerator gen, SerializerProvider serializers) - throws IOException { - if (value == null) { - gen.writeNull(); - return; - } - - gen.writeStartObject(); - gen.writeObjectField(TYPE_FIELD, value.getType()); - gen.writeObjectField(PUBLIC_KEY_FIELD, value.getPublicKey()); - gen.writeObjectField(ALGORITHM_FIELD, value.getSigningAlgorithm()); - gen.writeObjectField(HASH_ALGORITHM_FIELD, value.getHashAlgorithm().getValue()); - gen.writeObjectField(NONCE_FIELD, value.getNonce()); - gen.writeEndObject(); - } - } - - public static class Deserializer extends - JsonDeserializer { - - @Override - public MaskedPredicate deserialize(JsonParser p, DeserializationContext ctx) - throws IOException { - PredicateType type; - byte[] publicKey = null; - String algorithm = null; - HashAlgorithm hashAlgorithm = null; - byte[] nonce = null; - - Set fields = new HashSet<>(); - - if (!p.isExpectedStartObjectToken()) { - throw MismatchedInputException.from(p, MaskedPredicate.class, "Expected object value"); - } - - while (p.nextToken() != JsonToken.END_OBJECT) { - String fieldName = p.currentName(); - - if (!fields.add(fieldName)) { - throw MismatchedInputException.from(p, MaskedPredicate.class, - String.format("Duplicate field: %s", fieldName)); - } - - p.nextToken(); - try { - switch (fieldName) { - case TYPE_FIELD: - type = PredicateType.valueOf(p.readValueAs(String.class)); - if (type != PredicateType.MASKED) { - throw MismatchedInputException.from(p, MaskedPredicate.class, - String.format("Expected type to be %s, but got %s", PredicateType.MASKED, - type)); - } - break; - case PUBLIC_KEY_FIELD: - publicKey = p.readValueAs(byte[].class); - break; - case ALGORITHM_FIELD: - if (p.currentToken() != JsonToken.VALUE_STRING) { - throw MismatchedInputException.from(p, MaskedPredicate.class, - "Expected algorithm to be a string"); - } - algorithm = p.readValueAs(String.class); - break; - case HASH_ALGORITHM_FIELD: - if (p.currentToken() != JsonToken.VALUE_NUMBER_INT) { - throw MismatchedInputException.from(p, MaskedPredicate.class, - "Expected hashAlgorithm to be a string"); - } - hashAlgorithm = HashAlgorithm.fromValue(p.readValueAs(Integer.class)); - break; - case NONCE_FIELD: - nonce = p.readValueAs(byte[].class); - break; - default: - p.skipChildren(); - } - } catch (Exception e) { - throw MismatchedInputException.wrapWithPath(e, MaskedPredicate.class, fieldName); - } - } - - Set missingFields = new HashSet<>( - Set.of(TYPE_FIELD, PUBLIC_KEY_FIELD, ALGORITHM_FIELD, HASH_ALGORITHM_FIELD, NONCE_FIELD)); - missingFields.removeAll(fields); - if (!missingFields.isEmpty()) { - throw MismatchedInputException.from(p, MaskedPredicate.class, - String.format("Missing required fields: %s", missingFields)); - } - - return new MaskedPredicate(publicKey, algorithm, hashAlgorithm, nonce); - } - } -} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/PredicateJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/PredicateJson.java index 2c82f2d..5780433 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/PredicateJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/PredicateJson.java @@ -1,58 +1,60 @@ package org.unicitylabs.sdk.serializer.json.predicate; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import org.unicitylabs.sdk.predicate.BurnPredicate; +import org.unicitylabs.sdk.predicate.EncodedPredicate; import org.unicitylabs.sdk.predicate.Predicate; -import org.unicitylabs.sdk.predicate.MaskedPredicate; -import org.unicitylabs.sdk.predicate.PredicateType; -import org.unicitylabs.sdk.predicate.UnmaskedPredicate; +import org.unicitylabs.sdk.predicate.PredicateEngineType; import java.io.IOException; +import org.unicitylabs.sdk.predicate.SerializablePredicate; public class PredicateJson { - - private static final String TYPE_FIELD = "type"; - private PredicateJson() { } - public static class Deserializer extends JsonDeserializer { + public static class Serializer extends JsonSerializer { @Override - public Predicate deserialize(JsonParser p, DeserializationContext ctx) throws IOException { - JsonNode node = p.getCodec().readTree(p); - JsonNode typeNode = node.get(TYPE_FIELD); + public void serialize(SerializablePredicate value, JsonGenerator gen, + SerializerProvider serializers) + throws IOException { + if (value == null) { + gen.writeNull(); + return; + } - if (typeNode == null) { - throw MismatchedInputException.from(p, Predicate.class, "Missing predicate 'type' field"); + gen.writeStartArray(value, 3); + gen.writeObject(value.getEngine().ordinal()); + gen.writeObject(value.encode()); + gen.writeObject(value.encodeParameters()); + gen.writeEndArray(); + } + } + + public static class Deserializer extends JsonDeserializer { + + @Override + public SerializablePredicate deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + if (!p.isExpectedStartArrayToken()) { + throw MismatchedInputException.from(p, Predicate.class, "Expected array value"); } - switch (PredicateType.valueOf(typeNode.asText())) { - case MASKED: - { - JsonParser parser = node.traverse(p.getCodec()); - parser.nextToken(); - return ctx.readValue(parser, MaskedPredicate.class); - } - case UNMASKED: - { - JsonParser parser = node.traverse(p.getCodec()); - parser.nextToken(); - return ctx.readValue(parser, UnmaskedPredicate.class); - } - case BURN: { - JsonParser parser = node.traverse(p.getCodec()); - parser.nextToken(); - return ctx.readValue(parser, BurnPredicate.class); - } - default: - p.skipChildren(); + p.nextToken(); + PredicateEngineType engine = p.readValueAs(PredicateEngineType.class); + byte[] code = p.readValueAs(byte[].class); + byte[] parameters = p.readValueAs(byte[].class); + + if (p.nextToken() != JsonToken.END_ARRAY) { + throw MismatchedInputException.from(p, Predicate.class, "Expected end of array"); } - return null; + return new EncodedPredicate(engine, code, parameters); } } } \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/UnmaskedPredicateJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/UnmaskedPredicateJson.java deleted file mode 100644 index 9d04710..0000000 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/UnmaskedPredicateJson.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.unicitylabs.sdk.serializer.json.predicate; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.PredicateType; -import org.unicitylabs.sdk.predicate.UnmaskedPredicate; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - -public class UnmaskedPredicateJson { - - private static final String TYPE_FIELD = "type"; - private static final String PUBLIC_KEY_FIELD = "publicKey"; - private static final String ALGORITHM_FIELD = "algorithm"; - private static final String HASH_ALGORITHM_FIELD = "hashAlgorithm"; - private static final String NONCE_FIELD = "nonce"; - - private UnmaskedPredicateJson() { - } - - public static class Serializer extends JsonSerializer { - - @Override - public void serialize(UnmaskedPredicate value, JsonGenerator gen, - SerializerProvider serializers) - throws IOException { - if (value == null) { - gen.writeNull(); - return; - } - - gen.writeStartObject(); - gen.writeObjectField(TYPE_FIELD, value.getType()); - gen.writeObjectField(PUBLIC_KEY_FIELD, value.getPublicKey()); - gen.writeObjectField(ALGORITHM_FIELD, value.getSigningAlgorithm()); - gen.writeObjectField(HASH_ALGORITHM_FIELD, value.getHashAlgorithm().getValue()); - gen.writeObjectField(NONCE_FIELD, value.getNonce()); - gen.writeEndObject(); - } - } - - public static class Deserializer extends - JsonDeserializer { - - @Override - public UnmaskedPredicate deserialize(JsonParser p, DeserializationContext ctx) - throws IOException { - PredicateType type; - byte[] publicKey = null; - String algorithm = null; - HashAlgorithm hashAlgorithm = null; - byte[] nonce = null; - - Set fields = new HashSet<>(); - - if (!p.isExpectedStartObjectToken()) { - throw MismatchedInputException.from(p, UnmaskedPredicate.class, "Expected object value"); - } - - while (p.nextToken() != JsonToken.END_OBJECT) { - String fieldName = p.currentName(); - - if (!fields.add(fieldName)) { - throw MismatchedInputException.from(p, UnmaskedPredicate.class, - String.format("Duplicate field: %s", fieldName)); - } - - p.nextToken(); - try { - switch (fieldName) { - case TYPE_FIELD: - type = PredicateType.valueOf(p.readValueAs(String.class)); - if (type != PredicateType.UNMASKED) { - throw MismatchedInputException.from(p, UnmaskedPredicate.class, - String.format("Expected type to be %s, but got %s", PredicateType.MASKED, - type)); - } - break; - case PUBLIC_KEY_FIELD: - publicKey = p.readValueAs(byte[].class); - break; - case ALGORITHM_FIELD: - if (p.currentToken() != JsonToken.VALUE_STRING) { - throw MismatchedInputException.from(p, UnmaskedPredicate.class, - "Expected algorithm to be a string"); - } - algorithm = p.readValueAs(String.class); - break; - case HASH_ALGORITHM_FIELD: - if (p.currentToken() != JsonToken.VALUE_NUMBER_INT) { - throw MismatchedInputException.from(p, UnmaskedPredicate.class, - "Expected hashAlgorithm to be a string"); - } - hashAlgorithm = HashAlgorithm.fromValue(p.readValueAs(Integer.class)); - break; - case NONCE_FIELD: - nonce = p.readValueAs(byte[].class); - break; - default: - p.skipChildren(); - } - } catch (Exception e) { - throw MismatchedInputException.wrapWithPath(e, UnmaskedPredicate.class, fieldName); - } - } - - Set missingFields = new HashSet<>( - Set.of(TYPE_FIELD, PUBLIC_KEY_FIELD, ALGORITHM_FIELD, HASH_ALGORITHM_FIELD, NONCE_FIELD)); - missingFields.removeAll(fields); - if (!missingFields.isEmpty()) { - throw MismatchedInputException.from(p, UnmaskedPredicate.class, - String.format("Missing required fields: %s", missingFields)); - } - - return new UnmaskedPredicate(publicKey, algorithm, hashAlgorithm, nonce); - } - } -} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/token/TokenJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/token/TokenJson.java index 4d71fb0..3dbd44b 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/token/TokenJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/token/TokenJson.java @@ -21,6 +21,7 @@ public class TokenJson { + private static final String VERSION_FIELD = "version"; private static final String STATE_FIELD = "state"; private static final String GENESIS_FIELD = "genesis"; private static final String TRANSACTIONS_FIELD = "transactions"; @@ -40,6 +41,7 @@ public void serialize(Token value, JsonGenerator gen, } gen.writeStartObject(); + gen.writeObjectField(VERSION_FIELD, value.getVersion()); gen.writeObjectField(STATE_FIELD, value.getState()); gen.writeObjectField(GENESIS_FIELD, value.getGenesis()); gen.writeObjectField(TRANSACTIONS_FIELD, value.getTransactions()); @@ -55,6 +57,7 @@ public static class Deserializer extends public Token> deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + String version = null; TokenState state = null; Transaction> genesis = null; List> transactions = new ArrayList<>(); @@ -77,6 +80,13 @@ public Token> deserialize(JsonParser p, p.nextToken(); try { switch (fieldName) { + case VERSION_FIELD: + version = p.readValueAs(String.class); + if (!Token.TOKEN_VERSION.equals(version)) { + throw MismatchedInputException.from(p, Token.class, + String.format("Unsupported token version: %s", version)); + } + break; case STATE_FIELD: state = p.readValueAs(TokenState.class); break; @@ -113,7 +123,7 @@ public Token> deserialize(JsonParser p, } Set missingFields = new HashSet<>(Set.of( - STATE_FIELD, GENESIS_FIELD, TRANSACTIONS_FIELD, NAMETAG_FIELD)); + VERSION_FIELD, STATE_FIELD, GENESIS_FIELD, TRANSACTIONS_FIELD, NAMETAG_FIELD)); missingFields.removeAll(fields); if (!missingFields.isEmpty()) { throw MismatchedInputException.from(p, Token.class, diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/token/TokenStateJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/token/TokenStateJson.java index 0d8da7f..7426658 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/token/TokenStateJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/token/TokenStateJson.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import org.unicitylabs.sdk.predicate.Predicate; +import org.unicitylabs.sdk.predicate.SerializablePredicate; import org.unicitylabs.sdk.token.TokenState; import java.io.IOException; import java.util.HashSet; @@ -33,7 +34,7 @@ public void serialize(TokenState value, JsonGenerator gen, SerializerProvider se } gen.writeStartObject(); - gen.writeObjectField(UNLOCK_PREDICATE_FIELD, value.getUnlockPredicate()); + gen.writeObjectField(UNLOCK_PREDICATE_FIELD, value.getPredicate()); gen.writeObjectField(DATA_FIELD, value.getData()); gen.writeEndObject(); } @@ -45,7 +46,7 @@ public static class Deserializer extends @Override public TokenState deserialize(JsonParser p, DeserializationContext ctx) throws IOException { - Predicate predicate = null; + SerializablePredicate predicate = null; byte[] data = null; Set fields = new HashSet<>(); @@ -66,7 +67,7 @@ public TokenState deserialize(JsonParser p, DeserializationContext ctx) try { switch (fieldName) { case UNLOCK_PREDICATE_FIELD: - predicate = p.readValueAs(Predicate.class); + predicate = p.readValueAs(SerializablePredicate.class); break; case DATA_FIELD: data = diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/transaction/split/SplitMintReasonJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/transaction/split/SplitMintReasonJson.java index b5faec3..d49e86a 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/transaction/split/SplitMintReasonJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/transaction/split/SplitMintReasonJson.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import org.unicitylabs.sdk.token.Token; +import org.unicitylabs.sdk.transaction.MintReasonType; import org.unicitylabs.sdk.transaction.split.SplitMintReason; import org.unicitylabs.sdk.transaction.split.SplitMintReasonProof; import java.io.IOException; @@ -18,6 +19,7 @@ import java.util.Set; public class SplitMintReasonJson { + private static final String TYPE_FIELD = "type"; private static final String TOKEN_FIELD = "token"; private static final String PROOFS_FIELD = "proofs"; @@ -38,6 +40,7 @@ public void serialize(SplitMintReason value, JsonGenerator gen, SerializerProvid } gen.writeStartObject(); + gen.writeObjectField(TYPE_FIELD, value.getType()); gen.writeObjectField(TOKEN_FIELD, value.getToken()); gen.writeObjectField(PROOFS_FIELD, value.getProofs()); gen.writeEndObject(); @@ -51,6 +54,7 @@ public Deserializer() { @Override public SplitMintReason deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + String type = null; Token token = null; List proofs = new ArrayList<>(); @@ -71,6 +75,13 @@ public SplitMintReason deserialize(JsonParser p, DeserializationContext ctx) thr p.nextToken(); try { switch (fieldName) { + case TYPE_FIELD: + type = p.getValueAsString(); + if (!MintReasonType.TOKEN_SPLIT.name().equals(type)) { + throw MismatchedInputException.from(p, SplitMintReason.class, + String.format("Invalid type: %s", type)); + } + break; case TOKEN_FIELD: token = p.readValueAs(Token.class); break; @@ -91,7 +102,7 @@ public SplitMintReason deserialize(JsonParser p, DeserializationContext ctx) thr } } - Set missingFields = new HashSet<>(Set.of(TOKEN_FIELD, PROOFS_FIELD)); + Set missingFields = new HashSet<>(Set.of(TYPE_FIELD, TOKEN_FIELD, PROOFS_FIELD)); missingFields.removeAll(fields); if (!missingFields.isEmpty()) { throw MismatchedInputException.from(p, SplitMintReason.class, diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/transaction/split/SplitMintReasonProofJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/transaction/split/SplitMintReasonProofJson.java index 4f2563c..a3162ac 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/transaction/split/SplitMintReasonProofJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/transaction/split/SplitMintReasonProofJson.java @@ -41,7 +41,7 @@ public void serialize(SplitMintReasonProof value, JsonGenerator gen, } gen.writeStartObject(); - gen.writeObjectField(COIN_ID_FIELD, value.getCoinId()); + gen.writeObjectField(COIN_ID_FIELD, value.getCoinId().getBytes()); gen.writeObjectField(AGGREGATION_PATH_FIELD, value.getAggregationPath()); gen.writeObjectField(COIN_TREE_PATH_FIELD, value.getCoinTreePath()); gen.writeEndObject(); diff --git a/src/main/java/org/unicitylabs/sdk/token/Token.java b/src/main/java/org/unicitylabs/sdk/token/Token.java index 78f7035..1eb3a4d 100644 --- a/src/main/java/org/unicitylabs/sdk/token/Token.java +++ b/src/main/java/org/unicitylabs/sdk/token/Token.java @@ -12,14 +12,16 @@ import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.predicate.Predicate; +import org.unicitylabs.sdk.predicate.PredicateEngineService; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.fungible.TokenCoinData; +import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.transaction.InclusionProofVerificationStatus; import org.unicitylabs.sdk.transaction.MintCommitment; import org.unicitylabs.sdk.transaction.MintTransactionData; import org.unicitylabs.sdk.transaction.MintTransactionState; import org.unicitylabs.sdk.transaction.Transaction; -import org.unicitylabs.sdk.transaction.TransactionData; import org.unicitylabs.sdk.transaction.TransferTransactionData; import org.unicitylabs.sdk.util.VerificationResult; @@ -102,26 +104,13 @@ public Token update( Objects.requireNonNull(transaction, "Transaction is null"); Objects.requireNonNull(transactionNametags, "Nametag tokens are null"); - LinkedList> verificationTransactions = new LinkedList<>(this.transactions); - verificationTransactions.add(transaction); - if (!transaction.getData().getSourceState().getUnlockPredicate().verify(verificationTransactions, this)) { - throw new RuntimeException("Predicate verification failed"); + if (!this.verifyTransaction(this, transaction).isSuccessful()) { + // TODO: Add method to return why it failed + throw new RuntimeException("Transaction verification failed"); } - Address recipient = ProxyAddress.resolve( - transaction.getData().getRecipient(), - transactionNametags - ); - if (!state.getUnlockPredicate().getReference(this.getType()).toAddress().equals(recipient)) { - throw new RuntimeException("Recipient address mismatch"); - } - - if (!transaction.containsData(state.getData().orElse(null))) { - throw new RuntimeException("State data is not part of transaction."); - } - - List> transactions = new ArrayList<>( - this.getTransactions()); + LinkedList> transactions = new LinkedList<>( + this.transactions); transactions.add(transaction); return new Token<>(state, this.getGenesis(), transactions, transactionNametags); @@ -135,94 +124,64 @@ public VerificationResult verify() { List.of(this.verifyGenesis(this.genesis))) ); - Transaction> previousTransaction = this.genesis; for (int i = 0; i < this.transactions.size(); i++) { Transaction transaction = this.transactions.get(i); - Address recipient = previousTransaction.getData().getRecipient(); results.add( VerificationResult.fromChildren( "Transaction verification", List.of( this.verifyTransaction( - this.transactions.subList(0, i + 1), - previousTransaction.getData().getDataHash().orElse(null), - recipient + new Token<>( + transaction.getData().getSourceState(), + this.genesis, + this.transactions.subList(0, i), + transaction.getData().getNametags() + ), + transaction ) ) ) ); - - previousTransaction = transaction; - } - - results.add(VerificationResult.fromChildren( - "Token data verification", - List.of( - this.transactionContainsData( - previousTransaction.getData().getDataHash().orElse(null), - this.getState().getData().orElse(null) - ) - ? VerificationResult.success() - : VerificationResult.fail("Invalid token data") - ) - )); - - List nametagResults = new ArrayList<>(); - for (Token nametag : this.nametags) { - nametagResults.add(nametag.verify()); } - results.add(VerificationResult.fromChildren( - "Token nametags verification", - nametagResults - )); - - Address expectedAddress = this.getState().getUnlockPredicate().getReference(this.getType()) - .toAddress(); - - Address recipient = ProxyAddress.resolve(previousTransaction.getData().getRecipient(), - this.nametags); results.add(VerificationResult.fromChildren( - "Token recipient verification", - List.of( - expectedAddress.equals(recipient) - ? VerificationResult.success() - : VerificationResult.fail("Invalid recipient address") - ) + "Token current state verification", + List.of(this.verifyTransaction(this, null)) )); return VerificationResult.fromChildren("Token verification", results); } private VerificationResult verifyTransaction( - List> transactions, - DataHash dataHash, - Address recipient + Token token, + Transaction transaction ) { - Transaction transaction = transactions.get(transactions.size() - 1); - for (Token nametag : transaction.getData().getNametags()) { + for (Token nametag : token.getNametags()) { if (!nametag.verify().isSuccessful()) { return VerificationResult.fail( String.format("Nametag token %s verification failed", nametag.getId())); } } - Address expectedRecipient = transaction.getData().getSourceState().getUnlockPredicate() - .getReference(this.getType()).toAddress(); + Predicate predicate = PredicateEngineService.createPredicate(token.getState().getPredicate()); + Address expectedRecipient = predicate.getReference().toAddress(); + Transaction previousTransaction = !token.transactions.isEmpty() + ? token.transactions.getLast() + : token.genesis; if (!expectedRecipient.equals( - ProxyAddress.resolve(recipient, transaction.getData().getNametags()))) { + ProxyAddress.resolve(previousTransaction.getData().getRecipient(), token.getNametags()))) { return VerificationResult.fail("recipient mismatch"); } if (!this.transactionContainsData( - dataHash, - transaction.getData().getSourceState().getData().orElse(null))) { + previousTransaction.getData().getDataHash().orElse(null), + token.getState().getData().orElse(null))) { return VerificationResult.fail("data mismatch"); } - if (!transaction.getData().getSourceState().getUnlockPredicate().verify(transactions, this)) { + if (transaction != null && !predicate.verify(token, transaction)) { return VerificationResult.fail("predicate verification failed"); } @@ -308,7 +267,7 @@ public int hashCode() { @Override public String toString() { - return String.format("Token{state=%s, genesis=%s, transactions=%s, nametagTokens=%s}", + return String.format("Token{state=%s, genesis=%s, transactions=%s, nametags=%s}", this.state, this.genesis, this.transactions, this.nametags); } } \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/token/TokenState.java b/src/main/java/org/unicitylabs/sdk/token/TokenState.java index 61be45f..64ae45d 100644 --- a/src/main/java/org/unicitylabs/sdk/token/TokenState.java +++ b/src/main/java/org/unicitylabs/sdk/token/TokenState.java @@ -2,33 +2,34 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.Predicate; +import org.unicitylabs.sdk.predicate.PredicateEngineService; +import org.unicitylabs.sdk.predicate.SerializablePredicate; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.util.HexConverter; -import java.util.Arrays; -import java.util.Objects; -import java.util.Optional; /** * Represents a snapshot of token ownership and associated data. */ public class TokenState { - private final Predicate unlockPredicate; + private final SerializablePredicate predicate; private final byte[] data; - public TokenState(Predicate unlockPredicate, byte[] data) { - Objects.requireNonNull(unlockPredicate, "Unlock predicate cannot be null"); - this.unlockPredicate = unlockPredicate; + public TokenState(SerializablePredicate predicate, byte[] data) { + Objects.requireNonNull(predicate, "Predicate cannot be null"); + this.predicate = predicate; this.data = data != null ? Arrays.copyOf(data, data.length) : null; } - public Predicate getUnlockPredicate() { - return this.unlockPredicate; + public SerializablePredicate getPredicate() { + return this.predicate; } public Optional getData() { @@ -37,9 +38,9 @@ public Optional getData() { : Optional.empty(); } - public DataHash calculateHash(TokenId tokenId, TokenType tokenType) { + public DataHash calculateHash() { ArrayNode node = UnicityObjectMapper.CBOR.createArrayNode(); - node.addPOJO(this.unlockPredicate.calculateHash(tokenId, tokenType)); + node.addPOJO(PredicateEngineService.createPredicate(this.predicate).calculateHash()); node.add(this.data); try { @@ -56,20 +57,22 @@ public boolean equals(Object o) { return false; } TokenState that = (TokenState) o; - return Objects.equals(this.unlockPredicate, that.unlockPredicate) + return Arrays.equals(this.predicate.encode(), that.predicate.encode()) + && Arrays.equals(this.predicate.encodeParameters(), that.predicate.encodeParameters()) + && Objects.equals(this.predicate.getEngine(), that.predicate.getEngine()) && Objects.deepEquals(this.data, that.data); } @Override public int hashCode() { - return Objects.hash(this.unlockPredicate, Arrays.hashCode(this.data)); + return Objects.hash(this.predicate, Arrays.hashCode(this.data)); } @Override public String toString() { return String.format( - "TokenState{unlockPredicate=%s, data=%s}", - this.unlockPredicate, + "TokenState{predicate=%s, data=%s}", + this.predicate, this.data != null ? HexConverter.encode(this.data) : "null" ); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintReasonType.java b/src/main/java/org/unicitylabs/sdk/transaction/MintReasonType.java new file mode 100644 index 0000000..15d0f51 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintReasonType.java @@ -0,0 +1,5 @@ +package org.unicitylabs.sdk.transaction; + +public enum MintReasonType { + TOKEN_SPLIT, +} diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionData.java b/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionData.java index 36f08e8..e3cc969 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionData.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionData.java @@ -93,18 +93,14 @@ public MintTransactionState getSourceState() { } public DataHash calculateHash() { - DataHash tokenDataHash = this.tokenData == null ? null : new DataHasher(HashAlgorithm.SHA256) - .update(this.tokenData) - .digest(); - ArrayNode node = UnicityObjectMapper.CBOR.createArrayNode(); node.addPOJO(this.tokenId); node.addPOJO(this.tokenType); - node.addPOJO(tokenDataHash); - node.addPOJO(this.dataHash); + node.addPOJO(this.tokenData); node.addPOJO(this.coinData); node.add(this.recipient.getAddress()); node.add(this.salt); + node.addPOJO(this.dataHash); node.addPOJO(this.reason); try { diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionReason.java b/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionReason.java index dd2ea00..e065b2c 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionReason.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionReason.java @@ -3,5 +3,6 @@ import org.unicitylabs.sdk.util.VerificationResult; public interface MintTransactionReason { + String getType(); VerificationResult verify(Transaction> genesis); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/Transaction.java b/src/main/java/org/unicitylabs/sdk/transaction/Transaction.java index 9d2e4b6..a2dda0e 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/Transaction.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/Transaction.java @@ -32,7 +32,7 @@ public boolean containsData(byte[] stateData) { return false; } - if (!this.data.getDataHash().isPresent()) { + if (this.data.getDataHash().isEmpty()) { return true; } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/TransferCommitment.java b/src/main/java/org/unicitylabs/sdk/transaction/TransferCommitment.java index 7cfe93c..503bbfa 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/TransferCommitment.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/TransferCommitment.java @@ -35,9 +35,8 @@ public static TransferCommitment create( TransferTransactionData transactionData = new TransferTransactionData( token.getState(), recipient, salt, dataHash, message, token.getNametags()); - DataHash sourceStateHash = transactionData.getSourceState() - .calculateHash(token.getId(), token.getType()); - DataHash transactionHash = transactionData.calculateHash(token.getId(), token.getType()); + DataHash sourceStateHash = transactionData.getSourceState().calculateHash(); + DataHash transactionHash = transactionData.calculateHash(); RequestId requestId = RequestId.create(signingService.getPublicKey(), sourceStateHash); Authenticator authenticator = Authenticator.create(signingService, transactionHash, @@ -56,7 +55,7 @@ public Transaction toTransaction(Token token, throw new RuntimeException("Authenticator is missing from inclusion proof."); } - if (!this.getTransactionData().calculateHash(token.getId(), token.getType()) + if (!this.getTransactionData().calculateHash() .equals(inclusionProof.getTransactionHash().orElse(null))) { throw new RuntimeException("Payload hash mismatch."); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/TransferTransactionData.java b/src/main/java/org/unicitylabs/sdk/transaction/TransferTransactionData.java index f7cc8cb..c47cb1e 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/TransferTransactionData.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/TransferTransactionData.java @@ -77,9 +77,9 @@ public List> getNametags() { return this.nametags; } - public DataHash calculateHash(TokenId tokenId, TokenType tokenType) { + public DataHash calculateHash() { ArrayNode node = UnicityObjectMapper.CBOR.createArrayNode(); - node.addPOJO(this.sourceState.calculateHash(tokenId, tokenType)); + node.addPOJO(this.sourceState.calculateHash()); node.addPOJO(this.dataHash); node.addPOJO(this.recipient); node.add(this.salt); @@ -120,7 +120,7 @@ public String toString() { + "salt=%s, " + "dataHash=%s, " + "message=%s, " - + "nametagTokens=%s" + + "nametags=%s" + "}", this.sourceState, this.recipient, HexConverter.encode(this.salt), this.dataHash, this.message != null ? HexConverter.encode(this.message) : null, this.nametags); diff --git a/src/main/java/org/unicitylabs/sdk/transaction/split/SplitMintReason.java b/src/main/java/org/unicitylabs/sdk/transaction/split/SplitMintReason.java index b496717..155924e 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/split/SplitMintReason.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/split/SplitMintReason.java @@ -3,11 +3,15 @@ import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePathStep; import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTreePathStep.Branch; -import org.unicitylabs.sdk.predicate.BurnPredicate; -import org.unicitylabs.sdk.predicate.PredicateType; +import org.unicitylabs.sdk.predicate.PredicateEngineService; +import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; +import org.unicitylabs.sdk.predicate.embedded.EmbeddedPredicateType; +import org.unicitylabs.sdk.predicate.Predicate; +import org.unicitylabs.sdk.predicate.PredicateEngineType; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.fungible.CoinId; import org.unicitylabs.sdk.token.fungible.TokenCoinData; +import org.unicitylabs.sdk.transaction.MintReasonType; import org.unicitylabs.sdk.transaction.MintTransactionData; import org.unicitylabs.sdk.transaction.MintTransactionReason; import org.unicitylabs.sdk.transaction.Transaction; @@ -32,6 +36,10 @@ public SplitMintReason(Token token, List proofs) { this.proofs = List.copyOf(proofs); } + public String getType() { + return MintReasonType.TOKEN_SPLIT.name(); + } + public Token getToken() { return this.token; } @@ -41,11 +49,13 @@ public List getProofs() { } public VerificationResult verify(Transaction> transaction) { - if (!transaction.getData().getCoinData().isPresent()) { + if (transaction.getData().getCoinData().isEmpty()) { return VerificationResult.fail("Coin data is missing."); } - if (!PredicateType.BURN.name().equals(this.token.getState().getUnlockPredicate().getType())) { + Predicate predicate = PredicateEngineService.createPredicate( + this.token.getState().getPredicate()); + if (!(predicate instanceof BurnPredicate)) { return VerificationResult.fail("Token is not burned"); } @@ -85,8 +95,8 @@ public VerificationResult verify(Transaction> t return VerificationResult.fail("Coin amount in token does not match coin tree leaf."); } - BurnPredicate predicate = (BurnPredicate) this.token.getState().getUnlockPredicate(); - if (!proof.getAggregationPath().getRootHash().equals(predicate.getReason())) { + if (!proof.getAggregationPath().getRootHash() + .equals(((BurnPredicate) predicate).getReason())) { return VerificationResult.fail("Burn reason does not match aggregation root."); } } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilder.java b/src/main/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilder.java index 2514d5e..3cf78a9 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilder.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilder.java @@ -10,8 +10,8 @@ import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTree; import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTree.LeafValue; import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTreeRootNode; -import org.unicitylabs.sdk.predicate.BurnPredicate; -import org.unicitylabs.sdk.predicate.BurnPredicateReference; +import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; +import org.unicitylabs.sdk.predicate.embedded.BurnPredicateReference; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; @@ -139,10 +139,15 @@ public List>> createSplitMin Transaction burnTransaction) { Objects.requireNonNull(burnTransaction, "Burn transaction cannot be null"); - byte[] nonce = new byte[32]; - new SecureRandom().nextBytes(nonce); Token burnedToken = this.token.update( - new TokenState(new BurnPredicate(nonce, this.aggregationRoot.getRootHash()), null), + new TokenState( + new BurnPredicate( + this.token.getId(), + this.token.getType(), + this.aggregationRoot.getRootHash() + ), + null + ), burnTransaction, List.of() ); diff --git a/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java b/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java index f561448..a5f69f7 100644 --- a/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java +++ b/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java @@ -11,8 +11,9 @@ import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.api.SubmitCommitmentStatus; import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.UnmaskedPredicate; -import org.unicitylabs.sdk.predicate.UnmaskedPredicateReference; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicateReference; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.Token; @@ -95,6 +96,8 @@ private Token receiveToken(String[] tokenInfo, SigningService signingService, TokenState state = new TokenState( UnmaskedPredicate.create( + token.getId(), + token.getType(), signingService, HashAlgorithm.SHA256, transaction.getData().getSalt() @@ -114,16 +117,22 @@ private Token receiveToken(String[] tokenInfo, SigningService signingService, void testEscrow() throws Exception { // Make nametags unique for each test run Token bobToken = mintToken(BOB_SECRET); - String[] bobSerializedData = transferToken( + String[] bobSerializedData = this.transferToken( bobToken, - SigningService.createFromMaskedSecret(BOB_SECRET, bobToken.getState().getUnlockPredicate().getNonce()), + SigningService.createFromMaskedSecret( + BOB_SECRET, + ((MaskedPredicate) bobToken.getState().getPredicate()).getNonce() + ), ALICE_NAMETAG ); Token carolToken = mintToken(CAROL_SECRET); - String[] carolSerializedData = transferToken( + String[] carolSerializedData = this.transferToken( carolToken, - SigningService.createFromMaskedSecret(CAROL_SECRET, carolToken.getState().getUnlockPredicate().getNonce()), + SigningService.createFromMaskedSecret( + CAROL_SECRET, + ((MaskedPredicate) carolToken.getState().getPredicate()).getNonce() + ), ALICE_NAMETAG ); diff --git a/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java b/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java index 5c1a221..ab14438 100644 --- a/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java +++ b/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java @@ -14,8 +14,9 @@ import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.api.SubmitCommitmentStatus; import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.UnmaskedPredicate; -import org.unicitylabs.sdk.predicate.UnmaskedPredicateReference; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicateReference; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; @@ -49,12 +50,14 @@ void testTokenSplitFullAmounts() throws Exception { new TokenId(randomBytes(32)), tokenType, randomBytes(32), - new TokenCoinData(Map.of( - new CoinId("test_eur".getBytes(StandardCharsets.UTF_8)), - BigInteger.valueOf(100), - new CoinId("test_usd".getBytes(StandardCharsets.UTF_8)), - BigInteger.valueOf(100) - )), + new TokenCoinData( + Map.of( + new CoinId("test_eur".getBytes(StandardCharsets.UTF_8)), + BigInteger.valueOf(100), + new CoinId("test_usd".getBytes(StandardCharsets.UTF_8)), + BigInteger.valueOf(100) + ) + ), randomBytes(32), randomBytes(32), null @@ -103,7 +106,10 @@ void testTokenSplitFullAmounts() throws Exception { TransferCommitment burnCommitment = split.createBurnCommitment( randomBytes(32), - SigningService.createFromMaskedSecret(secret, token.getState().getUnlockPredicate().getNonce()) + SigningService.createFromMaskedSecret( + secret, + ((MaskedPredicate) token.getState().getPredicate()).getNonce() + ) ); SubmitCommitmentResponse burnCommitmentResponse = this.client @@ -134,6 +140,8 @@ void testTokenSplitFullAmounts() throws Exception { TokenState state = new TokenState( UnmaskedPredicate.create( + commitment.getTransactionData().getTokenId(), + commitment.getTransactionData().getTokenType(), SigningService.createFromSecret(secret), HashAlgorithm.SHA256, commitment.getTransactionData().getSalt() diff --git a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java b/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java index 4572ed8..3483bec 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java @@ -22,9 +22,12 @@ import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.MaskedPredicate; -import org.unicitylabs.sdk.predicate.UnmaskedPredicate; -import org.unicitylabs.sdk.predicate.UnmaskedPredicateReference; +import org.unicitylabs.sdk.predicate.PredicateEngineService; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicateReference; +import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicateReference; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; @@ -43,6 +46,7 @@ import org.unicitylabs.sdk.transaction.split.SplitMintReason; import org.unicitylabs.sdk.transaction.split.TokenSplitBuilder; import org.unicitylabs.sdk.transaction.split.TokenSplitBuilder.TokenSplit; +import org.unicitylabs.sdk.util.HexConverter; import org.unicitylabs.sdk.util.InclusionProofUtils; import org.unicitylabs.sdk.utils.TestTokenData; @@ -68,14 +72,12 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti SigningService aliceSigningService = SigningService.createFromMaskedSecret(ALICE_SECRET, aliceNonce); - MaskedPredicate alicePredicate = MaskedPredicate.create( + Address aliceAddress = MaskedPredicateReference.create( + tokenType, aliceSigningService, HashAlgorithm.SHA256, aliceNonce - ); - - Address aliceAddress = alicePredicate.getReference(tokenType).toAddress(); - TokenState aliceTokenState = new TokenState(alicePredicate, null); + ).toAddress(); MintCommitment> aliceMintCommitment = MintCommitment.create( new MintTransactionData<>( @@ -106,7 +108,16 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti // Create mint transaction Token aliceToken = new Token<>( - aliceTokenState, + new TokenState( + MaskedPredicate.create( + tokenId, + tokenType, + aliceSigningService, + HashAlgorithm.SHA256, + aliceNonce + ), + null + ), aliceMintCommitment.toTransaction(mintInclusionProof) ); @@ -158,13 +169,13 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti // Bob mints a name tag tokens byte[] bobNametagNonce = randomBytes(32); - MaskedPredicate bobNametagPredicate = MaskedPredicate.create( - SigningService.createFromMaskedSecret(BOB_SECRET, bobNametagNonce), - HashAlgorithm.SHA256, - bobNametagNonce - ); TokenType bobNametagTokenType = new TokenType(randomBytes(32)); - DirectAddress bobNametagAddress = bobNametagPredicate.getReference(bobNametagTokenType) + DirectAddress bobNametagAddress = MaskedPredicateReference.create( + bobNametagTokenType, + SigningService.createFromMaskedSecret(BOB_SECRET, bobNametagNonce), + HashAlgorithm.SHA256, + bobNametagNonce + ) .toAddress(); MintCommitment nametagMintCommitment = MintCommitment.create( new NametagMintTransactionData<>( @@ -189,7 +200,16 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti ).get() ); Token bobNametagToken = new Token<>( - new TokenState(bobNametagPredicate, null), + new TokenState( + MaskedPredicate.create( + bobNametagGenesis.getData().getTokenId(), + bobNametagGenesis.getData().getTokenType(), + SigningService.createFromMaskedSecret(BOB_SECRET, bobNametagNonce), + HashAlgorithm.SHA256, + bobNametagNonce + ), + null + ), bobNametagGenesis ); @@ -198,6 +218,8 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti aliceToken, new TokenState( UnmaskedPredicate.create( + aliceToken.getId(), + aliceToken.getType(), SigningService.createFromSecret(BOB_SECRET), HashAlgorithm.SHA256, aliceToBobTransferTransaction.getData().getSalt() @@ -210,7 +232,8 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti // Verify Bob is now the owner assertTrue(bobToken.verify().isSuccessful()); - assertTrue(bobToken.getState().getUnlockPredicate() + assertTrue(PredicateEngineService + .createPredicate(bobToken.getState().getPredicate()) .isOwner(SigningService.createFromSecret(BOB_SECRET).getPublicKey()) ); assertEquals(aliceToken.getId(), bobToken.getId()); @@ -253,6 +276,8 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti // Carol creates UnmaskedPredicate and finalizes UnmaskedPredicate carolPredicate = UnmaskedPredicate.create( + bobToken.getId(), + bobToken.getType(), SigningService.createFromSecret(CAROL_SECRET), HashAlgorithm.SHA256, bobToCarolTransaction.getData().getSalt() @@ -300,6 +325,8 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti carolToken, new TokenState( UnmaskedPredicate.create( + carolToken.getId(), + carolToken.getType(), SigningService.createFromSecret(BOB_SECRET), HashAlgorithm.SHA256, carolToBobTransaction.getData().getSalt() @@ -318,11 +345,6 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti TokenType splitTokenType = new TokenType(randomBytes(32)); byte[] splitTokenNonce = randomBytes(32); - MaskedPredicate splitTokenPredicate = MaskedPredicate.create( - SigningService.createFromMaskedSecret(BOB_SECRET, splitTokenNonce), - HashAlgorithm.SHA256, - splitTokenNonce - ); TokenSplit split = new TokenSplitBuilder() .createToken( @@ -330,7 +352,12 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti splitTokenType, null, new TokenCoinData(Map.ofEntries(splitCoins[0])), - splitTokenPredicate.getReference(splitTokenType).toAddress(), + MaskedPredicateReference.create( + splitTokenType, + SigningService.createFromMaskedSecret(BOB_SECRET, splitTokenNonce), + HashAlgorithm.SHA256, + splitTokenNonce + ).toAddress(), randomBytes(32), null ) @@ -339,7 +366,12 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti splitTokenType, null, new TokenCoinData(Map.ofEntries(splitCoins[1])), - splitTokenPredicate.getReference(splitTokenType).toAddress(), + MaskedPredicateReference.create( + splitTokenType, + SigningService.createFromMaskedSecret(BOB_SECRET, splitTokenNonce), + HashAlgorithm.SHA256, + splitTokenNonce + ).toAddress(), randomBytes(32), null ) @@ -380,13 +412,19 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti .count() ); + MaskedPredicate splitTokenPredicate = MaskedPredicate.create( + splitTransactions.get(0).getData().getTokenId(), + splitTransactions.get(0).getData().getTokenType(), + SigningService.createFromMaskedSecret(BOB_SECRET, splitTokenNonce), + HashAlgorithm.SHA256, + splitTokenNonce + ); + Assertions.assertTrue( new Token<>( new TokenState(splitTokenPredicate, null), splitTransactions.get(0) ).verify().isSuccessful() ); - - } } \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java b/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java index 4a2abf0..1976804 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java @@ -13,8 +13,9 @@ import org.unicitylabs.sdk.api.SubmitCommitmentStatus; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.BranchExistsException; -import org.unicitylabs.sdk.predicate.UnmaskedPredicate; -import org.unicitylabs.sdk.predicate.UnmaskedPredicateReference; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicateReference; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.Token; @@ -39,7 +40,10 @@ private String[] transferToken(Token token, byte[] secret, Address address) t randomBytes(32), null, null, - SigningService.createFromMaskedSecret(secret, token.getState().getUnlockPredicate().getNonce()) + SigningService.createFromMaskedSecret( + secret, + ((MaskedPredicate) token.getState().getPredicate()).getNonce() + ) ); SubmitCommitmentResponse response = this.client.submitCommitment(token, commitment).get(); @@ -78,6 +82,8 @@ private Token receiveToken(String[] tokenInfo, byte[] secret) throws Exceptio TokenState state = new TokenState( UnmaskedPredicate.create( + token.getId(), + token.getType(), SigningService.createFromSecret(secret), HashAlgorithm.SHA256, transaction.getData().getSalt() diff --git a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java b/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java index 0627e9a..4e0f0a7 100644 --- a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java +++ b/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java @@ -4,6 +4,7 @@ import org.unicitylabs.sdk.mtree.BranchExistsException; import org.unicitylabs.sdk.mtree.LeafOutOfBoundsException; import org.unicitylabs.sdk.mtree.MerkleTreePathVerificationResult; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.util.HexConverter; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -169,8 +170,8 @@ public void shouldGetWorkingPath() throws Exception { Assertions.assertTrue(result.isPathValid()); Assertions.assertFalse(result.isValid()); - path = root.getPath(BigInteger.valueOf(0b10)); - result = path.verify(BigInteger.valueOf(0b10)); + path = root.getPath(BigInteger.valueOf(0b111100101)); + result = path.verify(BigInteger.valueOf(0b111100101)); Assertions.assertTrue(result.isPathIncluded()); Assertions.assertTrue(result.isPathValid()); Assertions.assertTrue(result.isValid()); diff --git a/src/test/java/org/unicitylabs/sdk/predicate/MaskedPredicateReferenceTest.java b/src/test/java/org/unicitylabs/sdk/predicate/MaskedPredicateReferenceTest.java index 034a8bb..ed13156 100644 --- a/src/test/java/org/unicitylabs/sdk/predicate/MaskedPredicateReferenceTest.java +++ b/src/test/java/org/unicitylabs/sdk/predicate/MaskedPredicateReferenceTest.java @@ -1,6 +1,7 @@ package org.unicitylabs.sdk.predicate; import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicateReference; import org.unicitylabs.sdk.token.TokenType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/unicitylabs/sdk/token/TokenTest.java b/src/test/java/org/unicitylabs/sdk/token/TokenTest.java index 4b739b4..b1276af 100644 --- a/src/test/java/org/unicitylabs/sdk/token/TokenTest.java +++ b/src/test/java/org/unicitylabs/sdk/token/TokenTest.java @@ -4,7 +4,7 @@ import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; -import org.unicitylabs.sdk.predicate.MaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.fungible.CoinId; @@ -52,6 +52,8 @@ public void testJsonSerialization() throws IOException { Token nametagToken = new Token<>( new TokenState( MaskedPredicate.create( + nametagGenesisData.getTokenId(), + nametagGenesisData.getTokenType(), SigningService.createFromMaskedSecret(TestUtils.randomBytes(32), nametagNonce), HashAlgorithm.SHA256, nametagNonce), @@ -72,6 +74,8 @@ public void testJsonSerialization() throws IOException { Token token = new Token<>( new TokenState( MaskedPredicate.create( + genesisData.getTokenId(), + genesisData.getTokenType(), SigningService.createFromMaskedSecret( TestUtils.randomBytes(32), genesisData.getTokenId().getBytes() @@ -96,6 +100,8 @@ public void testJsonSerialization() throws IOException { new TransferTransactionData( new TokenState( new MaskedPredicate( + genesisData.getTokenId(), + genesisData.getTokenType(), new byte[24], "secp256k1", HashAlgorithm.SHA256, diff --git a/src/test/java/org/unicitylabs/sdk/transaction/CommitmentTest.java b/src/test/java/org/unicitylabs/sdk/transaction/CommitmentTest.java index 9e8bf63..db8f253 100644 --- a/src/test/java/org/unicitylabs/sdk/transaction/CommitmentTest.java +++ b/src/test/java/org/unicitylabs/sdk/transaction/CommitmentTest.java @@ -4,7 +4,7 @@ import org.unicitylabs.sdk.api.RequestId; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.MaskedPredicateReference; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicateReference; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.TokenId; diff --git a/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java b/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java index cd6818e..cb3edd7 100644 --- a/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java +++ b/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java @@ -5,7 +5,7 @@ import org.unicitylabs.sdk.mtree.BranchExistsException; import org.unicitylabs.sdk.mtree.LeafOutOfBoundsException; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; -import org.unicitylabs.sdk.predicate.MaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; import org.unicitylabs.sdk.predicate.Predicate; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; @@ -26,16 +26,18 @@ public class TokenSplitBuilderTest { private Token createToken(TokenCoinData coinData) { + TokenId tokenId = new TokenId(new byte[10]); + TokenType tokenType = new TokenType(new byte[10]); + Predicate predicate = new MaskedPredicate( + tokenId, + tokenType, new byte[32], "secp256k1", HashAlgorithm.SHA256, new byte[32] ); - TokenId tokenId = new TokenId(new byte[10]); - TokenType tokenType = new TokenType(new byte[10]); - return new Token<>( new TokenState(predicate, null), new Transaction<>( @@ -44,7 +46,7 @@ private Token createToken(TokenCoinData coinData) { tokenType, null, coinData, - predicate.getReference(tokenType).toAddress(), + predicate.getReference().toAddress(), new byte[20], null, null @@ -74,6 +76,8 @@ public void testTokenSplitIntoMultipleTokens() )); Predicate predicate = new MaskedPredicate( + token.getId(), + token.getType(), new byte[32], "secp256k1", HashAlgorithm.SHA256, @@ -88,7 +92,7 @@ public void testTokenSplitIntoMultipleTokens() token.getType(), null, new TokenCoinData(Map.of()), - predicate.getReference(token.getType()).toAddress(), + predicate.getReference().toAddress(), new byte[20], null ) @@ -104,7 +108,7 @@ public void testTokenSplitIntoMultipleTokens() BigInteger.valueOf(50) ) ), - predicate.getReference(token.getType()).toAddress(), + predicate.getReference().toAddress(), new byte[20], null ); @@ -126,7 +130,7 @@ public void testTokenSplitIntoMultipleTokens() BigInteger.valueOf(50) ) ), - predicate.getReference(token.getType()).toAddress(), + predicate.getReference().toAddress(), new byte[20], null ); @@ -136,15 +140,17 @@ public void testTokenSplitIntoMultipleTokens() @Test public void testTokenSplitUnknownSplitCoin() { + Token token = this.createToken(null); + Predicate predicate = new MaskedPredicate( + token.getId(), + token.getType(), new byte[32], "secp256k1", HashAlgorithm.SHA256, new byte[32] ); - Token token = this.createToken(null); - Exception exception = Assertions.assertThrows( IllegalArgumentException.class, () -> { TokenSplitBuilder builder = new TokenSplitBuilder(); @@ -159,7 +165,7 @@ public void testTokenSplitUnknownSplitCoin() { BigInteger.valueOf(100) ) ), - predicate.getReference(token.getType()).toAddress(), + predicate.getReference().toAddress(), new byte[20], null ) diff --git a/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java b/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java index e9fcd50..13abf51 100644 --- a/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java +++ b/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java @@ -9,7 +9,8 @@ import org.unicitylabs.sdk.api.SubmitCommitmentStatus; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.MaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicateReference; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; @@ -24,6 +25,7 @@ import org.unicitylabs.sdk.util.InclusionProofUtils; public class TokenUtils { + public static Token mintToken(StateTransitionClient client, byte[] secret) throws Exception { return TokenUtils.mintToken( client, @@ -52,12 +54,14 @@ public static Token mintToken( SigningService signingService = SigningService.createFromMaskedSecret(secret, nonce); MaskedPredicate predicate = MaskedPredicate.create( + tokenId, + tokenType, signingService, HashAlgorithm.SHA256, nonce ); - Address address = predicate.getReference(tokenType).toAddress(); + Address address = predicate.getReference().toAddress(); TokenState tokenState = new TokenState(predicate, null); MintCommitment> commitment = MintCommitment.create( @@ -123,13 +127,11 @@ public static Token mintNametagToken( ) throws Exception { SigningService signingService = SigningService.createFromMaskedSecret(secret, nonce); - MaskedPredicate predicate = MaskedPredicate.create( + Address address = MaskedPredicateReference.create( + tokenType, signingService, HashAlgorithm.SHA256, - nonce - ); - - Address address = predicate.getReference(tokenType).toAddress(); + nonce).toAddress(); MintCommitment> commitment = MintCommitment.create( new NametagMintTransactionData<>( @@ -158,7 +160,16 @@ public static Token mintNametagToken( // Create mint transaction return new Token<>( - new TokenState(predicate, null), + new TokenState( + MaskedPredicate.create( + commitment.getTransactionData().getTokenId(), + commitment.getTransactionData().getTokenType(), + signingService, + HashAlgorithm.SHA256, + nonce + ), + null + ), commitment.toTransaction(inclusionProof) ); } From 64010d3ba27c49071734ee74462a1e5b1ad219d7 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Fri, 19 Sep 2025 23:48:32 +0400 Subject: [PATCH 03/16] Remove unused imports --- .../org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java index 52b89c5..21d6163 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java @@ -13,7 +13,6 @@ import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenType; -import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.transaction.TransferTransactionData; From 6e713f9ca0bad744f0f7aa7784ed34c864d78c2e Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Sat, 20 Sep 2025 00:01:05 +0400 Subject: [PATCH 04/16] Fix checkstyle --- .../sdk/predicate/PredicateEngineService.java | 3 ++- .../sdk/serializer/UnicityObjectMapper.java | 13 ++++++------- ...cateCbor.java => SerializablePredicateCbor.java} | 9 ++------- ...cateJson.java => SerializablePredicateJson.java} | 4 ++-- 4 files changed, 12 insertions(+), 17 deletions(-) rename src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/{PredicateCbor.java => SerializablePredicateCbor.java} (84%) rename src/main/java/org/unicitylabs/sdk/serializer/json/predicate/{PredicateJson.java => SerializablePredicateJson.java} (96%) diff --git a/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngineService.java b/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngineService.java index 00c32be..46a9f9e 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngineService.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/PredicateEngineService.java @@ -14,7 +14,8 @@ public class PredicateEngineService { public static Predicate createPredicate(SerializablePredicate predicate) { PredicateEngine engine = PredicateEngineService.ENGINES.get(predicate.getEngine()); if (engine == null) { - throw new IllegalArgumentException("Unsupported predicate engine type: " + predicate.getEngine()); + throw new IllegalArgumentException( + "Unsupported predicate engine type: " + predicate.getEngine()); } return engine.create(predicate); diff --git a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java index 0406f6f..c3b24a9 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java @@ -27,7 +27,6 @@ import org.unicitylabs.sdk.predicate.SerializablePredicate; import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; -import org.unicitylabs.sdk.predicate.Predicate; import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; import org.unicitylabs.sdk.serializer.cbor.address.AddressCbor; import org.unicitylabs.sdk.serializer.cbor.api.AuthenticatorCbor; @@ -46,7 +45,7 @@ import org.unicitylabs.sdk.serializer.cbor.mtree.sum.SparseMerkleSumTreePathStepCbor; import org.unicitylabs.sdk.serializer.cbor.predicate.BurnPredicateCbor; import org.unicitylabs.sdk.serializer.cbor.predicate.MaskedPredicateCbor; -import org.unicitylabs.sdk.serializer.cbor.predicate.PredicateCbor; +import org.unicitylabs.sdk.serializer.cbor.predicate.SerializablePredicateCbor; import org.unicitylabs.sdk.serializer.cbor.predicate.UnmaskedPredicateCbor; import org.unicitylabs.sdk.serializer.cbor.token.TokenCbor; import org.unicitylabs.sdk.serializer.cbor.token.TokenIdCbor; @@ -77,7 +76,7 @@ import org.unicitylabs.sdk.serializer.json.mtree.sum.SparseMerkleSumTreePathJson; import org.unicitylabs.sdk.serializer.json.mtree.sum.SparseMerkleSumTreePathStepBranchJson; import org.unicitylabs.sdk.serializer.json.mtree.sum.SparseMerkleSumTreePathStepJson; -import org.unicitylabs.sdk.serializer.json.predicate.PredicateJson; +import org.unicitylabs.sdk.serializer.json.predicate.SerializablePredicateJson; import org.unicitylabs.sdk.serializer.json.token.fungible.TokenCoinDataJson; import org.unicitylabs.sdk.serializer.json.token.TokenIdJson; import org.unicitylabs.sdk.serializer.json.token.TokenJson; @@ -188,8 +187,8 @@ private static ObjectMapper createCborObjectMapper() { module.addSerializer(TokenState.class, new TokenStateCbor.Serializer()); module.addDeserializer(TokenState.class, new TokenStateCbor.Deserializer()); - module.addSerializer(SerializablePredicate.class, new PredicateCbor.Serializer()); - module.addDeserializer(SerializablePredicate.class, new PredicateCbor.Deserializer()); + module.addSerializer(SerializablePredicate.class, new SerializablePredicateCbor.Serializer()); + module.addDeserializer(SerializablePredicate.class, new SerializablePredicateCbor.Deserializer()); module.addDeserializer(MaskedPredicate.class, new MaskedPredicateCbor.Deserializer()); module.addDeserializer(UnmaskedPredicate.class, new UnmaskedPredicateCbor.Deserializer()); @@ -282,8 +281,8 @@ private static ObjectMapper createJsonObjectMapper() { module.addSerializer(TokenState.class, new TokenStateJson.Serializer()); module.addDeserializer(TokenState.class, new TokenStateJson.Deserializer()); - module.addSerializer(SerializablePredicate.class, new PredicateJson.Serializer()); - module.addDeserializer(SerializablePredicate.class, new PredicateJson.Deserializer()); + module.addSerializer(SerializablePredicate.class, new SerializablePredicateJson.Serializer()); + module.addDeserializer(SerializablePredicate.class, new SerializablePredicateJson.Deserializer()); module.addSerializer(Transaction.class, new TransactionJson.Serializer()); module.addDeserializer(Transaction.class, new TransactionJson.Deserializer()); diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/PredicateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/SerializablePredicateCbor.java similarity index 84% rename from src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/PredicateCbor.java rename to src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/SerializablePredicateCbor.java index d4d70b8..b1731ec 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/PredicateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/SerializablePredicateCbor.java @@ -5,23 +5,18 @@ import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import org.unicitylabs.sdk.predicate.EncodedPredicate; import org.unicitylabs.sdk.predicate.PredicateEngineType; import org.unicitylabs.sdk.predicate.SerializablePredicate; -import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; -import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; import org.unicitylabs.sdk.predicate.Predicate; -import org.unicitylabs.sdk.predicate.embedded.EmbeddedPredicateType; -import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; import java.io.IOException; -public class PredicateCbor { +public class SerializablePredicateCbor { - private PredicateCbor() { + private SerializablePredicateCbor() { } public static class Serializer extends JsonSerializer { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/PredicateJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/SerializablePredicateJson.java similarity index 96% rename from src/main/java/org/unicitylabs/sdk/serializer/json/predicate/PredicateJson.java rename to src/main/java/org/unicitylabs/sdk/serializer/json/predicate/SerializablePredicateJson.java index 5780433..b1ab8ae 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/PredicateJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/SerializablePredicateJson.java @@ -14,8 +14,8 @@ import java.io.IOException; import org.unicitylabs.sdk.predicate.SerializablePredicate; -public class PredicateJson { - private PredicateJson() { +public class SerializablePredicateJson { + private SerializablePredicateJson() { } public static class Serializer extends JsonSerializer { From 7053acb3456f00ab8ad2e0ad285e9a4914927194 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Sat, 20 Sep 2025 22:36:05 +0400 Subject: [PATCH 05/16] Added similar to js cbor serializer and deserializer --- .../sdk/serializer/cbor/CborDeserializer.java | 202 +++++++++++++++ .../sdk/serializer/cbor/CborMajorType.java | 32 +++ .../cbor/CborSerializationException.java | 4 + .../sdk/serializer/cbor/CborSerializer.java | 241 ++++++++++++++++++ .../serializer/cbor/CborDeserializerTest.java | 174 +++++++++++++ .../serializer/cbor/CborSerializerTest.java | 178 +++++++++++++ 6 files changed, 831 insertions(+) create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializer.java create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/cbor/CborMajorType.java create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/cbor/CborSerializer.java create mode 100644 src/test/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializerTest.java create mode 100644 src/test/java/org/unicitylabs/sdk/serializer/cbor/CborSerializerTest.java diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializer.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializer.java new file mode 100644 index 0000000..9746d7b --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializer.java @@ -0,0 +1,202 @@ +package org.unicitylabs.sdk.serializer.cbor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +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; + +public class CborDeserializer { + + private final static byte MAJOR_TYPE_MASK = (byte) 0b11100000; + private final static byte ADDITIONAL_INFORMATION_MASK = (byte) 0b00011111; + + public static T readOptional(byte[] data, Function reader) { + if (Byte.compareUnsigned(new CborReader(data).readByte(), (byte) 0xf6) == 0) { + return null; + } + + return reader.apply(data); + } + + public static long readUnsignedInteger(byte[] data) { + CborReader reader = new CborReader(data); + return reader.readLength(CborMajorType.UNSIGNED_INTEGER); + } + + public static byte[] readByteString(byte[] data) { + CborReader reader = new CborReader(data); + return reader.read((int) reader.readLength(CborMajorType.BYTE_STRING)); + } + + public static String readTextString(byte[] data) { + CborReader reader = new CborReader(data); + return new String( + reader.read((int) reader.readLength(CborMajorType.TEXT_STRING))); + } + + + public static List readArray(byte[] data) { + CborReader reader = new CborReader(data); + long length = reader.readLength(CborMajorType.ARRAY); + + ArrayList result = new ArrayList<>(); + for (int i = 0; i < length; i++) { + result.add(reader.readRawCBOR()); + } + + return result; + } + + public static Set readMap(byte[] data) { + CborReader reader = new CborReader(data); + long length = (int) reader.readLength(CborMajorType.MAP); + + Set result = new LinkedHashSet<>(); + for (int i = 0; i < length; i++) { + byte[] key = reader.readRawCBOR(); + byte[] value = reader.readRawCBOR(); + result.add(new CborMap.Entry(key, value)); + } + + return result; + } + + public static CborTag readTag(byte[] data) { + CborReader reader = new CborReader(data); + long tag = (int) reader.readLength(CborMajorType.TAG); + return new CborTag(tag, reader.readRawCBOR()); + } + + public static boolean readBoolean(byte[] data) { + byte byteValue = new CborReader(data).readByte(); + if (byteValue == (byte) 0xf5) { + return true; + } + if (byteValue == (byte) 0xf4) { + return false; + } + throw new CborSerializationException("Type mismatch, expected boolean."); + } + + private static class CborReader { + final byte[] data; + int position = 0; + + CborReader(byte[] data) { + Objects.requireNonNull(data, "Input byte array cannot be null."); + + this.data = data; + } + + public byte readByte() { + if (this.position >= this.data.length) { + throw new CborSerializationException("Premature end of data."); + } + + return this.data[this.position++]; + } + + public byte[] read(int length) { + try { + if ((this.position + length) > this.data.length) { + throw new CborSerializationException("Premature end of data."); + } + + return Arrays.copyOfRange(this.data, this.position, this.position + length); + } finally { + this.position += length; + } + } + + public long readLength(CborMajorType majorType) { + byte initialByte = this.readByte(); + + if (CborMajorType.fromType(initialByte & CborDeserializer.MAJOR_TYPE_MASK) != majorType) { + throw new CborSerializationException("Major type mismatch."); + } + + byte additionalInformation = (byte) (initialByte + & CborDeserializer.ADDITIONAL_INFORMATION_MASK); + if (Byte.compareUnsigned(additionalInformation, (byte) 24) < 0) { + return additionalInformation; + } + + switch (majorType) { + case ARRAY: + case BYTE_STRING: + case TEXT_STRING: + if (Byte.compareUnsigned(additionalInformation, (byte) 31) == 0) { + throw new CborSerializationException("Indefinite length array not supported."); + } + } + + if (Byte.compareUnsigned(additionalInformation, (byte) 27) > 0) { + throw new CborSerializationException("Encoded item is not well-formed."); + } + + long t = 0; + int length = (int) Math.pow(2, additionalInformation - 24); + for (int i = 0; i < length; ++i) { + t = (t << 8) | this.readByte() & 0xFF; + } + + return t; + } + + public byte[] readRawCBOR() { + if (this.position >= this.data.length) { + throw new CborSerializationException("Premature end of data."); + } + + CborMajorType majorType = CborMajorType.fromType(this.data[this.position] & CborDeserializer.MAJOR_TYPE_MASK); + int position = this.position; + int length = (int) this.readLength(majorType); + switch (majorType) { + case BYTE_STRING: + case TEXT_STRING: + this.read(length); + break; + case ARRAY: + for (int i = 0; i < length; i++) { + this.readRawCBOR(); + } + break; + case MAP: + for (int i = 0; i < length; i++) { + this.readRawCBOR(); + this.readRawCBOR(); + } + break; + case TAG: + this.readRawCBOR(); + break; + } + + return Arrays.copyOfRange(this.data, position, this.position); + } + } + + public static class CborTag { + private final long tag; + private final byte[] data; + + private CborTag(long tag, byte[] data) { + this.tag = tag; + this.data = data; + } + + public long getTag() { + return this.tag; + } + + public byte[] getData() { + return Arrays.copyOf(this.data, this.data.length); + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborMajorType.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborMajorType.java new file mode 100644 index 0000000..5262d17 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborMajorType.java @@ -0,0 +1,32 @@ +package org.unicitylabs.sdk.serializer.cbor; + +public enum CborMajorType { + UNSIGNED_INTEGER(0b00000000), + NEGATIVE_INTEGER(0b00100000), + BYTE_STRING(0b01000000), + TEXT_STRING(0b01100000), + ARRAY(0b10000000), + MAP(0b10100000), + TAG(0b11000000), + SIMPLE_AND_FLOAT(0b11100000); + + private final int type; + + CborMajorType(int type) { + this.type = type; + } + + public int getType() { + return this.type; + } + + public static CborMajorType fromType(int type) { + for (CborMajorType majorType : CborMajorType.values()) { + if (Integer.compareUnsigned(majorType.getType(), type & 0xFF) == 0) { + return majorType; + } + } + throw new IllegalArgumentException("Invalid CBOR major type: " + type); + } + +} \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborSerializationException.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborSerializationException.java index 7308492..6dc8311 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborSerializationException.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborSerializationException.java @@ -8,4 +8,8 @@ public CborSerializationException(String message, Throwable cause) { public CborSerializationException(Throwable cause) { super(cause); } + + public CborSerializationException(String message) { + super(message); + } } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborSerializer.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborSerializer.java new file mode 100644 index 0000000..f4389c5 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborSerializer.java @@ -0,0 +1,241 @@ +package org.unicitylabs.sdk.serializer.cbor; + +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.function.Function; + +public class CborSerializer { + + public static byte[] encodeOptional(T data, Function encoder) { + if (data == null) { + return new byte[]{(byte) 0xf6}; + } + return encoder.apply(data); + } + + public static byte[] encodeUnsignedInteger(long input) { + if (Long.compareUnsigned(input, 24) < 0) { + return new byte[]{(byte) (CborMajorType.UNSIGNED_INTEGER.getType() | input)}; + } + + byte[] bytes = CborSerializer.getUnsignedLongAsPaddedBytes(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) + ); + + return result; + } + + public static byte[] encodeByteString(byte[] input) { + if (input == null) { + throw new CborSerializationException("Input byte array cannot be null."); + } + + return CborSerializer.encodeRawArray(input, input.length, CborMajorType.BYTE_STRING); + } + + public static byte[] encodeTextString(String input) { + if (input == null) { + throw new CborSerializationException("Input string cannot be null."); + } + + byte[] bytes = input.getBytes(StandardCharsets.UTF_8); + return CborSerializer.encodeRawArray(bytes, bytes.length, CborMajorType.TEXT_STRING); + } + + public static byte[] encodeArray(List input) { + if (input == null) { + throw new CborSerializationException("Input byte array list cannot be null."); + } + + int length = 0; + for (byte[] bytes : input) { + length += bytes.length; + } + + byte[] data = new byte[length]; + length = 0; + for (byte[] bytes : input) { + System.arraycopy(bytes, 0, data, length, bytes.length); + length += bytes.length; + } + + return CborSerializer.encodeRawArray(data, input.size(), CborMajorType.ARRAY); + } + + /** + * + * @param input map with hex converted keys + * @return cbor representation of the map + */ + public static byte[] encodeMap(CborMap input) { + if (input == null) { + throw new CborSerializationException("Input set for map entry cannot be null."); + } + + int length = 0; + for (CborMap.Entry entry : input.entries) { + length += entry.key.length + entry.value.length; + } + + byte[] data = new byte[length]; + length = 0; + for (CborMap.Entry entry : input.entries) { + System.arraycopy(entry.key, 0, data, length, entry.key.length); + length += entry.key.length; + System.arraycopy(entry.value, 0, data, length, entry.value.length); + length += entry.value.length; + } + + return CborSerializer.encodeRawArray(data, input.entries.size(), CborMajorType.MAP); + } + + public static byte[] encodeTag(long tag, byte[] input) { + if (Long.compareUnsigned(tag, 24) < 0) { + byte[] result = new byte[1 + input.length]; + result[0] = (byte) (CborMajorType.TAG.getType() | tag); + System.arraycopy(input, 0, result, 1, input.length); + + return result; + } + + byte[] bytes = CborSerializer.getUnsignedLongAsPaddedBytes(tag); + byte[] result = new byte[1 + bytes.length + input.length]; + result[0] = (byte) ( + CborMajorType.TAG.getType() + | CborSerializer.getAdditionalInformationBits(bytes.length) + ); + System.arraycopy(bytes, 0, result, 1, bytes.length); + System.arraycopy(input, 0, result, 1 + bytes.length, input.length); + + return result; + } + + public static byte[] encodeBoolean(boolean input) { + return new byte[]{(byte) (input ? 0xf5 : 0xf4)}; + } + + public static byte[] encodeNull() { + return new byte[]{(byte) 0xf6}; + } + + private static byte[] encodeRawArray(byte[] input, int length, CborMajorType type) { + if (length < 24) { + byte[] result = new byte[1 + input.length]; + result[0] = (byte) (type.getType() | length); + System.arraycopy(input, 0, result, 1, input.length); + + return result; + } + + byte[] lengthBytes = CborSerializer.getUnsignedLongAsPaddedBytes(length); + byte[] result = new byte[1 + lengthBytes.length + input.length]; + result[0] = (byte) ( + type.getType() | CborSerializer.getAdditionalInformationBits(lengthBytes.length) + ); + System.arraycopy(lengthBytes, 0, result, 1, lengthBytes.length); + System.arraycopy(input, 0, result, 1 + lengthBytes.length, input.length); + + return result; + } + + private static int getAdditionalInformationBits(int length) { + return 24 + (int) Math.ceil(Math.log(length) / Math.log(2)); + } + + private static byte[] getUnsignedLongAsPaddedBytes(long input) { + int length = 0; + for (long t = input; Long.compareUnsigned(t, 0) > 0; t = t >>> 8) { + length++; + } + + ByteBuffer buffer = ByteBuffer + .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) { + buffer.putShort((short) input); + } else if (length <= 4) { + buffer.putInt((int) input); + } else { + buffer.putLong(input); + } + + return buffer.array(); + } + + public static final class CborMap { + + private final ArrayList entries; + + public CborMap(Set entries) { + this.entries = new ArrayList<>(entries); + this.entries.sort((a, b) -> { + if (a.key.length != b.key.length) { + return a.key.length - b.key.length; + } + + for (int i = 0; i < a.key.length; i++) { + if (a.key[i] != b.key[i]) { + return a.key[i] - b.key[i]; + } + } + + return 0; + }); + } + + public List getEntries() { + return List.copyOf(this.entries); + } + + public static final class Entry { + + private final byte[] key; + private final byte[] value; + + public Entry(byte[] key, byte[] value) { + Objects.requireNonNull(key, "Key cannot be null."); + Objects.requireNonNull(value, "Value cannot be null."); + + this.key = Arrays.copyOf(key, key.length); + this.value = Arrays.copyOf(value, value.length); + } + + public byte[] getKey() { + return Arrays.copyOf(this.key, this.key.length); + } + + public byte[] getValue() { + return Arrays.copyOf(this.value, this.value.length); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Entry)) { + return false; + } + Entry other = (Entry) o; + return Arrays.equals(this.key, other.key); + } + + @Override + public int hashCode() { + return Arrays.hashCode(this.key); + } + } + } +} diff --git a/src/test/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializerTest.java b/src/test/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializerTest.java new file mode 100644 index 0000000..6eb2fe4 --- /dev/null +++ b/src/test/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializerTest.java @@ -0,0 +1,174 @@ +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; +import org.unicitylabs.sdk.serializer.cbor.CborSerializer.CborMap; +import org.unicitylabs.sdk.serializer.cbor.CborSerializer.CborMap.Entry; +import org.unicitylabs.sdk.util.HexConverter; + +public class CborDeserializerTest { + + @Test + void testReadUnsignedInteger() { + Assertions.assertEquals( + 5, + CborDeserializer.readUnsignedInteger(HexConverter.decode("05")) + ); + + Assertions.assertEquals( + 100, + CborDeserializer.readUnsignedInteger(HexConverter.decode("1864")) + ); + + Assertions.assertEquals( + 10000, + CborDeserializer.readUnsignedInteger(HexConverter.decode("192710")) + ); + + Assertions.assertEquals( + 66000, + CborDeserializer.readUnsignedInteger(HexConverter.decode("1a000101d0")) + ); + + Assertions.assertEquals( + 8147483647L, + CborDeserializer.readUnsignedInteger(HexConverter.decode("1b00000001e5a0bbff")) + ); + + Assertions.assertEquals( + -5, + CborDeserializer.readUnsignedInteger(HexConverter.decode("1bfffffffffffffffb")) + ); + } + + @Test + void testReadByteString() { + Assertions.assertArrayEquals( + new byte[5], + CborDeserializer.readByteString(HexConverter.decode("450000000000")) + ); + + Assertions.assertArrayEquals( + new byte[25], + CborDeserializer.readByteString( + HexConverter.decode("581900000000000000000000000000000000000000000000000000")) + ); + } + + @Test + void testReadTextString() { + Assertions.assertEquals( + "Hello, world!", + CborDeserializer.readTextString(HexConverter.decode("6d48656c6c6f2c20776f726c6421")) + ); + + Assertions.assertEquals( + new String(new byte[25]), + CborDeserializer.readTextString( + HexConverter.decode("781900000000000000000000000000000000000000000000000000")) + ); + } + + @Test + void testReadArray() { + List data = CborDeserializer.readArray( + HexConverter.decode( + "98196d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c6421") + ); + + for (byte[] item : data) { + Assertions.assertEquals("Hello, world!", CborDeserializer.readTextString(item)); + } + } + + @Test + void testReadMap() { + Set data = CborDeserializer.readMap( + HexConverter.decode( + "a4430000006d48656c6c6f2c20776f726c6421430000016d48656c6c6f2c20776f726c64216454657374f66d48656c6c6f2c20776f726c6421581900000000000000000000000000000000000000000000000000") + ); + + Iterator iterator = data.iterator(); + Entry entry = iterator.next(); + Assertions.assertArrayEquals( + CborSerializer.encodeByteString(HexConverter.decode("000000")), + entry.getKey() + ); + Assertions.assertArrayEquals( + CborSerializer.encodeTextString("Hello, world!"), + entry.getValue() + ); + + entry = iterator.next(); + Assertions.assertArrayEquals( + CborSerializer.encodeByteString(HexConverter.decode("000001")), + entry.getKey() + ); + Assertions.assertArrayEquals( + CborSerializer.encodeTextString("Hello, world!"), + entry.getValue() + ); + + entry = iterator.next(); + Assertions.assertArrayEquals( + CborSerializer.encodeTextString("Test"), + entry.getKey() + ); + Assertions.assertArrayEquals( + CborSerializer.encodeNull(), + entry.getValue() + ); + + entry = iterator.next(); + Assertions.assertArrayEquals( + CborSerializer.encodeTextString("Hello, world!"), + entry.getKey() + ); + Assertions.assertArrayEquals( + CborSerializer.encodeByteString(new byte[25]), + entry.getValue() + ); + } + + @Test + void testReadBoolean() { + Assertions.assertEquals( + true, + CborDeserializer.readBoolean(HexConverter.decode("f5")) + ); + + Assertions.assertEquals( + false, + CborDeserializer.readBoolean(HexConverter.decode("f4")) + ); + } + + @Test + void testReadOptional() { + Assertions.assertEquals( + null, + CborDeserializer.readOptional(HexConverter.decode("f6"), + CborDeserializer::readUnsignedInteger) + ); + } + + @Test + void testEncodeTag() { + CborTag tag = CborDeserializer.readTag( + HexConverter.decode("d4781a746167206e756d62657220736d616c6c6572207468616e203234") + ); + Assertions.assertEquals( + 20, + tag.getTag() + ); + + Assertions.assertArrayEquals( + 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 new file mode 100644 index 0000000..4a74562 --- /dev/null +++ b/src/test/java/org/unicitylabs/sdk/serializer/cbor/CborSerializerTest.java @@ -0,0 +1,178 @@ +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; + +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])); + // Check that value cannot be null on entry + Assertions.assertThrows(NullPointerException.class, + () -> 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]) + )); + } + + @Test + void testEncodeUnsignedInteger() { + Assertions.assertArrayEquals( + HexConverter.decode("05"), + CborSerializer.encodeUnsignedInteger(5) + ); + + Assertions.assertArrayEquals( + HexConverter.decode("1864"), + CborSerializer.encodeUnsignedInteger(100) + ); + + Assertions.assertArrayEquals( + HexConverter.decode("192710"), + CborSerializer.encodeUnsignedInteger(10000) + ); + + Assertions.assertArrayEquals( + HexConverter.decode("1a000101d0"), + CborSerializer.encodeUnsignedInteger(66000) + ); + + Assertions.assertArrayEquals( + HexConverter.decode("1b00000001e5a0bbff"), + CborSerializer.encodeUnsignedInteger(8147483647L) + ); + + Assertions.assertArrayEquals( + HexConverter.decode("1bfffffffffffffffb"), + CborSerializer.encodeUnsignedInteger(-5) + ); + } + + @Test + void testEncodeByteString() { + Assertions.assertArrayEquals( + HexConverter.decode("450000000000"), + CborSerializer.encodeByteString(new byte[5]) + ); + + Assertions.assertArrayEquals( + HexConverter.decode("581900000000000000000000000000000000000000000000000000"), + CborSerializer.encodeByteString(new byte[25]) + ); + } + + @Test + void testEncodeTextString() { + Assertions.assertArrayEquals( + HexConverter.decode("6d48656c6c6f2c20776f726c6421"), + CborSerializer.encodeTextString("Hello, world!") + ); + + Assertions.assertArrayEquals( + HexConverter.decode("781900000000000000000000000000000000000000000000000000"), + CborSerializer.encodeTextString(new String(new byte[25])) + ); + } + + @Test + void testEncodeArray() { + Assertions.assertArrayEquals( + HexConverter.decode( + "826d48656c6c6f2c20776f726c6421581900000000000000000000000000000000000000000000000000"), + CborSerializer.encodeArray( + List.of( + CborSerializer.encodeTextString("Hello, world!"), + CborSerializer.encodeByteString(new byte[25]) + ) + ) + ); + + List list = new ArrayList<>(); + for (int i = 0; i < 25; i++) { + list.add(CborSerializer.encodeTextString("Hello, world!")); + } + + Assertions.assertArrayEquals( + HexConverter.decode( + "98196d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c64216d48656c6c6f2c20776f726c6421"), + CborSerializer.encodeArray(list) + ); + } + + @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() + ) + ) + ) + ) + ); + } + + @Test + void testEncodeBoolean() { + Assertions.assertArrayEquals( + HexConverter.decode("f5"), + CborSerializer.encodeBoolean(true) + ); + + Assertions.assertArrayEquals( + HexConverter.decode("f4"), + CborSerializer.encodeBoolean(false) + ); + } + + @Test + void testEncodeNull() { + Assertions.assertArrayEquals( + HexConverter.decode("f6"), + CborSerializer.encodeNull() + ); + } + + @Test + void testEncodeTag() { + Assertions.assertArrayEquals( + 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")) + ); + + } +} From 22ad77a0bf938e7c0571b0d2035e7378bad98f81 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Sun, 21 Sep 2025 13:59:42 +0400 Subject: [PATCH 06/16] Fix imports, add root trustbase objects, move cbor encoding for predicates --- .../org/unicitylabs/sdk/bft/NodeInfo.java | 20 ++++++ .../unicitylabs/sdk/bft/RootTrustBase.java | 70 +++++++++++++++++++ .../org/unicitylabs/sdk/bft/UnicitySeal.java | 1 - .../sdk/predicate/embedded/BurnPredicate.java | 7 +- .../predicate/embedded/DefaultPredicate.java | 10 +-- .../embedded/EmbeddedPredicateEngine.java | 12 ++++ .../predicate/embedded/MaskedPredicate.java | 5 +- .../sdk/serializer/UnicityObjectMapper.java | 11 +-- .../serializer/cbor/bft/InputRecordCbor.java | 2 - .../cbor/bft/ShardTreeCertificateCbor.java | 9 --- .../cbor/bft/UnicityCertificateCbor.java | 5 -- .../serializer/cbor/bft/UnicitySealCbor.java | 1 - .../cbor/bft/UnicityTreeCertificateCbor.java | 3 - .../UnicityTreeCertificateHashStepCbor.java | 2 - .../cbor/predicate/BurnPredicateCbor.java | 24 +++++++ .../cbor/predicate/DefaultPredicateCbor.java | 33 +++++++++ .../cbor/predicate/MaskedPredicateCbor.java | 1 - .../predicate/SerializablePredicateJson.java | 1 + .../java/org/unicitylabs/sdk/token/Token.java | 3 +- .../transaction/MintTransactionReason.java | 2 +- .../transaction/split/SplitMintReason.java | 4 +- .../VerificationResult.java | 2 +- .../verification/VerificationResultCode.java | 6 ++ .../sdk/verification/VerificationRule.java | 36 ++++++++++ .../unicitylabs/sdk/e2e/CommonTestFlow.java | 18 ++--- 25 files changed, 227 insertions(+), 61 deletions(-) create mode 100644 src/main/java/org/unicitylabs/sdk/bft/NodeInfo.java create mode 100644 src/main/java/org/unicitylabs/sdk/bft/RootTrustBase.java create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/DefaultPredicateCbor.java rename src/main/java/org/unicitylabs/sdk/{util => verification}/VerificationResult.java (96%) create mode 100644 src/main/java/org/unicitylabs/sdk/verification/VerificationResultCode.java create mode 100644 src/main/java/org/unicitylabs/sdk/verification/VerificationRule.java diff --git a/src/main/java/org/unicitylabs/sdk/bft/NodeInfo.java b/src/main/java/org/unicitylabs/sdk/bft/NodeInfo.java new file mode 100644 index 0000000..f854e49 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/bft/NodeInfo.java @@ -0,0 +1,20 @@ +package org.unicitylabs.sdk.bft; + +import java.util.Arrays; + +public class NodeInfo { + + public final String nodeId; + private final byte[] signingKey; + public final long stakedAmount; + + public NodeInfo(String nodeId, byte[] signingKey, long stakedAmount) { + this.nodeId = nodeId; + this.signingKey = Arrays.copyOf(signingKey, signingKey.length); + this.stakedAmount = stakedAmount; + } + + public byte[] getSigningKey() { + return Arrays.copyOf(this.signingKey, this.signingKey.length); + } +} \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/bft/RootTrustBase.java b/src/main/java/org/unicitylabs/sdk/bft/RootTrustBase.java new file mode 100644 index 0000000..6ceec35 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/bft/RootTrustBase.java @@ -0,0 +1,70 @@ +package org.unicitylabs.sdk.bft; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class RootTrustBase { + public final long version; + public final long epoch; + public final long epochStartRound; + public final List rootNodes; + public final long quorumThreshold; + private final byte[] stateHash; + private final byte[] changeRecordHash; + private final byte[] previousEntryHash; + private final Map signatures; + + public RootTrustBase( + long version, + long epoch, + long epochStartRound, + List rootNodes, + long quorumThreshold, + byte[] stateHash, + byte[] changeRecordHash, + byte[] previousEntryHash, + Map signatures + ) { + this.version = version; + this.epoch = epoch; + this.epochStartRound = epochStartRound; + this.rootNodes = Collections.unmodifiableList(rootNodes); + this.quorumThreshold = quorumThreshold; + this.stateHash = Arrays.copyOf(stateHash, stateHash.length); + this.changeRecordHash = Arrays.copyOf(changeRecordHash, changeRecordHash.length); + this.previousEntryHash = Arrays.copyOf(previousEntryHash, previousEntryHash.length); + this.signatures = signatures.entrySet().stream() + .collect( + Collectors.toUnmodifiableMap( + Map.Entry::getKey, + e -> Arrays.copyOf(e.getValue(), e.getValue().length) + ) + ); + } + + public byte[] getStateHash() { + return Arrays.copyOf(this.stateHash, this.stateHash.length); + } + + public byte[] getChangeRecordHash() { + return Arrays.copyOf(this.changeRecordHash, this.changeRecordHash.length); + } + + public byte[] getPreviousEntryHash() { + return Arrays.copyOf(this.previousEntryHash, this.previousEntryHash.length); + } + + public Map getSignatures() { + return this.signatures.entrySet().stream() + .collect( + Collectors.toUnmodifiableMap( + Map.Entry::getKey, + e -> Arrays.copyOf(e.getValue(), e.getValue().length) + ) + ); + } +} \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java b/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java index 9e7b1af..1997c9c 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java +++ b/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java @@ -3,7 +3,6 @@ import java.math.BigInteger; import java.util.Arrays; import java.util.Map; -import java.util.Map.Entry; import java.util.Objects; import java.util.stream.Collectors; import org.unicitylabs.sdk.util.HexConverter; diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java index 21d6163..907fe78 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java @@ -87,12 +87,7 @@ public byte[] encode() { @Override public byte[] encodeParameters() { try { - return UnicityObjectMapper.CBOR.writeValueAsBytes( - UnicityObjectMapper.CBOR.createArrayNode() - .addPOJO(this.tokenId) - .addPOJO(this.tokenType) - .addPOJO(this.burnReason) - ); + return UnicityObjectMapper.CBOR.writeValueAsBytes(this); } catch (JsonProcessingException e) { throw new CborSerializationException(e); } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java index 27ecb11..87d8716 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java @@ -155,15 +155,7 @@ public byte[] encode() { @Override public byte[] encodeParameters() { try { - return UnicityObjectMapper.CBOR.writeValueAsBytes( - UnicityObjectMapper.CBOR.createArrayNode() - .addPOJO(this.tokenId) - .addPOJO(this.tokenType) - .add(this.publicKey) - .add(this.signingAlgorithm) - .addPOJO(this.hashAlgorithm) - .add(this.nonce) - ); + return UnicityObjectMapper.CBOR.writeValueAsBytes(this); } catch (JsonProcessingException e) { throw new CborSerializationException(e); } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateEngine.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateEngine.java index 47e1e56..316eeff 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateEngine.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateEngine.java @@ -13,16 +13,28 @@ public Predicate create(SerializablePredicate predicate) { EmbeddedPredicateType type = EmbeddedPredicateType.fromBytes(predicate.encode()); switch (type) { case MASKED: + if (predicate instanceof MaskedPredicate) { + return (MaskedPredicate) predicate; + } + return UnicityObjectMapper.CBOR.readValue( predicate.encodeParameters(), MaskedPredicate.class ); case UNMASKED: + if (predicate instanceof UnmaskedPredicate) { + return (UnmaskedPredicate) predicate; + } + return UnicityObjectMapper.CBOR.readValue( predicate.encodeParameters(), UnmaskedPredicate.class ); case BURN: + if (predicate instanceof BurnPredicate) { + return (BurnPredicate) predicate; + } + return UnicityObjectMapper.CBOR.readValue( predicate.encodeParameters(), BurnPredicate.class diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/MaskedPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/MaskedPredicate.java index 5f5f54f..96b5116 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/MaskedPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/MaskedPredicate.java @@ -6,6 +6,7 @@ import org.unicitylabs.sdk.token.TokenType; public class MaskedPredicate extends DefaultPredicate { + public MaskedPredicate( TokenId tokenId, TokenType tokenType, @@ -30,8 +31,8 @@ public static MaskedPredicate create( SigningService signingService, HashAlgorithm hashAlgorithm, byte[] nonce) { - return new MaskedPredicate(tokenId, tokenType, signingService.getPublicKey(), signingService.getAlgorithm(), - hashAlgorithm, nonce); + return new MaskedPredicate(tokenId, tokenType, signingService.getPublicKey(), + signingService.getAlgorithm(), hashAlgorithm, nonce); } @Override diff --git a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java index c3b24a9..cab8ffe 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java @@ -26,6 +26,7 @@ import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTreePathStep; import org.unicitylabs.sdk.predicate.SerializablePredicate; import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; +import org.unicitylabs.sdk.predicate.embedded.DefaultPredicate; import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; import org.unicitylabs.sdk.serializer.cbor.address.AddressCbor; @@ -44,6 +45,7 @@ import org.unicitylabs.sdk.serializer.cbor.mtree.sum.SparseMerkleSumTreePathStepBranchCbor; import org.unicitylabs.sdk.serializer.cbor.mtree.sum.SparseMerkleSumTreePathStepCbor; import org.unicitylabs.sdk.serializer.cbor.predicate.BurnPredicateCbor; +import org.unicitylabs.sdk.serializer.cbor.predicate.DefaultPredicateCbor; import org.unicitylabs.sdk.serializer.cbor.predicate.MaskedPredicateCbor; import org.unicitylabs.sdk.serializer.cbor.predicate.SerializablePredicateCbor; import org.unicitylabs.sdk.serializer.cbor.predicate.UnmaskedPredicateCbor; @@ -77,12 +79,11 @@ import org.unicitylabs.sdk.serializer.json.mtree.sum.SparseMerkleSumTreePathStepBranchJson; import org.unicitylabs.sdk.serializer.json.mtree.sum.SparseMerkleSumTreePathStepJson; import org.unicitylabs.sdk.serializer.json.predicate.SerializablePredicateJson; -import org.unicitylabs.sdk.serializer.json.token.fungible.TokenCoinDataJson; import org.unicitylabs.sdk.serializer.json.token.TokenIdJson; import org.unicitylabs.sdk.serializer.json.token.TokenJson; import org.unicitylabs.sdk.serializer.json.token.TokenStateJson; import org.unicitylabs.sdk.serializer.json.token.TokenTypeJson; -import org.unicitylabs.sdk.serializer.json.transaction.split.SplitMintReasonJson; +import org.unicitylabs.sdk.serializer.json.token.fungible.TokenCoinDataJson; import org.unicitylabs.sdk.serializer.json.transaction.CommitmentJson; import org.unicitylabs.sdk.serializer.json.transaction.InclusionProofJson; import org.unicitylabs.sdk.serializer.json.transaction.MintCommitmentJson; @@ -91,13 +92,13 @@ import org.unicitylabs.sdk.serializer.json.transaction.TransactionJson; import org.unicitylabs.sdk.serializer.json.transaction.TransferCommitmentJson; import org.unicitylabs.sdk.serializer.json.transaction.TransferTransactionDataJson; +import org.unicitylabs.sdk.serializer.json.transaction.split.SplitMintReasonJson; import org.unicitylabs.sdk.serializer.json.transaction.split.SplitMintReasonProofJson; import org.unicitylabs.sdk.serializer.json.util.ByteArrayHexJson; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenState; import org.unicitylabs.sdk.token.TokenType; -import org.unicitylabs.sdk.transaction.split.SplitMintReason; import org.unicitylabs.sdk.token.fungible.TokenCoinData; import org.unicitylabs.sdk.transaction.Commitment; import org.unicitylabs.sdk.transaction.InclusionProof; @@ -107,6 +108,7 @@ import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.transaction.TransferCommitment; import org.unicitylabs.sdk.transaction.TransferTransactionData; +import org.unicitylabs.sdk.transaction.split.SplitMintReason; import org.unicitylabs.sdk.transaction.split.SplitMintReasonProof; public class UnicityObjectMapper { @@ -190,11 +192,12 @@ private static ObjectMapper createCborObjectMapper() { module.addSerializer(SerializablePredicate.class, new SerializablePredicateCbor.Serializer()); module.addDeserializer(SerializablePredicate.class, new SerializablePredicateCbor.Deserializer()); + module.addSerializer(DefaultPredicate.class, new DefaultPredicateCbor.Serializer()); + module.addSerializer(BurnPredicate.class, new BurnPredicateCbor.Serializer()); module.addDeserializer(MaskedPredicate.class, new MaskedPredicateCbor.Deserializer()); module.addDeserializer(UnmaskedPredicate.class, new UnmaskedPredicateCbor.Deserializer()); module.addDeserializer(BurnPredicate.class, new BurnPredicateCbor.Deserializer()); - // BFT - UnicityCertificate module.addSerializer(UnicityCertificate.class, new UnicityCertificateCbor.Serializer()); module.addDeserializer(UnicityCertificate.class, new UnicityCertificateCbor.Deserializer()); diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java index a01ba15..0253aea 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java @@ -8,12 +8,10 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import com.fasterxml.jackson.dataformat.cbor.CBORParser; import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import org.unicitylabs.sdk.bft.InputRecord; -import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.util.HexConverter; public class InputRecordCbor { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java index 53b2521..08009e6 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java @@ -8,19 +8,10 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import com.fasterxml.jackson.dataformat.cbor.CBORParser; import java.io.IOException; -import java.math.BigInteger; import java.util.ArrayList; import java.util.List; -import org.unicitylabs.sdk.bft.InputRecord; import org.unicitylabs.sdk.bft.ShardTreeCertificate; -import org.unicitylabs.sdk.bft.UnicityCertificate; -import org.unicitylabs.sdk.bft.UnicitySeal; -import org.unicitylabs.sdk.bft.UnicityTreeCertificate; -import org.unicitylabs.sdk.transaction.Transaction; -import org.unicitylabs.sdk.transaction.TransferTransactionData; -import org.unicitylabs.sdk.util.HexConverter; public class ShardTreeCertificateCbor { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityCertificateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityCertificateCbor.java index 31e4736..e7f47d8 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityCertificateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityCertificateCbor.java @@ -8,18 +8,13 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import com.fasterxml.jackson.dataformat.cbor.CBORParser; import java.io.IOException; import java.math.BigInteger; -import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.bft.InputRecord; import org.unicitylabs.sdk.bft.ShardTreeCertificate; import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.bft.UnicitySeal; import org.unicitylabs.sdk.bft.UnicityTreeCertificate; -import org.unicitylabs.sdk.hash.DataHash; -import org.unicitylabs.sdk.signing.Signature; -import org.unicitylabs.sdk.util.HexConverter; public class UnicityCertificateCbor { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java index e334424..29a6ade 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java @@ -8,7 +8,6 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import com.fasterxml.jackson.dataformat.cbor.CBORParser; import java.io.IOException; import java.math.BigInteger; import java.util.HashMap; diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java index e14e306..d7b5a64 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java @@ -8,14 +8,11 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import com.fasterxml.jackson.dataformat.cbor.CBORParser; import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; -import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.bft.UnicityTreeCertificate; -import org.unicitylabs.sdk.token.Token; public class UnicityTreeCertificateCbor { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java index 1f54542..ab88561 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java @@ -7,10 +7,8 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import com.fasterxml.jackson.dataformat.cbor.CBORParser; import java.io.IOException; import java.math.BigInteger; -import java.util.List; import org.unicitylabs.sdk.bft.UnicityTreeCertificate; public class UnicityTreeCertificateHashStepCbor { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/BurnPredicateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/BurnPredicateCbor.java index ba2ff63..1e0e2a6 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/BurnPredicateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/BurnPredicateCbor.java @@ -1,13 +1,18 @@ package org.unicitylabs.sdk.serializer.cbor.predicate; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import java.io.IOException; import org.unicitylabs.sdk.hash.DataHash; +import org.unicitylabs.sdk.predicate.SerializablePredicate; import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenType; @@ -16,6 +21,25 @@ public class BurnPredicateCbor { private BurnPredicateCbor() { } + public static class Serializer extends JsonSerializer { + + @Override + public void serialize(BurnPredicate value, JsonGenerator gen, + SerializerProvider serializers) + throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + + gen.writeStartArray(value, 3); + gen.writeObject(value.getTokenId()); + gen.writeObject(value.getTokenType()); + gen.writeObject(value.getReason()); + gen.writeEndArray(); + } + } + public static class Deserializer extends JsonDeserializer { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/DefaultPredicateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/DefaultPredicateCbor.java new file mode 100644 index 0000000..143e2e6 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/DefaultPredicateCbor.java @@ -0,0 +1,33 @@ +package org.unicitylabs.sdk.serializer.cbor.predicate; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; +import org.unicitylabs.sdk.predicate.embedded.DefaultPredicate; + +public class DefaultPredicateCbor { + private DefaultPredicateCbor() { + } + + public static class Serializer extends JsonSerializer { + @Override + public void serialize(DefaultPredicate value, JsonGenerator gen, + SerializerProvider serializers) + throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + + gen.writeStartArray(value, 6); + gen.writeObject(value.getTokenId()); + gen.writeObject(value.getTokenType()); + gen.writeObject(value.getPublicKey()); + gen.writeObject(value.getSigningAlgorithm()); + gen.writeObject(value.getHashAlgorithm()); + gen.writeObject(value.getNonce()); + gen.writeEndArray(); + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/MaskedPredicateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/MaskedPredicateCbor.java index cb5f851..c253e67 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/MaskedPredicateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/MaskedPredicateCbor.java @@ -8,7 +8,6 @@ import java.io.IOException; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; -import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenType; diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/SerializablePredicateJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/SerializablePredicateJson.java index b1ab8ae..d045f93 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/SerializablePredicateJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/SerializablePredicateJson.java @@ -13,6 +13,7 @@ import org.unicitylabs.sdk.predicate.PredicateEngineType; import java.io.IOException; import org.unicitylabs.sdk.predicate.SerializablePredicate; +import org.unicitylabs.sdk.predicate.embedded.EmbeddedPredicateType; public class SerializablePredicateJson { private SerializablePredicateJson() { diff --git a/src/main/java/org/unicitylabs/sdk/token/Token.java b/src/main/java/org/unicitylabs/sdk/token/Token.java index 1eb3a4d..3f9d36e 100644 --- a/src/main/java/org/unicitylabs/sdk/token/Token.java +++ b/src/main/java/org/unicitylabs/sdk/token/Token.java @@ -16,14 +16,13 @@ import org.unicitylabs.sdk.predicate.PredicateEngineService; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.fungible.TokenCoinData; -import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.transaction.InclusionProofVerificationStatus; import org.unicitylabs.sdk.transaction.MintCommitment; import org.unicitylabs.sdk.transaction.MintTransactionData; import org.unicitylabs.sdk.transaction.MintTransactionState; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.transaction.TransferTransactionData; -import org.unicitylabs.sdk.util.VerificationResult; +import org.unicitylabs.sdk.verification.VerificationResult; public class Token> { diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionReason.java b/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionReason.java index e065b2c..256cada 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionReason.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionReason.java @@ -1,6 +1,6 @@ package org.unicitylabs.sdk.transaction; -import org.unicitylabs.sdk.util.VerificationResult; +import org.unicitylabs.sdk.verification.VerificationResult; public interface MintTransactionReason { String getType(); diff --git a/src/main/java/org/unicitylabs/sdk/transaction/split/SplitMintReason.java b/src/main/java/org/unicitylabs/sdk/transaction/split/SplitMintReason.java index 155924e..75bd7a3 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/split/SplitMintReason.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/split/SplitMintReason.java @@ -5,9 +5,7 @@ import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTreePathStep.Branch; import org.unicitylabs.sdk.predicate.PredicateEngineService; import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; -import org.unicitylabs.sdk.predicate.embedded.EmbeddedPredicateType; import org.unicitylabs.sdk.predicate.Predicate; -import org.unicitylabs.sdk.predicate.PredicateEngineType; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.fungible.CoinId; import org.unicitylabs.sdk.token.fungible.TokenCoinData; @@ -15,7 +13,7 @@ import org.unicitylabs.sdk.transaction.MintTransactionData; import org.unicitylabs.sdk.transaction.MintTransactionReason; import org.unicitylabs.sdk.transaction.Transaction; -import org.unicitylabs.sdk.util.VerificationResult; +import org.unicitylabs.sdk.verification.VerificationResult; import java.math.BigInteger; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/org/unicitylabs/sdk/util/VerificationResult.java b/src/main/java/org/unicitylabs/sdk/verification/VerificationResult.java similarity index 96% rename from src/main/java/org/unicitylabs/sdk/util/VerificationResult.java rename to src/main/java/org/unicitylabs/sdk/verification/VerificationResult.java index c40ba71..0097a2e 100644 --- a/src/main/java/org/unicitylabs/sdk/util/VerificationResult.java +++ b/src/main/java/org/unicitylabs/sdk/verification/VerificationResult.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.util; +package org.unicitylabs.sdk.verification; import java.util.List; diff --git a/src/main/java/org/unicitylabs/sdk/verification/VerificationResultCode.java b/src/main/java/org/unicitylabs/sdk/verification/VerificationResultCode.java new file mode 100644 index 0000000..4952dc5 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/verification/VerificationResultCode.java @@ -0,0 +1,6 @@ +package org.unicitylabs.sdk.verification; + +public enum VerificationResultCode { + OK, + FAIL +} diff --git a/src/main/java/org/unicitylabs/sdk/verification/VerificationRule.java b/src/main/java/org/unicitylabs/sdk/verification/VerificationRule.java new file mode 100644 index 0000000..747571d --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/verification/VerificationRule.java @@ -0,0 +1,36 @@ +package org.unicitylabs.sdk.verification; + +public abstract class VerificationRule { + private final VerificationRule onSuccessRule; + private final VerificationRule onFailureRule; + + protected VerificationRule( + VerificationRule onSuccessRule, + VerificationRule onFailureRule + ) { + this.onSuccessRule = onSuccessRule; + this.onFailureRule = onFailureRule; + } + + protected VerificationRule( + VerificationRule onAny + ) { + this( + onAny, + onAny + ); + } + + public VerificationRule getNextRule(VerificationResultCode resultCode) { + switch (resultCode) { + case OK: + return this.onSuccessRule; + case FAIL: + return this.onFailureRule; + default: + return null; + } + } + + public abstract VerificationResult verify(CTX context); +} \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java b/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java index 3483bec..dd27622 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java @@ -10,7 +10,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.UUID; import org.junit.jupiter.api.Assertions; import org.unicitylabs.sdk.StateTransitionClient; @@ -27,7 +26,6 @@ import org.unicitylabs.sdk.predicate.embedded.MaskedPredicateReference; import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicateReference; -import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; @@ -46,7 +44,6 @@ import org.unicitylabs.sdk.transaction.split.SplitMintReason; import org.unicitylabs.sdk.transaction.split.TokenSplitBuilder; import org.unicitylabs.sdk.transaction.split.TokenSplitBuilder.TokenSplit; -import org.unicitylabs.sdk.util.HexConverter; import org.unicitylabs.sdk.util.InclusionProofUtils; import org.unicitylabs.sdk.utils.TestTokenData; @@ -340,8 +337,8 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti assertTrue(carolToBobToken.verify().isSuccessful()); // SPLIT - Entry[] splitCoins = coinData.getCoins().entrySet() - .toArray(Map.Entry[]::new); + List> splitCoins = + new ArrayList<>(coinData.getCoins().entrySet()); TokenType splitTokenType = new TokenType(randomBytes(32)); byte[] splitTokenNonce = randomBytes(32); @@ -351,7 +348,7 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti new TokenId(randomBytes(32)), splitTokenType, null, - new TokenCoinData(Map.ofEntries(splitCoins[0])), + new TokenCoinData(Map.ofEntries(splitCoins.get(0))), MaskedPredicateReference.create( splitTokenType, SigningService.createFromMaskedSecret(BOB_SECRET, splitTokenNonce), @@ -365,7 +362,7 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti new TokenId(randomBytes(32)), splitTokenType, null, - new TokenCoinData(Map.ofEntries(splitCoins[1])), + new TokenCoinData(Map.ofEntries(splitCoins.get(1))), MaskedPredicateReference.create( splitTokenType, SigningService.createFromMaskedSecret(BOB_SECRET, splitTokenNonce), @@ -406,8 +403,11 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti Assertions.assertEquals( 2, splitTransactions.stream() - .map(transaction -> transaction.getData().getReason().get().verify(transaction) - .isSuccessful()) + .map(transaction -> transaction.getData() + .getReason() + .map(reason -> reason.verify(transaction).isSuccessful()) + .orElse(false) + ) .filter(Boolean::booleanValue) .count() ); From 05863f3f388c09978dd8a223e1affe55cd98e8f5 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Mon, 22 Sep 2025 13:17:52 +0400 Subject: [PATCH 07/16] Create composite verification rule, remove unused throws, fix inclusion proof response --- .../sdk/StateTransitionClient.java | 5 +-- .../unicitylabs/sdk/api/AggregatorClient.java | 9 +++-- .../sdk/api/IAggregatorClient.java | 2 +- .../sdk/api/InclusionProofResponse.java | 16 +++++++++ .../sdk/jsonrpc/JsonRpcHttpTransport.java | 2 +- .../sdk/util/InclusionProofUtils.java | 9 ++--- .../CompositeVerificationRule.java | 33 +++++++++++++++++++ .../sdk/verification/VerificationRule.java | 17 +++++----- .../unicitylabs/sdk/TestAggregatorClient.java | 14 +++++--- .../unicitylabs/sdk/api/RequestIdTest.java | 2 +- .../unicitylabs/sdk/hash/DataHasherTest.java | 4 +-- .../sdk/mtree/plain/SparseMerkleTreeTest.java | 3 -- .../MaskedPredicateReferenceTest.java | 2 +- .../sdk/signing/SignatureRecoveryTest.java | 6 ++-- .../sdk/signing/SigningServiceTest.java | 8 ++--- 15 files changed, 91 insertions(+), 41 deletions(-) create mode 100644 src/main/java/org/unicitylabs/sdk/api/InclusionProofResponse.java create mode 100644 src/main/java/org/unicitylabs/sdk/verification/CompositeVerificationRule.java diff --git a/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java b/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java index 0b8e3bb..1dff3d7 100644 --- a/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java +++ b/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java @@ -2,6 +2,7 @@ package org.unicitylabs.sdk; import org.unicitylabs.sdk.api.IAggregatorClient; +import org.unicitylabs.sdk.api.InclusionProofResponse; import org.unicitylabs.sdk.api.RequestId; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.predicate.PredicateEngineService; @@ -76,10 +77,10 @@ public CompletableFuture getTokenStatus( byte[] publicKey) { RequestId requestId = RequestId.create(publicKey, token.getState().calculateHash()); return this.client.getInclusionProof(requestId) - .thenApply(inclusionProof -> inclusionProof.verify(requestId)); + .thenApply(response -> response.getInclusionProof().verify(requestId)); } - public CompletableFuture getInclusionProof(Commitment commitment) { + public CompletableFuture getInclusionProof(Commitment commitment) { return this.client.getInclusionProof(commitment.getRequestId()); } } diff --git a/src/main/java/org/unicitylabs/sdk/api/AggregatorClient.java b/src/main/java/org/unicitylabs/sdk/api/AggregatorClient.java index 66d4a64..a76b340 100644 --- a/src/main/java/org/unicitylabs/sdk/api/AggregatorClient.java +++ b/src/main/java/org/unicitylabs/sdk/api/AggregatorClient.java @@ -1,10 +1,9 @@ package org.unicitylabs.sdk.api; -import org.unicitylabs.sdk.hash.DataHash; -import org.unicitylabs.sdk.jsonrpc.JsonRpcHttpTransport; -import org.unicitylabs.sdk.transaction.InclusionProof; import java.util.Collections; import java.util.concurrent.CompletableFuture; +import org.unicitylabs.sdk.hash.DataHash; +import org.unicitylabs.sdk.jsonrpc.JsonRpcHttpTransport; public class AggregatorClient implements IAggregatorClient { @@ -24,10 +23,10 @@ public CompletableFuture submitCommitment( return this.transport.request("submit_commitment", request, SubmitCommitmentResponse.class); } - public CompletableFuture getInclusionProof(RequestId requestId) { + public CompletableFuture getInclusionProof(RequestId requestId) { InclusionProofRequest request = new InclusionProofRequest(requestId); - return this.transport.request("get_inclusion_proof", request, InclusionProof.class); + return this.transport.request("get_inclusion_proof", request, InclusionProofResponse.class); } public CompletableFuture getBlockHeight() { diff --git a/src/main/java/org/unicitylabs/sdk/api/IAggregatorClient.java b/src/main/java/org/unicitylabs/sdk/api/IAggregatorClient.java index 241d575..fec2038 100644 --- a/src/main/java/org/unicitylabs/sdk/api/IAggregatorClient.java +++ b/src/main/java/org/unicitylabs/sdk/api/IAggregatorClient.java @@ -13,7 +13,7 @@ CompletableFuture submitCommitment( DataHash transactionHash, Authenticator authenticator); - CompletableFuture getInclusionProof(RequestId requestId); + CompletableFuture getInclusionProof(RequestId requestId); CompletableFuture getBlockHeight(); } diff --git a/src/main/java/org/unicitylabs/sdk/api/InclusionProofResponse.java b/src/main/java/org/unicitylabs/sdk/api/InclusionProofResponse.java new file mode 100644 index 0000000..0f9b7f0 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/api/InclusionProofResponse.java @@ -0,0 +1,16 @@ +package org.unicitylabs.sdk.api; + +import org.unicitylabs.sdk.transaction.InclusionProof; + +public class InclusionProofResponse { + + private final InclusionProof inclusionProof; + + public InclusionProofResponse(InclusionProof inclusionProof) { + this.inclusionProof = inclusionProof; + } + + public InclusionProof getInclusionProof() { + return this.inclusionProof; + } +} diff --git a/src/main/java/org/unicitylabs/sdk/jsonrpc/JsonRpcHttpTransport.java b/src/main/java/org/unicitylabs/sdk/jsonrpc/JsonRpcHttpTransport.java index 3941b5d..56ea4a5 100644 --- a/src/main/java/org/unicitylabs/sdk/jsonrpc/JsonRpcHttpTransport.java +++ b/src/main/java/org/unicitylabs/sdk/jsonrpc/JsonRpcHttpTransport.java @@ -54,7 +54,7 @@ public void onFailure(Call call, IOException e) { } @Override - public void onResponse(Call call, Response response) throws IOException { + public void onResponse(Call call, Response response) { try (ResponseBody body = response.body()) { if (!response.isSuccessful()) { String error = body != null ? body.string() : ""; diff --git a/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java b/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java index 44e3582..2af5c5f 100644 --- a/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java +++ b/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java @@ -38,7 +38,7 @@ public static CompletableFuture waitInclusionProof( StateTransitionClient client, Commitment commitment, Duration timeout, - Duration interval) throws ExecutionException, InterruptedException { + Duration interval) { CompletableFuture future = new CompletableFuture<>(); @@ -62,10 +62,11 @@ private static void checkInclusionProof( future.completeExceptionally(new TimeoutException("Timeout waiting for inclusion proof")); } - client.getInclusionProof(commitment).thenAccept(inclusionProof -> { - InclusionProofVerificationStatus status = inclusionProof.verify(commitment.getRequestId()); + client.getInclusionProof(commitment).thenAccept(response -> { + InclusionProofVerificationStatus status = response.getInclusionProof() + .verify(commitment.getRequestId()); if (status == InclusionProofVerificationStatus.OK) { - future.complete(inclusionProof); + future.complete(response.getInclusionProof()); } if (status == InclusionProofVerificationStatus.PATH_NOT_INCLUDED) { diff --git a/src/main/java/org/unicitylabs/sdk/verification/CompositeVerificationRule.java b/src/main/java/org/unicitylabs/sdk/verification/CompositeVerificationRule.java new file mode 100644 index 0000000..9fef868 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/verification/CompositeVerificationRule.java @@ -0,0 +1,33 @@ +package org.unicitylabs.sdk.verification; + +import java.util.ArrayList; +import java.util.List; + +public abstract class CompositeVerificationRule extends VerificationRule { + + private final VerificationRule firstRule; + private final String message; + + public CompositeVerificationRule( + String message, + VerificationRule firstRule + ) { + super(firstRule); + + this.firstRule = firstRule; + this.message = message; + } + + public VerificationResult verify(CTX context) { + VerificationRule rule = this.firstRule; + List results = new ArrayList<>(); + + while (rule != null) { + VerificationResult result = rule.verify(context); + results.add(result); + rule = rule.getNextRule(result.isSuccessful() ? VerificationResultCode.OK : VerificationResultCode.FAIL); + } + + return VerificationResult.fromChildren(this.message, results); + } +} \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/verification/VerificationRule.java b/src/main/java/org/unicitylabs/sdk/verification/VerificationRule.java index 747571d..8459f11 100644 --- a/src/main/java/org/unicitylabs/sdk/verification/VerificationRule.java +++ b/src/main/java/org/unicitylabs/sdk/verification/VerificationRule.java @@ -1,9 +1,17 @@ package org.unicitylabs.sdk.verification; public abstract class VerificationRule { + private final VerificationRule onSuccessRule; private final VerificationRule onFailureRule; + protected VerificationRule(VerificationRule rule) { + this( + rule.onSuccessRule, + rule.onFailureRule + ); + } + protected VerificationRule( VerificationRule onSuccessRule, VerificationRule onFailureRule @@ -12,15 +20,6 @@ protected VerificationRule( this.onFailureRule = onFailureRule; } - protected VerificationRule( - VerificationRule onAny - ) { - this( - onAny, - onAny - ); - } - public VerificationRule getNextRule(VerificationResultCode resultCode) { switch (resultCode) { case OK: diff --git a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java index f59b780..f9b30fe 100644 --- a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java +++ b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java @@ -2,6 +2,7 @@ import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.IAggregatorClient; +import org.unicitylabs.sdk.api.InclusionProofResponse; import org.unicitylabs.sdk.api.LeafValue; import org.unicitylabs.sdk.api.RequestId; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; @@ -44,14 +45,17 @@ public CompletableFuture submitCommitment(RequestId re } @Override - public CompletableFuture getInclusionProof(RequestId requestId) { + public CompletableFuture getInclusionProof(RequestId requestId) { Entry entry = requests.get(requestId); SparseMerkleTreeRootNode root = tree.calculateRoot(); return CompletableFuture.completedFuture( - new InclusionProof( - root.getPath(requestId.toBitString().toBigInteger()), - entry.getKey(), - entry.getValue()) + new InclusionProofResponse( + new InclusionProof( + root.getPath(requestId.toBitString().toBigInteger()), + entry.getKey(), + entry.getValue() + ) + ) ); } diff --git a/src/test/java/org/unicitylabs/sdk/api/RequestIdTest.java b/src/test/java/org/unicitylabs/sdk/api/RequestIdTest.java index 41bf8a0..f3e73af 100644 --- a/src/test/java/org/unicitylabs/sdk/api/RequestIdTest.java +++ b/src/test/java/org/unicitylabs/sdk/api/RequestIdTest.java @@ -11,7 +11,7 @@ public class RequestIdTest { @Test - public void shouldResolveToBigInteger() throws JsonProcessingException { + public void shouldResolveToBigInteger() { RequestId requestId = RequestId.create(new byte[5], new DataHash(HashAlgorithm.SHA256, new byte[32])); Assertions.assertEquals( diff --git a/src/test/java/org/unicitylabs/sdk/hash/DataHasherTest.java b/src/test/java/org/unicitylabs/sdk/hash/DataHasherTest.java index 12f2d5d..6224b94 100644 --- a/src/test/java/org/unicitylabs/sdk/hash/DataHasherTest.java +++ b/src/test/java/org/unicitylabs/sdk/hash/DataHasherTest.java @@ -9,7 +9,7 @@ public class DataHasherTest { @Test - public void testSha256WithUpdate() throws Exception { + public void testSha256WithUpdate() { DataHasher hasher = new DataHasher(HashAlgorithm.SHA256); assertEquals(HashAlgorithm.SHA256, hasher.getAlgorithm()); @@ -33,7 +33,7 @@ public void testSha256WithUpdate() throws Exception { } @Test - public void testMultipleUpdates() throws Exception { + public void testMultipleUpdates() { DataHasher hasher = new DataHasher(HashAlgorithm.SHA256); hasher.update("hel".getBytes(StandardCharsets.UTF_8)); diff --git a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java b/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java index 4e0f0a7..f64b569 100644 --- a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java +++ b/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java @@ -77,9 +77,6 @@ public class SparseMerkleTreeTest { HashAlgorithm.SHA256 ); - public SparseMerkleTreeTest() throws Exception { - } - @Test public void treeShouldBeHalfCalculated() throws Exception { SparseMerkleTree smt = new SparseMerkleTree(HashAlgorithm.SHA256); diff --git a/src/test/java/org/unicitylabs/sdk/predicate/MaskedPredicateReferenceTest.java b/src/test/java/org/unicitylabs/sdk/predicate/MaskedPredicateReferenceTest.java index ed13156..75234b1 100644 --- a/src/test/java/org/unicitylabs/sdk/predicate/MaskedPredicateReferenceTest.java +++ b/src/test/java/org/unicitylabs/sdk/predicate/MaskedPredicateReferenceTest.java @@ -9,7 +9,7 @@ class MaskedPredicateReferenceTest { @Test - void testReferenceAddress() throws Exception { + void testReferenceAddress() { Assertions.assertEquals( "DIRECT://000095ca469ab7a37f8be976b7da1a6023369182ba3cdd1293c07a4b2bf40aa5118d60f5bf36", MaskedPredicateReference.create( diff --git a/src/test/java/org/unicitylabs/sdk/signing/SignatureRecoveryTest.java b/src/test/java/org/unicitylabs/sdk/signing/SignatureRecoveryTest.java index 7744327..21dd8e8 100644 --- a/src/test/java/org/unicitylabs/sdk/signing/SignatureRecoveryTest.java +++ b/src/test/java/org/unicitylabs/sdk/signing/SignatureRecoveryTest.java @@ -14,7 +14,7 @@ public class SignatureRecoveryTest { @Test - void testSignatureRecoveryId() throws Exception { + void testSignatureRecoveryId() { // Create a signing service with a known private key byte[] privateKey = HexConverter.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); SigningService signingService = new SigningService(privateKey); @@ -38,7 +38,7 @@ void testSignatureRecoveryId() throws Exception { } @Test - void testPublicKeyRecovery() throws Exception { + void testPublicKeyRecovery() { // Create a signing service with a known private key byte[] privateKey = HexConverter.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); SigningService signingService = new SigningService(privateKey); @@ -59,7 +59,7 @@ void testPublicKeyRecovery() throws Exception { } @Test - void testSignatureFormatCompliance() throws Exception { + void testSignatureFormatCompliance() { // Test with the exact values from TypeScript test String transactionHashHex = "0000d6035b65700f0af73cc62a580eb833c20f40aaee460087f5fb43ebb3c047f1d4"; String signatureHex = "301c7f19d5e0a7e350012ab7bbaf26a0152a751eec06d18563f96bcf06d2380e7de7ce6cebb8c11479d1bd9c463c3ba47396b5f815c552b344d430b0d011a2e701"; diff --git a/src/test/java/org/unicitylabs/sdk/signing/SigningServiceTest.java b/src/test/java/org/unicitylabs/sdk/signing/SigningServiceTest.java index a80136d..2994166 100644 --- a/src/test/java/org/unicitylabs/sdk/signing/SigningServiceTest.java +++ b/src/test/java/org/unicitylabs/sdk/signing/SigningServiceTest.java @@ -24,7 +24,7 @@ public void testGeneratePrivateKey() { } @Test - public void testCreateFromSecret() throws Exception { + public void testCreateFromSecret() { byte[] secret = "test secret".getBytes(StandardCharsets.UTF_8); byte[] nonce = "test nonce".getBytes(StandardCharsets.UTF_8); @@ -36,7 +36,7 @@ public void testCreateFromSecret() throws Exception { } @Test - public void testSignAndVerify() throws Exception { + public void testSignAndVerify() { byte[] privateKey = SigningService.generatePrivateKey(); SigningService service = new SigningService(privateKey); @@ -57,7 +57,7 @@ public void testSignAndVerify() throws Exception { } @Test - public void testVerifyWithPublicKey() throws Exception { + public void testVerifyWithPublicKey() { byte[] privateKey = SigningService.generatePrivateKey(); SigningService service = new SigningService(privateKey); byte[] publicKey = service.getPublicKey(); @@ -76,7 +76,7 @@ public void testVerifyWithPublicKey() throws Exception { } @Test - public void testInvalidSignature() throws Exception { + public void testInvalidSignature() { byte[] privateKey = SigningService.generatePrivateKey(); SigningService service = new SigningService(privateKey); From 165c1f50cee7728ab64af0006a90ee6c4b281f4e Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Mon, 22 Sep 2025 19:52:23 +0400 Subject: [PATCH 08/16] Add bft UC serializers, add UC to inclusionproof, temporary UC generation --- .../org/unicitylabs/sdk/bft/InputRecord.java | 66 ++++++++++++---- .../sdk/bft/ShardTreeCertificate.java | 17 ++++- .../sdk/bft/UnicityCertificate.java | 45 +++++++++-- .../org/unicitylabs/sdk/bft/UnicitySeal.java | 75 +++++++++++++----- .../sdk/bft/UnicityTreeCertificate.java | 42 ++++++---- .../sdk/predicate/embedded/BurnPredicate.java | 6 ++ .../serializer/cbor/bft/InputRecordCbor.java | 35 ++++++--- .../cbor/bft/ShardTreeCertificateCbor.java | 4 +- .../cbor/bft/UnicityCertificateCbor.java | 14 +++- .../serializer/cbor/bft/UnicitySealCbor.java | 29 +++++-- .../cbor/bft/UnicityTreeCertificateCbor.java | 12 ++- .../UnicityTreeCertificateHashStepCbor.java | 7 +- .../cbor/transaction/InclusionProofCbor.java | 7 +- .../json/transaction/InclusionProofJson.java | 28 ++++++- .../sdk/transaction/InclusionProof.java | 18 ++++- .../sdk/transaction/MintTransactionData.java | 6 +- .../transaction/split/SplitMintReason.java | 5 ++ .../unicitylabs/sdk/TestAggregatorClient.java | 4 +- .../sdk/bft/UnicityCertificateTest.java | 13 +++- .../org/unicitylabs/sdk/token/TokenTest.java | 15 +++- .../sdk/transaction/InclusionProofTest.java | 76 +++++++++++++++---- .../split/TokenSplitBuilderTest.java | 9 ++- .../sdk/utils/UnicityCertificateUtils.java | 24 ++++++ 23 files changed, 442 insertions(+), 115 deletions(-) create mode 100644 src/test/java/org/unicitylabs/sdk/utils/UnicityCertificateUtils.java diff --git a/src/main/java/org/unicitylabs/sdk/bft/InputRecord.java b/src/main/java/org/unicitylabs/sdk/bft/InputRecord.java index c5d85a8..c5d939d 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/InputRecord.java +++ b/src/main/java/org/unicitylabs/sdk/bft/InputRecord.java @@ -1,41 +1,35 @@ package org.unicitylabs.sdk.bft; -import java.math.BigInteger; import java.util.Arrays; import java.util.Objects; import org.unicitylabs.sdk.util.HexConverter; public class InputRecord { - private final BigInteger version; - private final BigInteger roundNumber; - private final BigInteger epoch; + private final int version; + private final long roundNumber; + private final long epoch; private final byte[] previousHash; private final byte[] hash; private final byte[] summaryValue; - private final BigInteger timestamp; + private final long timestamp; private final byte[] blockHash; - private final BigInteger sumOfEarnedFees; + private final long sumOfEarnedFees; private final byte[] executedTransactionsHash; public InputRecord( - BigInteger version, - BigInteger roundNumber, - BigInteger epoch, + int version, + long roundNumber, + long epoch, byte[] previousHash, byte[] hash, byte[] summaryValue, - BigInteger timestamp, + long timestamp, byte[] blockHash, - BigInteger sumOfEarnedFees, + long sumOfEarnedFees, byte[] executedTransactionsHash ) { - Objects.requireNonNull(version, "Version cannot be null"); - Objects.requireNonNull(roundNumber, "Round number cannot be null"); - Objects.requireNonNull(epoch, "Epoch cannot be null"); Objects.requireNonNull(summaryValue, "Summary value cannot be null"); - Objects.requireNonNull(timestamp, "Timestamp cannot be null"); - Objects.requireNonNull(sumOfEarnedFees, "Sum of earned fees cannot be null"); this.version = version; this.roundNumber = roundNumber; @@ -49,6 +43,46 @@ public InputRecord( this.executedTransactionsHash = executedTransactionsHash; } + public int getVersion() { + return this.version; + } + + public long getRoundNumber() { + return this.roundNumber; + } + + public long getEpoch() { + return this.epoch; + } + + public byte[] getPreviousHash() { + return this.previousHash != null ? Arrays.copyOf(this.previousHash, this.previousHash.length) : null; + } + + public byte[] getHash() { + return this.hash != null ? Arrays.copyOf(this.hash, this.hash.length) : null; + } + + public byte[] getSummaryValue() { + return Arrays.copyOf(this.summaryValue, this.summaryValue.length); + } + + public long getTimestamp() { + return this.timestamp; + } + + public byte[] getBlockHash() { + return this.blockHash != null ? Arrays.copyOf(this.blockHash, this.blockHash.length) : null; + } + + public long getSumOfEarnedFees() { + return this.sumOfEarnedFees; + } + + public byte[] getExecutedTransactionsHash() { + return this.executedTransactionsHash != null ? Arrays.copyOf(this.executedTransactionsHash, this.executedTransactionsHash.length) : null; + } + @Override public boolean equals(Object o) { if (!(o instanceof InputRecord)) { diff --git a/src/main/java/org/unicitylabs/sdk/bft/ShardTreeCertificate.java b/src/main/java/org/unicitylabs/sdk/bft/ShardTreeCertificate.java index ce8687a..8e48576 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/ShardTreeCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/bft/ShardTreeCertificate.java @@ -3,6 +3,7 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import org.unicitylabs.sdk.util.HexConverter; public class ShardTreeCertificate { @@ -14,8 +15,20 @@ public ShardTreeCertificate(byte[] shard, List siblingHashList) { Objects.requireNonNull(shard, "Shard cannot be null"); Objects.requireNonNull(siblingHashList, "Sibling hash list cannot be null"); - this.shard = shard; - this.siblingHashList = siblingHashList; + this.shard = Arrays.copyOf(shard, shard.length); + this.siblingHashList = siblingHashList.stream() + .map(hash -> Arrays.copyOf(hash, hash.length)) + .collect(Collectors.toList()); + } + + public byte[] getShard() { + return Arrays.copyOf(this.shard, this.shard.length); + } + + public List getSiblingHashList() { + return this.siblingHashList.stream() + .map(hash -> Arrays.copyOf(hash, hash.length)) + .collect(Collectors.toList()); } @Override diff --git a/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java b/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java index 411b0cf..4dca3f6 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java @@ -1,13 +1,12 @@ package org.unicitylabs.sdk.bft; -import java.math.BigInteger; import java.util.Arrays; import java.util.Objects; import org.unicitylabs.sdk.util.HexConverter; public class UnicityCertificate { - private final BigInteger version; + private final int version; private final InputRecord inputRecord; private final byte[] technicalRecordHash; private final byte[] shardConfigurationHash; @@ -16,7 +15,7 @@ public class UnicityCertificate { public final UnicitySeal unicitySeal; public UnicityCertificate( - BigInteger version, + int version, InputRecord inputRecord, byte[] technicalRecordHash, byte[] shardConfigurationHash, @@ -24,7 +23,6 @@ public UnicityCertificate( UnicityTreeCertificate unicityTreeCertificate, UnicitySeal unicitySeal ) { - Objects.requireNonNull(version, "Version cannot be null"); Objects.requireNonNull(inputRecord, "Input record cannot be null"); Objects.requireNonNull(shardConfigurationHash, "Shard configuration hash cannot be null"); Objects.requireNonNull(shardTreeCertificate, "Shard tree certificate cannot be null"); @@ -33,13 +31,44 @@ public UnicityCertificate( this.version = version; this.inputRecord = inputRecord; - this.technicalRecordHash = technicalRecordHash; - this.shardConfigurationHash = shardConfigurationHash; + this.technicalRecordHash = Arrays.copyOf(technicalRecordHash, technicalRecordHash.length); + this.shardConfigurationHash = Arrays.copyOf( + shardConfigurationHash, + shardConfigurationHash.length + ); this.shardTreeCertificate = shardTreeCertificate; this.unicityTreeCertificate = unicityTreeCertificate; this.unicitySeal = unicitySeal; } + public int getVersion() { + return this.version; + } + + public InputRecord getInputRecord() { + return this.inputRecord; + } + + public byte[] getTechnicalRecordHash() { + return Arrays.copyOf(this.technicalRecordHash, this.technicalRecordHash.length); + } + + public byte[] getShardConfigurationHash() { + return Arrays.copyOf(this.shardConfigurationHash, this.shardConfigurationHash.length); + } + + public ShardTreeCertificate getShardTreeCertificate() { + return this.shardTreeCertificate; + } + + public UnicityTreeCertificate getUnicityTreeCertificate() { + return this.unicityTreeCertificate; + } + + public UnicitySeal getUnicitySeal() { + return this.unicitySeal; + } + @Override public boolean equals(Object o) { if (!(o instanceof UnicityCertificate)) { @@ -64,8 +93,8 @@ public int hashCode() { @Override public String toString() { return String.format("UnicityCertificate{version=%s, inputRecord=%s, technicalRecordHash=%s, " - + "shardConfigurationHash=%s, shardTreeCertificate=%s, unicityTreeCertificate=%s, " - + "unicitySeal=%s}", + + "shardConfigurationHash=%s, shardTreeCertificate=%s, unicityTreeCertificate=%s, " + + "unicitySeal=%s}", this.version, this.inputRecord, this.technicalRecordHash != null ? HexConverter.encode(this.technicalRecordHash) : null, diff --git a/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java b/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java index 1997c9c..c82233b 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java +++ b/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java @@ -1,6 +1,5 @@ package org.unicitylabs.sdk.bft; -import java.math.BigInteger; import java.util.Arrays; import java.util.Map; import java.util.Objects; @@ -9,30 +8,25 @@ public class UnicitySeal { - private final BigInteger version; - private final BigInteger networkId; - private final BigInteger rootChainRoundNumber; - private final BigInteger epoch; - private final BigInteger timestamp; + private final int version; + private final short networkId; + private final long rootChainRoundNumber; + private final long epoch; + private final long timestamp; private final byte[] previousHash; // nullable private final byte[] hash; private final Map signatures; public UnicitySeal( - BigInteger version, - BigInteger networkId, - BigInteger rootChainRoundNumber, - BigInteger epoch, - BigInteger timestamp, + int version, + short networkId, + long rootChainRoundNumber, + long epoch, + long timestamp, byte[] previousHash, byte[] hash, Map signatures ) { - Objects.requireNonNull(version, "Version cannot be null"); - Objects.requireNonNull(networkId, "Network ID cannot be null"); - Objects.requireNonNull(rootChainRoundNumber, "Root chain round number cannot be null"); - Objects.requireNonNull(epoch, "Epoch cannot be null"); - Objects.requireNonNull(timestamp, "Timestamp cannot be null"); Objects.requireNonNull(hash, "Hash cannot be null"); Objects.requireNonNull(signatures, "Signatures cannot be null"); @@ -43,7 +37,51 @@ public UnicitySeal( this.timestamp = timestamp; this.previousHash = previousHash; this.hash = hash; - this.signatures = signatures; + this.signatures = signatures.entrySet().stream() + .map(entry -> Map.entry( + entry.getKey(), + Arrays.copyOf(entry.getValue(), entry.getValue().length) + ) + ) + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + public int getVersion() { + return this.version; + } + + public short getNetworkId() { + return this.networkId; + } + + public long getRootChainRoundNumber() { + return this.rootChainRoundNumber; + } + + public long getEpoch() { + return this.epoch; + } + + public long getTimestamp() { + return this.timestamp; + } + + public byte[] getPreviousHash() { + return this.previousHash != null ? Arrays.copyOf(this.previousHash, this.previousHash.length) : null; + } + + public byte[] getHash() { + return Arrays.copyOf(this.hash, this.hash.length); + } + + public Map getSignatures() { + return this.signatures.entrySet().stream() + .map(entry -> Map.entry( + entry.getKey(), + Arrays.copyOf(entry.getValue(), entry.getValue().length) + ) + ) + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override @@ -81,7 +119,8 @@ public String toString() { HexConverter.encode(this.hash), this.signatures.entrySet() .stream() - .map(entry -> String.format("%s: %s", entry.getKey(), HexConverter.encode(entry.getValue()))) + .map(entry -> String.format("%s: %s", entry.getKey(), + HexConverter.encode(entry.getValue()))) .collect(Collectors.toList()) ); } diff --git a/src/main/java/org/unicitylabs/sdk/bft/UnicityTreeCertificate.java b/src/main/java/org/unicitylabs/sdk/bft/UnicityTreeCertificate.java index 798f93e..4b0938d 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/UnicityTreeCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/bft/UnicityTreeCertificate.java @@ -1,6 +1,5 @@ package org.unicitylabs.sdk.bft; -import java.math.BigInteger; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -8,22 +7,32 @@ public class UnicityTreeCertificate { - private final BigInteger version; - private final BigInteger partitionIdentifier; + private final int version; + private final int partitionIdentifier; private final List steps; public UnicityTreeCertificate( - BigInteger version, - BigInteger partitionIdentifier, + int version, + int partitionIdentifier, List steps ) { - Objects.requireNonNull(version, "Version cannot be null"); - Objects.requireNonNull(partitionIdentifier, "Partition identifier cannot be null"); Objects.requireNonNull(steps, "Steps cannot be null"); this.version = version; this.partitionIdentifier = partitionIdentifier; - this.steps = steps; + this.steps = List.copyOf(steps); + } + + public int getVersion() { + return this.version; + } + + public int getPartitionIdentifier() { + return this.partitionIdentifier; + } + + public List getSteps() { + return this.steps; } @Override @@ -49,18 +58,25 @@ public String toString() { } public static class HashStep { - - private final BigInteger key; + private final int key; private final byte[] hash; - public HashStep(BigInteger key, byte[] hash) { - Objects.requireNonNull(key, "Key cannot be null"); + public HashStep(int key, byte[] hash) { Objects.requireNonNull(hash, "Hash cannot be null"); this.key = key; - this.hash = hash; + this.hash = Arrays.copyOf(hash, hash.length); + } + + public int getKey() { + return this.key; } + public byte[] getHash() { + return Arrays.copyOf(this.hash, this.hash.length); + } + + @Override public boolean equals(Object o) { if (!(o instanceof HashStep)) { diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java index 907fe78..a819de6 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java @@ -92,4 +92,10 @@ public byte[] encodeParameters() { throw new CborSerializationException(e); } } + + @Override + public String toString() { + return String.format("BurnPredicate{tokenId=%s, tokenType=%s, burnReason=%s}", this.tokenId, + this.tokenType, this.burnReason); + } } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java index 0253aea..e3701fe 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java @@ -8,8 +8,8 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; import java.io.IOException; -import java.math.BigInteger; import java.nio.charset.StandardCharsets; import org.unicitylabs.sdk.bft.InputRecord; import org.unicitylabs.sdk.util.HexConverter; @@ -29,7 +29,26 @@ public void serialize(InputRecord value, JsonGenerator gen, SerializerProvider s return; } - gen.writeStartArray(value, 0); + ((CBORGenerator) gen).writeTag(1008); + gen.writeStartArray(value, 10); + gen.writeObject(value.getVersion()); + gen.writeObject(value.getRoundNumber()); + gen.writeObject(value.getEpoch()); + gen.writeObject( + value.getPreviousHash() == null + ? null + : HexConverter.encode(value.getPreviousHash()).getBytes(StandardCharsets.UTF_8) + ); + gen.writeObject( + value.getHash() == null + ? null + : HexConverter.encode(value.getHash()).getBytes(StandardCharsets.UTF_8) + ); + gen.writeObject(value.getSummaryValue()); + gen.writeObject(value.getTimestamp()); + gen.writeObject(value.getBlockHash()); + gen.writeObject(value.getSumOfEarnedFees()); + gen.writeObject(value.getExecutedTransactionsHash()); gen.writeEndArray(); } } @@ -43,19 +62,17 @@ public InputRecord deserialize(JsonParser p, DeserializationContext ctx) throws } p.nextToken(); - BigInteger version = p.readValueAs(BigInteger.class); - - BigInteger roundNumber = p.readValueAs(BigInteger.class); - BigInteger epoch = p.readValueAs(BigInteger.class); + int version = p.readValueAs(int.class); + long roundNumber = p.readValueAs(long.class); + long epoch = p.readValueAs(long.class); byte[] previousHash = p.readValueAs(byte[].class); byte[] hash = p.readValueAs(byte[].class); byte[] summaryValue = p.readValueAs(byte[].class); - BigInteger timestamp = p.readValueAs(BigInteger.class); + long timestamp = p.readValueAs(long.class); byte[] blockHash = p.readValueAs(byte[].class); - BigInteger sumOfEarnedFees = p.readValueAs(BigInteger.class); + long sumOfEarnedFees = p.readValueAs(long.class); byte[] executedTransactionsHash = p.readValueAs(byte[].class); - InputRecord result = new InputRecord( version, roundNumber, diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java index 08009e6..76317a1 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java @@ -28,7 +28,9 @@ public void serialize(ShardTreeCertificate value, JsonGenerator gen, SerializerP return; } - gen.writeStartArray(value, 0); + gen.writeStartArray(value, 2); + gen.writeObject(value.getShard()); + gen.writeObject(value.getSiblingHashList()); gen.writeEndArray(); } } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityCertificateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityCertificateCbor.java index e7f47d8..8a76122 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityCertificateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityCertificateCbor.java @@ -8,8 +8,8 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; import java.io.IOException; -import java.math.BigInteger; import org.unicitylabs.sdk.bft.InputRecord; import org.unicitylabs.sdk.bft.ShardTreeCertificate; import org.unicitylabs.sdk.bft.UnicityCertificate; @@ -31,7 +31,15 @@ public void serialize(UnicityCertificate value, JsonGenerator gen, SerializerPro return; } - gen.writeStartArray(value, 0); + ((CBORGenerator) gen).writeTag(1007); + gen.writeStartArray(value, 7); + gen.writeObject(value.getVersion()); + gen.writeObject(value.getInputRecord()); + gen.writeObject(value.getTechnicalRecordHash()); + gen.writeObject(value.getShardConfigurationHash()); + gen.writeObject(value.getShardTreeCertificate()); + gen.writeObject(value.getUnicityTreeCertificate()); + gen.writeObject(value.getUnicitySeal()); gen.writeEndArray(); } } @@ -46,7 +54,7 @@ public UnicityCertificate deserialize(JsonParser p, DeserializationContext ctx) p.nextToken(); UnicityCertificate result = new UnicityCertificate( - p.readValueAs(BigInteger.class), + p.readValueAs(int.class), p.readValueAs(InputRecord.class), p.readValueAs(byte[].class), p.readValueAs(byte[].class), diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java index 29a6ade..ad46013 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java @@ -8,8 +8,8 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; import java.io.IOException; -import java.math.BigInteger; import java.util.HashMap; import java.util.Map; import org.unicitylabs.sdk.bft.UnicitySeal; @@ -29,7 +29,22 @@ public void serialize(UnicitySeal value, JsonGenerator gen, SerializerProvider s return; } - gen.writeStartArray(value, 0); + ((CBORGenerator) gen).writeTag(1001); + gen.writeStartArray(value, 8); + gen.writeObject(value.getVersion()); + gen.writeObject(value.getNetworkId()); + gen.writeObject(value.getRootChainRoundNumber()); + gen.writeObject(value.getEpoch()); + gen.writeObject(value.getTimestamp()); + gen.writeObject(value.getPreviousHash()); + gen.writeObject(value.getHash()); + gen.writeStartObject(value.getSignatures(), value.getSignatures().size()); + for (Map.Entry entry : value.getSignatures().entrySet()) { + gen.writeFieldName(entry.getKey()); + gen.writeObject(entry.getValue()); + } + gen.writeEndObject(); + gen.writeEndArray(); } } @@ -43,11 +58,11 @@ public UnicitySeal deserialize(JsonParser p, DeserializationContext ctx) throws } p.nextToken(); - BigInteger version = p.readValueAs(BigInteger.class); - BigInteger networkId = p.readValueAs(BigInteger.class); - BigInteger rootChainRoundNumber = p.readValueAs(BigInteger.class); - BigInteger epoch = p.readValueAs(BigInteger.class); - BigInteger timestamp = p.readValueAs(BigInteger.class); + int version = p.readValueAs(int.class); + short networkId = p.readValueAs(short.class); + long rootChainRoundNumber = p.readValueAs(long.class); + long epoch = p.readValueAs(long.class); + long timestamp = p.readValueAs(long.class); byte[] previousHash = p.readValueAs(byte[].class); byte[] hash = p.readValueAs(byte[].class); diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java index d7b5a64..1475d85 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java @@ -8,8 +8,8 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; import java.io.IOException; -import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import org.unicitylabs.sdk.bft.UnicityTreeCertificate; @@ -29,7 +29,11 @@ public void serialize(UnicityTreeCertificate value, JsonGenerator gen, Serialize return; } - gen.writeStartArray(value, 0); + ((CBORGenerator) gen).writeTag(1014); + gen.writeStartArray(value, 3); + gen.writeObject(value.getVersion()); + gen.writeObject(value.getPartitionIdentifier()); + gen.writeObject(value.getSteps()); gen.writeEndArray(); } } @@ -43,8 +47,8 @@ public UnicityTreeCertificate deserialize(JsonParser p, DeserializationContext c } p.nextToken(); - BigInteger version = p.readValueAs(BigInteger.class); - BigInteger partitionIdentifier = p.readValueAs(BigInteger.class); + int version = p.readValueAs(int.class); + int partitionIdentifier = p.readValueAs(int.class); if (p.nextToken() != JsonToken.START_ARRAY) { throw MismatchedInputException.from(p, UnicityTreeCertificate.class, "Expected hash step list"); diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java index ab88561..d65e135 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java @@ -8,7 +8,6 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import java.io.IOException; -import java.math.BigInteger; import org.unicitylabs.sdk.bft.UnicityTreeCertificate; public class UnicityTreeCertificateHashStepCbor { @@ -26,7 +25,9 @@ public void serialize(UnicityTreeCertificate.HashStep value, JsonGenerator gen, return; } - gen.writeStartArray(value, 0); + gen.writeStartArray(value, 2); + gen.writeObject(value.getKey()); + gen.writeObject(value.getHash()); gen.writeEndArray(); } } @@ -41,7 +42,7 @@ public UnicityTreeCertificate.HashStep deserialize(JsonParser p, Deserialization p.nextToken(); return new UnicityTreeCertificate.HashStep( - p.readValueAs(BigInteger.class), + p.readValueAs(int.class), p.readValueAs(byte[].class) ); } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/transaction/InclusionProofCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/transaction/InclusionProofCbor.java index 363fd75..8cc9d59 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/transaction/InclusionProofCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/transaction/InclusionProofCbor.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import org.unicitylabs.sdk.api.Authenticator; +import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; import org.unicitylabs.sdk.transaction.InclusionProof; @@ -28,10 +29,11 @@ public void serialize(InclusionProof value, JsonGenerator gen, SerializerProvide return; } - gen.writeStartArray(value, 3); + gen.writeStartArray(value, 4); gen.writeObject(value.getMerkleTreePath()); gen.writeObject(value.getAuthenticator()); gen.writeObject(value.getTransactionHash()); + gen.writeObject(value.getUnicityCertificate()); gen.writeEndArray(); } } @@ -47,7 +49,8 @@ public InclusionProof deserialize(JsonParser p, DeserializationContext ctx) thro return new InclusionProof( p.readValueAs(SparseMerkleTreePath.class), p.readValueAs(Authenticator.class), - p.readValueAs(DataHash.class) + p.readValueAs(DataHash.class), + p.readValueAs(UnicityCertificate.class) ); } } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/transaction/InclusionProofJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/transaction/InclusionProofJson.java index ca5191a..b89b0bf 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/transaction/InclusionProofJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/transaction/InclusionProofJson.java @@ -9,8 +9,11 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import org.unicitylabs.sdk.api.Authenticator; +import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.transaction.InclusionProof; import java.io.IOException; @@ -22,6 +25,7 @@ public class InclusionProofJson { private static final String MERKLE_TREE_PATH_FIELD = "merkleTreePath"; private static final String AUTHENTICATOR_FIELD = "authenticator"; private static final String TRANSACTION_HASH_FIELD = "transactionHash"; + private static final String UNICITY_CERTIFICATE_FIELD = "unicityCertificate"; private InclusionProofJson() { } @@ -40,6 +44,10 @@ public void serialize(InclusionProof value, JsonGenerator gen, SerializerProvide gen.writeObjectField(MERKLE_TREE_PATH_FIELD, value.getMerkleTreePath()); gen.writeObjectField(AUTHENTICATOR_FIELD, value.getAuthenticator()); gen.writeObjectField(TRANSACTION_HASH_FIELD, value.getTransactionHash()); + gen.writeObjectField( + UNICITY_CERTIFICATE_FIELD, + UnicityObjectMapper.CBOR.writeValueAsBytes(value.getUnicityCertificate()) + ); gen.writeEndObject(); } } @@ -51,6 +59,7 @@ public InclusionProof deserialize(JsonParser p, DeserializationContext ctx) thro SparseMerkleTreePath merkleTreePath = null; Authenticator authenticator = null; DataHash transactionHash = null; + UnicityCertificate unicityCertificate = null; Set fields = new HashSet<>(); @@ -81,6 +90,13 @@ public InclusionProof deserialize(JsonParser p, DeserializationContext ctx) thro transactionHash = p.currentToken() != JsonToken.VALUE_NULL ? p.readValueAs(DataHash.class) : null; break; + case UNICITY_CERTIFICATE_FIELD: + byte[] bytes = p.readValueAs(byte[].class); + unicityCertificate = UnicityObjectMapper.CBOR.readValue( + bytes, + UnicityCertificate.class + ); + break; default: p.skipChildren(); } @@ -89,12 +105,16 @@ public InclusionProof deserialize(JsonParser p, DeserializationContext ctx) thro } } - if (merkleTreePath == null) { - throw MismatchedInputException.from(p, InclusionProof.class, - String.format("Missing required fields: %s", MERKLE_TREE_PATH_FIELD)); + Set missingFields = new HashSet<>( + Set.of(MERKLE_TREE_PATH_FIELD, UNICITY_CERTIFICATE_FIELD) + ); + missingFields.removeAll(fields); + if (!missingFields.isEmpty()) { + throw MismatchedInputException.from(p, Token.class, + String.format("Missing required fields: %s", missingFields)); } - return new InclusionProof(merkleTreePath, authenticator, transactionHash); + return new InclusionProof(merkleTreePath, authenticator, transactionHash, unicityCertificate); } } } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java b/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java index 276396c..e586b73 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java @@ -4,6 +4,7 @@ import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.LeafValue; import org.unicitylabs.sdk.api.RequestId; +import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.mtree.MerkleTreePathVerificationResult; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; @@ -21,9 +22,17 @@ public class InclusionProof { private final SparseMerkleTreePath merkleTreePath; private final Authenticator authenticator; private final DataHash transactionHash; + private final UnicityCertificate unicityCertificate; + + public InclusionProof( + SparseMerkleTreePath merkleTreePath, + Authenticator authenticator, + DataHash transactionHash, + UnicityCertificate unicityCertificate + ) { + Objects.requireNonNull(merkleTreePath, "Merkle tree path cannot be null."); + Objects.requireNonNull(unicityCertificate, "Unicity certificate cannot be null."); - public InclusionProof(SparseMerkleTreePath merkleTreePath, Authenticator authenticator, - DataHash transactionHash) { if ((authenticator == null) != (transactionHash == null)) { throw new IllegalArgumentException( "Authenticator and transaction hash must be both set or both null."); @@ -31,12 +40,17 @@ public InclusionProof(SparseMerkleTreePath merkleTreePath, Authenticator authent this.merkleTreePath = merkleTreePath; this.authenticator = authenticator; this.transactionHash = transactionHash; + this.unicityCertificate = unicityCertificate; } public SparseMerkleTreePath getMerkleTreePath() { return this.merkleTreePath; } + public UnicityCertificate getUnicityCertificate() { + return this.unicityCertificate; + } + public Optional getAuthenticator() { return Optional.ofNullable(this.authenticator); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionData.java b/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionData.java index e3cc969..ebfc0c1 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionData.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionData.java @@ -104,8 +104,10 @@ public DataHash calculateHash() { node.addPOJO(this.reason); try { - return new DataHasher(HashAlgorithm.SHA256).update( - UnicityObjectMapper.CBOR.writeValueAsBytes(node)).digest(); + System.out.println(this.reason); + return new DataHasher(HashAlgorithm.SHA256) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(node)) + .digest(); } catch (JsonProcessingException e) { throw new CborSerializationException(e); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/split/SplitMintReason.java b/src/main/java/org/unicitylabs/sdk/transaction/split/SplitMintReason.java index 75bd7a3..bba6a15 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/split/SplitMintReason.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/split/SplitMintReason.java @@ -101,4 +101,9 @@ public VerificationResult verify(Transaction> t return VerificationResult.success(); } + + @Override + public String toString() { + return String.format("SplitMintReason{token=%s, proofs=%s}", this.token, this.proofs); + } } diff --git a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java index f9b30fe..7af5d79 100644 --- a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java +++ b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; +import org.unicitylabs.sdk.utils.UnicityCertificateUtils; public class TestAggregatorClient implements IAggregatorClient { @@ -53,7 +54,8 @@ public CompletableFuture getInclusionProof(RequestId req new InclusionProof( root.getPath(requestId.toBitString().toBigInteger()), entry.getKey(), - entry.getValue() + entry.getValue(), + UnicityCertificateUtils.generateCertificate() ) ) ); diff --git a/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java b/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java index b655489..aa96750 100644 --- a/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java +++ b/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java @@ -1,6 +1,7 @@ package org.unicitylabs.sdk.bft; import java.io.IOException; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.util.HexConverter; @@ -9,9 +10,15 @@ public class UnicityCertificateTest { @Test public void testUnicityCertificateDeserializationFromCbor() throws IOException { byte[] data = HexConverter.decode("d903ef8701d903f08a011a001ea47f005844303030306464313361666438613231333530336162663037313838353439333665333862393739633964396262393339323762333930393932346630373733313362313958443030303064643133616664386132313335303361626630373138383534393336653338623937396339643962623933393237623339303939323466303737333133623139401a68c413dff600f6582035ed27f40a24cdc321f92f3f59e265b817c986c98ff7d7e3e12367221b77735458207f58d8708258c4834849627c3110783be0c0ae9b344be807629e0b8e98e441cd82418080d903f683010780d903e98801031a009936f2001a68c413e15820c162885b569be72c83f14afc6e0e5533267022f337c1cac6f0028dbc77737fdd58209c08adef980baed2fe5444e116b777646b340fbb0a500168ac9477efc8a80386a1783531365569753248416d4562723766323557666d4a65457968713775444451646339536e656471667945585a70474e474e426177416e58410d6b0908ff4fa4c3b196c63c6420482d02e68e6ed3bdc4184d524833c50c4f0f58787dcb038b0a8d49b9a1bc260a5ceff4048a39e2984b9d4b349176d2039c0101"); - UnicityCertificate certificate = UnicityObjectMapper.CBOR.readValue(data, - UnicityCertificate.class); - System.out.println(certificate); + Assertions.assertEquals( + "d903ef8701d903f08a011a001ea47f005844303030306464313361666438613231333530336162663037313838353439333665333862393739633964396262393339323762333930393932346630373733313362313958443030303064643133616664386132313335303361626630373138383534393336653338623937396339643962623933393237623339303939323466303737333133623139401a68c413dff600f6582035ed27f40a24cdc321f92f3f59e265b817c986c98ff7d7e3e12367221b77735458207f58d8708258c4834849627c3110783be0c0ae9b344be807629e0b8e98e441cd82418080d903f683010780d903e98801031a009936f2001a68c413e15820c162885b569be72c83f14afc6e0e5533267022f337c1cac6f0028dbc77737fdd58209c08adef980baed2fe5444e116b777646b340fbb0a500168ac9477efc8a80386a1783531365569753248416d4562723766323557666d4a65457968713775444451646339536e656471667945585a70474e474e426177416e58410d6b0908ff4fa4c3b196c63c6420482d02e68e6ed3bdc4184d524833c50c4f0f58787dcb038b0a8d49b9a1bc260a5ceff4048a39e2984b9d4b349176d2039c0101", + HexConverter.encode( + UnicityObjectMapper.CBOR.writeValueAsBytes( + UnicityObjectMapper.CBOR.readValue(data, UnicityCertificate.class) + ) + ) + );; + } } diff --git a/src/test/java/org/unicitylabs/sdk/token/TokenTest.java b/src/test/java/org/unicitylabs/sdk/token/TokenTest.java index b1276af..379d7bf 100644 --- a/src/test/java/org/unicitylabs/sdk/token/TokenTest.java +++ b/src/test/java/org/unicitylabs/sdk/token/TokenTest.java @@ -1,6 +1,11 @@ package org.unicitylabs.sdk.token; import org.unicitylabs.sdk.address.DirectAddress; +import org.unicitylabs.sdk.bft.InputRecord; +import org.unicitylabs.sdk.bft.ShardTreeCertificate; +import org.unicitylabs.sdk.bft.UnicityCertificate; +import org.unicitylabs.sdk.bft.UnicitySeal; +import org.unicitylabs.sdk.bft.UnicityTreeCertificate; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; @@ -22,6 +27,7 @@ import java.util.UUID; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.unicitylabs.sdk.utils.UnicityCertificateUtils; public class TokenTest { @@ -66,7 +72,8 @@ public void testJsonSerialization() throws IOException { List.of() ), null, - null + null, + UnicityCertificateUtils.generateCertificate() ) ) ); @@ -92,7 +99,8 @@ public void testJsonSerialization() throws IOException { List.of() ), null, - null + null, + UnicityCertificateUtils.generateCertificate() ) ), List.of( @@ -121,7 +129,8 @@ public void testJsonSerialization() throws IOException { List.of() ), null, - null + null, + UnicityCertificateUtils.generateCertificate() ) ) ), diff --git a/src/test/java/org/unicitylabs/sdk/transaction/InclusionProofTest.java b/src/test/java/org/unicitylabs/sdk/transaction/InclusionProofTest.java index 707e3bf..2e15eb4 100644 --- a/src/test/java/org/unicitylabs/sdk/transaction/InclusionProofTest.java +++ b/src/test/java/org/unicitylabs/sdk/transaction/InclusionProofTest.java @@ -1,5 +1,9 @@ package org.unicitylabs.sdk.transaction; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.LeafValue; import org.unicitylabs.sdk.api.RequestId; @@ -10,10 +14,7 @@ import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.util.HexConverter; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; +import org.unicitylabs.sdk.utils.UnicityCertificateUtils; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class InclusionProofTest { @@ -43,8 +44,12 @@ public void createMerkleTreePath() throws Exception { @Test public void testJsonSerialization() throws Exception { - InclusionProof inclusionProof = new InclusionProof(merkleTreePath, authenticator, - transactionHash); + InclusionProof inclusionProof = new InclusionProof( + merkleTreePath, + authenticator, + transactionHash, + UnicityCertificateUtils.generateCertificate() + ); Assertions.assertEquals(inclusionProof, UnicityObjectMapper.JSON.readValue( UnicityObjectMapper.JSON.writeValueAsString(inclusionProof), InclusionProof.class)); } @@ -52,19 +57,63 @@ public void testJsonSerialization() throws Exception { @Test public void testStructure() { Assertions.assertThrows(IllegalArgumentException.class, - () -> new InclusionProof(merkleTreePath, authenticator, null)); + () -> new InclusionProof( + merkleTreePath, + authenticator, + null, + UnicityCertificateUtils.generateCertificate() + ) + ); Assertions.assertThrows(IllegalArgumentException.class, - () -> new InclusionProof(merkleTreePath, null, transactionHash)); + () -> new InclusionProof( + merkleTreePath, + null, + transactionHash, + UnicityCertificateUtils.generateCertificate() + ) + ); + Assertions.assertThrows(NullPointerException.class, + () -> new InclusionProof( + null, + authenticator, + transactionHash, + UnicityCertificateUtils.generateCertificate() + ) + ); + Assertions.assertThrows(NullPointerException.class, + () -> new InclusionProof( + merkleTreePath, + authenticator, + transactionHash, + null + ) + ); Assertions.assertInstanceOf(InclusionProof.class, - new InclusionProof(merkleTreePath, authenticator, transactionHash)); + new InclusionProof( + merkleTreePath, + authenticator, + transactionHash, + UnicityCertificateUtils.generateCertificate() + ) + ); Assertions.assertInstanceOf(InclusionProof.class, - new InclusionProof(merkleTreePath, null, null)); + new InclusionProof( + merkleTreePath, + null, + null, + UnicityCertificateUtils.generateCertificate() + ) + ); } @Test public void testItVerifies() { - InclusionProof inclusionProof = new InclusionProof(merkleTreePath, authenticator, - transactionHash); + InclusionProof inclusionProof = new InclusionProof( + merkleTreePath, + authenticator, + transactionHash, + UnicityCertificateUtils.generateCertificate() + ); Assertions.assertEquals(InclusionProofVerificationStatus.OK, inclusionProof.verify(requestId)); Assertions.assertEquals(InclusionProofVerificationStatus.PATH_NOT_INCLUDED, inclusionProof.verify( @@ -76,7 +125,8 @@ public void testItVerifies() { new DataHash( HashAlgorithm.SHA224, HexConverter.decode("FF000000000000000000000000000000000000000000000000000000000000FF") - ) + ), + UnicityCertificateUtils.generateCertificate() ); Assertions.assertEquals(InclusionProofVerificationStatus.NOT_AUTHENTICATED, diff --git a/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java b/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java index cb3edd7..4dda206 100644 --- a/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java +++ b/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java @@ -1,5 +1,10 @@ package org.unicitylabs.sdk.transaction.split; +import org.unicitylabs.sdk.bft.InputRecord; +import org.unicitylabs.sdk.bft.ShardTreeCertificate; +import org.unicitylabs.sdk.bft.UnicityCertificate; +import org.unicitylabs.sdk.bft.UnicitySeal; +import org.unicitylabs.sdk.bft.UnicityTreeCertificate; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.BranchExistsException; @@ -22,6 +27,7 @@ import java.util.UUID; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.unicitylabs.sdk.utils.UnicityCertificateUtils; public class TokenSplitBuilderTest { @@ -57,7 +63,8 @@ private Token createToken(TokenCoinData coinData) { List.of() ), null, - null + null, + UnicityCertificateUtils.generateCertificate() ) ) ); diff --git a/src/test/java/org/unicitylabs/sdk/utils/UnicityCertificateUtils.java b/src/test/java/org/unicitylabs/sdk/utils/UnicityCertificateUtils.java new file mode 100644 index 0000000..a5f9935 --- /dev/null +++ b/src/test/java/org/unicitylabs/sdk/utils/UnicityCertificateUtils.java @@ -0,0 +1,24 @@ +package org.unicitylabs.sdk.utils; + +import java.util.List; +import java.util.Map; +import org.unicitylabs.sdk.bft.InputRecord; +import org.unicitylabs.sdk.bft.ShardTreeCertificate; +import org.unicitylabs.sdk.bft.UnicityCertificate; +import org.unicitylabs.sdk.bft.UnicitySeal; +import org.unicitylabs.sdk.bft.UnicityTreeCertificate; + +public class UnicityCertificateUtils { + public static UnicityCertificate generateCertificate() { + return new UnicityCertificate( + 0, + new InputRecord(0, 0, 0, new byte[10], new byte[10], new byte[10], 0, + new byte[10], 0, new byte[10]), + new byte[10], + new byte[10], + new ShardTreeCertificate(new byte[10], List.of()), + new UnicityTreeCertificate(0, 0, List.of()), + new UnicitySeal(0, (short) 0, 0L, 0L, 0L, new byte[10], new byte[10], Map.of()) + ); + } +} From a0c3952084a3996d561608b962524768d914f715 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Tue, 23 Sep 2025 20:32:21 +0400 Subject: [PATCH 09/16] Add unicity certificate verification to inclusionproof --- .../org/unicitylabs/sdk/bft/NodeInfo.java | 20 --- .../unicitylabs/sdk/bft/RootTrustBase.java | 84 +++++++-- .../sdk/bft/UnicityCertificate.java | 6 +- .../org/unicitylabs/sdk/bft/UnicitySeal.java | 24 ++- ...UnicityCertificateVerificationContext.java | 37 ++++ .../UnicityCertificateVerificationRule.java | 21 +++ ...nputRecordCurrentHashVerificationRule.java | 36 ++++ ...nicitySealHashMatchesWithRootHashRule.java | 140 +++++++++++++++ ...ySealQuorumSignaturesVerificationRule.java | 133 +++++++++++++++ .../sdk/jsonrpc/JsonRpcHttpTransport.java | 3 +- .../sdk/serializer/UnicityObjectMapper.java | 35 +++- .../serializer/cbor/bft/InputRecordCbor.java | 32 +--- .../cbor/bft/ShardTreeCertificateCbor.java | 4 +- .../serializer/cbor/bft/UnicitySealCbor.java | 9 +- .../cbor/bft/UnicityTreeCertificateCbor.java | 4 +- .../UnicityTreeCertificateHashStepCbor.java | 10 +- .../json/api/InclusionProofResponseJson.java | 73 ++++++++ .../json/bft/RootTrustBaseJson.java | 159 ++++++++++++++++++ .../json/bft/RootTrustBaseNodeInfoJson.java | 97 +++++++++++ .../sdk/signing/SigningService.java | 11 +- .../sdk/transaction/InclusionProof.java | 13 ++ .../sdk/transaction/MintTransactionData.java | 1 - .../unicitylabs/sdk/util/HexConverter.java | 80 +++++---- .../CompositeVerificationRule.java | 2 +- .../sdk/verification/VerificationResult.java | 21 ++- .../sdk/verification/VerificationRule.java | 18 +- .../sdk/bft/RootTrustBaseTest.java | 32 ++++ .../sdk/bft/UnicityCertificateTest.java | 23 ++- .../split/TokenSplitBuilderTest.java | 19 +-- 29 files changed, 997 insertions(+), 150 deletions(-) delete mode 100644 src/main/java/org/unicitylabs/sdk/bft/NodeInfo.java create mode 100644 src/main/java/org/unicitylabs/sdk/bft/verification/UnicityCertificateVerificationContext.java create mode 100644 src/main/java/org/unicitylabs/sdk/bft/verification/UnicityCertificateVerificationRule.java create mode 100644 src/main/java/org/unicitylabs/sdk/bft/verification/rule/InputRecordCurrentHashVerificationRule.java create mode 100644 src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java create mode 100644 src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/json/api/InclusionProofResponseJson.java create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/json/bft/RootTrustBaseJson.java create mode 100644 src/main/java/org/unicitylabs/sdk/serializer/json/bft/RootTrustBaseNodeInfoJson.java create mode 100644 src/test/java/org/unicitylabs/sdk/bft/RootTrustBaseTest.java diff --git a/src/main/java/org/unicitylabs/sdk/bft/NodeInfo.java b/src/main/java/org/unicitylabs/sdk/bft/NodeInfo.java deleted file mode 100644 index f854e49..0000000 --- a/src/main/java/org/unicitylabs/sdk/bft/NodeInfo.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.unicitylabs.sdk.bft; - -import java.util.Arrays; - -public class NodeInfo { - - public final String nodeId; - private final byte[] signingKey; - public final long stakedAmount; - - public NodeInfo(String nodeId, byte[] signingKey, long stakedAmount) { - this.nodeId = nodeId; - this.signingKey = Arrays.copyOf(signingKey, signingKey.length); - this.stakedAmount = stakedAmount; - } - - public byte[] getSigningKey() { - return Arrays.copyOf(this.signingKey, this.signingKey.length); - } -} \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/bft/RootTrustBase.java b/src/main/java/org/unicitylabs/sdk/bft/RootTrustBase.java index 6ceec35..94c58bb 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/RootTrustBase.java +++ b/src/main/java/org/unicitylabs/sdk/bft/RootTrustBase.java @@ -1,18 +1,18 @@ package org.unicitylabs.sdk.bft; -import java.math.BigInteger; import java.util.Arrays; -import java.util.Collections; -import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; public class RootTrustBase { - public final long version; - public final long epoch; - public final long epochStartRound; - public final List rootNodes; - public final long quorumThreshold; + private final long version; + private final int networkId; + private final long epoch; + private final long epochStartRound; + private final Set rootNodes; + private final long quorumThreshold; private final byte[] stateHash; private final byte[] changeRecordHash; private final byte[] previousEntryHash; @@ -20,9 +20,10 @@ public class RootTrustBase { public RootTrustBase( long version, + int networkId, long epoch, long epochStartRound, - List rootNodes, + Set rootNodes, long quorumThreshold, byte[] stateHash, byte[] changeRecordHash, @@ -30,9 +31,10 @@ public RootTrustBase( Map signatures ) { this.version = version; + this.networkId = networkId; this.epoch = epoch; this.epochStartRound = epochStartRound; - this.rootNodes = Collections.unmodifiableList(rootNodes); + this.rootNodes = Set.copyOf(rootNodes); this.quorumThreshold = quorumThreshold; this.stateHash = Arrays.copyOf(stateHash, stateHash.length); this.changeRecordHash = Arrays.copyOf(changeRecordHash, changeRecordHash.length); @@ -46,6 +48,30 @@ public RootTrustBase( ); } + public long getVersion() { + return this.version; + } + + public int getNetworkId() { + return this.networkId; + } + + public long getEpoch() { + return this.epoch; + } + + public long getEpochStartRound() { + return this.epochStartRound; + } + + public Set getRootNodes() { + return this.rootNodes; + } + + public long getQuorumThreshold() { + return this.quorumThreshold; + } + public byte[] getStateHash() { return Arrays.copyOf(this.stateHash, this.stateHash.length); } @@ -67,4 +93,42 @@ public Map getSignatures() { ) ); } + + public static class NodeInfo { + private final String nodeId; + private final byte[] signingKey; + private final long stakedAmount; + + public NodeInfo(String nodeId, byte[] signingKey, long stakedAmount) { + this.nodeId = nodeId; + this.signingKey = Arrays.copyOf(signingKey, signingKey.length); + this.stakedAmount = stakedAmount; + } + + public String getNodeId() { + return this.nodeId; + } + + public byte[] getSigningKey() { + return Arrays.copyOf(this.signingKey, this.signingKey.length); + } + + public long getStakedAmount() { + return this.stakedAmount; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof NodeInfo)) { + return false; + } + NodeInfo nodeInfo = (NodeInfo) o; + return Objects.equals(this.nodeId, nodeInfo.nodeId); + } + + @Override + public int hashCode() { + return Objects.hash(nodeId); + } + } } \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java b/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java index 4dca3f6..d3de9b4 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java @@ -10,9 +10,9 @@ public class UnicityCertificate { private final InputRecord inputRecord; private final byte[] technicalRecordHash; private final byte[] shardConfigurationHash; - public final ShardTreeCertificate shardTreeCertificate; - public final UnicityTreeCertificate unicityTreeCertificate; - public final UnicitySeal unicitySeal; + private final ShardTreeCertificate shardTreeCertificate; + private final UnicityTreeCertificate unicityTreeCertificate; + private final UnicitySeal unicitySeal; public UnicityCertificate( int version, diff --git a/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java b/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java index c82233b..3dda757 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java +++ b/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java @@ -1,6 +1,7 @@ package org.unicitylabs.sdk.bft; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -15,7 +16,7 @@ public class UnicitySeal { private final long timestamp; private final byte[] previousHash; // nullable private final byte[] hash; - private final Map signatures; + private final LinkedHashMap signatures; public UnicitySeal( int version, @@ -43,7 +44,14 @@ public UnicitySeal( Arrays.copyOf(entry.getValue(), entry.getValue().length) ) ) - .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); + .collect( + Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (e1, e2) -> e1, + LinkedHashMap::new + ) + ); } public int getVersion() { @@ -67,7 +75,8 @@ public long getTimestamp() { } public byte[] getPreviousHash() { - return this.previousHash != null ? Arrays.copyOf(this.previousHash, this.previousHash.length) : null; + return this.previousHash != null ? Arrays.copyOf(this.previousHash, this.previousHash.length) + : null; } public byte[] getHash() { @@ -81,7 +90,14 @@ public Map getSignatures() { Arrays.copyOf(entry.getValue(), entry.getValue().length) ) ) - .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); + .collect( + Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (e1, e2) -> e1, + LinkedHashMap::new + ) + ); } @Override diff --git a/src/main/java/org/unicitylabs/sdk/bft/verification/UnicityCertificateVerificationContext.java b/src/main/java/org/unicitylabs/sdk/bft/verification/UnicityCertificateVerificationContext.java new file mode 100644 index 0000000..58bd201 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/bft/verification/UnicityCertificateVerificationContext.java @@ -0,0 +1,37 @@ +package org.unicitylabs.sdk.bft.verification; + +import org.unicitylabs.sdk.bft.RootTrustBase; +import org.unicitylabs.sdk.bft.UnicityCertificate; +import org.unicitylabs.sdk.hash.DataHash; + +public class UnicityCertificateVerificationContext { + + private final DataHash inputHash; + private final UnicityCertificate unicityCertificate; + private final RootTrustBase trustBase; + + + public UnicityCertificateVerificationContext( + DataHash inputHash, + UnicityCertificate unicityCertificate, + RootTrustBase trustBase + ) { + this.inputHash = inputHash; + this.unicityCertificate = unicityCertificate; + this.trustBase = trustBase; + } + + public DataHash getInputHash() { + return this.inputHash; + } + + public UnicityCertificate getUnicityCertificate() { + return this.unicityCertificate; + } + + public RootTrustBase getTrustBase() { + return this.trustBase; + } + + +} diff --git a/src/main/java/org/unicitylabs/sdk/bft/verification/UnicityCertificateVerificationRule.java b/src/main/java/org/unicitylabs/sdk/bft/verification/UnicityCertificateVerificationRule.java new file mode 100644 index 0000000..d610481 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/bft/verification/UnicityCertificateVerificationRule.java @@ -0,0 +1,21 @@ +package org.unicitylabs.sdk.bft.verification; + +import org.unicitylabs.sdk.bft.verification.rule.InputRecordCurrentHashVerificationRule; +import org.unicitylabs.sdk.bft.verification.rule.UnicitySealHashMatchesWithRootHashRule; +import org.unicitylabs.sdk.bft.verification.rule.UnicitySealQuorumSignaturesVerificationRule; +import org.unicitylabs.sdk.verification.CompositeVerificationRule; + +public class UnicityCertificateVerificationRule extends + CompositeVerificationRule { + + public UnicityCertificateVerificationRule() { + super("Verify unicity certificate", + new InputRecordCurrentHashVerificationRule( + new UnicitySealHashMatchesWithRootHashRule( + new UnicitySealQuorumSignaturesVerificationRule(), + null + ), + null + )); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/bft/verification/rule/InputRecordCurrentHashVerificationRule.java b/src/main/java/org/unicitylabs/sdk/bft/verification/rule/InputRecordCurrentHashVerificationRule.java new file mode 100644 index 0000000..ce5bb42 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/bft/verification/rule/InputRecordCurrentHashVerificationRule.java @@ -0,0 +1,36 @@ +package org.unicitylabs.sdk.bft.verification.rule; + +import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationContext; +import org.unicitylabs.sdk.hash.DataHash; +import org.unicitylabs.sdk.verification.VerificationResult; +import org.unicitylabs.sdk.verification.VerificationRule; + +public class InputRecordCurrentHashVerificationRule extends + VerificationRule { + + public InputRecordCurrentHashVerificationRule() { + this(null, null); + } + + public InputRecordCurrentHashVerificationRule( + VerificationRule onSuccessRule, + VerificationRule onFailureRule + ) { + super( + "Verifying input record if current hash matches input hash.", + onSuccessRule, + onFailureRule + ); + } + + @Override + public VerificationResult verify(UnicityCertificateVerificationContext context) { + if (context.getInputHash() + .equals(DataHash.fromImprint(context.getUnicityCertificate().getInputRecord().getHash()))) { + return VerificationResult.success(); + } + + return VerificationResult.fail("Input record current hash does not match input hash."); + } + +} diff --git a/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java b/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java new file mode 100644 index 0000000..3cf0adb --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java @@ -0,0 +1,140 @@ +package org.unicitylabs.sdk.bft.verification.rule; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.List; +import org.unicitylabs.sdk.bft.UnicityCertificate; +import org.unicitylabs.sdk.bft.UnicityTreeCertificate; +import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationContext; +import org.unicitylabs.sdk.hash.DataHash; +import org.unicitylabs.sdk.hash.DataHasher; +import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.verification.VerificationResult; +import org.unicitylabs.sdk.verification.VerificationRule; + +public class UnicitySealHashMatchesWithRootHashRule extends + VerificationRule { + + public UnicitySealHashMatchesWithRootHashRule() { + this(null, null); + } + + public UnicitySealHashMatchesWithRootHashRule( + VerificationRule onSuccessRule, + VerificationRule onFailureRule + ) { + super( + "Verifying UnicitySeal hash matches with tree root hash.", + onSuccessRule, + onFailureRule + ); + } + + @Override + public VerificationResult verify(UnicityCertificateVerificationContext context) { + DataHash shardTreeCertificateRootHash = UnicitySealHashMatchesWithRootHashRule + .calculateShardTreeCertificateRootHash(context.getUnicityCertificate()); + + if (shardTreeCertificateRootHash == null) { + return VerificationResult.fail("Could not calculate shard tree certificate root hash."); + } + + UnicityTreeCertificate unicityTreeCertificate = context.getUnicityCertificate() + .getUnicityTreeCertificate(); + byte[] key = ByteBuffer.allocate(4) + .order(ByteOrder.BIG_ENDIAN) + .putInt(unicityTreeCertificate.getPartitionIdentifier()) + .array(); + + try { + DataHash result = new DataHasher(HashAlgorithm.SHA256) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(new byte[]{(byte) 0x01})) // LEAF + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(key)) + .update( + UnicityObjectMapper.CBOR.writeValueAsBytes( + new DataHasher(HashAlgorithm.SHA256) + .update( + UnicityObjectMapper.CBOR.writeValueAsBytes( + shardTreeCertificateRootHash.getData() + ) + ) + .digest() + .getData() + ) + ) + .digest(); + + for (UnicityTreeCertificate.HashStep step : unicityTreeCertificate.getSteps()) { + byte[] stepKey = ByteBuffer.allocate(4) + .order(ByteOrder.BIG_ENDIAN) + .putInt(step.getKey()) + .array(); + + DataHasher hasher = new DataHasher(HashAlgorithm.SHA256) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(new byte[]{(byte) 0x00})) // NODE + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(stepKey)); + + if (Arrays.compare(key, stepKey) > 0) { + hasher + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(step.getHash())) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(result.getData())); + } else { + hasher + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(result.getData())) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(step.getHash())); + } + + result = hasher.digest(); + } + + byte[] unicitySealHash = context.getUnicityCertificate().getUnicitySeal().getHash(); + + if (Arrays.compare(unicitySealHash, result.getData()) != 0) { + return VerificationResult.fail("Unicity seal hash does not match tree root."); + } + } catch (IOException e) { + // TODO: Fix message + return VerificationResult.fail(e.getMessage()); + } + + return VerificationResult.success(); + } + + private static DataHash calculateShardTreeCertificateRootHash( + UnicityCertificate unicityCertificate) { + try { + DataHash rootHash = new DataHasher(HashAlgorithm.SHA256) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(unicityCertificate.getInputRecord())) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes( + unicityCertificate.getTechnicalRecordHash())) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes( + unicityCertificate.getShardConfigurationHash())) + .digest(); + + byte[] shardId = unicityCertificate.getShardTreeCertificate().getShard(); + List siblingHashes = unicityCertificate.getShardTreeCertificate() + .getSiblingHashList(); + for (int i = 0; i < siblingHashes.size(); i++) { + boolean isRight = shardId[(shardId.length - 1) - (i / 8)] == 1; + if (isRight) { + rootHash = new DataHasher(HashAlgorithm.SHA256) + .update(siblingHashes.get(i)) + .update(rootHash.getData()) + .digest(); + } else { + rootHash = new DataHasher(HashAlgorithm.SHA256) + .update(rootHash.getData()) + .update(siblingHashes.get(i)) + .digest(); + } + } + + return rootHash; + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java b/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java new file mode 100644 index 0000000..17234fb --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java @@ -0,0 +1,133 @@ +package org.unicitylabs.sdk.bft.verification.rule; + +import com.fasterxml.jackson.dataformat.cbor.CBORFactory; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import org.unicitylabs.sdk.bft.RootTrustBase; +import org.unicitylabs.sdk.bft.UnicitySeal; +import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationContext; +import org.unicitylabs.sdk.hash.DataHash; +import org.unicitylabs.sdk.hash.DataHasher; +import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.signing.SigningService; +import org.unicitylabs.sdk.verification.VerificationResult; +import org.unicitylabs.sdk.verification.VerificationRule; + +public class UnicitySealQuorumSignaturesVerificationRule extends + VerificationRule { + + public UnicitySealQuorumSignaturesVerificationRule() { + this(null, null); + } + + public UnicitySealQuorumSignaturesVerificationRule( + VerificationRule onSuccessRule, + VerificationRule onFailureRule + ) { + super( + "Verifying UnicitySeal quorum signatures.", + onSuccessRule, + onFailureRule + ); + } + + @Override + public VerificationResult verify(UnicityCertificateVerificationContext context) { + UnicitySeal unicitySeal = context.getUnicityCertificate().getUnicitySeal(); + byte[] unicitySealBytes = UnicitySealQuorumSignaturesVerificationRule.encodeUnicitySeal( + unicitySeal + ); + if (unicitySealBytes == null) { + return VerificationResult.fail("Could not encode UnicitySeal."); + } + + RootTrustBase trustBase = context.getTrustBase(); + + List results = new ArrayList<>(); + DataHash hash = new DataHasher(HashAlgorithm.SHA256).update(unicitySealBytes).digest(); + int successful = 0; + for (Map.Entry entry : unicitySeal.getSignatures().entrySet()) { + String nodeId = entry.getKey(); + byte[] signature = entry.getValue(); + + VerificationResult result = UnicitySealQuorumSignaturesVerificationRule.verifySignature( + trustBase.getRootNodes().stream() + .filter(node -> node.getNodeId().equals(nodeId)) + .findFirst() + .orElse(null), + signature, + hash.getData() + ); + results.add( + VerificationResult.fromChildren( + String.format("Verifying node '%s' signature.", nodeId), + List.of(result) + ) + ); + + if (result.isSuccessful()) { + successful++; + } + } + + if (successful >= trustBase.getQuorumThreshold()) { + return VerificationResult.success(); + } + + return VerificationResult.fail("Quorum threshold not reached."); + } + + private static VerificationResult verifySignature( + RootTrustBase.NodeInfo node, + byte[] signature, + byte[] hash + ) { + if (node == null) { + return VerificationResult.fail("No root node defined."); + } + + if (!SigningService.verifyWithPublicKey( + hash, + Arrays.copyOf(signature, signature.length - 1), + node.getSigningKey() + )) { + return VerificationResult.fail( + "Signature verification failed." + ); + } + + return VerificationResult.success(); + } + + private static byte[] encodeUnicitySeal(UnicitySeal seal) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORFactory factory = (CBORFactory) UnicityObjectMapper.CBOR.getFactory(); + CBORGenerator gen = factory.createGenerator(out); + + gen.writeTag(1001); + gen.writeStartArray(seal, 8); + gen.writeObject(seal.getVersion()); + gen.writeObject(seal.getNetworkId()); + gen.writeObject(seal.getRootChainRoundNumber()); + gen.writeObject(seal.getEpoch()); + gen.writeObject(seal.getTimestamp()); + gen.writeObject(seal.getPreviousHash()); + gen.writeObject(seal.getHash()); + gen.writeObject(null); + gen.writeEndArray(); + + gen.close(); + return out.toByteArray(); + } catch (IOException e) { + return null; + } + } + +} diff --git a/src/main/java/org/unicitylabs/sdk/jsonrpc/JsonRpcHttpTransport.java b/src/main/java/org/unicitylabs/sdk/jsonrpc/JsonRpcHttpTransport.java index 56ea4a5..316ddef 100644 --- a/src/main/java/org/unicitylabs/sdk/jsonrpc/JsonRpcHttpTransport.java +++ b/src/main/java/org/unicitylabs/sdk/jsonrpc/JsonRpcHttpTransport.java @@ -1,7 +1,6 @@ package org.unicitylabs.sdk.jsonrpc; -import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import java.io.IOException; import java.util.concurrent.CompletableFuture; import okhttp3.Call; @@ -12,6 +11,7 @@ import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; /** * JSON-RPC HTTP service. @@ -68,6 +68,7 @@ public void onResponse(Call call, Response response) { .constructParametricType(JsonRpcResponse.class, resultType)); if (data.getError() != null) { + future.completeExceptionally(new JsonRpcDataError(data.getError())); return; } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java index cab8ffe..24d3f8e 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java @@ -1,17 +1,21 @@ package org.unicitylabs.sdk.serializer; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; import com.fasterxml.jackson.dataformat.cbor.databind.CBORMapper; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import org.unicitylabs.sdk.address.Address; import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.BlockHeightResponse; import org.unicitylabs.sdk.api.InclusionProofRequest; +import org.unicitylabs.sdk.api.InclusionProofResponse; import org.unicitylabs.sdk.api.RequestId; import org.unicitylabs.sdk.api.SubmitCommitmentRequest; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.bft.InputRecord; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.bft.ShardTreeCertificate; import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.bft.UnicitySeal; @@ -65,9 +69,12 @@ import org.unicitylabs.sdk.serializer.json.api.AuthenticatorJson; import org.unicitylabs.sdk.serializer.json.api.BlockHeightResponseJson; import org.unicitylabs.sdk.serializer.json.api.InclusionProofRequestJson; +import org.unicitylabs.sdk.serializer.json.api.InclusionProofResponseJson; import org.unicitylabs.sdk.serializer.json.api.RequestIdJson; import org.unicitylabs.sdk.serializer.json.api.SubmitCommitmentRequestJson; import org.unicitylabs.sdk.serializer.json.api.SubmitCommitmentResponseJson; +import org.unicitylabs.sdk.serializer.json.bft.RootTrustBaseJson; +import org.unicitylabs.sdk.serializer.json.bft.RootTrustBaseNodeInfoJson; import org.unicitylabs.sdk.serializer.json.hash.DataHashJson; import org.unicitylabs.sdk.serializer.json.jsonrpc.JsonRpcErrorJson; import org.unicitylabs.sdk.serializer.json.jsonrpc.JsonRpcRequestJson; @@ -127,7 +134,8 @@ private static ObjectMapper createCborObjectMapper() { module.addSerializer(SparseMerkleTreePath.class, new SparseMerkleTreePathCbor.Serializer()); module.addDeserializer(SparseMerkleTreePath.class, new SparseMerkleTreePathCbor.Deserializer()); - module.addSerializer(SparseMerkleTreePathStep.class, new SparseMerkleTreePathStepCbor.Serializer()); + module.addSerializer(SparseMerkleTreePathStep.class, + new SparseMerkleTreePathStepCbor.Serializer()); module.addDeserializer(SparseMerkleTreePathStep.class, new SparseMerkleTreePathStepCbor.Deserializer()); module.addSerializer(SparseMerkleTreePathStep.Branch.class, @@ -190,7 +198,8 @@ private static ObjectMapper createCborObjectMapper() { module.addDeserializer(TokenState.class, new TokenStateCbor.Deserializer()); module.addSerializer(SerializablePredicate.class, new SerializablePredicateCbor.Serializer()); - module.addDeserializer(SerializablePredicate.class, new SerializablePredicateCbor.Deserializer()); + module.addDeserializer(SerializablePredicate.class, + new SerializablePredicateCbor.Deserializer()); module.addSerializer(DefaultPredicate.class, new DefaultPredicateCbor.Serializer()); module.addSerializer(BurnPredicate.class, new BurnPredicateCbor.Serializer()); @@ -206,9 +215,12 @@ private static ObjectMapper createCborObjectMapper() { module.addSerializer(ShardTreeCertificate.class, new ShardTreeCertificateCbor.Serializer()); module.addDeserializer(ShardTreeCertificate.class, new ShardTreeCertificateCbor.Deserializer()); module.addSerializer(UnicityTreeCertificate.class, new UnicityTreeCertificateCbor.Serializer()); - module.addDeserializer(UnicityTreeCertificate.class, new UnicityTreeCertificateCbor.Deserializer()); - module.addSerializer(UnicityTreeCertificate.HashStep.class, new UnicityTreeCertificateHashStepCbor.Serializer()); - module.addDeserializer(UnicityTreeCertificate.HashStep.class, new UnicityTreeCertificateHashStepCbor.Deserializer()); + module.addDeserializer(UnicityTreeCertificate.class, + new UnicityTreeCertificateCbor.Deserializer()); + module.addSerializer(UnicityTreeCertificate.HashStep.class, + new UnicityTreeCertificateHashStepCbor.Serializer()); + module.addDeserializer(UnicityTreeCertificate.HashStep.class, + new UnicityTreeCertificateHashStepCbor.Deserializer()); module.addSerializer(UnicitySeal.class, new UnicitySealCbor.Serializer()); module.addDeserializer(UnicitySeal.class, new UnicitySealCbor.Deserializer()); @@ -244,7 +256,8 @@ private static ObjectMapper createJsonObjectMapper() { module.addSerializer(SparseMerkleTreePath.class, new SparseMerkleTreePathJson.Serializer()); module.addDeserializer(SparseMerkleTreePath.class, new SparseMerkleTreePathJson.Deserializer()); - module.addSerializer(SparseMerkleTreePathStep.class, new SparseMerkleTreePathStepJson.Serializer()); + module.addSerializer(SparseMerkleTreePathStep.class, + new SparseMerkleTreePathStepJson.Serializer()); module.addDeserializer(SparseMerkleTreePathStep.class, new SparseMerkleTreePathStepJson.Deserializer()); @@ -285,7 +298,8 @@ private static ObjectMapper createJsonObjectMapper() { module.addDeserializer(TokenState.class, new TokenStateJson.Deserializer()); module.addSerializer(SerializablePredicate.class, new SerializablePredicateJson.Serializer()); - module.addDeserializer(SerializablePredicate.class, new SerializablePredicateJson.Deserializer()); + module.addDeserializer(SerializablePredicate.class, + new SerializablePredicateJson.Deserializer()); module.addSerializer(Transaction.class, new TransactionJson.Serializer()); module.addDeserializer(Transaction.class, new TransactionJson.Deserializer()); @@ -315,9 +329,16 @@ private static ObjectMapper createJsonObjectMapper() { module.addDeserializer(JsonRpcResponse.class, new JsonRpcResponseJson.Deserializer()); module.addDeserializer(JsonRpcError.class, new JsonRpcErrorJson.Deserializer()); module.addDeserializer(BlockHeightResponse.class, new BlockHeightResponseJson.Deserializer()); + module.addDeserializer(InclusionProofResponse.class, + new InclusionProofResponseJson.Deserializer()); module.addDeserializer(SubmitCommitmentResponse.class, new SubmitCommitmentResponseJson.Deserializer()); + // BFT + module.addDeserializer(RootTrustBase.NodeInfo.class, + new RootTrustBaseNodeInfoJson.Deserializer()); + module.addDeserializer(RootTrustBase.class, new RootTrustBaseJson.Deserializer()); + ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new Jdk8Module()); objectMapper.registerModule(module); diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java index e3701fe..9d65c61 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java @@ -34,16 +34,8 @@ public void serialize(InputRecord value, JsonGenerator gen, SerializerProvider s gen.writeObject(value.getVersion()); gen.writeObject(value.getRoundNumber()); gen.writeObject(value.getEpoch()); - gen.writeObject( - value.getPreviousHash() == null - ? null - : HexConverter.encode(value.getPreviousHash()).getBytes(StandardCharsets.UTF_8) - ); - gen.writeObject( - value.getHash() == null - ? null - : HexConverter.encode(value.getHash()).getBytes(StandardCharsets.UTF_8) - ); + gen.writeObject(value.getPreviousHash()); + gen.writeObject(value.getHash()); gen.writeObject(value.getSummaryValue()); gen.writeObject(value.getTimestamp()); gen.writeObject(value.getBlockHash()); @@ -73,28 +65,22 @@ public InputRecord deserialize(JsonParser p, DeserializationContext ctx) throws long sumOfEarnedFees = p.readValueAs(long.class); byte[] executedTransactionsHash = p.readValueAs(byte[].class); - InputRecord result = new InputRecord( + if (p.nextToken() != JsonToken.END_ARRAY) { + throw MismatchedInputException.from(p, InputRecord.class, "Expected end of array"); + } + + return new InputRecord( version, roundNumber, epoch, - previousHash != null - ? HexConverter.decode(new String(previousHash, StandardCharsets.UTF_8)) - : null, - hash != null - ? HexConverter.decode(new String(hash, StandardCharsets.UTF_8)) - : null, + previousHash, + hash, summaryValue, timestamp, blockHash, sumOfEarnedFees, executedTransactionsHash ); - - if (p.nextToken() != JsonToken.END_ARRAY) { - throw MismatchedInputException.from(p, InputRecord.class, "Expected end of array"); - } - - return result; } } } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java index 76317a1..fd36440 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/ShardTreeCertificateCbor.java @@ -53,13 +53,11 @@ public ShardTreeCertificate deserialize(JsonParser p, DeserializationContext ctx siblings.add(p.readValueAs(byte[].class)); } - ShardTreeCertificate result = new ShardTreeCertificate(shard, siblings); - if (p.nextToken() != JsonToken.END_ARRAY) { throw MismatchedInputException.from(p, ShardTreeCertificate.class, "Expected end of array"); } - return result; + return new ShardTreeCertificate(shard, siblings); } } } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java index ad46013..bd6284f 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java @@ -10,8 +10,9 @@ import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; import java.io.IOException; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; +import org.unicitylabs.sdk.bft.InputRecord; import org.unicitylabs.sdk.bft.UnicitySeal; public class UnicitySealCbor { @@ -70,13 +71,17 @@ public UnicitySeal deserialize(JsonParser p, DeserializationContext ctx) throws throw MismatchedInputException.from(p, UnicitySeal.class, "Expected map value"); } - Map signatures = new HashMap<>(); + Map signatures = new LinkedHashMap<>(); while (p.nextToken() != JsonToken.END_OBJECT) { String name = p.currentName(); p.nextToken(); signatures.put(name, p.readValueAs(byte[].class)); } + if (p.nextToken() != JsonToken.END_ARRAY) { + throw MismatchedInputException.from(p, InputRecord.class, "Expected end of array"); + } + return new UnicitySeal( version, networkId, diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java index 1475d85..c2961e9 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateCbor.java @@ -59,13 +59,11 @@ public UnicityTreeCertificate deserialize(JsonParser p, DeserializationContext c steps.add(ctx.readValue(p, UnicityTreeCertificate.HashStep.class)); } - UnicityTreeCertificate result = new UnicityTreeCertificate(version, partitionIdentifier, steps); - if (p.nextToken() != JsonToken.END_ARRAY) { throw MismatchedInputException.from(p, UnicityTreeCertificate.HashStep.class, "Expected end of array"); } - return result; + return new UnicityTreeCertificate(version, partitionIdentifier, steps); } } } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java index d65e135..7fecbd4 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicityTreeCertificateHashStepCbor.java @@ -2,12 +2,14 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import java.io.IOException; +import org.unicitylabs.sdk.bft.InputRecord; import org.unicitylabs.sdk.bft.UnicityTreeCertificate; public class UnicityTreeCertificateHashStepCbor { @@ -41,10 +43,16 @@ public UnicityTreeCertificate.HashStep deserialize(JsonParser p, Deserialization } p.nextToken(); - return new UnicityTreeCertificate.HashStep( + UnicityTreeCertificate.HashStep result = new UnicityTreeCertificate.HashStep( p.readValueAs(int.class), p.readValueAs(byte[].class) ); + + if (p.nextToken() != JsonToken.END_ARRAY) { + throw MismatchedInputException.from(p, InputRecord.class, "Expected end of array"); + } + + return result; } } } diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/api/InclusionProofResponseJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/api/InclusionProofResponseJson.java new file mode 100644 index 0000000..b18eddd --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/api/InclusionProofResponseJson.java @@ -0,0 +1,73 @@ +package org.unicitylabs.sdk.serializer.json.api; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import org.unicitylabs.sdk.api.InclusionProofResponse; +import org.unicitylabs.sdk.api.SubmitCommitmentResponse; +import org.unicitylabs.sdk.api.SubmitCommitmentStatus; +import org.unicitylabs.sdk.transaction.InclusionProof; + +public class InclusionProofResponseJson { + + private static final String INCLUSION_PROOF_FIELD = "inclusionProof"; + + private InclusionProofResponseJson() { + } + + public static class Deserializer extends JsonDeserializer { + + public Deserializer() { + } + + + @Override + public InclusionProofResponse deserialize(JsonParser p, DeserializationContext ctx) + throws IOException { + InclusionProof inclusionProof = null; + + Set fields = new HashSet<>(); + + if (!p.isExpectedStartObjectToken()) { + throw MismatchedInputException.from(p, InclusionProofResponse.class, + "Expected object value"); + } + + while (p.nextToken() != JsonToken.END_OBJECT) { + String fieldName = p.currentName(); + + if (!fields.add(fieldName)) { + throw MismatchedInputException.from(p, InclusionProofResponse.class, + String.format("Duplicate field: %s", fieldName)); + } + + p.nextToken(); + try { + switch (fieldName) { + case INCLUSION_PROOF_FIELD: + inclusionProof = p.readValueAs(InclusionProof.class); + break; + default: + p.skipChildren(); + } + } catch (Exception e) { + throw MismatchedInputException.wrapWithPath(e, InclusionProofResponse.class, fieldName); + } + } + + Set missingFields = new HashSet<>(Set.of(INCLUSION_PROOF_FIELD)); + missingFields.removeAll(fields); + if (!missingFields.isEmpty()) { + throw MismatchedInputException.from(p, InclusionProofResponse.class, + String.format("Missing required fields: %s", missingFields)); + } + + return new InclusionProofResponse(inclusionProof); + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/bft/RootTrustBaseJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/bft/RootTrustBaseJson.java new file mode 100644 index 0000000..8d3ce39 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/bft/RootTrustBaseJson.java @@ -0,0 +1,159 @@ +package org.unicitylabs.sdk.serializer.json.bft; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import java.io.IOException; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.unicitylabs.sdk.bft.RootTrustBase; + +public class RootTrustBaseJson { + private static final String VERSION_FIELD = "version"; + private static final String NETWORK_ID_FIELD = "networkId"; + private static final String EPOCH_FIELD = "epoch"; + private static final String EPOCH_START_ROUND_FIELD = "epochStartRound"; + private static final String ROOT_NODES_FIELD = "rootNodes"; + private static final String QUORUM_THRESHOLD_FIELD = "quorumThreshold"; + private static final String STATE_HASH_FIELD = "stateHash"; + private static final String CHANGE_RECORD_HASH_FIELD = "changeRecordHash"; + private static final String PREVIOUS_ENTRY_HASH_FIELD = "previousEntryHash"; + private static final String SIGNATURES_FIELD = "signatures"; + + private RootTrustBaseJson() { + } + + public static class Serializer extends JsonSerializer { + + @Override + public void serialize(RootTrustBase value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + + gen.writeStartObject(); + gen.writeObjectField(VERSION_FIELD, value.getVersion()); + gen.writeObjectField(NETWORK_ID_FIELD, value.getNetworkId()); + gen.writeObjectField(EPOCH_FIELD, value.getEpoch()); + gen.writeObjectField(EPOCH_START_ROUND_FIELD, value.getEpochStartRound()); + gen.writeObjectField(ROOT_NODES_FIELD, value.getRootNodes()); + gen.writeObjectField(QUORUM_THRESHOLD_FIELD, value.getQuorumThreshold()); + gen.writeObjectField(STATE_HASH_FIELD, value.getStateHash()); + gen.writeObjectField(CHANGE_RECORD_HASH_FIELD, value.getChangeRecordHash()); + gen.writeObjectField(PREVIOUS_ENTRY_HASH_FIELD, value.getPreviousEntryHash()); + gen.writeObjectField(SIGNATURES_FIELD, value.getSignatures()); + gen.writeEndObject(); + } + } + + public static class Deserializer extends JsonDeserializer { + + @Override + public RootTrustBase deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + Long version = null; + Integer networkId = null; + Long epoch = null; + Long epochStartRound = null; + Set rootNodes = null; + Integer quorumThreshold = null; + byte[] stateHash = null; + byte[] changeRecordHash = null; + byte[] previousEntryHash = null; + Map signatures = null; + + + Set fields = new HashSet<>(); + + if (!p.isExpectedStartObjectToken()) { + throw MismatchedInputException.from(p, RootTrustBase.class, "Expected object value"); + } + + while (p.nextToken() != JsonToken.END_OBJECT) { + String fieldName = p.currentName(); + + if (!fields.add(fieldName)) { + throw MismatchedInputException.from(p, RootTrustBase.class, + String.format("Duplicate field: %s", fieldName)); + } + + p.nextToken(); + + try { + switch (fieldName) { + case VERSION_FIELD: + version = p.readValueAs(Long.class); + break; + case NETWORK_ID_FIELD: + networkId = p.readValueAs(Integer.class); + break; + case EPOCH_FIELD: + epoch = p.readValueAs(Long.class); + break; + case EPOCH_START_ROUND_FIELD: + epochStartRound = p.readValueAs(Long.class); + break; + case ROOT_NODES_FIELD: + rootNodes = ctx.readValue( + p, + ctx.getTypeFactory().constructCollectionType(Set.class, RootTrustBase.NodeInfo.class) + ); + break; + case QUORUM_THRESHOLD_FIELD: + quorumThreshold = p.readValueAs(Integer.class); + break; + case STATE_HASH_FIELD: + stateHash = p.readValueAs(byte[].class); + break; + case CHANGE_RECORD_HASH_FIELD: + changeRecordHash = p.readValueAs(byte[].class); + break; + case PREVIOUS_ENTRY_HASH_FIELD: + previousEntryHash = p.readValueAs(byte[].class); + break; + case SIGNATURES_FIELD: + signatures = ctx.readValue( + p, + ctx.getTypeFactory().constructMapType(Map.class, String.class, byte[].class) + ); + break; + default: + p.skipChildren(); + } + } catch (Exception e) { + throw MismatchedInputException.wrapWithPath(e, RootTrustBase.class, fieldName); + } + } + + Set missingFields = new HashSet<>( + Set.of(VERSION_FIELD, NETWORK_ID_FIELD, EPOCH_FIELD, EPOCH_START_ROUND_FIELD, + ROOT_NODES_FIELD, QUORUM_THRESHOLD_FIELD, STATE_HASH_FIELD, CHANGE_RECORD_HASH_FIELD, + PREVIOUS_ENTRY_HASH_FIELD, SIGNATURES_FIELD)); + missingFields.removeAll(fields); + if (!missingFields.isEmpty()) { + throw MismatchedInputException.from(p, RootTrustBase.class, + String.format("Missing required fields: %s", missingFields)); + } + + return new RootTrustBase( + version, + networkId, + epoch, + epochStartRound, + rootNodes, + quorumThreshold, + stateHash, + changeRecordHash, + previousEntryHash, + signatures + ); + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/bft/RootTrustBaseNodeInfoJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/bft/RootTrustBaseNodeInfoJson.java new file mode 100644 index 0000000..0ad7cf3 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/bft/RootTrustBaseNodeInfoJson.java @@ -0,0 +1,97 @@ +package org.unicitylabs.sdk.serializer.json.bft; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import org.unicitylabs.sdk.bft.RootTrustBase; +import org.unicitylabs.sdk.util.HexConverter; + +public class RootTrustBaseNodeInfoJson { + private static final String NODE_ID_FIELD = "nodeId"; + private static final String SIGNING_KEY_FIELD = "sigKey"; + private static final String STAKED_AMOUNT_FIELD = "stake"; + + private RootTrustBaseNodeInfoJson() { + } + + public static class Serializer extends JsonSerializer { + + @Override + public void serialize(RootTrustBase.NodeInfo value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + + gen.writeStartObject(); + gen.writeObjectField(NODE_ID_FIELD, value.getNodeId()); + gen.writeObjectField(SIGNING_KEY_FIELD, value.getSigningKey()); + gen.writeObjectField(STAKED_AMOUNT_FIELD, value.getStakedAmount()); + gen.writeEndObject(); + } + } + + public static class Deserializer extends JsonDeserializer { + + @Override + public RootTrustBase.NodeInfo deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + String nodeId = null; + byte[] signingKey = null; + long stakedAmount = 0; + + Set fields = new HashSet<>(); + + if (!p.isExpectedStartObjectToken()) { + throw MismatchedInputException.from(p, RootTrustBase.NodeInfo.class, "Expected object value"); + } + + while (p.nextToken() != JsonToken.END_OBJECT) { + String fieldName = p.currentName(); + + if (!fields.add(fieldName)) { + throw MismatchedInputException.from(p, RootTrustBase.NodeInfo.class, + String.format("Duplicate field: %s", fieldName)); + } + + p.nextToken(); + + try { + switch (fieldName) { + case NODE_ID_FIELD: + nodeId = p.readValueAs(String.class); + break; + case SIGNING_KEY_FIELD: + signingKey = p.readValueAs(byte[].class); + break; + case STAKED_AMOUNT_FIELD: + stakedAmount = p.readValueAs(Long.class); + break; + default: + p.skipChildren(); + } + } catch (Exception e) { + throw MismatchedInputException.wrapWithPath(e, RootTrustBase.NodeInfo.class, fieldName); + } + } + + Set missingFields = new HashSet<>( + Set.of(NODE_ID_FIELD, SIGNING_KEY_FIELD, STAKED_AMOUNT_FIELD)); + missingFields.removeAll(fields); + if (!missingFields.isEmpty()) { + throw MismatchedInputException.from(p, RootTrustBase.NodeInfo.class, + String.format("Missing required fields: %s", missingFields)); + } + + return new RootTrustBase.NodeInfo(nodeId, signingKey, stakedAmount); + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/signing/SigningService.java b/src/main/java/org/unicitylabs/sdk/signing/SigningService.java index 1d4bc9c..3d7bd0b 100644 --- a/src/main/java/org/unicitylabs/sdk/signing/SigningService.java +++ b/src/main/java/org/unicitylabs/sdk/signing/SigningService.java @@ -138,9 +138,16 @@ public boolean verify(DataHash hash, Signature signature) { } /** - * Verify signature with public key. + * Verify signature with public key and hash. */ public static boolean verifyWithPublicKey(DataHash hash, byte[] signature, byte[] publicKey) { + return SigningService.verifyWithPublicKey(hash.getData(), signature, publicKey); + } + + /** + * Verify signature with public key and hash bytes. + */ + public static boolean verifyWithPublicKey(byte[] hash, byte[] signature, byte[] publicKey) { ECPoint pubPoint = EC_SPEC.getCurve().decodePoint(publicKey); ECPublicKeyParameters pubKey = new ECPublicKeyParameters(pubPoint, EC_DOMAIN_PARAMETERS); @@ -151,7 +158,7 @@ public static boolean verifyWithPublicKey(DataHash hash, byte[] signature, byte[ BigInteger r = new BigInteger(1, Arrays.copyOfRange(signature, 0, 32)); BigInteger s = new BigInteger(1, Arrays.copyOfRange(signature, 32, 64)); - return verifier.verifySignature(hash.getData(), r, s); + return verifier.verifySignature(hash, r, s); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java b/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java index e586b73..4960c0c 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java @@ -5,6 +5,8 @@ import org.unicitylabs.sdk.api.LeafValue; import org.unicitylabs.sdk.api.RequestId; import org.unicitylabs.sdk.bft.UnicityCertificate; +import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationContext; +import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationRule; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.mtree.MerkleTreePathVerificationResult; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; @@ -13,6 +15,7 @@ import java.util.Arrays; import java.util.Objects; import java.util.Optional; +import org.unicitylabs.sdk.verification.VerificationResult; /** * Represents a proof of inclusion or non-inclusion in a sparse merkle tree. @@ -77,6 +80,16 @@ public InclusionProofVerificationStatus verify(RequestId requestId) { } } + if (!new UnicityCertificateVerificationRule().verify( + new UnicityCertificateVerificationContext( + this.merkleTreePath.getRootHash(), + this.unicityCertificate, + null + ) + ).isSuccessful()) { + return InclusionProofVerificationStatus.NOT_AUTHENTICATED; + } + MerkleTreePathVerificationResult result = this.merkleTreePath.verify( requestId.toBitString().toBigInteger()); if (!result.isPathValid()) { diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionData.java b/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionData.java index ebfc0c1..364ec42 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionData.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintTransactionData.java @@ -104,7 +104,6 @@ public DataHash calculateHash() { node.addPOJO(this.reason); try { - System.out.println(this.reason); return new DataHasher(HashAlgorithm.SHA256) .update(UnicityObjectMapper.CBOR.writeValueAsBytes(node)) .digest(); diff --git a/src/main/java/org/unicitylabs/sdk/util/HexConverter.java b/src/main/java/org/unicitylabs/sdk/util/HexConverter.java index 20637e1..395111a 100644 --- a/src/main/java/org/unicitylabs/sdk/util/HexConverter.java +++ b/src/main/java/org/unicitylabs/sdk/util/HexConverter.java @@ -4,46 +4,52 @@ * Utility class for converting between byte arrays and hexadecimal strings. */ public class HexConverter { - private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); - /** - * Convert byte array to hex - * @param data byte array - * @return hex string - */ - public static String encode(byte[] data) { - char[] hexChars = new char[data.length * 2]; - for (int j = 0; j < data.length; j++) { - int v = data[j] & 0xFF; - hexChars[j * 2] = HEX_ARRAY[v >>> 4]; - hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; - } - return new String(hexChars); + private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); + + /** + * Convert byte array to hex + * + * @param data byte array + * @return hex string + */ + public static String encode(byte[] data) { + char[] hexChars = new char[data.length * 2]; + for (int j = 0; j < data.length; j++) { + int v = data[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars); + } + + /** + * Convert hex string to bytes + * + * @param value hex string + * @return byte array + */ + public static byte[] decode(String value) { + if (value == null) { + throw new IllegalArgumentException("Input is null"); } + if (value.length() % 2 != 0) { + throw new IllegalArgumentException("Hex string must have even length"); + } + + value = value.startsWith("0x") || value.startsWith("0X") ? value.substring(2) : value; - /** - * Convert hex string to bytes - * @param value hex string - * @return byte array - */ - public static byte[] decode(String value) { - if (value == null) { - throw new IllegalArgumentException("Input is null"); - } - if (value.length() % 2 != 0) { - throw new IllegalArgumentException("Hex string must have even length"); - } + int len = value.length(); + byte[] data = new byte[len / 2]; - int len = value.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - int hi = Character.digit(value.charAt(i), 16); - int lo = Character.digit(value.charAt(i + 1), 16); - if (hi == -1 || lo == -1) { - throw new IllegalArgumentException("Invalid hex character at position " + i); - } - data[i / 2] = (byte) ((hi << 4) + lo); - } - return data; + for (int i = 0; i < len; i += 2) { + int hi = Character.digit(value.charAt(i), 16); + int lo = Character.digit(value.charAt(i + 1), 16); + if (hi == -1 || lo == -1) { + throw new IllegalArgumentException("Invalid hex character at position " + i); + } + data[i / 2] = (byte) ((hi << 4) + lo); } + return data; + } } \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/verification/CompositeVerificationRule.java b/src/main/java/org/unicitylabs/sdk/verification/CompositeVerificationRule.java index 9fef868..150d6de 100644 --- a/src/main/java/org/unicitylabs/sdk/verification/CompositeVerificationRule.java +++ b/src/main/java/org/unicitylabs/sdk/verification/CompositeVerificationRule.java @@ -12,7 +12,7 @@ public CompositeVerificationRule( String message, VerificationRule firstRule ) { - super(firstRule); + super(message); this.firstRule = firstRule; this.message = message; diff --git a/src/main/java/org/unicitylabs/sdk/verification/VerificationResult.java b/src/main/java/org/unicitylabs/sdk/verification/VerificationResult.java index 0097a2e..b8fab7b 100644 --- a/src/main/java/org/unicitylabs/sdk/verification/VerificationResult.java +++ b/src/main/java/org/unicitylabs/sdk/verification/VerificationResult.java @@ -4,40 +4,45 @@ public class VerificationResult { - private final boolean isSuccessful; + private final VerificationResultCode status; private final List results; private final String message; - private VerificationResult(boolean isSuccessful, String message, + private VerificationResult(VerificationResultCode status, String message, List results) { this.message = message; this.results = List.copyOf(results); - this.isSuccessful = isSuccessful; + this.status = status; } public static VerificationResult success() { - return new VerificationResult(true, "Verification successful", List.of()); + return new VerificationResult(VerificationResultCode.OK, "Verification successful", List.of()); } public static VerificationResult fail(String error) { - return new VerificationResult(false, error, List.of()); + return new VerificationResult(VerificationResultCode.FAIL, error, List.of()); } public static VerificationResult fromChildren(String message, List children) { return new VerificationResult( - children.stream().allMatch(VerificationResult::isSuccessful), message, children); + children.stream().allMatch(VerificationResult::isSuccessful) + ? VerificationResultCode.OK + : VerificationResultCode.FAIL, + message, + children + ); } public boolean isSuccessful() { - return this.isSuccessful; + return this.status == VerificationResultCode.OK; } @Override public String toString() { return String.format( "TokenVerificationResult{isSuccessful=%s, message='%s', results=%s}", - this.isSuccessful, this.message, this.results + this.status, this.message, this.results ); } } diff --git a/src/main/java/org/unicitylabs/sdk/verification/VerificationRule.java b/src/main/java/org/unicitylabs/sdk/verification/VerificationRule.java index 8459f11..9410eee 100644 --- a/src/main/java/org/unicitylabs/sdk/verification/VerificationRule.java +++ b/src/main/java/org/unicitylabs/sdk/verification/VerificationRule.java @@ -1,25 +1,33 @@ package org.unicitylabs.sdk.verification; +import java.util.Objects; + public abstract class VerificationRule { + private final String message; private final VerificationRule onSuccessRule; private final VerificationRule onFailureRule; - protected VerificationRule(VerificationRule rule) { - this( - rule.onSuccessRule, - rule.onFailureRule - ); + protected VerificationRule(String message) { + this(message, null, null); } protected VerificationRule( + String message, VerificationRule onSuccessRule, VerificationRule onFailureRule ) { + Objects.requireNonNull(message, "Message cannot be null"); + + this.message = message; this.onSuccessRule = onSuccessRule; this.onFailureRule = onFailureRule; } + public String getMessage() { + return this.message; + } + public VerificationRule getNextRule(VerificationResultCode resultCode) { switch (resultCode) { case OK: diff --git a/src/test/java/org/unicitylabs/sdk/bft/RootTrustBaseTest.java b/src/test/java/org/unicitylabs/sdk/bft/RootTrustBaseTest.java new file mode 100644 index 0000000..b938a32 --- /dev/null +++ b/src/test/java/org/unicitylabs/sdk/bft/RootTrustBaseTest.java @@ -0,0 +1,32 @@ +package org.unicitylabs.sdk.bft; + +import java.io.IOException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.unicitylabs.sdk.bft.verification.BftVerificationContext; +import org.unicitylabs.sdk.bft.verification.rule.UnicitySealHashMatchesWithRootHashRule; +import org.unicitylabs.sdk.bft.verification.rule.UnicitySealQuorumSignaturesVerificationRule; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.util.HexConverter; + +public class RootTrustBaseTest { + + @Test + public void testRootTrustBaseDeserializationFromJson() throws IOException { + RootTrustBase trustBase = UnicityObjectMapper.JSON.readValue( + "{\"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\"}}", + RootTrustBase.class); + + Assertions.assertEquals(1, trustBase.getVersion()); + Assertions.assertEquals(3, trustBase.getNetworkId()); + Assertions.assertEquals(1, trustBase.getEpoch()); + Assertions.assertEquals(1, trustBase.getEpochStartRound()); + Assertions.assertEquals(4, trustBase.getRootNodes().size()); + Assertions.assertEquals(3, trustBase.getQuorumThreshold()); + Assertions.assertEquals(0, trustBase.getStateHash().length); + Assertions.assertEquals(0, trustBase.getChangeRecordHash().length); + Assertions.assertEquals(0, trustBase.getPreviousEntryHash().length); + Assertions.assertEquals(4, trustBase.getSignatures().size()); + } + +} diff --git a/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java b/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java index aa96750..bbe4d51 100644 --- a/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java +++ b/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java @@ -3,22 +3,31 @@ import java.io.IOException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.unicitylabs.sdk.api.InclusionProofResponse; +import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationContext; +import org.unicitylabs.sdk.bft.verification.rule.InputRecordCurrentHashVerificationRule; +import org.unicitylabs.sdk.bft.verification.rule.UnicitySealHashMatchesWithRootHashRule; +import org.unicitylabs.sdk.bft.verification.rule.UnicitySealQuorumSignaturesVerificationRule; +import org.unicitylabs.sdk.jsonrpc.JsonRpcResponse; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.util.HexConverter; public class UnicityCertificateTest { + @Test public void testUnicityCertificateDeserializationFromCbor() throws IOException { - byte[] data = HexConverter.decode("d903ef8701d903f08a011a001ea47f005844303030306464313361666438613231333530336162663037313838353439333665333862393739633964396262393339323762333930393932346630373733313362313958443030303064643133616664386132313335303361626630373138383534393336653338623937396339643962623933393237623339303939323466303737333133623139401a68c413dff600f6582035ed27f40a24cdc321f92f3f59e265b817c986c98ff7d7e3e12367221b77735458207f58d8708258c4834849627c3110783be0c0ae9b344be807629e0b8e98e441cd82418080d903f683010780d903e98801031a009936f2001a68c413e15820c162885b569be72c83f14afc6e0e5533267022f337c1cac6f0028dbc77737fdd58209c08adef980baed2fe5444e116b777646b340fbb0a500168ac9477efc8a80386a1783531365569753248416d4562723766323557666d4a65457968713775444451646339536e656471667945585a70474e474e426177416e58410d6b0908ff4fa4c3b196c63c6420482d02e68e6ed3bdc4184d524833c50c4f0f58787dcb038b0a8d49b9a1bc260a5ceff4048a39e2984b9d4b349176d2039c0101"); + byte[] data = HexConverter.decode( + "d903ef8701d903f08a0118f70058220000ea2fc549e86b1f8666bd7a6d5dd0721b446856ad80c67ea958c4045dfb0b627c58220000ea2fc549e86b1f8666bd7a6d5dd0721b446856ad80c67ea958c4045dfb0b627c401a68d27af6f600f6582024bf5304778618efe3303e1ac36a5c23b7d9d64bbd2c2ae92ea1488ad59e9cfa58206a362e77353752942e9c8101511861ee7fac83ba0873ee88f34416535ae17c5c82418080d903f68301078182055820b206f60d312bf4422815c861e9330ddc3a29b0c744864d7bea41f94d1d34c669d903e988010319073b001a68d27af958204b3f7f036452271a1245ca79f6c2e35adc0b192e6eeb001dff35d70d1be3b2d55820380451b471673eb68cf750ceedb09755c08e3b0b7dea32d0101465970ca3a661a4783531365569753248416d33506141397a386a6f6e5a7a6676755431574a675478437062466b5634577134505353426b375663746b6d475841c5103160b79e7691cec53448949bbd93a870a38c89ad4c929aa1c8e77f2a1d9053d3f8f04e96bcb38ba234bf3dee52baad4b1df94be0b8a5e569918fb94e18ec00783531365569753248416d383931384473326e5069564c586735356b7970796f586f6977656f6b705178746e67755a6a67787a33704e455841fa3736ebdc729b31d6e3c964787dfcefcbd39286a1619665eac1313e6d35049b4385156f652ad9ac87a769d3bf7b8cc819bb5a6f838e595d2e2092df2592849400783531365569753248416d454545477976595a6e6f37686d3247733846776650656a57647076574333484c6976514435685846624e55685841e49ea9e0ca1bb21a37a203fef7bc64efc660ff208f4e572ad3d58ae1d5f1e86e7af1c030c66b3325f99886b1a1d0836487c16112c43c07248b490121d473287701783531365569753248416d4e776772753751537356526163477158647466656166316f71747a6e7658413672537a4b55313832326b75575841ecb36e24df58876277643117344d5329fc1bb22b51e9e6835ac587c894db06a11110e966fd97acd91659c778494d9b4dcdc1bd22ffb47a90e3bec38db8a7310f01"); + UnicityCertificate unicityCertificate = UnicityObjectMapper.CBOR.readValue(data, + UnicityCertificate.class); + Assertions.assertEquals( - "d903ef8701d903f08a011a001ea47f005844303030306464313361666438613231333530336162663037313838353439333665333862393739633964396262393339323762333930393932346630373733313362313958443030303064643133616664386132313335303361626630373138383534393336653338623937396339643962623933393237623339303939323466303737333133623139401a68c413dff600f6582035ed27f40a24cdc321f92f3f59e265b817c986c98ff7d7e3e12367221b77735458207f58d8708258c4834849627c3110783be0c0ae9b344be807629e0b8e98e441cd82418080d903f683010780d903e98801031a009936f2001a68c413e15820c162885b569be72c83f14afc6e0e5533267022f337c1cac6f0028dbc77737fdd58209c08adef980baed2fe5444e116b777646b340fbb0a500168ac9477efc8a80386a1783531365569753248416d4562723766323557666d4a65457968713775444451646339536e656471667945585a70474e474e426177416e58410d6b0908ff4fa4c3b196c63c6420482d02e68e6ed3bdc4184d524833c50c4f0f58787dcb038b0a8d49b9a1bc260a5ceff4048a39e2984b9d4b349176d2039c0101", + "d903ef8701d903f08a0118f70058220000ea2fc549e86b1f8666bd7a6d5dd0721b446856ad80c67ea958c4045dfb0b627c58220000ea2fc549e86b1f8666bd7a6d5dd0721b446856ad80c67ea958c4045dfb0b627c401a68d27af6f600f6582024bf5304778618efe3303e1ac36a5c23b7d9d64bbd2c2ae92ea1488ad59e9cfa58206a362e77353752942e9c8101511861ee7fac83ba0873ee88f34416535ae17c5c82418080d903f68301078182055820b206f60d312bf4422815c861e9330ddc3a29b0c744864d7bea41f94d1d34c669d903e988010319073b001a68d27af958204b3f7f036452271a1245ca79f6c2e35adc0b192e6eeb001dff35d70d1be3b2d55820380451b471673eb68cf750ceedb09755c08e3b0b7dea32d0101465970ca3a661a4783531365569753248416d33506141397a386a6f6e5a7a6676755431574a675478437062466b5634577134505353426b375663746b6d475841c5103160b79e7691cec53448949bbd93a870a38c89ad4c929aa1c8e77f2a1d9053d3f8f04e96bcb38ba234bf3dee52baad4b1df94be0b8a5e569918fb94e18ec00783531365569753248416d383931384473326e5069564c586735356b7970796f586f6977656f6b705178746e67755a6a67787a33704e455841fa3736ebdc729b31d6e3c964787dfcefcbd39286a1619665eac1313e6d35049b4385156f652ad9ac87a769d3bf7b8cc819bb5a6f838e595d2e2092df2592849400783531365569753248416d454545477976595a6e6f37686d3247733846776650656a57647076574333484c6976514435685846624e55685841e49ea9e0ca1bb21a37a203fef7bc64efc660ff208f4e572ad3d58ae1d5f1e86e7af1c030c66b3325f99886b1a1d0836487c16112c43c07248b490121d473287701783531365569753248416d4e776772753751537356526163477158647466656166316f71747a6e7658413672537a4b55313832326b75575841ecb36e24df58876277643117344d5329fc1bb22b51e9e6835ac587c894db06a11110e966fd97acd91659c778494d9b4dcdc1bd22ffb47a90e3bec38db8a7310f01", HexConverter.encode( - UnicityObjectMapper.CBOR.writeValueAsBytes( - UnicityObjectMapper.CBOR.readValue(data, UnicityCertificate.class) - ) + UnicityObjectMapper.CBOR.writeValueAsBytes(unicityCertificate) ) - );; - + ); } } diff --git a/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java b/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java index 4dda206..19d11eb 100644 --- a/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java +++ b/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java @@ -1,17 +1,18 @@ package org.unicitylabs.sdk.transaction.split; -import org.unicitylabs.sdk.bft.InputRecord; -import org.unicitylabs.sdk.bft.ShardTreeCertificate; -import org.unicitylabs.sdk.bft.UnicityCertificate; -import org.unicitylabs.sdk.bft.UnicitySeal; -import org.unicitylabs.sdk.bft.UnicityTreeCertificate; +import java.math.BigInteger; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.BranchExistsException; import org.unicitylabs.sdk.mtree.LeafOutOfBoundsException; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; -import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; import org.unicitylabs.sdk.predicate.Predicate; +import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenState; @@ -21,12 +22,6 @@ import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.transaction.MintTransactionData; import org.unicitylabs.sdk.transaction.Transaction; -import java.math.BigInteger; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.utils.UnicityCertificateUtils; public class TokenSplitBuilderTest { From dc5373c1b2201477c0aa96973ebf2791b1b83104 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Tue, 23 Sep 2025 22:24:54 +0400 Subject: [PATCH 10/16] Move toTransaction to Commitment base class --- .../sdk/transaction/Commitment.java | 17 +++++++++++++++++ .../sdk/transaction/MintCommitment.java | 19 +------------------ .../sdk/transaction/TransactionData.java | 1 + 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/unicitylabs/sdk/transaction/Commitment.java b/src/main/java/org/unicitylabs/sdk/transaction/Commitment.java index 11458a7..2169834 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/Commitment.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/Commitment.java @@ -4,6 +4,7 @@ import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.RequestId; import java.util.Objects; +import org.unicitylabs.sdk.bft.RootTrustBase; /** * Commitment representing a submitted transaction @@ -48,6 +49,22 @@ public Authenticator getAuthenticator() { return authenticator; } + public Transaction toTransaction(InclusionProof inclusionProof) { + if (inclusionProof.verify(this.getRequestId()) != InclusionProofVerificationStatus.OK) { + throw new RuntimeException("Inclusion proof verification failed."); + } + + if (inclusionProof.getAuthenticator().isEmpty()) { + throw new RuntimeException("Authenticator is missing from inclusion proof."); + } + + if (!this.getTransactionData().calculateHash().equals(inclusionProof.getTransactionHash().orElse(null))) { + throw new RuntimeException("Payload hash mismatch."); + } + + return new Transaction<>(this.getTransactionData(), inclusionProof); + } + @Override public boolean equals(Object o) { if (!(o instanceof Commitment)) { diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintCommitment.java b/src/main/java/org/unicitylabs/sdk/transaction/MintCommitment.java index 76d6e52..1384db7 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintCommitment.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintCommitment.java @@ -1,12 +1,12 @@ package org.unicitylabs.sdk.transaction; +import java.util.Objects; import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.RequestId; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.util.HexConverter; -import java.util.Objects; /** * Commitment representing a submitted transaction @@ -14,7 +14,6 @@ * @param the type of transaction data */ public class MintCommitment> extends Commitment { - public static final byte[] MINTER_SECRET = HexConverter.decode( "495f414d5f554e4956455253414c5f4d494e5445525f464f525f"); @@ -42,20 +41,4 @@ public static > MintCommitment create( public static SigningService createSigningService(MintTransactionData transactionData) { return SigningService.createFromMaskedSecret(MINTER_SECRET, transactionData.getTokenId().getBytes()); } - - public Transaction toTransaction(InclusionProof inclusionProof) { - if (inclusionProof.verify(this.getRequestId()) != InclusionProofVerificationStatus.OK) { - throw new RuntimeException("Inclusion proof verification failed."); - } - - if (inclusionProof.getAuthenticator().isEmpty()) { - throw new RuntimeException("Authenticator is missing from inclusion proof."); - } - - if (!this.getTransactionData().calculateHash().equals(inclusionProof.getTransactionHash().orElse(null))) { - throw new RuntimeException("Payload hash mismatch."); - } - - return new Transaction<>(this.getTransactionData(), inclusionProof); - } } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/TransactionData.java b/src/main/java/org/unicitylabs/sdk/transaction/TransactionData.java index cade5ad..e51b78a 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/TransactionData.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/TransactionData.java @@ -7,5 +7,6 @@ public interface TransactionData { T getSourceState(); Address getRecipient(); + DataHash calculateHash(); Optional getDataHash(); } From 5e7f0af027a33bf69f046f98b406087bde65a3d8 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Tue, 23 Sep 2025 22:53:49 +0400 Subject: [PATCH 11/16] Remove unused parameters --- .../sdk/StateTransitionClient.java | 17 +++++------ .../sdk/transaction/InclusionProof.java | 28 +++++++++---------- .../sdk/transaction/TransferCommitment.java | 18 ------------ .../sdk/common/BaseEscrowSwapTest.java | 12 +++++--- .../sdk/common/split/BaseTokenSplitTest.java | 3 +- .../unicitylabs/sdk/e2e/CommonTestFlow.java | 22 +++++++-------- ...nedPredicateDoubleSpendPreventionTest.java | 13 ++++++--- 7 files changed, 51 insertions(+), 62 deletions(-) diff --git a/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java b/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java index 1dff3d7..893da51 100644 --- a/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java +++ b/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java @@ -1,6 +1,9 @@ package org.unicitylabs.sdk; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; import org.unicitylabs.sdk.api.IAggregatorClient; import org.unicitylabs.sdk.api.InclusionProofResponse; import org.unicitylabs.sdk.api.RequestId; @@ -9,16 +12,12 @@ import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenState; import org.unicitylabs.sdk.transaction.Commitment; -import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.transaction.InclusionProofVerificationStatus; import org.unicitylabs.sdk.transaction.MintCommitment; import org.unicitylabs.sdk.transaction.MintTransactionData; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.transaction.TransferCommitment; import org.unicitylabs.sdk.transaction.TransferTransactionData; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; public class StateTransitionClient { @@ -29,7 +28,8 @@ public StateTransitionClient(IAggregatorClient client) { } public > CompletableFuture submitCommitment( - MintCommitment commitment) { + MintCommitment commitment + ) { return this.client.submitCommitment( commitment.getRequestId(), commitment.getTransactionData().calculateHash(), @@ -38,8 +38,8 @@ public > CompletableFuture submitCommitment( - Token token, - TransferCommitment commitment) { + TransferCommitment commitment + ) { if ( !PredicateEngineService.createPredicate( commitment.getTransactionData().getSourceState().getPredicate() @@ -74,7 +74,8 @@ public > Token finalizeTransaction( public CompletableFuture getTokenStatus( Token> token, - byte[] publicKey) { + byte[] publicKey + ) { RequestId requestId = RequestId.create(publicKey, token.getState().calculateHash()); return this.client.getInclusionProof(requestId) .thenApply(response -> response.getInclusionProof().verify(requestId)); diff --git a/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java b/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java index 4960c0c..1d3f9fe 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java @@ -1,9 +1,12 @@ - package org.unicitylabs.sdk.transaction; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.LeafValue; import org.unicitylabs.sdk.api.RequestId; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationContext; import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationRule; @@ -12,10 +15,6 @@ import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePathStep; import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; -import java.util.Arrays; -import java.util.Objects; -import java.util.Optional; -import org.unicitylabs.sdk.verification.VerificationResult; /** * Represents a proof of inclusion or non-inclusion in a sparse merkle tree. @@ -80,15 +79,16 @@ public InclusionProofVerificationStatus verify(RequestId requestId) { } } - if (!new UnicityCertificateVerificationRule().verify( - new UnicityCertificateVerificationContext( - this.merkleTreePath.getRootHash(), - this.unicityCertificate, - null - ) - ).isSuccessful()) { - return InclusionProofVerificationStatus.NOT_AUTHENTICATED; - } +// TODO: Fix Unicity certificate verification +// if (!new UnicityCertificateVerificationRule().verify( +// new UnicityCertificateVerificationContext( +// this.merkleTreePath.getRootHash(), +// this.unicityCertificate, +// null +// ) +// ).isSuccessful()) { +// return InclusionProofVerificationStatus.NOT_AUTHENTICATED; +// } MerkleTreePathVerificationResult result = this.merkleTreePath.verify( requestId.toBitString().toBigInteger()); diff --git a/src/main/java/org/unicitylabs/sdk/transaction/TransferCommitment.java b/src/main/java/org/unicitylabs/sdk/transaction/TransferCommitment.java index 503bbfa..542754e 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/TransferCommitment.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/TransferCommitment.java @@ -44,22 +44,4 @@ public static TransferCommitment create( return new TransferCommitment(requestId, transactionData, authenticator); } - - public Transaction toTransaction(Token token, - InclusionProof inclusionProof) { - if (inclusionProof.verify(this.getRequestId()) != InclusionProofVerificationStatus.OK) { - throw new RuntimeException("Inclusion proof verification failed."); - } - - if (inclusionProof.getAuthenticator().isEmpty()) { - throw new RuntimeException("Authenticator is missing from inclusion proof."); - } - - if (!this.getTransactionData().calculateHash() - .equals(inclusionProof.getTransactionHash().orElse(null))) { - throw new RuntimeException("Payload hash mismatch."); - } - - return new Transaction<>(this.getTransactionData(), inclusionProof); - } } diff --git a/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java b/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java index a5f69f7..32e3ab5 100644 --- a/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java +++ b/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java @@ -50,7 +50,8 @@ public abstract class BaseEscrowSwapTest { private final String BOB_NAMETAG = String.format("BOB_%s", System.currentTimeMillis()); private final String CAROL_NAMETAG = String.format("CAROL_%s", System.currentTimeMillis()); - private String[] transferToken(Token token, SigningService signingService, String nametag) throws Exception { + private String[] transferToken(Token token, SigningService signingService, String nametag) + throws Exception { TransferCommitment commitment = TransferCommitment.create( token, ProxyAddress.create(nametag), @@ -60,15 +61,18 @@ private String[] transferToken(Token token, SigningService signingService, St signingService ); - SubmitCommitmentResponse response = this.client.submitCommitment(token, commitment).get(); + SubmitCommitmentResponse response = this.client.submitCommitment(commitment).get(); if (response.getStatus() != SubmitCommitmentStatus.SUCCESS) { throw new RuntimeException("Failed to submit transfer commitment: " + response); } return new String[]{ UnicityObjectMapper.JSON.writeValueAsString(token), - UnicityObjectMapper.JSON.writeValueAsString(commitment.toTransaction(token, - InclusionProofUtils.waitInclusionProof(client, commitment).get())) + UnicityObjectMapper.JSON.writeValueAsString( + commitment.toTransaction( + InclusionProofUtils.waitInclusionProof(client, commitment).get() + ) + ) }; } diff --git a/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java b/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java index ab14438..7679e77 100644 --- a/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java +++ b/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java @@ -113,7 +113,7 @@ void testTokenSplitFullAmounts() throws Exception { ); SubmitCommitmentResponse burnCommitmentResponse = this.client - .submitCommitment(token, burnCommitment) + .submitCommitment(burnCommitment) .get(); if (burnCommitmentResponse.getStatus() != SubmitCommitmentStatus.SUCCESS) { @@ -123,7 +123,6 @@ void testTokenSplitFullAmounts() throws Exception { List>> mintCommitments = split.createSplitMintCommitments( burnCommitment.toTransaction( - token, InclusionProofUtils.waitInclusionProof(this.client, burnCommitment).get() ) ); diff --git a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java b/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java index dd27622..c7204c9 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java @@ -136,8 +136,9 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti null, aliceSigningService ); - SubmitCommitmentResponse aliceToBobTransferSubmitResponse = client.submitCommitment(aliceToken, - aliceToBobTransferCommitment).get(); + SubmitCommitmentResponse aliceToBobTransferSubmitResponse = client.submitCommitment( + aliceToBobTransferCommitment + ).get(); if (aliceToBobTransferSubmitResponse.getStatus() != SubmitCommitmentStatus.SUCCESS) { throw new Exception(String.format("Failed to submit transaction commitment: %s", @@ -152,7 +153,6 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti // Create transfer transaction Transaction aliceToBobTransferTransaction = aliceToBobTransferCommitment.toTransaction( - aliceToken, aliceToBobTransferInclusionProof ); @@ -253,7 +253,6 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti SigningService.createFromSecret(BOB_SECRET) ); SubmitCommitmentResponse bobToCarolTransferSubmitResponse = client.submitCommitment( - bobToken, bobToCarolTransferCommitment ).get(); @@ -267,7 +266,6 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti bobToCarolTransferCommitment ).get(); Transaction bobToCarolTransaction = bobToCarolTransferCommitment.toTransaction( - bobToken, bobToCarolInclusionProof ); @@ -299,7 +297,6 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti SigningService.createFromSecret(CAROL_SECRET) ); SubmitCommitmentResponse carolToBobTransferSubmitResponse = client.submitCommitment( - carolToken, carolToBobTransferCommitment ).get(); @@ -314,7 +311,6 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti ).get(); Transaction carolToBobTransaction = carolToBobTransferCommitment.toTransaction( - carolToken, carolToBobInclusionProof ); @@ -379,16 +375,18 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti SigningService.createFromSecret(BOB_SECRET) ); - if (client.submitCommitment(carolToBobToken, burnCommitment).get().getStatus() + if (client.submitCommitment(burnCommitment).get().getStatus() != SubmitCommitmentStatus.SUCCESS) { throw new Exception("Failed to submit burn commitment"); } List>> splitCommitments = split.createSplitMintCommitments( - burnCommitment.toTransaction(carolToBobToken, InclusionProofUtils.waitInclusionProof( - client, - burnCommitment - ).get()) + burnCommitment.toTransaction( + InclusionProofUtils.waitInclusionProof( + client, + burnCommitment + ).get() + ) ); List>> splitTransactions = new ArrayList<>(); diff --git a/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java b/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java index 1976804..a1ddffd 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java @@ -30,7 +30,9 @@ import org.unicitylabs.sdk.utils.TokenUtils; public class FunctionalUnsignedPredicateDoubleSpendPreventionTest { - protected final StateTransitionClient client = new StateTransitionClient(new TestAggregatorClient()); + + protected final StateTransitionClient client = new StateTransitionClient( + new TestAggregatorClient()); private final byte[] BOB_SECRET = "BOB_SECRET".getBytes(StandardCharsets.UTF_8); private String[] transferToken(Token token, byte[] secret, Address address) throws Exception { @@ -46,15 +48,18 @@ private String[] transferToken(Token token, byte[] secret, Address address) t ) ); - SubmitCommitmentResponse response = this.client.submitCommitment(token, commitment).get(); + SubmitCommitmentResponse response = this.client.submitCommitment(commitment).get(); if (response.getStatus() != SubmitCommitmentStatus.SUCCESS) { throw new RuntimeException("Failed to submit transfer commitment: " + response); } return new String[]{ UnicityObjectMapper.JSON.writeValueAsString(token), - UnicityObjectMapper.JSON.writeValueAsString(commitment.toTransaction(token, - InclusionProofUtils.waitInclusionProof(client, commitment).get())) + UnicityObjectMapper.JSON.writeValueAsString( + commitment.toTransaction( + InclusionProofUtils.waitInclusionProof(client, commitment).get() + ) + ) }; } From 461610725ff9dd5d3b51f4c45366f2e628603ed4 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Wed, 24 Sep 2025 11:18:39 +0400 Subject: [PATCH 12/16] Remove unused imports --- .../sdk/predicate/embedded/UnmaskedPredicate.java | 5 ----- .../java/org/unicitylabs/sdk/transaction/InclusionProof.java | 3 --- 2 files changed, 8 deletions(-) diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java index b48ac8e..477b85f 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java @@ -1,18 +1,13 @@ package org.unicitylabs.sdk.predicate.embedded; -import com.fasterxml.jackson.core.JsonProcessingException; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.PredicateEngineType; -import org.unicitylabs.sdk.serializer.UnicityObjectMapper; -import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.signing.Signature; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenType; -import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.transaction.TransferTransactionData; diff --git a/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java b/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java index 1d3f9fe..7711d00 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java @@ -6,10 +6,7 @@ import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.LeafValue; import org.unicitylabs.sdk.api.RequestId; -import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.bft.UnicityCertificate; -import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationContext; -import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationRule; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.mtree.MerkleTreePathVerificationResult; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; From 164972217fa2ffe4e08b5a1c4146b7e57625eeeb Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Wed, 24 Sep 2025 16:24:26 +0400 Subject: [PATCH 13/16] Initial version of trustbase --- .../sdk/StateTransitionClient.java | 19 +- .../org/unicitylabs/sdk/bft/InputRecord.java | 1 + .../unicitylabs/sdk/bft/RootTrustBase.java | 18 +- .../sdk/bft/UnicityCertificate.java | 44 +++++ .../org/unicitylabs/sdk/bft/UnicitySeal.java | 94 ++++++--- ...nicitySealHashMatchesWithRootHashRule.java | 43 +---- ...ySealQuorumSignaturesVerificationRule.java | 45 +---- .../unicitylabs/sdk/predicate/Predicate.java | 3 +- .../sdk/predicate/embedded/BurnPredicate.java | 7 +- .../predicate/embedded/DefaultPredicate.java | 9 +- .../predicate/embedded/UnmaskedPredicate.java | 9 +- .../serializer/cbor/bft/UnicitySealCbor.java | 14 +- .../java/org/unicitylabs/sdk/token/Token.java | 94 ++++++--- .../org/unicitylabs/sdk/token/TokenState.java | 2 +- .../sdk/transaction/Commitment.java | 12 -- .../sdk/transaction/InclusionProof.java | 24 +-- .../transaction/split/TokenSplitBuilder.java | 12 +- .../sdk/util/InclusionProofUtils.java | 26 ++- .../verification/VerificationException.java | 19 ++ .../sdk/verification/VerificationResult.java | 8 + .../unicitylabs/sdk/TestAggregatorClient.java | 13 +- .../sdk/bft/RootTrustBaseTest.java | 4 - .../sdk/common/BaseEscrowSwapTest.java | 21 +- .../sdk/common/split/BaseTokenSplitTest.java | 18 +- .../unicitylabs/sdk/e2e/CommonTestFlow.java | 181 ++++++------------ .../org/unicitylabs/sdk/e2e/TokenE2ETest.java | 24 +-- .../functional/FunctionalCommonFlowTest.java | 15 +- .../functional/FunctionalEscrowSwapTest.java | 6 +- .../functional/FunctionalTokenSplitTest.java | 6 +- ...nedPredicateDoubleSpendPreventionTest.java | 23 ++- .../org/unicitylabs/sdk/token/TokenTest.java | 54 ++---- .../sdk/transaction/InclusionProofTest.java | 53 +++-- .../split/TokenSplitBuilderTest.java | 23 ++- .../sdk/utils/RootTrustBaseUtils.java | 29 +++ .../org/unicitylabs/sdk/utils/TokenUtils.java | 20 +- .../sdk/utils/UnicityCertificateUtils.java | 97 ++++++++-- 36 files changed, 641 insertions(+), 449 deletions(-) create mode 100644 src/main/java/org/unicitylabs/sdk/verification/VerificationException.java create mode 100644 src/test/java/org/unicitylabs/sdk/utils/RootTrustBaseUtils.java diff --git a/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java b/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java index 893da51..2ee859a 100644 --- a/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java +++ b/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java @@ -8,9 +8,11 @@ import org.unicitylabs.sdk.api.InclusionProofResponse; import org.unicitylabs.sdk.api.RequestId; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.predicate.PredicateEngineService; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenState; +import org.unicitylabs.sdk.verification.VerificationException; import org.unicitylabs.sdk.transaction.Commitment; import org.unicitylabs.sdk.transaction.InclusionProofVerificationStatus; import org.unicitylabs.sdk.transaction.MintCommitment; @@ -56,29 +58,32 @@ public CompletableFuture submitCommitment( public > Token finalizeTransaction( Token token, TokenState state, - Transaction transaction - ) { - return this.finalizeTransaction(token, state, transaction, List.of()); + Transaction transaction, + RootTrustBase trustBase + ) throws VerificationException { + return this.finalizeTransaction(token, state, transaction, trustBase, List.of()); } public > Token finalizeTransaction( Token token, TokenState state, Transaction transaction, + RootTrustBase trustBase, List> nametags - ) { + ) throws VerificationException { Objects.requireNonNull(token, "Token is null"); - return token.update(state, transaction, nametags); + return token.update(trustBase, state, transaction, nametags); } public CompletableFuture getTokenStatus( Token> token, - byte[] publicKey + byte[] publicKey, + RootTrustBase trustBase ) { RequestId requestId = RequestId.create(publicKey, token.getState().calculateHash()); return this.client.getInclusionProof(requestId) - .thenApply(response -> response.getInclusionProof().verify(requestId)); + .thenApply(response -> response.getInclusionProof().verify(requestId, trustBase)); } public CompletableFuture getInclusionProof(Commitment commitment) { diff --git a/src/main/java/org/unicitylabs/sdk/bft/InputRecord.java b/src/main/java/org/unicitylabs/sdk/bft/InputRecord.java index c5d939d..bca9f7c 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/InputRecord.java +++ b/src/main/java/org/unicitylabs/sdk/bft/InputRecord.java @@ -29,6 +29,7 @@ public InputRecord( long sumOfEarnedFees, byte[] executedTransactionsHash ) { + Objects.requireNonNull(hash, "Hash cannot be null"); Objects.requireNonNull(summaryValue, "Summary value cannot be null"); this.version = version; diff --git a/src/main/java/org/unicitylabs/sdk/bft/RootTrustBase.java b/src/main/java/org/unicitylabs/sdk/bft/RootTrustBase.java index 94c58bb..dcbc225 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/RootTrustBase.java +++ b/src/main/java/org/unicitylabs/sdk/bft/RootTrustBase.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; public class RootTrustBase { + private final long version; private final int networkId; private final long epoch; @@ -37,8 +38,12 @@ public RootTrustBase( this.rootNodes = Set.copyOf(rootNodes); this.quorumThreshold = quorumThreshold; this.stateHash = Arrays.copyOf(stateHash, stateHash.length); - this.changeRecordHash = Arrays.copyOf(changeRecordHash, changeRecordHash.length); - this.previousEntryHash = Arrays.copyOf(previousEntryHash, previousEntryHash.length); + this.changeRecordHash = changeRecordHash == null + ? null + : Arrays.copyOf(changeRecordHash, changeRecordHash.length); + this.previousEntryHash = previousEntryHash == null + ? null + : Arrays.copyOf(previousEntryHash, previousEntryHash.length); this.signatures = signatures.entrySet().stream() .collect( Collectors.toUnmodifiableMap( @@ -77,11 +82,15 @@ public byte[] getStateHash() { } public byte[] getChangeRecordHash() { - return Arrays.copyOf(this.changeRecordHash, this.changeRecordHash.length); + return this.changeRecordHash == null + ? null + : Arrays.copyOf(this.changeRecordHash, this.changeRecordHash.length); } public byte[] getPreviousEntryHash() { - return Arrays.copyOf(this.previousEntryHash, this.previousEntryHash.length); + return this.previousEntryHash == null + ? null + : Arrays.copyOf(this.previousEntryHash, this.previousEntryHash.length); } public Map getSignatures() { @@ -95,6 +104,7 @@ public Map getSignatures() { } public static class NodeInfo { + private final String nodeId; private final byte[] signingKey; private final long stakedAmount; diff --git a/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java b/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java index d3de9b4..99a2df6 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java @@ -1,7 +1,15 @@ package org.unicitylabs.sdk.bft; +import java.io.IOException; import java.util.Arrays; +import java.util.List; import java.util.Objects; +import org.unicitylabs.sdk.hash.DataHash; +import org.unicitylabs.sdk.hash.DataHasher; +import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; +import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.util.HexConverter; public class UnicityCertificate { @@ -69,6 +77,42 @@ public UnicitySeal getUnicitySeal() { return this.unicitySeal; } + public static DataHash calculateShardTreeCertificateRootHash( + InputRecord inputRecord, + byte[] technicalRecordHash, + byte[] shardConfigurationHash, + ShardTreeCertificate shardTreeCertificate + ) { + try { + DataHash rootHash = new DataHasher(HashAlgorithm.SHA256) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(inputRecord)) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(technicalRecordHash)) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(shardConfigurationHash)) + .digest(); + + byte[] shardId = shardTreeCertificate.getShard(); + List siblingHashes = shardTreeCertificate.getSiblingHashList(); + for (int i = 0; i < siblingHashes.size(); i++) { + boolean isRight = shardId[(shardId.length - 1) - (i / 8)] == 1; + if (isRight) { + rootHash = new DataHasher(HashAlgorithm.SHA256) + .update(siblingHashes.get(i)) + .update(rootHash.getData()) + .digest(); + } else { + rootHash = new DataHasher(HashAlgorithm.SHA256) + .update(rootHash.getData()) + .update(siblingHashes.get(i)) + .digest(); + } + } + + return rootHash; + } catch (IOException e) { + throw new CborSerializationException(e); + } + } + @Override public boolean equals(Object o) { if (!(o instanceof UnicityCertificate)) { diff --git a/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java b/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java index 3dda757..3f74ee2 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java +++ b/src/main/java/org/unicitylabs/sdk/bft/UnicitySeal.java @@ -1,10 +1,13 @@ package org.unicitylabs.sdk.bft; +import java.io.IOException; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.util.HexConverter; public class UnicitySeal { @@ -29,7 +32,6 @@ public UnicitySeal( Map signatures ) { Objects.requireNonNull(hash, "Hash cannot be null"); - Objects.requireNonNull(signatures, "Signatures cannot be null"); this.version = version; this.networkId = networkId; @@ -38,20 +40,48 @@ public UnicitySeal( this.timestamp = timestamp; this.previousHash = previousHash; this.hash = hash; - this.signatures = signatures.entrySet().stream() - .map(entry -> Map.entry( - entry.getKey(), - Arrays.copyOf(entry.getValue(), entry.getValue().length) + 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 - ) - ); + .collect( + Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (e1, e2) -> e1, + LinkedHashMap::new + ) + ); + } + + public static UnicitySeal fromUnicitySealWithoutSignatures(UnicitySeal seal) { + return new UnicitySeal( + seal.version, + seal.networkId, + seal.rootChainRoundNumber, + seal.epoch, + seal.timestamp, + seal.previousHash, + seal.hash, + null + ); + } + + public UnicitySeal withSignatures(Map signatures) { + return new UnicitySeal( + this.version, + this.networkId, + this.rootChainRoundNumber, + this.epoch, + this.timestamp, + this.previousHash, + this.hash, + signatures + ); } public int getVersion() { @@ -84,20 +114,30 @@ public byte[] getHash() { } public Map getSignatures() { - return this.signatures.entrySet().stream() - .map(entry -> Map.entry( - entry.getKey(), - Arrays.copyOf(entry.getValue(), entry.getValue().length) + 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 - ) - ); + .collect( + Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (e1, e2) -> e1, + LinkedHashMap::new + ) + ); + } + + public byte[] encode() { + try { + return UnicityObjectMapper.CBOR.writeValueAsBytes(this); + } catch (IOException e) { + throw new CborSerializationException(e); + } } @Override diff --git a/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java b/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java index 3cf0adb..283cf45 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java +++ b/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java @@ -35,8 +35,12 @@ public UnicitySealHashMatchesWithRootHashRule( @Override public VerificationResult verify(UnicityCertificateVerificationContext context) { - DataHash shardTreeCertificateRootHash = UnicitySealHashMatchesWithRootHashRule - .calculateShardTreeCertificateRootHash(context.getUnicityCertificate()); + DataHash shardTreeCertificateRootHash = UnicityCertificate.calculateShardTreeCertificateRootHash( + context.getUnicityCertificate().getInputRecord(), + context.getUnicityCertificate().getTechnicalRecordHash(), + context.getUnicityCertificate().getShardConfigurationHash(), + context.getUnicityCertificate().getShardTreeCertificate() + ); if (shardTreeCertificateRootHash == null) { return VerificationResult.fail("Could not calculate shard tree certificate root hash."); @@ -102,39 +106,4 @@ public VerificationResult verify(UnicityCertificateVerificationContext context) return VerificationResult.success(); } - - private static DataHash calculateShardTreeCertificateRootHash( - UnicityCertificate unicityCertificate) { - try { - DataHash rootHash = new DataHasher(HashAlgorithm.SHA256) - .update(UnicityObjectMapper.CBOR.writeValueAsBytes(unicityCertificate.getInputRecord())) - .update(UnicityObjectMapper.CBOR.writeValueAsBytes( - unicityCertificate.getTechnicalRecordHash())) - .update(UnicityObjectMapper.CBOR.writeValueAsBytes( - unicityCertificate.getShardConfigurationHash())) - .digest(); - - byte[] shardId = unicityCertificate.getShardTreeCertificate().getShard(); - List siblingHashes = unicityCertificate.getShardTreeCertificate() - .getSiblingHashList(); - for (int i = 0; i < siblingHashes.size(); i++) { - boolean isRight = shardId[(shardId.length - 1) - (i / 8)] == 1; - if (isRight) { - rootHash = new DataHasher(HashAlgorithm.SHA256) - .update(siblingHashes.get(i)) - .update(rootHash.getData()) - .digest(); - } else { - rootHash = new DataHasher(HashAlgorithm.SHA256) - .update(rootHash.getData()) - .update(siblingHashes.get(i)) - .digest(); - } - } - - return rootHash; - } catch (Exception e) { - return null; - } - } } diff --git a/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java b/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java index 17234fb..28effcb 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java +++ b/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.java @@ -1,9 +1,5 @@ package org.unicitylabs.sdk.bft.verification.rule; -import com.fasterxml.jackson.dataformat.cbor.CBORFactory; -import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -14,7 +10,6 @@ import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.verification.VerificationResult; import org.unicitylabs.sdk.verification.VerificationRule; @@ -40,17 +35,12 @@ public UnicitySealQuorumSignaturesVerificationRule( @Override public VerificationResult verify(UnicityCertificateVerificationContext context) { UnicitySeal unicitySeal = context.getUnicityCertificate().getUnicitySeal(); - byte[] unicitySealBytes = UnicitySealQuorumSignaturesVerificationRule.encodeUnicitySeal( - unicitySeal - ); - if (unicitySealBytes == null) { - return VerificationResult.fail("Could not encode UnicitySeal."); - } - RootTrustBase trustBase = context.getTrustBase(); List results = new ArrayList<>(); - DataHash hash = new DataHasher(HashAlgorithm.SHA256).update(unicitySealBytes).digest(); + DataHash hash = new DataHasher(HashAlgorithm.SHA256) + .update(UnicitySeal.fromUnicitySealWithoutSignatures(unicitySeal).encode()) + .digest(); int successful = 0; for (Map.Entry entry : unicitySeal.getSignatures().entrySet()) { String nodeId = entry.getKey(); @@ -77,10 +67,10 @@ public VerificationResult verify(UnicityCertificateVerificationContext context) } if (successful >= trustBase.getQuorumThreshold()) { - return VerificationResult.success(); + return VerificationResult.success(results); } - return VerificationResult.fail("Quorum threshold not reached."); + return VerificationResult.fail("Quorum threshold not reached.", results); } private static VerificationResult verifySignature( @@ -105,29 +95,4 @@ private static VerificationResult verifySignature( return VerificationResult.success(); } - private static byte[] encodeUnicitySeal(UnicitySeal seal) { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - CBORFactory factory = (CBORFactory) UnicityObjectMapper.CBOR.getFactory(); - CBORGenerator gen = factory.createGenerator(out); - - gen.writeTag(1001); - gen.writeStartArray(seal, 8); - gen.writeObject(seal.getVersion()); - gen.writeObject(seal.getNetworkId()); - gen.writeObject(seal.getRootChainRoundNumber()); - gen.writeObject(seal.getEpoch()); - gen.writeObject(seal.getTimestamp()); - gen.writeObject(seal.getPreviousHash()); - gen.writeObject(seal.getHash()); - gen.writeObject(null); - gen.writeEndArray(); - - gen.close(); - return out.toByteArray(); - } catch (IOException e) { - return null; - } - } - } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java b/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java index e70cbde..36912e2 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java @@ -1,5 +1,6 @@ package org.unicitylabs.sdk.predicate; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.transaction.Transaction; @@ -12,6 +13,6 @@ public interface Predicate extends SerializablePredicate { boolean isOwner(byte[] publicKey); - boolean verify(Token token, Transaction transaction); + boolean verify(Token token, Transaction transaction, RootTrustBase trustBase); } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java index a819de6..b35fd21 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/BurnPredicate.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; import java.util.Objects; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; @@ -50,7 +51,11 @@ public boolean isOwner(byte[] publicKey) { } @Override - public boolean verify(Token token, Transaction transaction) { + public boolean verify( + Token token, + Transaction transaction, + RootTrustBase trustBase + ) { return false; } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java index 87d8716..ab91642 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java @@ -7,6 +7,7 @@ import java.util.Objects; import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.RequestId; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; @@ -115,7 +116,8 @@ public boolean isOwner(byte[] publicKey) { } @Override - public boolean verify(Token token, Transaction transaction) { + public boolean verify(Token token, Transaction transaction, + RootTrustBase trustBase) { if (!this.tokenId.equals(token.getId()) || !this.tokenType.equals(token.getType())) { return false; } @@ -139,7 +141,10 @@ public boolean verify(Token token, Transaction trans this.publicKey, transaction.getData().getSourceState().calculateHash() ); - return transaction.getInclusionProof().verify(requestId) == InclusionProofVerificationStatus.OK; + return transaction.getInclusionProof().verify( + requestId, + trustBase + ) == InclusionProofVerificationStatus.OK; } @Override diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java index 477b85f..c2ec791 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java @@ -1,6 +1,7 @@ package org.unicitylabs.sdk.predicate.embedded; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.signing.Signature; @@ -45,8 +46,12 @@ public static UnmaskedPredicate create( } @Override - public boolean verify(Token token, Transaction transaction) { - return super.verify(token, transaction) && SigningService.verifyWithPublicKey( + public boolean verify( + Token token, + Transaction transaction, + RootTrustBase trustBase + ) { + return super.verify(token, transaction, trustBase) && SigningService.verifyWithPublicKey( new DataHasher(HashAlgorithm.SHA256) .update( token.getTransactions().isEmpty() diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java index bd6284f..cfe8b67 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/UnicitySealCbor.java @@ -39,12 +39,16 @@ public void serialize(UnicitySeal value, JsonGenerator gen, SerializerProvider s gen.writeObject(value.getTimestamp()); gen.writeObject(value.getPreviousHash()); gen.writeObject(value.getHash()); - gen.writeStartObject(value.getSignatures(), value.getSignatures().size()); - for (Map.Entry entry : value.getSignatures().entrySet()) { - gen.writeFieldName(entry.getKey()); - gen.writeObject(entry.getValue()); + if (value.getSignatures() == null) { + gen.writeNull(); + } else { + gen.writeStartObject(value.getSignatures(), value.getSignatures().size()); + for (Map.Entry entry : value.getSignatures().entrySet()) { + gen.writeFieldName(entry.getKey()); + gen.writeObject(entry.getValue()); + } + gen.writeEndObject(); } - gen.writeEndObject(); gen.writeEndArray(); } diff --git a/src/main/java/org/unicitylabs/sdk/token/Token.java b/src/main/java/org/unicitylabs/sdk/token/Token.java index 3f9d36e..5ef20b9 100644 --- a/src/main/java/org/unicitylabs/sdk/token/Token.java +++ b/src/main/java/org/unicitylabs/sdk/token/Token.java @@ -9,6 +9,7 @@ import org.unicitylabs.sdk.address.Address; import org.unicitylabs.sdk.address.ProxyAddress; import org.unicitylabs.sdk.api.RequestId; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; @@ -22,6 +23,7 @@ import org.unicitylabs.sdk.transaction.MintTransactionState; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.transaction.TransferTransactionData; +import org.unicitylabs.sdk.verification.VerificationException; import org.unicitylabs.sdk.verification.VerificationResult; public class Token> { @@ -50,14 +52,6 @@ public Token( this.nametags = List.copyOf(nametags); } - public Token(TokenState state, Transaction genesis, List> nametags) { - this(state, genesis, List.of(), nametags); - } - - public Token(TokenState state, Transaction genesis) { - this(state, genesis, List.of(), List.of()); - } - public TokenId getId() { return this.genesis.getData().getTokenId(); } @@ -94,33 +88,65 @@ public List> getNametags() { return this.nametags; } + public static > Token create( + RootTrustBase trustBase, + TokenState state, + Transaction transaction + ) throws VerificationException { + return Token.create(trustBase, state, transaction, List.of()); + } + + public static > Token create( + RootTrustBase trustBase, + TokenState state, + Transaction transaction, + List> nametags + ) throws VerificationException { + Objects.requireNonNull(state, "State cannot be null"); + Objects.requireNonNull(transaction, "Genesis cannot be null"); + Objects.requireNonNull(trustBase, "Trust base cannot be null"); + Objects.requireNonNull(nametags, "Nametag tokens cannot be null"); + + Token token = new Token<>(state, transaction, List.of(), nametags); + VerificationResult result = token.verify(trustBase); + if (!result.isSuccessful()) { + throw new VerificationException("Token verification failed", result); + } + + return token; + } + public Token update( + RootTrustBase trustBase, TokenState state, Transaction transaction, - List> transactionNametags - ) { - Objects.requireNonNull(state, "State is null"); - Objects.requireNonNull(transaction, "Transaction is null"); - Objects.requireNonNull(transactionNametags, "Nametag tokens are null"); + List> nametags + ) throws VerificationException { + Objects.requireNonNull(state, "State cannot be null"); + Objects.requireNonNull(transaction, "Transaction cannot be null"); + Objects.requireNonNull(nametags, "Nametag tokens cannot be null"); + Objects.requireNonNull(trustBase, "Trust base cannot be null"); + + VerificationResult result = Token.verifyTransaction(this, transaction, trustBase); - if (!this.verifyTransaction(this, transaction).isSuccessful()) { - // TODO: Add method to return why it failed - throw new RuntimeException("Transaction verification failed"); + if (!result.isSuccessful()) { + throw new VerificationException("Transaction verification failed", result); } LinkedList> transactions = new LinkedList<>( - this.transactions); + this.transactions + ); transactions.add(transaction); - return new Token<>(state, this.getGenesis(), transactions, transactionNametags); + return new Token<>(state, this.getGenesis(), transactions, nametags); } - public VerificationResult verify() { + public VerificationResult verify(RootTrustBase trustBase) { List results = new ArrayList<>(); results.add( VerificationResult.fromChildren( "Genesis verification", - List.of(this.verifyGenesis(this.genesis))) + List.of(Token.verifyGenesis(this.genesis, trustBase))) ); for (int i = 0; i < this.transactions.size(); i++) { @@ -130,14 +156,15 @@ public VerificationResult verify() { VerificationResult.fromChildren( "Transaction verification", List.of( - this.verifyTransaction( + Token.verifyTransaction( new Token<>( transaction.getData().getSourceState(), this.genesis, this.transactions.subList(0, i), transaction.getData().getNametags() ), - transaction + transaction, + trustBase ) ) ) @@ -146,18 +173,19 @@ public VerificationResult verify() { results.add(VerificationResult.fromChildren( "Token current state verification", - List.of(this.verifyTransaction(this, null)) + List.of(Token.verifyTransaction(this, null, trustBase)) )); return VerificationResult.fromChildren("Token verification", results); } - private VerificationResult verifyTransaction( + private static VerificationResult verifyTransaction( Token token, - Transaction transaction + Transaction transaction, + RootTrustBase trustBase ) { for (Token nametag : token.getNametags()) { - if (!nametag.verify().isSuccessful()) { + if (!nametag.verify(trustBase).isSuccessful()) { return VerificationResult.fail( String.format("Nametag token %s verification failed", nametag.getId())); } @@ -174,20 +202,23 @@ private VerificationResult verifyTransaction( return VerificationResult.fail("recipient mismatch"); } - if (!this.transactionContainsData( + if (!Token.transactionContainsData( previousTransaction.getData().getDataHash().orElse(null), token.getState().getData().orElse(null))) { return VerificationResult.fail("data mismatch"); } - if (transaction != null && !predicate.verify(token, transaction)) { + if (transaction != null && !predicate.verify(token, transaction, trustBase)) { return VerificationResult.fail("predicate verification failed"); } return VerificationResult.success(); } - private VerificationResult verifyGenesis(Transaction transaction) { + private static > VerificationResult verifyGenesis( + Transaction transaction, + RootTrustBase trustBase + ) { if (transaction.getInclusionProof().getAuthenticator().isEmpty()) { return VerificationResult.fail("Missing authenticator."); } @@ -227,14 +258,15 @@ private VerificationResult verifyGenesis(Transaction transaction) { RequestId requestId = RequestId.create(signingService.getPublicKey(), transaction.getData().getSourceState().getHash()); - if (transaction.getInclusionProof().verify(requestId) != InclusionProofVerificationStatus.OK) { + if (transaction.getInclusionProof().verify(requestId, trustBase) + != InclusionProofVerificationStatus.OK) { return VerificationResult.fail("Inclusion proof verification failed."); } return VerificationResult.success(); } - private boolean transactionContainsData(DataHash hash, byte[] stateData) { + private static boolean transactionContainsData(DataHash hash, byte[] stateData) { if ((hash == null) != (stateData == null)) { return false; } diff --git a/src/main/java/org/unicitylabs/sdk/token/TokenState.java b/src/main/java/org/unicitylabs/sdk/token/TokenState.java index 64ae45d..5cba2a6 100644 --- a/src/main/java/org/unicitylabs/sdk/token/TokenState.java +++ b/src/main/java/org/unicitylabs/sdk/token/TokenState.java @@ -17,7 +17,7 @@ /** * Represents a snapshot of token ownership and associated data. */ -public class TokenState { +public class TokenState{ private final SerializablePredicate predicate; private final byte[] data; diff --git a/src/main/java/org/unicitylabs/sdk/transaction/Commitment.java b/src/main/java/org/unicitylabs/sdk/transaction/Commitment.java index 2169834..f7b388f 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/Commitment.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/Commitment.java @@ -50,18 +50,6 @@ public Authenticator getAuthenticator() { } public Transaction toTransaction(InclusionProof inclusionProof) { - if (inclusionProof.verify(this.getRequestId()) != InclusionProofVerificationStatus.OK) { - throw new RuntimeException("Inclusion proof verification failed."); - } - - if (inclusionProof.getAuthenticator().isEmpty()) { - throw new RuntimeException("Authenticator is missing from inclusion proof."); - } - - if (!this.getTransactionData().calculateHash().equals(inclusionProof.getTransactionHash().orElse(null))) { - throw new RuntimeException("Payload hash mismatch."); - } - return new Transaction<>(this.getTransactionData(), inclusionProof); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java b/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java index 7711d00..a27dd3f 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java @@ -6,7 +6,10 @@ import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.LeafValue; import org.unicitylabs.sdk.api.RequestId; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.bft.UnicityCertificate; +import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationContext; +import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationRule; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.mtree.MerkleTreePathVerificationResult; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; @@ -58,7 +61,7 @@ public Optional getTransactionHash() { return Optional.ofNullable(this.transactionHash); } - public InclusionProofVerificationStatus verify(RequestId requestId) { + public InclusionProofVerificationStatus verify(RequestId requestId, RootTrustBase trustBase) { if (this.authenticator != null && this.transactionHash != null) { if (!this.authenticator.verify(this.transactionHash)) { return InclusionProofVerificationStatus.NOT_AUTHENTICATED; @@ -76,16 +79,15 @@ public InclusionProofVerificationStatus verify(RequestId requestId) { } } -// TODO: Fix Unicity certificate verification -// if (!new UnicityCertificateVerificationRule().verify( -// new UnicityCertificateVerificationContext( -// this.merkleTreePath.getRootHash(), -// this.unicityCertificate, -// null -// ) -// ).isSuccessful()) { -// return InclusionProofVerificationStatus.NOT_AUTHENTICATED; -// } + if (!new UnicityCertificateVerificationRule().verify( + new UnicityCertificateVerificationContext( + this.merkleTreePath.getRootHash(), + this.unicityCertificate, + trustBase + ) + ).isSuccessful()) { + return InclusionProofVerificationStatus.NOT_AUTHENTICATED; + } MerkleTreePathVerificationResult result = this.merkleTreePath.verify( requestId.toBitString().toBigInteger()); diff --git a/src/main/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilder.java b/src/main/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilder.java index 3cf78a9..6d6dbdc 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilder.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilder.java @@ -1,6 +1,7 @@ package org.unicitylabs.sdk.transaction.split; import org.unicitylabs.sdk.address.Address; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.BranchExistsException; @@ -17,6 +18,7 @@ import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenState; import org.unicitylabs.sdk.token.TokenType; +import org.unicitylabs.sdk.verification.VerificationException; import org.unicitylabs.sdk.token.fungible.CoinId; import org.unicitylabs.sdk.token.fungible.TokenCoinData; import org.unicitylabs.sdk.transaction.MintCommitment; @@ -25,7 +27,6 @@ import org.unicitylabs.sdk.transaction.TransferCommitment; import org.unicitylabs.sdk.transaction.TransferTransactionData; import java.math.BigInteger; -import java.security.SecureRandom; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -136,18 +137,21 @@ public TransferCommitment createBurnCommitment(byte[] salt, SigningService signi } public List>> createSplitMintCommitments( - Transaction burnTransaction) { + RootTrustBase trustBase, + Transaction burnTransaction + ) throws VerificationException { Objects.requireNonNull(burnTransaction, "Burn transaction cannot be null"); Token burnedToken = this.token.update( + trustBase, new TokenState( new BurnPredicate( this.token.getId(), this.token.getType(), this.aggregationRoot.getRootHash() - ), + ), null - ), + ), burnTransaction, List.of() ); diff --git a/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java b/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java index 2af5c5f..ec23cbf 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 org.unicitylabs.sdk.StateTransitionClient; -import org.unicitylabs.sdk.transaction.Commitment; -import org.unicitylabs.sdk.transaction.InclusionProof; -import org.unicitylabs.sdk.transaction.InclusionProofVerificationStatus; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -11,6 +7,11 @@ import java.util.concurrent.TimeoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.unicitylabs.sdk.StateTransitionClient; +import org.unicitylabs.sdk.bft.RootTrustBase; +import org.unicitylabs.sdk.transaction.Commitment; +import org.unicitylabs.sdk.transaction.InclusionProof; +import org.unicitylabs.sdk.transaction.InclusionProofVerificationStatus; /** * Utility class for working with inclusion proofs @@ -27,8 +28,10 @@ public class InclusionProofUtils { */ public static CompletableFuture waitInclusionProof( StateTransitionClient client, - Commitment commitment) throws ExecutionException, InterruptedException { - return waitInclusionProof(client, commitment, DEFAULT_TIMEOUT, DEFAULT_INTERVAL); + RootTrustBase trustBase, + Commitment commitment + ) { + return waitInclusionProof(client, trustBase, commitment, DEFAULT_TIMEOUT, DEFAULT_INTERVAL); } /** @@ -36,22 +39,25 @@ public static CompletableFuture waitInclusionProof( */ public static CompletableFuture waitInclusionProof( StateTransitionClient client, + RootTrustBase trustBase, Commitment commitment, Duration timeout, - Duration interval) { + Duration interval + ) { CompletableFuture future = new CompletableFuture<>(); long startTime = System.currentTimeMillis(); long timeoutMillis = timeout.toMillis(); - checkInclusionProof(client, commitment, future, startTime, timeoutMillis, interval.toMillis()); + checkInclusionProof(client, trustBase, commitment, future, startTime, timeoutMillis, interval.toMillis()); return future; } private static void checkInclusionProof( StateTransitionClient client, + RootTrustBase trustBase, Commitment commitment, CompletableFuture future, long startTime, @@ -64,14 +70,14 @@ private static void checkInclusionProof( client.getInclusionProof(commitment).thenAccept(response -> { InclusionProofVerificationStatus status = response.getInclusionProof() - .verify(commitment.getRequestId()); + .verify(commitment.getRequestId(), trustBase); if (status == InclusionProofVerificationStatus.OK) { future.complete(response.getInclusionProof()); } if (status == InclusionProofVerificationStatus.PATH_NOT_INCLUDED) { CompletableFuture.delayedExecutor(intervalMillis, TimeUnit.MILLISECONDS) - .execute(() -> checkInclusionProof(client, commitment, future, startTime, timeoutMillis, + .execute(() -> checkInclusionProof(client, trustBase, commitment, future, startTime, timeoutMillis, intervalMillis)); } else { future.completeExceptionally( diff --git a/src/main/java/org/unicitylabs/sdk/verification/VerificationException.java b/src/main/java/org/unicitylabs/sdk/verification/VerificationException.java new file mode 100644 index 0000000..fd61e75 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/verification/VerificationException.java @@ -0,0 +1,19 @@ +package org.unicitylabs.sdk.verification; + +import java.util.Objects; + +public class VerificationException extends Exception { + + private final VerificationResult verificationResult; + + public VerificationException(String message, VerificationResult verificationResult) { + super(message); + Objects.requireNonNull(verificationResult, "verificationResult cannot be null"); + + this.verificationResult = verificationResult; + } + + public VerificationResult getVerificationResult() { + return verificationResult; + } +} diff --git a/src/main/java/org/unicitylabs/sdk/verification/VerificationResult.java b/src/main/java/org/unicitylabs/sdk/verification/VerificationResult.java index b8fab7b..a585b50 100644 --- a/src/main/java/org/unicitylabs/sdk/verification/VerificationResult.java +++ b/src/main/java/org/unicitylabs/sdk/verification/VerificationResult.java @@ -19,10 +19,18 @@ public static VerificationResult success() { return new VerificationResult(VerificationResultCode.OK, "Verification successful", List.of()); } + public static VerificationResult success(List results) { + return new VerificationResult(VerificationResultCode.OK, "Verification successful", results); + } + public static VerificationResult fail(String error) { return new VerificationResult(VerificationResultCode.FAIL, error, List.of()); } + public static VerificationResult fail(String error, List results) { + return new VerificationResult(VerificationResultCode.FAIL, error, results); + } + public static VerificationResult fromChildren(String message, List children) { return new VerificationResult( diff --git a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java index 7af5d79..27de032 100644 --- a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java +++ b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java @@ -1,5 +1,6 @@ package org.unicitylabs.sdk; +import java.util.Objects; import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.IAggregatorClient; import org.unicitylabs.sdk.api.InclusionProofResponse; @@ -7,22 +8,32 @@ import org.unicitylabs.sdk.api.RequestId; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.api.SubmitCommitmentStatus; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTree; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreeRootNode; +import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.transaction.InclusionProof; import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; +import org.unicitylabs.sdk.utils.RootTrustBaseUtils; +import org.unicitylabs.sdk.utils.TestUtils; import org.unicitylabs.sdk.utils.UnicityCertificateUtils; public class TestAggregatorClient implements IAggregatorClient { private final SparseMerkleTree tree = new SparseMerkleTree(HashAlgorithm.SHA256); private final HashMap> requests = new HashMap<>(); + private final SigningService signingService; + + public TestAggregatorClient(SigningService signingService) { + Objects.requireNonNull(signingService, "Signing service cannot be null"); + this.signingService = signingService; + } @Override @@ -55,7 +66,7 @@ public CompletableFuture getInclusionProof(RequestId req root.getPath(requestId.toBitString().toBigInteger()), entry.getKey(), entry.getValue(), - UnicityCertificateUtils.generateCertificate() + UnicityCertificateUtils.generateCertificate(signingService, root.getRootHash()) ) ) ); diff --git a/src/test/java/org/unicitylabs/sdk/bft/RootTrustBaseTest.java b/src/test/java/org/unicitylabs/sdk/bft/RootTrustBaseTest.java index b938a32..7e13681 100644 --- a/src/test/java/org/unicitylabs/sdk/bft/RootTrustBaseTest.java +++ b/src/test/java/org/unicitylabs/sdk/bft/RootTrustBaseTest.java @@ -3,11 +3,7 @@ import java.io.IOException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.unicitylabs.sdk.bft.verification.BftVerificationContext; -import org.unicitylabs.sdk.bft.verification.rule.UnicitySealHashMatchesWithRootHashRule; -import org.unicitylabs.sdk.bft.verification.rule.UnicitySealQuorumSignaturesVerificationRule; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; -import org.unicitylabs.sdk.util.HexConverter; public class RootTrustBaseTest { diff --git a/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java b/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java index 32e3ab5..e9f22b6 100644 --- a/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java +++ b/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java @@ -10,6 +10,7 @@ import org.unicitylabs.sdk.address.ProxyAddress; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.api.SubmitCommitmentStatus; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; @@ -38,6 +39,7 @@ public abstract class BaseEscrowSwapTest { protected StateTransitionClient client; + protected RootTrustBase trustBase; private final TokenType tokenType = new TokenType(HexConverter.decode( "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509")); @@ -70,7 +72,11 @@ private String[] transferToken(Token token, SigningService signingService, St UnicityObjectMapper.JSON.writeValueAsString(token), UnicityObjectMapper.JSON.writeValueAsString( commitment.toTransaction( - InclusionProofUtils.waitInclusionProof(client, commitment).get() + InclusionProofUtils.waitInclusionProof( + this.client, + this.trustBase, + commitment + ).get() ) ) }; @@ -79,6 +85,7 @@ private String[] transferToken(Token token, SigningService signingService, St private Token mintToken(byte[] secret) throws Exception { return TokenUtils.mintToken( this.client, + this.trustBase, secret, new TokenId(randomBytes(32)), this.tokenType, @@ -113,6 +120,7 @@ private Token receiveToken(String[] tokenInfo, SigningService signingService, token, state, transaction, + this.trustBase, List.of(nametagToken) ); } @@ -142,6 +150,7 @@ void testEscrow() throws Exception { Token aliceNametagToken = TokenUtils.mintNametagToken( this.client, + this.trustBase, ALICE_SECRET, this.tokenType, ALICE_NAMETAG, @@ -159,13 +168,13 @@ void testEscrow() throws Exception { SigningService.createFromSecret(ALICE_SECRET), aliceNametagToken ); - Assertions.assertTrue(aliceBobToken.verify().isSuccessful()); + Assertions.assertTrue(aliceBobToken.verify(this.trustBase).isSuccessful()); Token aliceCarolToken = receiveToken( carolSerializedData, SigningService.createFromSecret(ALICE_SECRET), aliceNametagToken ); - Assertions.assertTrue(aliceCarolToken.verify().isSuccessful()); + Assertions.assertTrue(aliceCarolToken.verify(this.trustBase).isSuccessful()); Token aliceToCarolToken = receiveToken( transferToken( @@ -176,6 +185,7 @@ void testEscrow() throws Exception { SigningService.createFromSecret(CAROL_SECRET), TokenUtils.mintNametagToken( this.client, + this.trustBase, CAROL_SECRET, this.tokenType, CAROL_NAMETAG, @@ -188,7 +198,7 @@ void testEscrow() throws Exception { randomBytes(32) ) ); - Assertions.assertTrue(aliceToCarolToken.verify().isSuccessful()); + Assertions.assertTrue(aliceToCarolToken.verify(this.trustBase).isSuccessful()); Token aliceToBobToken = receiveToken( transferToken( @@ -199,6 +209,7 @@ void testEscrow() throws Exception { SigningService.createFromSecret(BOB_SECRET), TokenUtils.mintNametagToken( this.client, + this.trustBase, BOB_SECRET, this.tokenType, BOB_NAMETAG, @@ -211,6 +222,6 @@ void testEscrow() throws Exception { randomBytes(32) ) ); - Assertions.assertTrue(aliceToBobToken.verify().isSuccessful()); + Assertions.assertTrue(aliceToBobToken.verify(this.trustBase).isSuccessful()); } } diff --git a/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java b/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java index 7679e77..937de54 100644 --- a/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java +++ b/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java @@ -13,6 +13,7 @@ import org.unicitylabs.sdk.address.ProxyAddress; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.api.SubmitCommitmentStatus; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; @@ -37,6 +38,7 @@ public abstract class BaseTokenSplitTest { protected StateTransitionClient client; + protected RootTrustBase trustBase; @Test void testTokenSplitFullAmounts() throws Exception { @@ -46,6 +48,7 @@ void testTokenSplitFullAmounts() throws Exception { Token token = TokenUtils.mintToken( this.client, + this.trustBase, secret, new TokenId(randomBytes(32)), tokenType, @@ -67,6 +70,7 @@ void testTokenSplitFullAmounts() throws Exception { Token nametagToken = TokenUtils.mintNametagToken( this.client, + this.trustBase, secret, nametag, UnmaskedPredicateReference.create( @@ -122,8 +126,13 @@ void testTokenSplitFullAmounts() throws Exception { } List>> mintCommitments = split.createSplitMintCommitments( + this.trustBase, burnCommitment.toTransaction( - InclusionProofUtils.waitInclusionProof(this.client, burnCommitment).get() + InclusionProofUtils.waitInclusionProof( + this.client, + this.trustBase, + burnCommitment + ).get() ) ); @@ -148,15 +157,16 @@ void testTokenSplitFullAmounts() throws Exception { null ); - Token> splitToken = new Token<>( + Token> splitToken = Token.create( + this.trustBase, state, commitment.toTransaction( - InclusionProofUtils.waitInclusionProof(this.client, commitment).get() + InclusionProofUtils.waitInclusionProof(this.client, this.trustBase, commitment).get() ), List.of(nametagToken) ); - Assertions.assertTrue(splitToken.verify().isSuccessful()); + Assertions.assertTrue(splitToken.verify(this.trustBase).isSuccessful()); } diff --git a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java b/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java index c7204c9..6cf09de 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java @@ -11,13 +11,15 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.StateTransitionClient; -import org.unicitylabs.sdk.address.Address; import org.unicitylabs.sdk.address.DirectAddress; import org.unicitylabs.sdk.address.ProxyAddress; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.api.SubmitCommitmentStatus; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; @@ -36,8 +38,6 @@ import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.transaction.MintCommitment; import org.unicitylabs.sdk.transaction.MintTransactionData; -import org.unicitylabs.sdk.transaction.MintTransactionReason; -import org.unicitylabs.sdk.transaction.NametagMintTransactionData; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.transaction.TransferCommitment; import org.unicitylabs.sdk.transaction.TransferTransactionData; @@ -45,12 +45,15 @@ import org.unicitylabs.sdk.transaction.split.TokenSplitBuilder; import org.unicitylabs.sdk.transaction.split.TokenSplitBuilder.TokenSplit; import org.unicitylabs.sdk.util.InclusionProofUtils; -import org.unicitylabs.sdk.utils.TestTokenData; +import org.unicitylabs.sdk.utils.TokenUtils; /** * Common test flows for token operations, matching TypeScript SDK's CommonTestFlow. */ -public class CommonTestFlow { +public abstract class CommonTestFlow { + + protected StateTransitionClient client; + protected RootTrustBase trustBase; private static final byte[] ALICE_SECRET = "Alice".getBytes(StandardCharsets.UTF_8); private static final byte[] BOB_SECRET = "Bob".getBytes(StandardCharsets.UTF_8); @@ -59,66 +62,15 @@ public class CommonTestFlow { /** * Test basic token transfer flow: Alice -> Bob -> Carol */ - public static void testTransferFlow(StateTransitionClient client) throws Exception { - TokenId tokenId = new TokenId(randomBytes(32)); - TokenType tokenType = new TokenType(randomBytes(32)); - TokenCoinData coinData = randomCoinData(2); - - // Alice mints a token - byte[] aliceNonce = randomBytes(32); - SigningService aliceSigningService = SigningService.createFromMaskedSecret(ALICE_SECRET, - aliceNonce); - - Address aliceAddress = MaskedPredicateReference.create( - tokenType, - aliceSigningService, - HashAlgorithm.SHA256, - aliceNonce - ).toAddress(); - - MintCommitment> aliceMintCommitment = MintCommitment.create( - new MintTransactionData<>( - tokenId, - tokenType, - new TestTokenData(randomBytes(32)).getData(), - coinData, - aliceAddress, - new byte[5], - null, - null - )); - - // Submit mint transaction using StateTransitionClient - SubmitCommitmentResponse aliceMintTokenResponse = client - .submitCommitment(aliceMintCommitment) - .get(); - if (aliceMintTokenResponse.getStatus() != SubmitCommitmentStatus.SUCCESS) { - throw new Exception(String.format("Failed to submit mint commitment: %s", - aliceMintTokenResponse.getStatus())); - } - - // Wait for inclusion proof - InclusionProof mintInclusionProof = InclusionProofUtils.waitInclusionProof( - client, - aliceMintCommitment - ).get(); - - // Create mint transaction - Token aliceToken = new Token<>( - new TokenState( - MaskedPredicate.create( - tokenId, - tokenType, - aliceSigningService, - HashAlgorithm.SHA256, - aliceNonce - ), - null - ), - aliceMintCommitment.toTransaction(mintInclusionProof) + @Test + public void testTransferFlow() throws Exception { + Token aliceToken = TokenUtils.mintToken( + this.client, + this.trustBase, + ALICE_SECRET ); - assertTrue(aliceToken.verify().isSuccessful()); + assertTrue(aliceToken.verify(this.trustBase).isSuccessful()); String bobNameTag = UUID.randomUUID().toString(); @@ -134,9 +86,12 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti randomBytes(32), bobDataHash, null, - aliceSigningService + SigningService.createFromMaskedSecret( + ALICE_SECRET, + ((MaskedPredicate) aliceToken.getState().getPredicate()).getNonce() + ) ); - SubmitCommitmentResponse aliceToBobTransferSubmitResponse = client.submitCommitment( + SubmitCommitmentResponse aliceToBobTransferSubmitResponse = this.client.submitCommitment( aliceToBobTransferCommitment ).get(); @@ -147,7 +102,8 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti // Wait for inclusion proof InclusionProof aliceToBobTransferInclusionProof = InclusionProofUtils.waitInclusionProof( - client, + this.client, + this.trustBase, aliceToBobTransferCommitment ).get(); @@ -158,56 +114,18 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti // Bob prepares to receive the token DirectAddress bobAddress = UnmaskedPredicateReference.create( - tokenType, + aliceToken.getType(), SigningService.createFromSecret(BOB_SECRET), HashAlgorithm.SHA256 ).toAddress(); // Bob mints a name tag tokens - - byte[] bobNametagNonce = randomBytes(32); - TokenType bobNametagTokenType = new TokenType(randomBytes(32)); - DirectAddress bobNametagAddress = MaskedPredicateReference.create( - bobNametagTokenType, - SigningService.createFromMaskedSecret(BOB_SECRET, bobNametagNonce), - HashAlgorithm.SHA256, - bobNametagNonce - ) - .toAddress(); - MintCommitment nametagMintCommitment = MintCommitment.create( - new NametagMintTransactionData<>( - bobNameTag, - bobNametagTokenType, - bobNametagAddress, - randomBytes(32), - bobAddress - )); - - SubmitCommitmentResponse nametagMintResponse = client.submitCommitment(nametagMintCommitment) - .get(); - if (nametagMintResponse.getStatus() != SubmitCommitmentStatus.SUCCESS) { - throw new Exception(String.format("Failed to submit nametag mint commitment: %s", - nametagMintResponse.getStatus())); - } - - Transaction> bobNametagGenesis = nametagMintCommitment.toTransaction( - InclusionProofUtils.waitInclusionProof( - client, - nametagMintCommitment - ).get() - ); - Token bobNametagToken = new Token<>( - new TokenState( - MaskedPredicate.create( - bobNametagGenesis.getData().getTokenId(), - bobNametagGenesis.getData().getTokenType(), - SigningService.createFromMaskedSecret(BOB_SECRET, bobNametagNonce), - HashAlgorithm.SHA256, - bobNametagNonce - ), - null - ), - bobNametagGenesis + Token bobNametagToken = TokenUtils.mintNametagToken( + this.client, + this.trustBase, + BOB_SECRET, + bobNameTag, + bobAddress ); // Bob finalizes the token @@ -224,11 +142,12 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti bobStateData ), aliceToBobTransferTransaction, + this.trustBase, List.of(bobNametagToken) ); // Verify Bob is now the owner - assertTrue(bobToken.verify().isSuccessful()); + assertTrue(bobToken.verify(this.trustBase).isSuccessful()); assertTrue(PredicateEngineService .createPredicate(bobToken.getState().getPredicate()) .isOwner(SigningService.createFromSecret(BOB_SECRET).getPublicKey()) @@ -238,7 +157,7 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti // Transfer to Carol with UnmaskedPredicate DirectAddress carolAddress = UnmaskedPredicateReference.create( - tokenType, + bobToken.getType(), SigningService.createFromSecret(CAROL_SECRET), HashAlgorithm.SHA256).toAddress(); @@ -262,7 +181,8 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti } InclusionProof bobToCarolInclusionProof = InclusionProofUtils.waitInclusionProof( - client, + this.client, + this.trustBase, bobToCarolTransferCommitment ).get(); Transaction bobToCarolTransaction = bobToCarolTransferCommitment.toTransaction( @@ -278,13 +198,14 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti bobToCarolTransaction.getData().getSalt() ); - Token carolToken = client.finalizeTransaction( + Token carolToken = this.client.finalizeTransaction( bobToken, new TokenState(carolPredicate, null), - bobToCarolTransaction + bobToCarolTransaction, + this.trustBase ); - assertTrue(carolToken.verify().isSuccessful()); + assertTrue(carolToken.verify(this.trustBase).isSuccessful()); assertEquals(2, carolToken.getTransactions().size()); // Bob receives carol token with nametag @@ -296,7 +217,7 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti null, SigningService.createFromSecret(CAROL_SECRET) ); - SubmitCommitmentResponse carolToBobTransferSubmitResponse = client.submitCommitment( + SubmitCommitmentResponse carolToBobTransferSubmitResponse = this.client.submitCommitment( carolToBobTransferCommitment ).get(); @@ -306,7 +227,8 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti } InclusionProof carolToBobInclusionProof = InclusionProofUtils.waitInclusionProof( - client, + this.client, + this.trustBase, carolToBobTransferCommitment ).get(); @@ -327,14 +249,16 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti null ), carolToBobTransaction, + this.trustBase, List.of(bobNametagToken) ); - assertTrue(carolToBobToken.verify().isSuccessful()); + assertTrue(carolToBobToken.verify(this.trustBase).isSuccessful()); // SPLIT - List> splitCoins = - new ArrayList<>(coinData.getCoins().entrySet()); + List> splitCoins = carolToken.getCoins() + .map(data -> List.copyOf(data.getCoins().entrySet())) + .orElse(List.of()); TokenType splitTokenType = new TokenType(randomBytes(32)); byte[] splitTokenNonce = randomBytes(32); @@ -381,9 +305,11 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti } List>> splitCommitments = split.createSplitMintCommitments( + this.trustBase, burnCommitment.toTransaction( InclusionProofUtils.waitInclusionProof( - client, + this.client, + this.trustBase, burnCommitment ).get() ) @@ -396,7 +322,7 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti } splitTransactions.add(commitment.toTransaction( - InclusionProofUtils.waitInclusionProof(client, commitment).get())); + InclusionProofUtils.waitInclusionProof(this.client, this.trustBase, commitment).get())); } Assertions.assertEquals( 2, @@ -418,11 +344,12 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti splitTokenNonce ); - Assertions.assertTrue( - new Token<>( + Assertions.assertDoesNotThrow(() -> + Token.create( + this.trustBase, new TokenState(splitTokenPredicate, null), splitTransactions.get(0) - ).verify().isSuccessful() + ) ); } } \ 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 f326964..9ea7e93 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java @@ -16,18 +16,16 @@ */ @Tag("integration") @EnabledIfEnvironmentVariable(named = "AGGREGATOR_URL", matches = ".+") -public class TokenE2ETest { - +public class TokenE2ETest extends CommonTestFlow { private AggregatorClient aggregatorClient; - private StateTransitionClient client; @BeforeEach void setUp() { String aggregatorUrl = System.getenv("AGGREGATOR_URL"); assertNotNull(aggregatorUrl, "AGGREGATOR_URL environment variable must be set"); - aggregatorClient = new AggregatorClient(aggregatorUrl); - client = new StateTransitionClient(aggregatorClient); + this.aggregatorClient = new AggregatorClient(aggregatorUrl); + this.client = new StateTransitionClient(this.aggregatorClient); } @Test @@ -36,20 +34,4 @@ void testGetBlockHeight() throws Exception { assertNotNull(blockHeight); assertTrue(blockHeight > 0); } - - @Test - void testTransferFlow() throws Exception { - CommonTestFlow.testTransferFlow(client); - } -// -// @Test -// void testOfflineTransferFlow() throws Exception { -// CommonTestFlow.testOfflineTransferFlow(client); -// } - - // Token splitting will be added once implemented - // @Test - // void testSplitFlow() throws Exception { - // CommonTestFlow.testSplitFlow(client); - // } } \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java b/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java index dce9cbf..fa5b596 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java @@ -5,18 +5,15 @@ import org.unicitylabs.sdk.StateTransitionClient; import org.unicitylabs.sdk.TestAggregatorClient; import org.unicitylabs.sdk.e2e.CommonTestFlow; +import org.unicitylabs.sdk.signing.SigningService; +import org.unicitylabs.sdk.utils.RootTrustBaseUtils; -public class FunctionalCommonFlowTest { - - private StateTransitionClient client; +public class FunctionalCommonFlowTest extends CommonTestFlow { @BeforeEach void setUp() { - this.client = new StateTransitionClient(new TestAggregatorClient()); - } - - @Test - void testTransferFlow() throws Exception { - CommonTestFlow.testTransferFlow(this.client); + SigningService signingService = new SigningService(SigningService.generatePrivateKey()); + this.client = new StateTransitionClient(new TestAggregatorClient(signingService)); + this.trustBase = RootTrustBaseUtils.generateRootTrustBase(signingService.getPublicKey()); } } \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/functional/FunctionalEscrowSwapTest.java b/src/test/java/org/unicitylabs/sdk/functional/FunctionalEscrowSwapTest.java index e9b6c49..603d74a 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/FunctionalEscrowSwapTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/FunctionalEscrowSwapTest.java @@ -4,10 +4,14 @@ import org.unicitylabs.sdk.StateTransitionClient; import org.unicitylabs.sdk.TestAggregatorClient; import org.unicitylabs.sdk.common.BaseEscrowSwapTest; +import org.unicitylabs.sdk.signing.SigningService; +import org.unicitylabs.sdk.utils.RootTrustBaseUtils; public class FunctionalEscrowSwapTest extends BaseEscrowSwapTest { @BeforeEach void setUp() { - this.client = new StateTransitionClient(new TestAggregatorClient()); + SigningService signingService = new SigningService(SigningService.generatePrivateKey()); + this.client = new StateTransitionClient(new TestAggregatorClient(signingService)); + this.trustBase = RootTrustBaseUtils.generateRootTrustBase(signingService.getPublicKey()); } } diff --git a/src/test/java/org/unicitylabs/sdk/functional/FunctionalTokenSplitTest.java b/src/test/java/org/unicitylabs/sdk/functional/FunctionalTokenSplitTest.java index efebba6..4748962 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/FunctionalTokenSplitTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/FunctionalTokenSplitTest.java @@ -4,10 +4,14 @@ import org.unicitylabs.sdk.TestAggregatorClient; import org.unicitylabs.sdk.common.split.BaseTokenSplitTest; import org.junit.jupiter.api.BeforeEach; +import org.unicitylabs.sdk.signing.SigningService; +import org.unicitylabs.sdk.utils.RootTrustBaseUtils; public class FunctionalTokenSplitTest extends BaseTokenSplitTest { @BeforeEach void setUp() { - this.client = new StateTransitionClient(new TestAggregatorClient()); + SigningService signingService = new SigningService(SigningService.generatePrivateKey()); + this.client = new StateTransitionClient(new TestAggregatorClient(signingService)); + this.trustBase = RootTrustBaseUtils.generateRootTrustBase(signingService.getPublicKey()); } } diff --git a/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java b/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java index a1ddffd..deacd2e 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java @@ -5,12 +5,15 @@ import java.nio.charset.StandardCharsets; import java.util.List; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.StateTransitionClient; import org.unicitylabs.sdk.TestAggregatorClient; import org.unicitylabs.sdk.address.Address; +import org.unicitylabs.sdk.api.AggregatorClient; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.api.SubmitCommitmentStatus; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.BranchExistsException; import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; @@ -27,12 +30,13 @@ import org.unicitylabs.sdk.transaction.TransferTransactionData; import org.unicitylabs.sdk.util.HexConverter; import org.unicitylabs.sdk.util.InclusionProofUtils; +import org.unicitylabs.sdk.utils.RootTrustBaseUtils; import org.unicitylabs.sdk.utils.TokenUtils; public class FunctionalUnsignedPredicateDoubleSpendPreventionTest { + protected StateTransitionClient client; + protected RootTrustBase trustBase; - protected final StateTransitionClient client = new StateTransitionClient( - new TestAggregatorClient()); private final byte[] BOB_SECRET = "BOB_SECRET".getBytes(StandardCharsets.UTF_8); private String[] transferToken(Token token, byte[] secret, Address address) throws Exception { @@ -57,7 +61,7 @@ private String[] transferToken(Token token, byte[] secret, Address address) t UnicityObjectMapper.JSON.writeValueAsString(token), UnicityObjectMapper.JSON.writeValueAsString( commitment.toTransaction( - InclusionProofUtils.waitInclusionProof(client, commitment).get() + InclusionProofUtils.waitInclusionProof(this.client, this.trustBase, commitment).get() ) ) }; @@ -66,6 +70,7 @@ private String[] transferToken(Token token, byte[] secret, Address address) t private Token mintToken(byte[] secret) throws Exception { return TokenUtils.mintToken( this.client, + this.trustBase, secret, new TokenId(randomBytes(32)), new TokenType(HexConverter.decode( @@ -100,10 +105,18 @@ private Token receiveToken(String[] tokenInfo, byte[] secret) throws Exceptio token, state, transaction, + this.trustBase, List.of() ); } + @BeforeEach + void setUp() { + SigningService signingService = new SigningService(SigningService.generatePrivateKey()); + this.client = new StateTransitionClient(new TestAggregatorClient(signingService)); + this.trustBase = RootTrustBaseUtils.generateRootTrustBase(signingService.getPublicKey()); + } + @Test void testDoubleSpend() throws Exception { Token token = mintToken(BOB_SECRET); @@ -118,13 +131,13 @@ void testDoubleSpend() throws Exception { receiveToken( transferToken(token, BOB_SECRET, reference.toAddress()), BOB_SECRET - ).verify().isSuccessful()); + ).verify(trustBase).isSuccessful()); RuntimeException ex = Assertions.assertThrows( RuntimeException.class, () -> receiveToken( transferToken(token, BOB_SECRET, reference.toAddress()), BOB_SECRET - ).verify() + ).verify(trustBase) ); Assertions.assertInstanceOf(BranchExistsException.class, ex.getCause()); diff --git a/src/test/java/org/unicitylabs/sdk/token/TokenTest.java b/src/test/java/org/unicitylabs/sdk/token/TokenTest.java index 379d7bf..cc93eba 100644 --- a/src/test/java/org/unicitylabs/sdk/token/TokenTest.java +++ b/src/test/java/org/unicitylabs/sdk/token/TokenTest.java @@ -1,11 +1,8 @@ package org.unicitylabs.sdk.token; import org.unicitylabs.sdk.address.DirectAddress; -import org.unicitylabs.sdk.bft.InputRecord; -import org.unicitylabs.sdk.bft.ShardTreeCertificate; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.bft.UnicityCertificate; -import org.unicitylabs.sdk.bft.UnicitySeal; -import org.unicitylabs.sdk.bft.UnicityTreeCertificate; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; @@ -18,7 +15,7 @@ import org.unicitylabs.sdk.transaction.MintTransactionData; import org.unicitylabs.sdk.transaction.NametagMintTransactionData; import org.unicitylabs.sdk.transaction.Transaction; -import org.unicitylabs.sdk.transaction.TransferTransactionData; +import org.unicitylabs.sdk.utils.RootTrustBaseUtils; import org.unicitylabs.sdk.utils.TestUtils; import java.io.IOException; import java.math.BigInteger; @@ -28,11 +25,16 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.utils.UnicityCertificateUtils; +import org.unicitylabs.sdk.verification.VerificationException; public class TokenTest { @Test - public void testJsonSerialization() throws IOException { + public void testJsonSerialization() throws IOException, VerificationException { + SigningService signingService = new SigningService(SigningService.generatePrivateKey()); + UnicityCertificate unicityCertificate = UnicityCertificateUtils.generateCertificate( + signingService, DataHash.fromImprint(new byte[34])); + MintTransactionData genesisData = new MintTransactionData<>( new TokenId(TestUtils.randomBytes(32)), new TokenType(TestUtils.randomBytes(32)), @@ -73,9 +75,11 @@ public void testJsonSerialization() throws IOException { ), null, null, - UnicityCertificateUtils.generateCertificate() + unicityCertificate ) - ) + ), + List.of(), + List.of() ); Token token = new Token<>( @@ -100,40 +104,10 @@ public void testJsonSerialization() throws IOException { ), null, null, - UnicityCertificateUtils.generateCertificate() - ) - ), - List.of( - new Transaction<>( - new TransferTransactionData( - new TokenState( - new MaskedPredicate( - genesisData.getTokenId(), - genesisData.getTokenType(), - new byte[24], - "secp256k1", - HashAlgorithm.SHA256, - new byte[25] - ), - null - ), - DirectAddress.create(new DataHash(HashAlgorithm.SHA256, new byte[32])), - new byte[20], - null, - "Transfer".getBytes(), - List.of(nametagToken) - ), - new InclusionProof( - new SparseMerkleTreePath( - new DataHash(HashAlgorithm.SHA256, TestUtils.randomBytes(32)), - List.of() - ), - null, - null, - UnicityCertificateUtils.generateCertificate() - ) + unicityCertificate ) ), + List.of(), List.of(nametagToken) ); diff --git a/src/test/java/org/unicitylabs/sdk/transaction/InclusionProofTest.java b/src/test/java/org/unicitylabs/sdk/transaction/InclusionProofTest.java index 2e15eb4..b551189 100644 --- a/src/test/java/org/unicitylabs/sdk/transaction/InclusionProofTest.java +++ b/src/test/java/org/unicitylabs/sdk/transaction/InclusionProofTest.java @@ -1,5 +1,6 @@ package org.unicitylabs.sdk.transaction; +import java.util.Set; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -7,13 +8,18 @@ import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.LeafValue; import org.unicitylabs.sdk.api.RequestId; +import org.unicitylabs.sdk.bft.InputRecord; +import org.unicitylabs.sdk.bft.RootTrustBase; +import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTree; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; +import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTreePath.Root; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.util.HexConverter; +import org.unicitylabs.sdk.utils.RootTrustBaseUtils; import org.unicitylabs.sdk.utils.UnicityCertificateUtils; @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -23,6 +29,8 @@ public class InclusionProofTest { DataHash transactionHash; SparseMerkleTreePath merkleTreePath; Authenticator authenticator; + RootTrustBase trustBase; + UnicityCertificate unicityCertificate; @BeforeAll public void createMerkleTreePath() throws Exception { @@ -40,6 +48,9 @@ public void createMerkleTreePath() throws Exception { smt.addLeaf(requestId.toBitString().toBigInteger(), leaf.getBytes()); merkleTreePath = smt.calculateRoot().getPath(requestId.toBitString().toBigInteger()); + SigningService ucSigningService = new SigningService(SigningService.generatePrivateKey()); + trustBase = RootTrustBaseUtils.generateRootTrustBase(ucSigningService.getPublicKey()); + unicityCertificate = UnicityCertificateUtils.generateCertificate(ucSigningService, merkleTreePath.getRootHash()); } @Test @@ -48,7 +59,7 @@ public void testJsonSerialization() throws Exception { merkleTreePath, authenticator, transactionHash, - UnicityCertificateUtils.generateCertificate() + unicityCertificate ); Assertions.assertEquals(inclusionProof, UnicityObjectMapper.JSON.readValue( UnicityObjectMapper.JSON.writeValueAsString(inclusionProof), InclusionProof.class)); @@ -61,7 +72,7 @@ public void testStructure() { merkleTreePath, authenticator, null, - UnicityCertificateUtils.generateCertificate() + unicityCertificate ) ); Assertions.assertThrows(IllegalArgumentException.class, @@ -69,7 +80,7 @@ public void testStructure() { merkleTreePath, null, transactionHash, - UnicityCertificateUtils.generateCertificate() + unicityCertificate ) ); Assertions.assertThrows(NullPointerException.class, @@ -77,7 +88,7 @@ public void testStructure() { null, authenticator, transactionHash, - UnicityCertificateUtils.generateCertificate() + unicityCertificate ) ); Assertions.assertThrows(NullPointerException.class, @@ -93,7 +104,7 @@ public void testStructure() { merkleTreePath, authenticator, transactionHash, - UnicityCertificateUtils.generateCertificate() + unicityCertificate ) ); Assertions.assertInstanceOf(InclusionProof.class, @@ -101,7 +112,7 @@ public void testStructure() { merkleTreePath, null, null, - UnicityCertificateUtils.generateCertificate() + unicityCertificate ) ); } @@ -109,27 +120,35 @@ public void testStructure() { @Test public void testItVerifies() { InclusionProof inclusionProof = new InclusionProof( - merkleTreePath, - authenticator, - transactionHash, - UnicityCertificateUtils.generateCertificate() + this.merkleTreePath, + this.authenticator, + this.transactionHash, + this.unicityCertificate + ); + Assertions.assertEquals( + InclusionProofVerificationStatus.OK, + inclusionProof.verify(this.requestId, this.trustBase) ); - Assertions.assertEquals(InclusionProofVerificationStatus.OK, inclusionProof.verify(requestId)); Assertions.assertEquals(InclusionProofVerificationStatus.PATH_NOT_INCLUDED, inclusionProof.verify( - RequestId.create(new byte[32], new DataHash(HashAlgorithm.SHA256, new byte[32])))); + RequestId.create(new byte[32], new DataHash(HashAlgorithm.SHA256, new byte[32])), + this.trustBase + ) + ); InclusionProof invalidInclusionProof = new InclusionProof( - merkleTreePath, - authenticator, + this.merkleTreePath, + this.authenticator, new DataHash( HashAlgorithm.SHA224, HexConverter.decode("FF000000000000000000000000000000000000000000000000000000000000FF") ), - UnicityCertificateUtils.generateCertificate() + this.unicityCertificate ); - Assertions.assertEquals(InclusionProofVerificationStatus.NOT_AUTHENTICATED, - invalidInclusionProof.verify(requestId)); + Assertions.assertEquals( + InclusionProofVerificationStatus.NOT_AUTHENTICATED, + invalidInclusionProof.verify(this.requestId, this.trustBase) + ); } } diff --git a/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java b/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java index 19d11eb..ddb409a 100644 --- a/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java +++ b/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java @@ -1,11 +1,14 @@ package org.unicitylabs.sdk.transaction.split; +import java.io.IOException; import java.math.BigInteger; import java.util.List; import java.util.Map; import java.util.UUID; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.unicitylabs.sdk.bft.RootTrustBase; +import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.BranchExistsException; @@ -13,6 +16,7 @@ import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; import org.unicitylabs.sdk.predicate.Predicate; import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; +import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenState; @@ -22,11 +26,18 @@ import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.transaction.MintTransactionData; import org.unicitylabs.sdk.transaction.Transaction; +import org.unicitylabs.sdk.utils.RootTrustBaseUtils; +import org.unicitylabs.sdk.utils.TestUtils; import org.unicitylabs.sdk.utils.UnicityCertificateUtils; +import org.unicitylabs.sdk.verification.VerificationException; public class TokenSplitBuilderTest { - private Token createToken(TokenCoinData coinData) { + private Token createToken(TokenCoinData coinData) throws VerificationException { + SigningService signingService = new SigningService(SigningService.generatePrivateKey()); + UnicityCertificate unicityCertificate = UnicityCertificateUtils.generateCertificate( + signingService, DataHash.fromImprint(new byte[34])); + TokenId tokenId = new TokenId(new byte[10]); TokenType tokenType = new TokenType(new byte[10]); @@ -59,15 +70,17 @@ private Token createToken(TokenCoinData coinData) { ), null, null, - UnicityCertificateUtils.generateCertificate() + unicityCertificate ) - ) + ), + List.of(), + List.of() ); } @Test public void testTokenSplitIntoMultipleTokens() - throws LeafOutOfBoundsException, BranchExistsException { + throws LeafOutOfBoundsException, BranchExistsException, VerificationException, IOException { Token token = this.createToken( new TokenCoinData( @@ -141,7 +154,7 @@ public void testTokenSplitIntoMultipleTokens() } @Test - public void testTokenSplitUnknownSplitCoin() { + public void testTokenSplitUnknownSplitCoin() throws VerificationException, IOException { Token token = this.createToken(null); Predicate predicate = new MaskedPredicate( diff --git a/src/test/java/org/unicitylabs/sdk/utils/RootTrustBaseUtils.java b/src/test/java/org/unicitylabs/sdk/utils/RootTrustBaseUtils.java new file mode 100644 index 0000000..24a9a46 --- /dev/null +++ b/src/test/java/org/unicitylabs/sdk/utils/RootTrustBaseUtils.java @@ -0,0 +1,29 @@ +package org.unicitylabs.sdk.utils; + +import java.util.Map; +import java.util.Set; +import org.unicitylabs.sdk.bft.RootTrustBase; +import org.unicitylabs.sdk.bft.UnicityCertificate; + +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() + ); + } +} diff --git a/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java b/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java index 13abf51..f23d781 100644 --- a/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java +++ b/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java @@ -7,6 +7,7 @@ import org.unicitylabs.sdk.address.Address; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.api.SubmitCommitmentStatus; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.predicate.embedded.MaskedPredicate; @@ -26,9 +27,14 @@ public class TokenUtils { - public static Token mintToken(StateTransitionClient client, byte[] secret) throws Exception { + public static Token mintToken( + StateTransitionClient client, + RootTrustBase trustBase, + byte[] secret + ) throws Exception { return TokenUtils.mintToken( client, + trustBase, secret, new TokenId(randomBytes(32)), new TokenType(randomBytes(32)), @@ -42,6 +48,7 @@ public static Token mintToken(StateTransitionClient client, byte[] secret) th public static Token mintToken( StateTransitionClient client, + RootTrustBase trustBase, byte[] secret, TokenId tokenId, TokenType tokenType, @@ -89,11 +96,13 @@ public static Token mintToken( // Wait for inclusion proof InclusionProof inclusionProof = InclusionProofUtils.waitInclusionProof( client, + trustBase, commitment ).get(); // Create mint transaction - return new Token<>( + return Token.create( + trustBase, tokenState, commitment.toTransaction(inclusionProof) ); @@ -101,12 +110,14 @@ public static Token mintToken( public static Token mintNametagToken( StateTransitionClient client, + RootTrustBase trustBase, byte[] secret, String nametag, Address targetAddress ) throws Exception { return mintNametagToken( client, + trustBase, secret, new TokenType(randomBytes(32)), nametag, @@ -118,6 +129,7 @@ public static Token mintNametagToken( public static Token mintNametagToken( StateTransitionClient client, + RootTrustBase trustBase, byte[] secret, TokenType tokenType, String nametag, @@ -155,11 +167,13 @@ public static Token mintNametagToken( // Wait for inclusion proof InclusionProof inclusionProof = InclusionProofUtils.waitInclusionProof( client, + trustBase, commitment ).get(); // Create mint transaction - return new Token<>( + return Token.create( + trustBase, new TokenState( MaskedPredicate.create( commitment.getTransactionData().getTokenId(), diff --git a/src/test/java/org/unicitylabs/sdk/utils/UnicityCertificateUtils.java b/src/test/java/org/unicitylabs/sdk/utils/UnicityCertificateUtils.java index a5f9935..07b095a 100644 --- a/src/test/java/org/unicitylabs/sdk/utils/UnicityCertificateUtils.java +++ b/src/test/java/org/unicitylabs/sdk/utils/UnicityCertificateUtils.java @@ -1,24 +1,99 @@ package org.unicitylabs.sdk.utils; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; import java.util.List; import java.util.Map; import org.unicitylabs.sdk.bft.InputRecord; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.bft.ShardTreeCertificate; import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.bft.UnicitySeal; import org.unicitylabs.sdk.bft.UnicityTreeCertificate; +import org.unicitylabs.sdk.hash.DataHash; +import org.unicitylabs.sdk.hash.DataHasher; +import org.unicitylabs.sdk.hash.HashAlgorithm; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; +import org.unicitylabs.sdk.signing.SigningService; public class UnicityCertificateUtils { - public static UnicityCertificate generateCertificate() { - return new UnicityCertificate( - 0, - new InputRecord(0, 0, 0, new byte[10], new byte[10], new byte[10], 0, - new byte[10], 0, new byte[10]), - new byte[10], - new byte[10], - new ShardTreeCertificate(new byte[10], List.of()), - new UnicityTreeCertificate(0, 0, List.of()), - new UnicitySeal(0, (short) 0, 0L, 0L, 0L, new byte[10], new byte[10], Map.of()) - ); + + public static UnicityCertificate generateCertificate( + SigningService signingService, + DataHash rootHash + ) { + try { + 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()); + byte[] technicalRecordHash = new byte[32]; + byte[] shardConfigurationHash = new byte[32]; + ShardTreeCertificate shardTreeCertificate = new ShardTreeCertificate( + new byte[32], List.of() + ); + + DataHash shardTreeCertificateRootHash = UnicityCertificate.calculateShardTreeCertificateRootHash( + inputRecord, + technicalRecordHash, + shardConfigurationHash, + shardTreeCertificate + ); + + byte[] key = ByteBuffer.allocate(4) + .order(ByteOrder.BIG_ENDIAN) + .putInt(unicityTreeCertificate.getPartitionIdentifier()) + .array(); + + DataHash unicitySealHash = new DataHasher(HashAlgorithm.SHA256) + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(new byte[]{(byte) 0x01})) // LEAF + .update(UnicityObjectMapper.CBOR.writeValueAsBytes(key)) + .update( + UnicityObjectMapper.CBOR.writeValueAsBytes( + new DataHasher(HashAlgorithm.SHA256) + .update( + UnicityObjectMapper.CBOR.writeValueAsBytes( + shardTreeCertificateRootHash.getData() + ) + ) + .digest() + .getData() + ) + ) + .digest(); + + UnicitySeal seal = new UnicitySeal( + 0, + (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.encode()).digest() + ).encode() + ) + ) + ); + } catch (IOException e) { + throw new RuntimeException("Failed to generate UnicityCertificate", e); + } } } From 36035b23b748d1397d4b1282df20775867c826a6 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Wed, 24 Sep 2025 16:59:35 +0400 Subject: [PATCH 14/16] Remove unused imports. Fix android errors --- .../unicitylabs/sdk/api/IAggregatorClient.java | 4 +--- .../unicitylabs/sdk/bft/UnicityCertificate.java | 1 - .../UnicitySealHashMatchesWithRootHashRule.java | 1 - .../sdk/predicate/embedded/DefaultPredicate.java | 3 --- .../embedded/EmbeddedPredicateType.java | 1 - .../predicate/embedded/UnmaskedPredicate.java | 7 +++++-- .../sdk/serializer/UnicityObjectMapper.java | 2 -- .../sdk/serializer/cbor/CborDeserializer.java | 1 - .../sdk/serializer/cbor/bft/InputRecordCbor.java | 2 -- .../cbor/predicate/BurnPredicateCbor.java | 2 -- .../cbor/predicate/UnmaskedPredicateCbor.java | 2 -- .../json/api/InclusionProofResponseJson.java | 2 -- .../json/bft/RootTrustBaseNodeInfoJson.java | 1 - .../predicate/SerializablePredicateJson.java | 3 +-- .../serializer/json/token/TokenStateJson.java | 5 ++--- .../java/org/unicitylabs/sdk/token/Token.java | 2 +- .../unicitylabs/sdk/transaction/Commitment.java | 3 +-- .../sdk/transaction/TransferTransactionData.java | 10 ++++------ .../sdk/util/InclusionProofUtils.java | 1 - .../unicitylabs/sdk/TestAggregatorClient.java | 13 +++++-------- .../sdk/bft/UnicityCertificateTest.java | 7 ------- .../org/unicitylabs/sdk/e2e/CommonTestFlow.java | 2 -- .../sdk/functional/FunctionalCommonFlowTest.java | 1 - ...signedPredicateDoubleSpendPreventionTest.java | 1 - .../sdk/mtree/plain/SparseMerkleTreeTest.java | 14 ++++++-------- .../org/unicitylabs/sdk/token/TokenTest.java | 16 +++++++--------- .../sdk/transaction/InclusionProofTest.java | 3 --- .../transaction/split/TokenSplitBuilderTest.java | 3 --- .../sdk/utils/RootTrustBaseUtils.java | 1 - .../sdk/utils/UnicityCertificateUtils.java | 2 -- 30 files changed, 33 insertions(+), 83 deletions(-) diff --git a/src/main/java/org/unicitylabs/sdk/api/IAggregatorClient.java b/src/main/java/org/unicitylabs/sdk/api/IAggregatorClient.java index fec2038..db53333 100644 --- a/src/main/java/org/unicitylabs/sdk/api/IAggregatorClient.java +++ b/src/main/java/org/unicitylabs/sdk/api/IAggregatorClient.java @@ -1,10 +1,8 @@ package org.unicitylabs.sdk.api; -import org.unicitylabs.sdk.hash.DataHash; -import org.unicitylabs.sdk.transaction.InclusionProof; - import java.util.concurrent.CompletableFuture; +import org.unicitylabs.sdk.hash.DataHash; public interface IAggregatorClient { diff --git a/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java b/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java index 99a2df6..62646e0 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java +++ b/src/main/java/org/unicitylabs/sdk/bft/UnicityCertificate.java @@ -9,7 +9,6 @@ import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; -import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.util.HexConverter; public class UnicityCertificate { diff --git a/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java b/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java index 283cf45..127976d 100644 --- a/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java +++ b/src/main/java/org/unicitylabs/sdk/bft/verification/rule/UnicitySealHashMatchesWithRootHashRule.java @@ -4,7 +4,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; -import java.util.List; import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.bft.UnicityTreeCertificate; import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationContext; diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java index ab91642..5d1fea5 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/DefaultPredicate.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; import java.util.Arrays; -import java.util.List; import java.util.Objects; import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.RequestId; @@ -11,7 +10,6 @@ import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.EncodedPredicate; import org.unicitylabs.sdk.predicate.Predicate; import org.unicitylabs.sdk.predicate.PredicateEngineType; import org.unicitylabs.sdk.predicate.PredicateReference; @@ -20,7 +18,6 @@ import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenType; -import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.transaction.InclusionProofVerificationStatus; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.transaction.TransferTransactionData; diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateType.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateType.java index 2f40181..f8a794f 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateType.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/EmbeddedPredicateType.java @@ -2,7 +2,6 @@ package org.unicitylabs.sdk.predicate.embedded; import java.util.Arrays; -import org.unicitylabs.sdk.util.HexConverter; public enum EmbeddedPredicateType { UNMASKED(new byte[] {0x0}), diff --git a/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java index c2ec791..6473972 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/embedded/UnmaskedPredicate.java @@ -1,6 +1,7 @@ package org.unicitylabs.sdk.predicate.embedded; +import java.util.List; import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.DataHasher; import org.unicitylabs.sdk.hash.HashAlgorithm; @@ -51,12 +52,14 @@ public boolean verify( Transaction transaction, RootTrustBase trustBase ) { + List> transactions = token.getTransactions(); + return super.verify(token, transaction, trustBase) && SigningService.verifyWithPublicKey( new DataHasher(HashAlgorithm.SHA256) .update( - token.getTransactions().isEmpty() + transactions.isEmpty() ? token.getGenesis().getData().getSalt() - : token.getTransactions().getLast().getData().getSalt() + : transactions.get(transactions.size() - 1).getData().getSalt() ) .digest(), this.getNonce(), diff --git a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java index 24d3f8e..b326c20 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java @@ -1,9 +1,7 @@ package org.unicitylabs.sdk.serializer; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; import com.fasterxml.jackson.dataformat.cbor.databind.CBORMapper; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import org.unicitylabs.sdk.address.Address; 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 9746d7b..d10a806 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializer.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/CborDeserializer.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java index 9d65c61..24163a5 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/bft/InputRecordCbor.java @@ -10,9 +10,7 @@ import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; import java.io.IOException; -import java.nio.charset.StandardCharsets; import org.unicitylabs.sdk.bft.InputRecord; -import org.unicitylabs.sdk.util.HexConverter; public class InputRecordCbor { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/BurnPredicateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/BurnPredicateCbor.java index 1e0e2a6..aab9113 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/BurnPredicateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/BurnPredicateCbor.java @@ -10,9 +10,7 @@ import com.fasterxml.jackson.databind.exc.MismatchedInputException; import java.io.IOException; import org.unicitylabs.sdk.hash.DataHash; -import org.unicitylabs.sdk.predicate.SerializablePredicate; import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; -import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenType; diff --git a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/UnmaskedPredicateCbor.java b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/UnmaskedPredicateCbor.java index 9a510db..852221f 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/UnmaskedPredicateCbor.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/cbor/predicate/UnmaskedPredicateCbor.java @@ -6,9 +6,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import java.io.IOException; -import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.embedded.BurnPredicate; import org.unicitylabs.sdk.predicate.embedded.UnmaskedPredicate; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenType; diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/api/InclusionProofResponseJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/api/InclusionProofResponseJson.java index b18eddd..4c94a82 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/api/InclusionProofResponseJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/api/InclusionProofResponseJson.java @@ -9,8 +9,6 @@ import java.util.HashSet; import java.util.Set; import org.unicitylabs.sdk.api.InclusionProofResponse; -import org.unicitylabs.sdk.api.SubmitCommitmentResponse; -import org.unicitylabs.sdk.api.SubmitCommitmentStatus; import org.unicitylabs.sdk.transaction.InclusionProof; public class InclusionProofResponseJson { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/bft/RootTrustBaseNodeInfoJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/bft/RootTrustBaseNodeInfoJson.java index 0ad7cf3..05edc08 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/bft/RootTrustBaseNodeInfoJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/bft/RootTrustBaseNodeInfoJson.java @@ -12,7 +12,6 @@ import java.util.HashSet; import java.util.Set; import org.unicitylabs.sdk.bft.RootTrustBase; -import org.unicitylabs.sdk.util.HexConverter; public class RootTrustBaseNodeInfoJson { private static final String NODE_ID_FIELD = "nodeId"; diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/SerializablePredicateJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/SerializablePredicateJson.java index d045f93..58a36c3 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/SerializablePredicateJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/SerializablePredicateJson.java @@ -8,12 +8,11 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import java.io.IOException; import org.unicitylabs.sdk.predicate.EncodedPredicate; import org.unicitylabs.sdk.predicate.Predicate; import org.unicitylabs.sdk.predicate.PredicateEngineType; -import java.io.IOException; import org.unicitylabs.sdk.predicate.SerializablePredicate; -import org.unicitylabs.sdk.predicate.embedded.EmbeddedPredicateType; public class SerializablePredicateJson { private SerializablePredicateJson() { diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/token/TokenStateJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/token/TokenStateJson.java index 7426658..6edad82 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/token/TokenStateJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/token/TokenStateJson.java @@ -8,12 +8,11 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import org.unicitylabs.sdk.predicate.Predicate; -import org.unicitylabs.sdk.predicate.SerializablePredicate; -import org.unicitylabs.sdk.token.TokenState; import java.io.IOException; import java.util.HashSet; import java.util.Set; +import org.unicitylabs.sdk.predicate.SerializablePredicate; +import org.unicitylabs.sdk.token.TokenState; public class TokenStateJson { diff --git a/src/main/java/org/unicitylabs/sdk/token/Token.java b/src/main/java/org/unicitylabs/sdk/token/Token.java index 5ef20b9..e6204dc 100644 --- a/src/main/java/org/unicitylabs/sdk/token/Token.java +++ b/src/main/java/org/unicitylabs/sdk/token/Token.java @@ -195,7 +195,7 @@ private static VerificationResult verifyTransaction( Address expectedRecipient = predicate.getReference().toAddress(); Transaction previousTransaction = !token.transactions.isEmpty() - ? token.transactions.getLast() + ? token.transactions.get(token.transactions.size() - 1) : token.genesis; if (!expectedRecipient.equals( ProxyAddress.resolve(previousTransaction.getData().getRecipient(), token.getNametags()))) { diff --git a/src/main/java/org/unicitylabs/sdk/transaction/Commitment.java b/src/main/java/org/unicitylabs/sdk/transaction/Commitment.java index f7b388f..38dbfaa 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/Commitment.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/Commitment.java @@ -1,10 +1,9 @@ package org.unicitylabs.sdk.transaction; +import java.util.Objects; import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.RequestId; -import java.util.Objects; -import org.unicitylabs.sdk.bft.RootTrustBase; /** * Commitment representing a submitted transaction diff --git a/src/main/java/org/unicitylabs/sdk/transaction/TransferTransactionData.java b/src/main/java/org/unicitylabs/sdk/transaction/TransferTransactionData.java index c47cb1e..5dab8d0 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/TransferTransactionData.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/TransferTransactionData.java @@ -3,6 +3,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import org.unicitylabs.sdk.address.Address; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.DataHasher; @@ -10,14 +14,8 @@ import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; import org.unicitylabs.sdk.token.Token; -import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenState; -import org.unicitylabs.sdk.token.TokenType; import org.unicitylabs.sdk.util.HexConverter; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; /** * Transaction data for token state transitions diff --git a/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java b/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java index ec23cbf..5c3610f 100644 --- a/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java +++ b/src/main/java/org/unicitylabs/sdk/util/InclusionProofUtils.java @@ -2,7 +2,6 @@ import java.time.Duration; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.slf4j.Logger; diff --git a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java index 27de032..7c29d8a 100644 --- a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java +++ b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java @@ -1,6 +1,11 @@ package org.unicitylabs.sdk; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.IAggregatorClient; import org.unicitylabs.sdk.api.InclusionProofResponse; @@ -8,20 +13,12 @@ import org.unicitylabs.sdk.api.RequestId; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.api.SubmitCommitmentStatus; -import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTree; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreeRootNode; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.transaction.InclusionProof; -import java.util.AbstractMap; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.CompletableFuture; -import org.unicitylabs.sdk.utils.RootTrustBaseUtils; -import org.unicitylabs.sdk.utils.TestUtils; import org.unicitylabs.sdk.utils.UnicityCertificateUtils; public class TestAggregatorClient implements IAggregatorClient { diff --git a/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java b/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java index bbe4d51..34158d5 100644 --- a/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java +++ b/src/test/java/org/unicitylabs/sdk/bft/UnicityCertificateTest.java @@ -3,14 +3,7 @@ import java.io.IOException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.unicitylabs.sdk.api.InclusionProofResponse; -import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationContext; -import org.unicitylabs.sdk.bft.verification.rule.InputRecordCurrentHashVerificationRule; -import org.unicitylabs.sdk.bft.verification.rule.UnicitySealHashMatchesWithRootHashRule; -import org.unicitylabs.sdk.bft.verification.rule.UnicitySealQuorumSignaturesVerificationRule; -import org.unicitylabs.sdk.jsonrpc.JsonRpcResponse; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; -import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.util.HexConverter; public class UnicityCertificateTest { diff --git a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java b/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java index 6cf09de..15ec379 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.unicitylabs.sdk.utils.TestUtils.randomBytes; -import static org.unicitylabs.sdk.utils.TestUtils.randomCoinData; import java.math.BigInteger; import java.nio.charset.StandardCharsets; @@ -11,7 +10,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.StateTransitionClient; diff --git a/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java b/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java index fa5b596..232edfc 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java @@ -1,7 +1,6 @@ package org.unicitylabs.sdk.functional; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.StateTransitionClient; import org.unicitylabs.sdk.TestAggregatorClient; import org.unicitylabs.sdk.e2e.CommonTestFlow; diff --git a/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java b/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java index deacd2e..e31a906 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java @@ -10,7 +10,6 @@ import org.unicitylabs.sdk.StateTransitionClient; import org.unicitylabs.sdk.TestAggregatorClient; import org.unicitylabs.sdk.address.Address; -import org.unicitylabs.sdk.api.AggregatorClient; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.api.SubmitCommitmentStatus; import org.unicitylabs.sdk.bft.RootTrustBase; diff --git a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java b/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java index f64b569..f9306d5 100644 --- a/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java +++ b/src/test/java/org/unicitylabs/sdk/mtree/plain/SparseMerkleTreeTest.java @@ -1,18 +1,16 @@ 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.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.BranchExistsException; import org.unicitylabs.sdk.mtree.LeafOutOfBoundsException; import org.unicitylabs.sdk.mtree.MerkleTreePathVerificationResult; -import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.util.HexConverter; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Field; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.util.Map; public class SparseMerkleTreeTest { diff --git a/src/test/java/org/unicitylabs/sdk/token/TokenTest.java b/src/test/java/org/unicitylabs/sdk/token/TokenTest.java index cc93eba..ccd9545 100644 --- a/src/test/java/org/unicitylabs/sdk/token/TokenTest.java +++ b/src/test/java/org/unicitylabs/sdk/token/TokenTest.java @@ -1,7 +1,13 @@ package org.unicitylabs.sdk.token; +import java.io.IOException; +import java.math.BigInteger; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.address.DirectAddress; -import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; @@ -15,15 +21,7 @@ import org.unicitylabs.sdk.transaction.MintTransactionData; import org.unicitylabs.sdk.transaction.NametagMintTransactionData; import org.unicitylabs.sdk.transaction.Transaction; -import org.unicitylabs.sdk.utils.RootTrustBaseUtils; import org.unicitylabs.sdk.utils.TestUtils; -import java.io.IOException; -import java.math.BigInteger; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; import org.unicitylabs.sdk.utils.UnicityCertificateUtils; import org.unicitylabs.sdk.verification.VerificationException; diff --git a/src/test/java/org/unicitylabs/sdk/transaction/InclusionProofTest.java b/src/test/java/org/unicitylabs/sdk/transaction/InclusionProofTest.java index b551189..a74b2cb 100644 --- a/src/test/java/org/unicitylabs/sdk/transaction/InclusionProofTest.java +++ b/src/test/java/org/unicitylabs/sdk/transaction/InclusionProofTest.java @@ -1,6 +1,5 @@ package org.unicitylabs.sdk.transaction; -import java.util.Set; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -8,14 +7,12 @@ import org.unicitylabs.sdk.api.Authenticator; import org.unicitylabs.sdk.api.LeafValue; import org.unicitylabs.sdk.api.RequestId; -import org.unicitylabs.sdk.bft.InputRecord; import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTree; import org.unicitylabs.sdk.mtree.plain.SparseMerkleTreePath; -import org.unicitylabs.sdk.mtree.sum.SparseMerkleSumTreePath.Root; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.util.HexConverter; diff --git a/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java b/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java index ddb409a..bbc59c7 100644 --- a/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java +++ b/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java @@ -7,7 +7,6 @@ import java.util.UUID; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.hash.DataHash; import org.unicitylabs.sdk.hash.HashAlgorithm; @@ -26,8 +25,6 @@ import org.unicitylabs.sdk.transaction.InclusionProof; import org.unicitylabs.sdk.transaction.MintTransactionData; import org.unicitylabs.sdk.transaction.Transaction; -import org.unicitylabs.sdk.utils.RootTrustBaseUtils; -import org.unicitylabs.sdk.utils.TestUtils; import org.unicitylabs.sdk.utils.UnicityCertificateUtils; import org.unicitylabs.sdk.verification.VerificationException; diff --git a/src/test/java/org/unicitylabs/sdk/utils/RootTrustBaseUtils.java b/src/test/java/org/unicitylabs/sdk/utils/RootTrustBaseUtils.java index 24a9a46..4636271 100644 --- a/src/test/java/org/unicitylabs/sdk/utils/RootTrustBaseUtils.java +++ b/src/test/java/org/unicitylabs/sdk/utils/RootTrustBaseUtils.java @@ -3,7 +3,6 @@ import java.util.Map; import java.util.Set; import org.unicitylabs.sdk.bft.RootTrustBase; -import org.unicitylabs.sdk.bft.UnicityCertificate; public class RootTrustBaseUtils { public static RootTrustBase generateRootTrustBase(byte[] publicKey) { diff --git a/src/test/java/org/unicitylabs/sdk/utils/UnicityCertificateUtils.java b/src/test/java/org/unicitylabs/sdk/utils/UnicityCertificateUtils.java index 07b095a..a28fdcf 100644 --- a/src/test/java/org/unicitylabs/sdk/utils/UnicityCertificateUtils.java +++ b/src/test/java/org/unicitylabs/sdk/utils/UnicityCertificateUtils.java @@ -3,11 +3,9 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.Arrays; import java.util.List; import java.util.Map; import org.unicitylabs.sdk.bft.InputRecord; -import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.bft.ShardTreeCertificate; import org.unicitylabs.sdk.bft.UnicityCertificate; import org.unicitylabs.sdk.bft.UnicitySeal; From b4270103f2b9d3df2dbdc0377923cc7732fe0dc9 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Wed, 24 Sep 2025 17:35:50 +0400 Subject: [PATCH 15/16] Temporarily remove integration tests --- .../sdk/StateTransitionClient.java | 8 +- .../sdk/common/BaseEscrowSwapTest.java | 2 +- .../sdk/{e2e => common}/CommonTestFlow.java | 12 +- .../sdk/e2e/CucumberTestRunner.java | 30 +- .../sdk/e2e/E2EEscrowSwapTest.java | 21 +- .../org/unicitylabs/sdk/e2e/TokenE2ETest.java | 45 +- .../sdk/e2e/config/CucumberConfiguration.java | 106 +- .../sdk/e2e/context/TestContext.java | 536 +++---- .../e2e/steps/AdvancedStepDefinitions.java | 530 +++---- .../sdk/e2e/steps/StepDefinitions.java | 688 ++++----- .../steps/shared/SharedStepDefinitions.java | 1232 ++++++++--------- .../sdk/e2e/steps/shared/StepHelper.java | 956 ++++++------- .../functional/FunctionalCommonFlowTest.java | 2 +- ...nedPredicateDoubleSpendPreventionTest.java | 2 +- .../org/unicitylabs/sdk/utils/TestUtils.java | 533 ++++--- 15 files changed, 2343 insertions(+), 2360 deletions(-) rename src/test/java/org/unicitylabs/sdk/{e2e => common}/CommonTestFlow.java (99%) diff --git a/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java b/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java index 2ee859a..9599d4e 100644 --- a/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java +++ b/src/main/java/org/unicitylabs/sdk/StateTransitionClient.java @@ -56,19 +56,19 @@ public CompletableFuture submitCommitment( } public > Token finalizeTransaction( + RootTrustBase trustBase, Token token, TokenState state, - Transaction transaction, - RootTrustBase trustBase + Transaction transaction ) throws VerificationException { - return this.finalizeTransaction(token, state, transaction, trustBase, List.of()); + return this.finalizeTransaction(trustBase, token, state, transaction, List.of()); } public > Token finalizeTransaction( + RootTrustBase trustBase, Token token, TokenState state, Transaction transaction, - RootTrustBase trustBase, List> nametags ) throws VerificationException { Objects.requireNonNull(token, "Token is null"); diff --git a/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java b/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java index e9f22b6..4441ed6 100644 --- a/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java +++ b/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java @@ -117,10 +117,10 @@ private Token receiveToken(String[] tokenInfo, SigningService signingService, ); return this.client.finalizeTransaction( + this.trustBase, token, state, transaction, - this.trustBase, List.of(nametagToken) ); } diff --git a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java b/src/test/java/org/unicitylabs/sdk/common/CommonTestFlow.java similarity index 99% rename from src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java rename to src/test/java/org/unicitylabs/sdk/common/CommonTestFlow.java index 15ec379..52de7cd 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java +++ b/src/test/java/org/unicitylabs/sdk/common/CommonTestFlow.java @@ -1,4 +1,4 @@ -package org.unicitylabs.sdk.e2e; +package org.unicitylabs.sdk.common; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -128,6 +128,7 @@ public void testTransferFlow() throws Exception { // Bob finalizes the token Token bobToken = client.finalizeTransaction( + this.trustBase, aliceToken, new TokenState( UnmaskedPredicate.create( @@ -140,7 +141,6 @@ public void testTransferFlow() throws Exception { bobStateData ), aliceToBobTransferTransaction, - this.trustBase, List.of(bobNametagToken) ); @@ -197,11 +197,11 @@ public void testTransferFlow() throws Exception { ); Token carolToken = this.client.finalizeTransaction( + this.trustBase, bobToken, new TokenState(carolPredicate, null), - bobToCarolTransaction, - this.trustBase - ); + bobToCarolTransaction + ); assertTrue(carolToken.verify(this.trustBase).isSuccessful()); assertEquals(2, carolToken.getTransactions().size()); @@ -235,6 +235,7 @@ public void testTransferFlow() throws Exception { ); Token carolToBobToken = client.finalizeTransaction( + this.trustBase, carolToken, new TokenState( UnmaskedPredicate.create( @@ -247,7 +248,6 @@ public void testTransferFlow() throws Exception { null ), carolToBobTransaction, - this.trustBase, List.of(bobNametagToken) ); diff --git a/src/test/java/org/unicitylabs/sdk/e2e/CucumberTestRunner.java b/src/test/java/org/unicitylabs/sdk/e2e/CucumberTestRunner.java index dad2d86..4029145 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/CucumberTestRunner.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/CucumberTestRunner.java @@ -8,18 +8,18 @@ * This class configures the test execution environment and feature discovery * with the new shared step definitions approach. */ -@Suite -@IncludeEngines("cucumber") -@SelectPackages("org.unicitylabs.sdk.features") -@ConfigurationParameter(key = Constants.GLUE_PROPERTY_NAME, value = "org.unicitylabs.sdk.e2e.steps,org.unicitylabs.sdk.e2e.steps.shared,org.unicitylabs.sdk.e2e.config") -@ConfigurationParameter(key = Constants.PLUGIN_PROPERTY_NAME, value = "pretty,html:build/cucumber-reports/cucumber.html,json:build/cucumber-reports/cucumber.json") -@ConfigurationParameter(key = Constants.EXECUTION_DRY_RUN_PROPERTY_NAME, value = "false") -@ConfigurationParameter(key = Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, value = "true") -public class CucumberTestRunner { - static { - // Only set default tags if no tags are specified - if (System.getProperty("cucumber.filter.tags") == null) { - System.setProperty("cucumber.filter.tags", "not @ignore"); - } - } -} \ No newline at end of file +//@Suite +//@IncludeEngines("cucumber") +//@SelectPackages("org.unicitylabs.sdk.features") +//@ConfigurationParameter(key = Constants.GLUE_PROPERTY_NAME, value = "org.unicitylabs.sdk.e2e.steps,org.unicitylabs.sdk.e2e.steps.shared,org.unicitylabs.sdk.e2e.config") +//@ConfigurationParameter(key = Constants.PLUGIN_PROPERTY_NAME, value = "pretty,html:build/cucumber-reports/cucumber.html,json:build/cucumber-reports/cucumber.json") +//@ConfigurationParameter(key = Constants.EXECUTION_DRY_RUN_PROPERTY_NAME, value = "false") +//@ConfigurationParameter(key = Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, value = "true") +//public class CucumberTestRunner { +// static { +// // Only set default tags if no tags are specified +// if (System.getProperty("cucumber.filter.tags") == null) { +// System.setProperty("cucumber.filter.tags", "not @ignore"); +// } +// } +//} \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/e2e/E2EEscrowSwapTest.java b/src/test/java/org/unicitylabs/sdk/e2e/E2EEscrowSwapTest.java index 1a5ec97..5a4fb44 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/E2EEscrowSwapTest.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/E2EEscrowSwapTest.java @@ -8,14 +8,15 @@ import org.unicitylabs.sdk.api.AggregatorClient; import org.unicitylabs.sdk.common.BaseEscrowSwapTest; -@Tag("integration") -@EnabledIfEnvironmentVariable(named = "AGGREGATOR_URL", matches = ".+") -public class E2EEscrowSwapTest extends BaseEscrowSwapTest { - @BeforeEach - void setUp() { - String aggregatorUrl = System.getenv("AGGREGATOR_URL"); - Assertions.assertNotNull(aggregatorUrl, "AGGREGATOR_URL environment variable must be set"); - this.client = new StateTransitionClient(new AggregatorClient(aggregatorUrl)); - } -} +//@Tag("integration") +//@EnabledIfEnvironmentVariable(named = "AGGREGATOR_URL", matches = ".+") +//public class E2EEscrowSwapTest extends BaseEscrowSwapTest { +// @BeforeEach +// void setUp() { +// String aggregatorUrl = System.getenv("AGGREGATOR_URL"); +// Assertions.assertNotNull(aggregatorUrl, "AGGREGATOR_URL environment variable must be set"); +// +// this.client = new StateTransitionClient(new AggregatorClient(aggregatorUrl)); +// } +//} diff --git a/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java b/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java index 9ea7e93..57d5b41 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.unicitylabs.sdk.common.CommonTestFlow; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -14,24 +15,26 @@ * End-to-end tests for token operations using CommonTestFlow. Matches TypeScript SDK's test * structure. */ -@Tag("integration") -@EnabledIfEnvironmentVariable(named = "AGGREGATOR_URL", matches = ".+") -public class TokenE2ETest extends CommonTestFlow { - private AggregatorClient aggregatorClient; - - @BeforeEach - void setUp() { - String aggregatorUrl = System.getenv("AGGREGATOR_URL"); - assertNotNull(aggregatorUrl, "AGGREGATOR_URL environment variable must be set"); - - this.aggregatorClient = new AggregatorClient(aggregatorUrl); - this.client = new StateTransitionClient(this.aggregatorClient); - } - - @Test - void testGetBlockHeight() throws Exception { - Long blockHeight = aggregatorClient.getBlockHeight().get(); - assertNotNull(blockHeight); - assertTrue(blockHeight > 0); - } -} \ No newline at end of file +// TODO: Need to load trustbase from real aggregator but currently it has to be predefined +//@Tag("integration") +//@EnabledIfEnvironmentVariable(named = "AGGREGATOR_URL", matches = ".+") +//public class TokenE2ETest extends CommonTestFlow { +// private AggregatorClient aggregatorClient; +// +// @BeforeEach +// void setUp() { +// String aggregatorUrl = System.getenv("AGGREGATOR_URL"); +// assertNotNull(aggregatorUrl, "AGGREGATOR_URL environment variable must be set"); +// +// this.aggregatorClient = new AggregatorClient(aggregatorUrl); +// this.client = new StateTransitionClient(this.aggregatorClient); +// this.trustBase = null; +// } +// +// @Test +// void testGetBlockHeight() throws Exception { +// Long blockHeight = aggregatorClient.getBlockHeight().get(); +// assertNotNull(blockHeight); +// assertTrue(blockHeight > 0); +// } +//} \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/e2e/config/CucumberConfiguration.java b/src/test/java/org/unicitylabs/sdk/e2e/config/CucumberConfiguration.java index ddb7f9c..b3759a8 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/config/CucumberConfiguration.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/config/CucumberConfiguration.java @@ -1,53 +1,53 @@ -package org.unicitylabs.sdk.e2e.config; - -import org.unicitylabs.sdk.e2e.context.TestContext; -import io.cucumber.java.Before; -import io.cucumber.java.After; - -/** - * Cucumber configuration for dependency injection and test lifecycle management. - * This ensures that TestContext is properly shared across all step definition classes. - */ -public class CucumberConfiguration { - - private static TestContext testContext = new TestContext(); - - /** - * Provides a shared TestContext instance for all step definition classes. - * This method will be called by step definition classes to get - * the shared TestContext instance. - */ - public static TestContext getTestContext() { - return testContext; - } - - /** - * Hook that runs before each scenario to reset the test context. - * This ensures each scenario starts with a clean state. - */ - @Before - public void setUp() { - testContext.clearTestState(); // Clear test state but keep clients if they exist - System.out.println("Test context cleared for new scenario"); - } - - /** - * Hook that runs after each scenario for cleanup. - * This can be used for any additional cleanup if needed. - */ - @After - public void tearDown() { - // Optional: Add any cleanup logic here - // For now, we keep the context alive for potential debugging - System.out.println("Scenario completed"); - } - - /** - * Hook that runs after scenarios tagged with @reset to completely reset the context. - */ - @After("@reset") - public void fullReset() { - testContext.reset(); - System.out.println("Full context reset performed"); - } -} \ No newline at end of file +//package org.unicitylabs.sdk.e2e.config; +// +//import org.unicitylabs.sdk.e2e.context.TestContext; +//import io.cucumber.java.Before; +//import io.cucumber.java.After; +// +///** +// * Cucumber configuration for dependency injection and test lifecycle management. +// * This ensures that TestContext is properly shared across all step definition classes. +// */ +//public class CucumberConfiguration { +// +// private static TestContext testContext = new TestContext(); +// +// /** +// * Provides a shared TestContext instance for all step definition classes. +// * This method will be called by step definition classes to get +// * the shared TestContext instance. +// */ +// public static TestContext getTestContext() { +// return testContext; +// } +// +// /** +// * Hook that runs before each scenario to reset the test context. +// * This ensures each scenario starts with a clean state. +// */ +// @Before +// public void setUp() { +// testContext.clearTestState(); // Clear test state but keep clients if they exist +// System.out.println("Test context cleared for new scenario"); +// } +// +// /** +// * Hook that runs after each scenario for cleanup. +// * This can be used for any additional cleanup if needed. +// */ +// @After +// public void tearDown() { +// // Optional: Add any cleanup logic here +// // For now, we keep the context alive for potential debugging +// System.out.println("Scenario completed"); +// } +// +// /** +// * Hook that runs after scenarios tagged with @reset to completely reset the context. +// */ +// @After("@reset") +// public void fullReset() { +// testContext.reset(); +// System.out.println("Full context reset performed"); +// } +//} \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/e2e/context/TestContext.java b/src/test/java/org/unicitylabs/sdk/e2e/context/TestContext.java index 9dfa328..e63a8f3 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/context/TestContext.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/context/TestContext.java @@ -1,268 +1,268 @@ -package org.unicitylabs.sdk.e2e.context; - -import org.unicitylabs.sdk.StateTransitionClient; -import org.unicitylabs.sdk.TestAggregatorClient; -import org.unicitylabs.sdk.api.AggregatorClient; -import org.unicitylabs.sdk.api.SubmitCommitmentResponse; -import org.unicitylabs.sdk.hash.DataHash; -import org.unicitylabs.sdk.predicate.Predicate; -import org.unicitylabs.sdk.signing.SigningService; -import org.unicitylabs.sdk.token.Token; -import org.unicitylabs.sdk.transaction.Transaction; -import org.unicitylabs.sdk.transaction.TransferTransactionData; -import org.unicitylabs.sdk.utils.TestUtils; -import org.unicitylabs.sdk.utils.helpers.CommitmentResult; -import org.unicitylabs.sdk.utils.helpers.PendingTransfer; - -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; - -/** - * Shared test context that maintains state across all step definition classes. - * This allows different step definition classes to share data and avoid duplication. - */ -public class TestContext { - - // Core clients - private AggregatorClient aggregatorClient; - private TestAggregatorClient testAggregatorClient; - private StateTransitionClient client; - - // User management - private Map userSigningServices = new HashMap<>(); - private Map userNonces = new HashMap<>(); - private Map userSecrets = new HashMap<>(); - private Map userPredicate = new HashMap<>(); - private Map> userTokens = new HashMap<>(); - private Map> nameTagTokens = new HashMap<>(); - private final Map> pendingTransfers = new HashMap<>(); - - - // Test execution state - private Long blockHeight; - private byte[] randomSecret; - private byte[] stateBytes; - private DataHash stateHash; - private DataHash txDataHash; - private SubmitCommitmentResponse commitmentResponse; - private long submissionDuration; - private Exception lastError; - private boolean operationSucceeded; - - // Performance testing - private int configuredThreadCount; - private int configuredCommitmentsPerThread; - private List aggregatorClients; - private List> concurrentResults = new ArrayList<>(); - private long concurrentSubmissionDuration; - private List bulkResults = new ArrayList<>(); - private long bulkOperationDuration; - - // Transfer chain tracking - private List transferChain = new ArrayList<>(); - private Token chainToken; - private Map transferCustomData = new HashMap<>(); - - // Current operation context - private String currentUser; - private String expectedErrorType; - private int expectedSplitCount; - private int configuredUserCount; - private int configuredTokensPerUser; - - - // Getters and Setters - public AggregatorClient getAggregatorClient() { return aggregatorClient; } - public void setAggregatorClient(AggregatorClient aggregatorClient) { this.aggregatorClient = aggregatorClient; } - - public TestAggregatorClient getTestAggregatorClient() { return testAggregatorClient; } - public void setTestAggregatorClient(TestAggregatorClient testAggregatorClient) { this.testAggregatorClient = testAggregatorClient; } - - public StateTransitionClient getClient() { return client; } - public void setClient(StateTransitionClient client) { this.client = client; } - - public Map getUserSigningServices() { return userSigningServices; } - public void setUserSigningServices(Map userSigningServices) { this.userSigningServices = userSigningServices; } - - public Map getUserNonces() { return userNonces; } - public void setUserNonces(Map userNonces) { this.userNonces = userNonces; } - - public Map getUserSecret() { return userSecrets; } - public void setUserSecret(Map userSecrets) { this.userSecrets = userSecrets; } - - public Map getUserPredicate() { - return userPredicate; - } - - public void setUserPredicate(Map userPredicate) { - this.userPredicate = userPredicate; - } - - public List getAggregatorClients() { - return aggregatorClients; - } - - public void setAggregatorClients(List aggregatorClients) { - this.aggregatorClients = aggregatorClients; - } - - public Map> getUserTokens() { return userTokens; } - public void setUserTokens(Map> userTokens) { this.userTokens = userTokens; } - - public Map> getNameTagTokens() { return nameTagTokens; } - public void setNameTagTokens(Map> nameTagTokens) { this.nameTagTokens = nameTagTokens; } - - public Long getBlockHeight() { return blockHeight; } - public void setBlockHeight(Long blockHeight) { this.blockHeight = blockHeight; } - - public byte[] getRandomSecret() { return randomSecret; } - public void setRandomSecret(byte[] randomSecret) { this.randomSecret = randomSecret; } - - public byte[] getStateBytes() { return stateBytes; } - public void setStateBytes(byte[] stateBytes) { this.stateBytes = stateBytes; } - - public DataHash getStateHash() { return stateHash; } - public void setStateHash(DataHash stateHash) { this.stateHash = stateHash; } - - public DataHash getTxDataHash() { return txDataHash; } - public void setTxDataHash(DataHash txDataHash) { this.txDataHash = txDataHash; } - - public SubmitCommitmentResponse getCommitmentResponse() { return commitmentResponse; } - public void setCommitmentResponse(SubmitCommitmentResponse commitmentResponse) { this.commitmentResponse = commitmentResponse; } - - public long getSubmissionDuration() { return submissionDuration; } - public void setSubmissionDuration(long submissionDuration) { this.submissionDuration = submissionDuration; } - - public Exception getLastError() { return lastError; } - public void setLastError(Exception lastError) { this.lastError = lastError; } - - public boolean isOperationSucceeded() { return operationSucceeded; } - public void setOperationSucceeded(boolean operationSucceeded) { this.operationSucceeded = operationSucceeded; } - - public int getConfiguredThreadCount() { return configuredThreadCount; } - public void setConfiguredThreadCount(int configuredThreadCount) { this.configuredThreadCount = configuredThreadCount; } - - public int getConfiguredCommitmentsPerThread() { return configuredCommitmentsPerThread; } - public void setConfiguredCommitmentsPerThread(int configuredCommitmentsPerThread) { this.configuredCommitmentsPerThread = configuredCommitmentsPerThread; } - - public List> getConcurrentResults() { return concurrentResults; } - public void setConcurrentResults(List> concurrentResults) { this.concurrentResults = concurrentResults; } - - public long getConcurrentSubmissionDuration() { return concurrentSubmissionDuration; } - public void setConcurrentSubmissionDuration(long concurrentSubmissionDuration) { this.concurrentSubmissionDuration = concurrentSubmissionDuration; } - - public List getBulkResults() { return bulkResults; } - public void setBulkResults(List bulkResults) { this.bulkResults = bulkResults; } - - public long getBulkOperationDuration() { return bulkOperationDuration; } - public void setBulkOperationDuration(long bulkOperationDuration) { this.bulkOperationDuration = bulkOperationDuration; } - - public List getTransferChain() { return transferChain; } - public void setTransferChain(List transferChain) { this.transferChain = transferChain; } - - public Token getChainToken() { return chainToken; } - public void setChainToken(Token chainToken) { this.chainToken = chainToken; } - - public Map getTransferCustomData() { return transferCustomData; } - public void setTransferCustomData(Map transferCustomData) { this.transferCustomData = transferCustomData; } - - public String getCurrentUser() { return currentUser; } - public void setCurrentUser(String currentUser) { this.currentUser = currentUser; } - - public String getExpectedErrorType() { return expectedErrorType; } - public void setExpectedErrorType(String expectedErrorType) { this.expectedErrorType = expectedErrorType; } - - public int getExpectedSplitCount() { return expectedSplitCount; } - public void setExpectedSplitCount(int expectedSplitCount) { this.expectedSplitCount = expectedSplitCount; } - - public int getConfiguredUserCount() { return configuredUserCount; } - public void setConfiguredUserCount(int configuredUserCount) { this.configuredUserCount = configuredUserCount; } - - public int getConfiguredTokensPerUser() { return configuredTokensPerUser; } - public void setConfiguredTokensPerUser(int configuredTokensPerUser) { this.configuredTokensPerUser = configuredTokensPerUser; } - - public void savePendingTransfer(String user, Token token, Transaction tx) { - pendingTransfers.computeIfAbsent(user, k -> new ArrayList<>()) - .add(new PendingTransfer(token, tx)); - } - - public List getPendingTransfers(String user) { - return pendingTransfers.getOrDefault(user, List.of()); - } - - public void clearPendingTransfers(String user) { - pendingTransfers.remove(user); - } - - - // Utility methods - public void addUserToken(String userName, Token token) { - userTokens.computeIfAbsent(userName, k -> new ArrayList<>()).add(token); - } - - public Token getUserToken(String userName) { - List tokens = userTokens.get(userName); - return (tokens != null && !tokens.isEmpty()) ? tokens.get(0) : null; - } - - public Token getUserToken(String userName, int index) { - List tokens = userTokens.get(userName); - return (tokens != null && tokens.size() > index) ? tokens.get(index) : null; - } - - public void addNameTagToken(String userName, Token nameTagToken) { - nameTagTokens.computeIfAbsent(userName, k -> new ArrayList<>()).add(nameTagToken); - } - - public Token getNameTagToken(String userName) { - List tokens = nameTagTokens.get(userName); - return (tokens != null && !tokens.isEmpty()) ? tokens.get(0) : null; - } - - public void clearUserData() { - userSigningServices.clear(); - userNonces.clear(); - userSecrets.clear(); - userTokens.clear(); - nameTagTokens.clear(); - } - - public void clearTestState() { - configuredUserCount = 0; - blockHeight = null; - randomSecret = null; - stateBytes = null; - stateHash = null; - txDataHash = null; - commitmentResponse = null; - submissionDuration = 0; - lastError = null; - operationSucceeded = false; - concurrentResults.clear(); - bulkResults.clear(); - transferChain.clear(); - chainToken = null; - transferCustomData.clear(); - currentUser = null; - expectedErrorType = null; - } - - public void reset() { - clearUserData(); - clearTestState(); - aggregatorClient = null; - testAggregatorClient = null; - client = null; - } - - private List> commitmentFutures = new ArrayList<>(); - - public void setCommitmentFutures(List> futures) { - this.commitmentFutures = futures; - } - - public List> getCommitmentFutures() { - return commitmentFutures; - } -} \ No newline at end of file +//package org.unicitylabs.sdk.e2e.context; +// +//import org.unicitylabs.sdk.StateTransitionClient; +//import org.unicitylabs.sdk.TestAggregatorClient; +//import org.unicitylabs.sdk.api.AggregatorClient; +//import org.unicitylabs.sdk.api.SubmitCommitmentResponse; +//import org.unicitylabs.sdk.hash.DataHash; +//import org.unicitylabs.sdk.predicate.Predicate; +//import org.unicitylabs.sdk.signing.SigningService; +//import org.unicitylabs.sdk.token.Token; +//import org.unicitylabs.sdk.transaction.Transaction; +//import org.unicitylabs.sdk.transaction.TransferTransactionData; +//import org.unicitylabs.sdk.utils.TestUtils; +//import org.unicitylabs.sdk.utils.helpers.CommitmentResult; +//import org.unicitylabs.sdk.utils.helpers.PendingTransfer; +// +//import java.util.*; +//import java.util.concurrent.CompletableFuture; +//import java.util.concurrent.Future; +// +///** +// * Shared test context that maintains state across all step definition classes. +// * This allows different step definition classes to share data and avoid duplication. +// */ +//public class TestContext { +// +// // Core clients +// private AggregatorClient aggregatorClient; +// private TestAggregatorClient testAggregatorClient; +// private StateTransitionClient client; +// +// // User management +// private Map userSigningServices = new HashMap<>(); +// private Map userNonces = new HashMap<>(); +// private Map userSecrets = new HashMap<>(); +// private Map userPredicate = new HashMap<>(); +// private Map> userTokens = new HashMap<>(); +// private Map> nameTagTokens = new HashMap<>(); +// private final Map> pendingTransfers = new HashMap<>(); +// +// +// // Test execution state +// private Long blockHeight; +// private byte[] randomSecret; +// private byte[] stateBytes; +// private DataHash stateHash; +// private DataHash txDataHash; +// private SubmitCommitmentResponse commitmentResponse; +// private long submissionDuration; +// private Exception lastError; +// private boolean operationSucceeded; +// +// // Performance testing +// private int configuredThreadCount; +// private int configuredCommitmentsPerThread; +// private List aggregatorClients; +// private List> concurrentResults = new ArrayList<>(); +// private long concurrentSubmissionDuration; +// private List bulkResults = new ArrayList<>(); +// private long bulkOperationDuration; +// +// // Transfer chain tracking +// private List transferChain = new ArrayList<>(); +// private Token chainToken; +// private Map transferCustomData = new HashMap<>(); +// +// // Current operation context +// private String currentUser; +// private String expectedErrorType; +// private int expectedSplitCount; +// private int configuredUserCount; +// private int configuredTokensPerUser; +// +// +// // Getters and Setters +// public AggregatorClient getAggregatorClient() { return aggregatorClient; } +// public void setAggregatorClient(AggregatorClient aggregatorClient) { this.aggregatorClient = aggregatorClient; } +// +// public TestAggregatorClient getTestAggregatorClient() { return testAggregatorClient; } +// public void setTestAggregatorClient(TestAggregatorClient testAggregatorClient) { this.testAggregatorClient = testAggregatorClient; } +// +// public StateTransitionClient getClient() { return client; } +// public void setClient(StateTransitionClient client) { this.client = client; } +// +// public Map getUserSigningServices() { return userSigningServices; } +// public void setUserSigningServices(Map userSigningServices) { this.userSigningServices = userSigningServices; } +// +// public Map getUserNonces() { return userNonces; } +// public void setUserNonces(Map userNonces) { this.userNonces = userNonces; } +// +// public Map getUserSecret() { return userSecrets; } +// public void setUserSecret(Map userSecrets) { this.userSecrets = userSecrets; } +// +// public Map getUserPredicate() { +// return userPredicate; +// } +// +// public void setUserPredicate(Map userPredicate) { +// this.userPredicate = userPredicate; +// } +// +// public List getAggregatorClients() { +// return aggregatorClients; +// } +// +// public void setAggregatorClients(List aggregatorClients) { +// this.aggregatorClients = aggregatorClients; +// } +// +// public Map> getUserTokens() { return userTokens; } +// public void setUserTokens(Map> userTokens) { this.userTokens = userTokens; } +// +// public Map> getNameTagTokens() { return nameTagTokens; } +// public void setNameTagTokens(Map> nameTagTokens) { this.nameTagTokens = nameTagTokens; } +// +// public Long getBlockHeight() { return blockHeight; } +// public void setBlockHeight(Long blockHeight) { this.blockHeight = blockHeight; } +// +// public byte[] getRandomSecret() { return randomSecret; } +// public void setRandomSecret(byte[] randomSecret) { this.randomSecret = randomSecret; } +// +// public byte[] getStateBytes() { return stateBytes; } +// public void setStateBytes(byte[] stateBytes) { this.stateBytes = stateBytes; } +// +// public DataHash getStateHash() { return stateHash; } +// public void setStateHash(DataHash stateHash) { this.stateHash = stateHash; } +// +// public DataHash getTxDataHash() { return txDataHash; } +// public void setTxDataHash(DataHash txDataHash) { this.txDataHash = txDataHash; } +// +// public SubmitCommitmentResponse getCommitmentResponse() { return commitmentResponse; } +// public void setCommitmentResponse(SubmitCommitmentResponse commitmentResponse) { this.commitmentResponse = commitmentResponse; } +// +// public long getSubmissionDuration() { return submissionDuration; } +// public void setSubmissionDuration(long submissionDuration) { this.submissionDuration = submissionDuration; } +// +// public Exception getLastError() { return lastError; } +// public void setLastError(Exception lastError) { this.lastError = lastError; } +// +// public boolean isOperationSucceeded() { return operationSucceeded; } +// public void setOperationSucceeded(boolean operationSucceeded) { this.operationSucceeded = operationSucceeded; } +// +// public int getConfiguredThreadCount() { return configuredThreadCount; } +// public void setConfiguredThreadCount(int configuredThreadCount) { this.configuredThreadCount = configuredThreadCount; } +// +// public int getConfiguredCommitmentsPerThread() { return configuredCommitmentsPerThread; } +// public void setConfiguredCommitmentsPerThread(int configuredCommitmentsPerThread) { this.configuredCommitmentsPerThread = configuredCommitmentsPerThread; } +// +// public List> getConcurrentResults() { return concurrentResults; } +// public void setConcurrentResults(List> concurrentResults) { this.concurrentResults = concurrentResults; } +// +// public long getConcurrentSubmissionDuration() { return concurrentSubmissionDuration; } +// public void setConcurrentSubmissionDuration(long concurrentSubmissionDuration) { this.concurrentSubmissionDuration = concurrentSubmissionDuration; } +// +// public List getBulkResults() { return bulkResults; } +// public void setBulkResults(List bulkResults) { this.bulkResults = bulkResults; } +// +// public long getBulkOperationDuration() { return bulkOperationDuration; } +// public void setBulkOperationDuration(long bulkOperationDuration) { this.bulkOperationDuration = bulkOperationDuration; } +// +// public List getTransferChain() { return transferChain; } +// public void setTransferChain(List transferChain) { this.transferChain = transferChain; } +// +// public Token getChainToken() { return chainToken; } +// public void setChainToken(Token chainToken) { this.chainToken = chainToken; } +// +// public Map getTransferCustomData() { return transferCustomData; } +// public void setTransferCustomData(Map transferCustomData) { this.transferCustomData = transferCustomData; } +// +// public String getCurrentUser() { return currentUser; } +// public void setCurrentUser(String currentUser) { this.currentUser = currentUser; } +// +// public String getExpectedErrorType() { return expectedErrorType; } +// public void setExpectedErrorType(String expectedErrorType) { this.expectedErrorType = expectedErrorType; } +// +// public int getExpectedSplitCount() { return expectedSplitCount; } +// public void setExpectedSplitCount(int expectedSplitCount) { this.expectedSplitCount = expectedSplitCount; } +// +// public int getConfiguredUserCount() { return configuredUserCount; } +// public void setConfiguredUserCount(int configuredUserCount) { this.configuredUserCount = configuredUserCount; } +// +// public int getConfiguredTokensPerUser() { return configuredTokensPerUser; } +// public void setConfiguredTokensPerUser(int configuredTokensPerUser) { this.configuredTokensPerUser = configuredTokensPerUser; } +// +// public void savePendingTransfer(String user, Token token, Transaction tx) { +// pendingTransfers.computeIfAbsent(user, k -> new ArrayList<>()) +// .add(new PendingTransfer(token, tx)); +// } +// +// public List getPendingTransfers(String user) { +// return pendingTransfers.getOrDefault(user, List.of()); +// } +// +// public void clearPendingTransfers(String user) { +// pendingTransfers.remove(user); +// } +// +// +// // Utility methods +// public void addUserToken(String userName, Token token) { +// userTokens.computeIfAbsent(userName, k -> new ArrayList<>()).add(token); +// } +// +// public Token getUserToken(String userName) { +// List tokens = userTokens.get(userName); +// return (tokens != null && !tokens.isEmpty()) ? tokens.get(0) : null; +// } +// +// public Token getUserToken(String userName, int index) { +// List tokens = userTokens.get(userName); +// return (tokens != null && tokens.size() > index) ? tokens.get(index) : null; +// } +// +// public void addNameTagToken(String userName, Token nameTagToken) { +// nameTagTokens.computeIfAbsent(userName, k -> new ArrayList<>()).add(nameTagToken); +// } +// +// public Token getNameTagToken(String userName) { +// List tokens = nameTagTokens.get(userName); +// return (tokens != null && !tokens.isEmpty()) ? tokens.get(0) : null; +// } +// +// public void clearUserData() { +// userSigningServices.clear(); +// userNonces.clear(); +// userSecrets.clear(); +// userTokens.clear(); +// nameTagTokens.clear(); +// } +// +// public void clearTestState() { +// configuredUserCount = 0; +// blockHeight = null; +// randomSecret = null; +// stateBytes = null; +// stateHash = null; +// txDataHash = null; +// commitmentResponse = null; +// submissionDuration = 0; +// lastError = null; +// operationSucceeded = false; +// concurrentResults.clear(); +// bulkResults.clear(); +// transferChain.clear(); +// chainToken = null; +// transferCustomData.clear(); +// currentUser = null; +// expectedErrorType = null; +// } +// +// public void reset() { +// clearUserData(); +// clearTestState(); +// aggregatorClient = null; +// testAggregatorClient = null; +// client = null; +// } +// +// private List> commitmentFutures = new ArrayList<>(); +// +// public void setCommitmentFutures(List> futures) { +// this.commitmentFutures = futures; +// } +// +// public List> getCommitmentFutures() { +// return commitmentFutures; +// } +//} \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/e2e/steps/AdvancedStepDefinitions.java b/src/test/java/org/unicitylabs/sdk/e2e/steps/AdvancedStepDefinitions.java index 830b888..9ab4885 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/steps/AdvancedStepDefinitions.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/steps/AdvancedStepDefinitions.java @@ -1,265 +1,265 @@ -package org.unicitylabs.sdk.e2e.steps; - -import org.unicitylabs.sdk.StateTransitionClient; -import org.unicitylabs.sdk.address.ProxyAddress; -import org.unicitylabs.sdk.api.AggregatorClient; -import org.unicitylabs.sdk.api.SubmitCommitmentResponse; -import org.unicitylabs.sdk.api.SubmitCommitmentStatus; -import org.unicitylabs.sdk.e2e.config.CucumberConfiguration; -import org.unicitylabs.sdk.e2e.context.TestContext; -import org.unicitylabs.sdk.e2e.steps.shared.StepHelper; -import org.unicitylabs.sdk.hash.DataHasher; -import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.MaskedPredicate; -import org.unicitylabs.sdk.token.TokenState; -import org.unicitylabs.sdk.transaction.InclusionProof; -import org.unicitylabs.sdk.transaction.Transaction; -import org.unicitylabs.sdk.transaction.TransferCommitment; -import org.unicitylabs.sdk.transaction.TransferTransactionData; -import org.unicitylabs.sdk.util.InclusionProofUtils; -import org.unicitylabs.sdk.utils.TestUtils; -import org.unicitylabs.sdk.signing.SigningService; -import org.unicitylabs.sdk.token.Token; -import org.unicitylabs.sdk.token.TokenId; -import org.unicitylabs.sdk.token.TokenType; -import org.unicitylabs.sdk.token.fungible.TokenCoinData; -import org.unicitylabs.sdk.utils.helpers.PendingTransfer; -import io.cucumber.java.en.Given; -import io.cucumber.java.en.When; -import io.cucumber.java.en.Then; -import io.cucumber.java.en.And; - -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.*; - -import static org.unicitylabs.sdk.utils.TestUtils.randomBytes; -import static org.junit.jupiter.api.Assertions.*; - -/** - * Advanced step definitions for complex scenarios and edge cases. - */ -public class AdvancedStepDefinitions { - - private final TestContext context; - - public AdvancedStepDefinitions() { - this.context = CucumberConfiguration.getTestContext(); - } - - StepHelper helper = new StepHelper(); - - @When("the token is transferred through the chain of existing users") - public void theTokenIsTransferredThroughTheChain() throws Exception { - List users = new ArrayList<>(context.getUserSigningServices().keySet()); - - // remove the one that should go first (if it’s already inside) - users.removeAll(context.getTransferChain()); - - // prepend the transfer chain at the beginning - List orderedUsers = new ArrayList<>(); - orderedUsers.addAll(context.getTransferChain()); // first element(s) - orderedUsers.addAll(users); - - - Token currentToken = context.getChainToken(); - - for (int i = 0; i < orderedUsers.size() - 1; i++) { - String fromUser = orderedUsers.get(i); - String toUser = orderedUsers.get(i + 1); - - SigningService fromSigningService = context.getUserSigningServices().get(fromUser); - SigningService toSigningService = context.getUserSigningServices().get(toUser); - byte[] toNonce = context.getUserNonces().get(toUser); - - // Create a simple direct address for transfer - var toPredicate = org.unicitylabs.sdk.predicate.MaskedPredicate.create( - toSigningService, - org.unicitylabs.sdk.hash.HashAlgorithm.SHA256, - toNonce - ); - var toAddress = toPredicate.getReference(currentToken.getType()).toAddress(); - - String customData = "Transfer from " + fromUser + " to " + toUser; - System.out.println(customData); - context.getTransferCustomData().put(toUser, customData); - - currentToken = TestUtils.transferToken( - context.getClient(), - currentToken, - fromSigningService, - toSigningService, - toNonce, - toAddress, - customData.getBytes(StandardCharsets.UTF_8), - List.of() - ); - - context.getTransferChain().add(toUser); - } - - context.setChainToken(currentToken); - } - - @Then("the final token should maintain original properties") - public void theFinalTokenShouldMaintainOriginalProperties() { - assertNotNull(context.getChainToken(), "Final token should exist"); - assertTrue(context.getChainToken().verify().isSuccessful(), "Final token should be valid"); - - // Additional property validation can be added based on requirements - } - - @And("all intermediate transfers should be recorded correctly") - public void allIntermediateTransfersShouldBeRecordedCorrectly() { - assertEquals(4, context.getTransferChain().size(), "Transfer chain should have 4 users"); - assertEquals("Alice", context.getTransferChain().get(0), "Chain should start with Alice"); - assertEquals("Bob", context.getTransferChain().get(3), "Chain should end with Dave"); - } - - @And("the token should have transfers in history") - public void theTokenShouldHaveTransfersInHistory(int expectedTransfers) { - int actualTransfers = context.getChainToken().getTransactions().size() - 1; // Subtract mint transaction - assertEquals(expectedTransfers, actualTransfers, "Token should have expected number of transfers"); - } - - // Name Tag Scenarios Steps - @Given("{string} creates {int} name tag tokens with different addresses") - public void createsNameTagTokensWithDifferentAddresses(String username, int nametagCount) throws Exception { - List bobNametags = new ArrayList<>(); - - for (int i = 0; i < nametagCount; i++) { - String tokenIdentifier = TestUtils.generateRandomString(10) + i; - Token nametagToken = helper.createNameTagTokenForUser( - username, - TestUtils.createTokenTypeFromString(tokenIdentifier), - tokenIdentifier, - TestUtils.generateRandomString(10) - ); - bobNametags.add(nametagToken); - } - - context.getNameTagTokens().put(username, bobNametags); - } - - @When("{string} transfers tokens to each of {string} name tags") - public void userTransfersTokensToEachOfBobsNameTags(String fromUser, String toUser) throws Exception { - List nametagTokens = context.getNameTagTokens().get(toUser); - - // Create tokens for Alice to transfer - for (int i = 0; i < nametagTokens.size(); i++) { - TokenId tokenId = TestUtils.generateRandomTokenId(); - TokenCoinData coinData = TestUtils.createRandomCoinData(1); - - Token aliceToken = TestUtils.mintTokenForUser( - context.getClient(), - context.getUserSigningServices().get(fromUser), - context.getUserNonces().get(fromUser), - tokenId, - nametagTokens.get(i).getType(), - coinData - ); - - // Transfer to Bob's nametag - ProxyAddress proxyAddress = ProxyAddress.create(nametagTokens.get(i).getId()); - - helper.transferToken( - fromUser, - toUser, - aliceToken, - proxyAddress, - null - ); - } - } - - @And("{string} consolidates all received tokens") - public void userConsolidatesAllReceivedTokens(String username) { - // Consolidation logic would depend on specific requirements - // For now, we ensure Bob has received all the tokens - List tokens = context.getUserTokens().getOrDefault(username, new ArrayList<>()); - // Verify Bob has received tokens - assertFalse(tokens.isEmpty(), "Bob should have received tokens"); - } - - @Then("{string} should own {int} tokens") - public void userShouldOwnTokens(String username, int expectedTokenCount) { - List tokens = context.getUserTokens().getOrDefault(username, new ArrayList<>()); - assertEquals(expectedTokenCount, tokens.size(), "Bob should own expected number of tokens"); - - // Verify ownership - for (Token token : tokens) { - SigningService signingService = SigningService.createFromSecret( - context.getUserSecret().get(username) - ); - assertTrue(token.verify().isSuccessful(), "Token should be valid"); - assertTrue(TestUtils.validateTokenOwnership(token, signingService), - "Bob should own all tokens"); - } - } - - @And("all {string} nametag tokens should remain valid") - public void allNameTagTokensShouldRemainValid(String username) { - List bobNametags = context.getNameTagTokens().get(username); - for (Token nametag : bobNametags) { - assertTrue(nametag.verify().isSuccessful(), "All name tag tokens should remain valid"); - } - } - - @And("proxy addressing should work for all {string} name tags") - public void proxyAddressingShouldWorkForAllNameTags(String username) { - List bobNametags = context.getNameTagTokens().get(username); - - for (Token nametag : bobNametags) { - var proxyAddress = org.unicitylabs.sdk.address.ProxyAddress.create(nametag.getId()); - assertNotNull(proxyAddress, "Proxy address should be creatable for all name tags"); - } - } - - // Large Data Handling Steps - @Given("a token with custom data of size {int} bytes") - public void aTokenWithCustomDataOfSizeBytes(int dataSize) throws Exception { - String alice = "Alice"; - byte[] largeData = new byte[dataSize]; - Arrays.fill(largeData, (byte) 'A'); // Fill with 'A' characters - - TokenId tokenId = TestUtils.generateRandomTokenId(); - TokenType tokenType = TestUtils.generateRandomTokenType(); - TokenCoinData coinData = TestUtils.createRandomCoinData(1); - - // Create token with large custom data - MaskedPredicate predicate = MaskedPredicate.create( - context.getUserSigningServices().get(alice), - org.unicitylabs.sdk.hash.HashAlgorithm.SHA256, - context.getUserNonces().get(alice) - ); - - var tokenState = new org.unicitylabs.sdk.token.TokenState(predicate, largeData); - - // Store for later use in transfer - context.setChainToken(TestUtils.mintTokenForUser( - context.getClient(), - context.getUserSigningServices().get(alice), - context.getUserNonces().get(alice), - tokenId, - tokenType, - coinData - )); - } - - @And("{string} finalizes all received tokens") - public void finalizesAllReceivedTokens(String username) throws Exception { - List pendingTransfers = context.getPendingTransfers(username); - - for (PendingTransfer pending : pendingTransfers) { - Token token = pending.getSourceToken(); - Transaction tx = pending.getTransaction(); - helper.finalizeTransfer( - username, - token, - tx - ); - } - context.clearPendingTransfers(username); - } -} \ No newline at end of file +//package org.unicitylabs.sdk.e2e.steps; +// +//import org.unicitylabs.sdk.StateTransitionClient; +//import org.unicitylabs.sdk.address.ProxyAddress; +//import org.unicitylabs.sdk.api.AggregatorClient; +//import org.unicitylabs.sdk.api.SubmitCommitmentResponse; +//import org.unicitylabs.sdk.api.SubmitCommitmentStatus; +//import org.unicitylabs.sdk.e2e.config.CucumberConfiguration; +//import org.unicitylabs.sdk.e2e.context.TestContext; +//import org.unicitylabs.sdk.e2e.steps.shared.StepHelper; +//import org.unicitylabs.sdk.hash.DataHasher; +//import org.unicitylabs.sdk.hash.HashAlgorithm; +//import org.unicitylabs.sdk.predicate.MaskedPredicate; +//import org.unicitylabs.sdk.token.TokenState; +//import org.unicitylabs.sdk.transaction.InclusionProof; +//import org.unicitylabs.sdk.transaction.Transaction; +//import org.unicitylabs.sdk.transaction.TransferCommitment; +//import org.unicitylabs.sdk.transaction.TransferTransactionData; +//import org.unicitylabs.sdk.util.InclusionProofUtils; +//import org.unicitylabs.sdk.utils.TestUtils; +//import org.unicitylabs.sdk.signing.SigningService; +//import org.unicitylabs.sdk.token.Token; +//import org.unicitylabs.sdk.token.TokenId; +//import org.unicitylabs.sdk.token.TokenType; +//import org.unicitylabs.sdk.token.fungible.TokenCoinData; +//import org.unicitylabs.sdk.utils.helpers.PendingTransfer; +//import io.cucumber.java.en.Given; +//import io.cucumber.java.en.When; +//import io.cucumber.java.en.Then; +//import io.cucumber.java.en.And; +// +//import java.math.BigInteger; +//import java.nio.charset.StandardCharsets; +//import java.util.*; +//import java.util.concurrent.*; +// +//import static org.unicitylabs.sdk.utils.TestUtils.randomBytes; +//import static org.junit.jupiter.api.Assertions.*; +// +///** +// * Advanced step definitions for complex scenarios and edge cases. +// */ +//public class AdvancedStepDefinitions { +// +// private final TestContext context; +// +// public AdvancedStepDefinitions() { +// this.context = CucumberConfiguration.getTestContext(); +// } +// +// StepHelper helper = new StepHelper(); +// +// @When("the token is transferred through the chain of existing users") +// public void theTokenIsTransferredThroughTheChain() throws Exception { +// List users = new ArrayList<>(context.getUserSigningServices().keySet()); +// +// // remove the one that should go first (if it’s already inside) +// users.removeAll(context.getTransferChain()); +// +// // prepend the transfer chain at the beginning +// List orderedUsers = new ArrayList<>(); +// orderedUsers.addAll(context.getTransferChain()); // first element(s) +// orderedUsers.addAll(users); +// +// +// Token currentToken = context.getChainToken(); +// +// for (int i = 0; i < orderedUsers.size() - 1; i++) { +// String fromUser = orderedUsers.get(i); +// String toUser = orderedUsers.get(i + 1); +// +// SigningService fromSigningService = context.getUserSigningServices().get(fromUser); +// SigningService toSigningService = context.getUserSigningServices().get(toUser); +// byte[] toNonce = context.getUserNonces().get(toUser); +// +// // Create a simple direct address for transfer +// var toPredicate = org.unicitylabs.sdk.predicate.MaskedPredicate.create( +// toSigningService, +// org.unicitylabs.sdk.hash.HashAlgorithm.SHA256, +// toNonce +// ); +// var toAddress = toPredicate.getReference(currentToken.getType()).toAddress(); +// +// String customData = "Transfer from " + fromUser + " to " + toUser; +// System.out.println(customData); +// context.getTransferCustomData().put(toUser, customData); +// +// currentToken = TestUtils.transferToken( +// context.getClient(), +// currentToken, +// fromSigningService, +// toSigningService, +// toNonce, +// toAddress, +// customData.getBytes(StandardCharsets.UTF_8), +// List.of() +// ); +// +// context.getTransferChain().add(toUser); +// } +// +// context.setChainToken(currentToken); +// } +// +// @Then("the final token should maintain original properties") +// public void theFinalTokenShouldMaintainOriginalProperties() { +// assertNotNull(context.getChainToken(), "Final token should exist"); +// assertTrue(context.getChainToken().verify().isSuccessful(), "Final token should be valid"); +// +// // Additional property validation can be added based on requirements +// } +// +// @And("all intermediate transfers should be recorded correctly") +// public void allIntermediateTransfersShouldBeRecordedCorrectly() { +// assertEquals(4, context.getTransferChain().size(), "Transfer chain should have 4 users"); +// assertEquals("Alice", context.getTransferChain().get(0), "Chain should start with Alice"); +// assertEquals("Bob", context.getTransferChain().get(3), "Chain should end with Dave"); +// } +// +// @And("the token should have transfers in history") +// public void theTokenShouldHaveTransfersInHistory(int expectedTransfers) { +// int actualTransfers = context.getChainToken().getTransactions().size() - 1; // Subtract mint transaction +// assertEquals(expectedTransfers, actualTransfers, "Token should have expected number of transfers"); +// } +// +// // Name Tag Scenarios Steps +// @Given("{string} creates {int} name tag tokens with different addresses") +// public void createsNameTagTokensWithDifferentAddresses(String username, int nametagCount) throws Exception { +// List bobNametags = new ArrayList<>(); +// +// for (int i = 0; i < nametagCount; i++) { +// String tokenIdentifier = TestUtils.generateRandomString(10) + i; +// Token nametagToken = helper.createNameTagTokenForUser( +// username, +// TestUtils.createTokenTypeFromString(tokenIdentifier), +// tokenIdentifier, +// TestUtils.generateRandomString(10) +// ); +// bobNametags.add(nametagToken); +// } +// +// context.getNameTagTokens().put(username, bobNametags); +// } +// +// @When("{string} transfers tokens to each of {string} name tags") +// public void userTransfersTokensToEachOfBobsNameTags(String fromUser, String toUser) throws Exception { +// List nametagTokens = context.getNameTagTokens().get(toUser); +// +// // Create tokens for Alice to transfer +// for (int i = 0; i < nametagTokens.size(); i++) { +// TokenId tokenId = TestUtils.generateRandomTokenId(); +// TokenCoinData coinData = TestUtils.createRandomCoinData(1); +// +// Token aliceToken = TestUtils.mintTokenForUser( +// context.getClient(), +// context.getUserSigningServices().get(fromUser), +// context.getUserNonces().get(fromUser), +// tokenId, +// nametagTokens.get(i).getType(), +// coinData +// ); +// +// // Transfer to Bob's nametag +// ProxyAddress proxyAddress = ProxyAddress.create(nametagTokens.get(i).getId()); +// +// helper.transferToken( +// fromUser, +// toUser, +// aliceToken, +// proxyAddress, +// null +// ); +// } +// } +// +// @And("{string} consolidates all received tokens") +// public void userConsolidatesAllReceivedTokens(String username) { +// // Consolidation logic would depend on specific requirements +// // For now, we ensure Bob has received all the tokens +// List tokens = context.getUserTokens().getOrDefault(username, new ArrayList<>()); +// // Verify Bob has received tokens +// assertFalse(tokens.isEmpty(), "Bob should have received tokens"); +// } +// +// @Then("{string} should own {int} tokens") +// public void userShouldOwnTokens(String username, int expectedTokenCount) { +// List tokens = context.getUserTokens().getOrDefault(username, new ArrayList<>()); +// assertEquals(expectedTokenCount, tokens.size(), "Bob should own expected number of tokens"); +// +// // Verify ownership +// for (Token token : tokens) { +// SigningService signingService = SigningService.createFromSecret( +// context.getUserSecret().get(username) +// ); +// assertTrue(token.verify().isSuccessful(), "Token should be valid"); +// assertTrue(TestUtils.validateTokenOwnership(token, signingService), +// "Bob should own all tokens"); +// } +// } +// +// @And("all {string} nametag tokens should remain valid") +// public void allNameTagTokensShouldRemainValid(String username) { +// List bobNametags = context.getNameTagTokens().get(username); +// for (Token nametag : bobNametags) { +// assertTrue(nametag.verify().isSuccessful(), "All name tag tokens should remain valid"); +// } +// } +// +// @And("proxy addressing should work for all {string} name tags") +// public void proxyAddressingShouldWorkForAllNameTags(String username) { +// List bobNametags = context.getNameTagTokens().get(username); +// +// for (Token nametag : bobNametags) { +// var proxyAddress = org.unicitylabs.sdk.address.ProxyAddress.create(nametag.getId()); +// assertNotNull(proxyAddress, "Proxy address should be creatable for all name tags"); +// } +// } +// +// // Large Data Handling Steps +// @Given("a token with custom data of size {int} bytes") +// public void aTokenWithCustomDataOfSizeBytes(int dataSize) throws Exception { +// String alice = "Alice"; +// byte[] largeData = new byte[dataSize]; +// Arrays.fill(largeData, (byte) 'A'); // Fill with 'A' characters +// +// TokenId tokenId = TestUtils.generateRandomTokenId(); +// TokenType tokenType = TestUtils.generateRandomTokenType(); +// TokenCoinData coinData = TestUtils.createRandomCoinData(1); +// +// // Create token with large custom data +// MaskedPredicate predicate = MaskedPredicate.create( +// context.getUserSigningServices().get(alice), +// org.unicitylabs.sdk.hash.HashAlgorithm.SHA256, +// context.getUserNonces().get(alice) +// ); +// +// var tokenState = new org.unicitylabs.sdk.token.TokenState(predicate, largeData); +// +// // Store for later use in transfer +// context.setChainToken(TestUtils.mintTokenForUser( +// context.getClient(), +// context.getUserSigningServices().get(alice), +// context.getUserNonces().get(alice), +// tokenId, +// tokenType, +// coinData +// )); +// } +// +// @And("{string} finalizes all received tokens") +// public void finalizesAllReceivedTokens(String username) throws Exception { +// List pendingTransfers = context.getPendingTransfers(username); +// +// for (PendingTransfer pending : pendingTransfers) { +// Token token = pending.getSourceToken(); +// Transaction tx = pending.getTransaction(); +// helper.finalizeTransfer( +// username, +// token, +// tx +// ); +// } +// context.clearPendingTransfers(username); +// } +//} \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/e2e/steps/StepDefinitions.java b/src/test/java/org/unicitylabs/sdk/e2e/steps/StepDefinitions.java index f5f536e..99b2c89 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/steps/StepDefinitions.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/steps/StepDefinitions.java @@ -1,344 +1,344 @@ -package org.unicitylabs.sdk.e2e.steps; - -import org.unicitylabs.sdk.address.ProxyAddress; -import org.unicitylabs.sdk.e2e.config.CucumberConfiguration; -import org.unicitylabs.sdk.e2e.context.TestContext; -import org.unicitylabs.sdk.e2e.steps.shared.StepHelper; -import org.unicitylabs.sdk.token.fungible.CoinId; -import org.unicitylabs.sdk.utils.TestUtils; -import org.unicitylabs.sdk.signing.SigningService; -import org.unicitylabs.sdk.token.Token; -import org.unicitylabs.sdk.token.TokenId; -import org.unicitylabs.sdk.token.TokenType; -import org.unicitylabs.sdk.token.fungible.TokenCoinData; -import io.cucumber.java.en.And; -import io.cucumber.java.en.Given; -import io.cucumber.java.en.Then; -import io.cucumber.java.en.When; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.*; -import java.util.stream.Collectors; - - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Refactored step definitions that use TestContext and SharedStepDefinitions. - * These steps handle specific scenarios that aren't covered by the shared steps. - */ -public class StepDefinitions { - - private final TestContext context; - - public StepDefinitions() { - this.context = CucumberConfiguration.getTestContext(); - } - - StepHelper helper = new StepHelper(); - - // Token Properties Validation - @And("the token should maintain its original ID and type") - public void theTokenShouldMaintainItsOriginalIdAndType() { - String currentUser = context.getCurrentUser(); - if (currentUser == null) currentUser = "Alice"; // fallback - - // Get the first user's token (original) and current user's token - List userNames = new ArrayList<>(context.getUserTokens().keySet()); - if (userNames.size() >= 2) { - Token originalToken = context.getUserToken(userNames.get(0)); - Token currentToken = context.getUserToken(currentUser); - - if (originalToken != null && currentToken != null) { - assertEquals(originalToken.getId(), currentToken.getId(), "Token ID should remain the same"); - assertEquals(originalToken.getType(), currentToken.getType(), "Token type should remain the same"); - } - } - } - - @And("the token should have {int} transactions in its history") - public void theTokenShouldHaveTransactionsInItsHistory(int expectedTransactionCount) { - String currentUser = context.getCurrentUser(); - if (currentUser == null) { - // Find the last user who received a token - List userNames = new ArrayList<>(context.getUserTokens().keySet()); - currentUser = userNames.get(userNames.size() - 1); - } - - Token token = context.getUserToken(currentUser); - assertNotNull(token, "Token should exist for validation"); - assertEquals(expectedTransactionCount, token.getTransactions().size(), // Subtract mint transaction - "Token should have the expected number of transactions"); - } - - // Minting with Parameters - @Given("user {string} with nonce of {int} bytes") - public void userWithNonceOfBytes(String userName, int nonceLength) { - byte[] nonce = TestUtils.generateRandomBytes(nonceLength); - SigningService signingService = TestUtils.createSigningServiceForUser(userName, nonce); - - context.getUserSigningServices().put(userName, signingService); - context.getUserNonces().put(userName, nonce); - context.getUserTokens().put(userName, new ArrayList<>()); - context.setCurrentUser(userName); - } - - @When("the user mints a token of type {string} with coin data containing {int} coins") - public void theUserMintsATokenOfTypeWithCoinDataContainingCoins(String tokenType, int coinCount) throws Exception { - String user = context.getCurrentUser(); - TokenId tokenId = TestUtils.generateRandomTokenId(); - TokenType type = TestUtils.createTokenTypeFromString(tokenType); - TokenCoinData coinData = TestUtils.createRandomCoinData(coinCount); - - Token token = TestUtils.mintTokenForUser( - context.getClient(), - context.getUserSigningServices().get(user), - context.getUserNonces().get(user), - tokenId, - type, - coinData - ); - context.addUserToken(user, token); - } - - @Then("the token should be minted successfully") - public void theTokenShouldBeMintedSuccessfully() { - String user = context.getCurrentUser(); - Token token = context.getUserToken(user); - assertNotNull(token, "Token should be minted"); - } - - @And("the token should be verified successfully") - public void theTokenShouldBeVerifiedSuccessfully() { - String user = context.getCurrentUser(); - Token token = context.getUserToken(user); - assertTrue(token.verify().isSuccessful(), "Token should be verified successfully"); - } - - @And("the token should belong to the user") - public void theTokenShouldBelongToTheUser() { - String user = context.getCurrentUser(); - Token token = context.getUserToken(user); - SigningService signingService = context.getUserSigningServices().get(user); - - assertTrue(TestUtils.validateTokenOwnership(token, signingService), - "Token should belong to the user"); - } - - @Then("the name tag token should be created successfully") - public void theNameTagTokenShouldBeCreatedSuccessfully() { - String user = context.getCurrentUser(); - Token nametagToken = context.getNameTagToken(user); - assertNotNull(nametagToken, "Name tag token should be created"); - assertTrue(nametagToken.verify().isSuccessful(), "Name tag token should be valid"); - } - - @And("the name tag should be usable for proxy addressing") - public void theNameTagShouldBeUsableForProxyAddressing() { - String user = context.getCurrentUser(); - Token nametagToken = context.getNameTagToken(user); - ProxyAddress proxyAddress = ProxyAddress.create(nametagToken.getId()); - assertNotNull(proxyAddress, "Proxy address should be creatable from name tag"); - } - - // Bulk Operations - @Given("{int} users are configured for bulk operations") - public void usersAreConfiguredForBulkOperations(int userCount) { - context.setConfiguredUserCount(userCount); - - // Setup additional users if needed - for (int i = 0; i < userCount; i++) { - String userName = "BulkUser" + i; - TestUtils.setupUser(userName, context.getUserSigningServices(), context.getUserNonces(), context.getUserSecret()); - context.getUserTokens().put(userName, new ArrayList<>()); - } - } - - @When("each user mints {int} tokens simultaneously") - public void eachUserMintsTokensSimultaneously(int tokensPerUser) throws Exception { - context.setConfiguredTokensPerUser(tokensPerUser); - - //Lower the thread pool size to avoid overload - int poolSize = Math.min(500, context.getConfiguredUserCount() * 2); - ExecutorService executor = Executors.newFixedThreadPool(poolSize); - - //ExecutorService executor = Executors.newFixedThreadPool(context.getConfiguredUserCount() * 2); - List> futures = new ArrayList<>(); - Map, String> futureOwners = new ConcurrentHashMap<>(); - - long startTime = System.currentTimeMillis(); - - // Create minting tasks for each user - for (int userIndex = 0; userIndex < context.getConfiguredUserCount(); userIndex++) { - String userName = "BulkUser" + userIndex; - SigningService signingService = context.getUserSigningServices().get(userName); - byte[] nonce = context.getUserNonces().get(userName); - - for (int tokenIndex = 0; tokenIndex < tokensPerUser; tokenIndex++) { - String requestId = userName + "-token" + tokenIndex; // helpful identifier - - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - TokenId tokenId = TestUtils.generateRandomTokenId(); - TokenType tokenType = TestUtils.generateRandomTokenType(); - TokenCoinData coinData = TestUtils.createRandomCoinData(2); - - Token token = TestUtils.mintTokenForUser(context.getClient(), signingService, nonce, tokenId, tokenType, coinData); - System.out.println(token.getGenesis().getData().getSourceState()); - // do post-processing here (still in parallel) - for (var entry : context.getUserSigningServices().entrySet()) { - if (TestUtils.validateTokenOwnership(token, entry.getValue())) { - context.addUserToken(entry.getKey(), token); - break; - } - } - System.out.println("[Collector] Got result from " + requestId + - " on thread " + Thread.currentThread().getName()); - return TestUtils.TokenOperationResult.success("Token minted successfully (" + requestId + ")", token); - } catch (Exception e) { - e.printStackTrace(); - System.out.println("[Collector] Failed " + requestId + " on thread " + Thread.currentThread().getName() + - " with " + e.getMessage()); - return TestUtils.TokenOperationResult.failure("Failed to mint token (" + requestId + ")", e); - } - }, executor).orTimeout(30, TimeUnit.SECONDS) - .exceptionally(ex -> TestUtils.TokenOperationResult.failure("Timeout (" + requestId + ")", (Exception) ex));; - - futures.add(future); - futureOwners.put(future, requestId); - } - } - - // Start monitoring thread - ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor(); - monitor.scheduleAtFixedRate(() -> { - long doneCount = futures.stream().filter(CompletableFuture::isDone).count(); - long total = futures.size(); - long pending = total - doneCount; - - System.out.println("[Monitor] " + doneCount + "/" + total + " completed, " + pending + " still pending"); - - if (pending == 0) { - System.out.println("[Monitor] All requests completed. Stopping monitor."); - monitor.shutdown(); // ✅ stop the monitor here - } - - // After 15s, dump details of stuck ones - if (System.currentTimeMillis() - startTime > 15_000 && pending > 0) { - List pendingRequests = futures.stream() - .filter(f -> !f.isDone()) - .map(futureOwners::get) - .collect(Collectors.toList()); - System.out.println("[Monitor] Still waiting for requests: " + pendingRequests); - } - }, 5, 5, TimeUnit.SECONDS); - - // Wait for all operations to complete and collect results - List results = futures.stream() - .map(CompletableFuture::join) - .collect(Collectors.toList()); - - long successes = results.stream().filter(TestUtils.TokenOperationResult::isSuccess).count(); - long failures = results.size() - successes; - - System.out.println("[Summary] Successes: " + successes + ", Failures: " + failures); - - long endTime = System.currentTimeMillis(); - context.setBulkResults(results); - context.setBulkOperationDuration(endTime - startTime); - - executor.shutdown(); - } - - @And("all tokens are verified in parallel") - public void allTokensAreVerifiedInParallel() { - // Verification is already done during token creation - long successfulTokens = context.getBulkResults().stream() - .mapToLong(result -> result.isSuccess() ? 1 : 0) - .sum(); - - System.out.println("Successfully created tokens: " + successfulTokens); - System.out.println("Total operation time: " + context.getBulkOperationDuration() + " ms"); - } - - @Then("all {int} tokens should be created successfully") - public void allTokensShouldBeCreatedSuccessfully(int expectedTotalTokens) { - long successfulTokens = context.getBulkResults().stream() - .mapToLong(result -> result.isSuccess() ? 1 : 0) - .sum(); - - assertEquals(expectedTotalTokens, successfulTokens, - "All tokens should be created successfully"); - } - - @And("the operation should complete within {int} seconds") - public void theOperationShouldCompleteWithinSeconds(int maxSeconds) { - long maxMilliseconds = maxSeconds * 1000L; - TestUtils.PerformanceValidator.validateDuration( - context.getBulkOperationDuration(), - maxMilliseconds, - "Bulk token creation" - ); - } - - @And("the success rate should be at least {int}%") - public void theSuccessRateShouldBeAtLeast(int minSuccessRate) { - long successful = context.getBulkResults().stream() - .mapToLong(result -> result.isSuccess() ? 1 : 0) - .sum(); - long total = context.getBulkResults().size(); - - TestUtils.PerformanceValidator.validateSuccessRate( - successful, - total, - minSuccessRate / 100.0, - "Bulk operations" - ); - } - - // Transfer Chain Operations - @Given("{string} mints a token with {int} coin value") - public void userMintsATokenWithCoinValue(String userName, int coinValue) throws Exception { - TokenId tokenId = TestUtils.generateRandomTokenId(); - TokenType tokenType = TestUtils.generateRandomTokenType(); - - // Create coin data with specified value - TokenCoinData coinData = createCoinDataWithValue(BigInteger.valueOf(coinValue)); - - Token token = TestUtils.mintTokenForUser( - context.getClient(), - context.getUserSigningServices().get(userName), - context.getUserNonces().get(userName), - tokenId, - tokenType, - coinData - ); - - context.setChainToken(token); - context.getTransferChain().add(userName); - context.setCurrentUser(userName); - } - - @And("each transfer includes custom data validation") - public void eachTransferIncludesCustomDataValidation() { - // Validation is included in the transfer process - for (Map.Entry entry : context.getTransferCustomData().entrySet()) { - assertNotNull(entry.getValue(), "Custom data should be present for " + entry.getKey()); - assertTrue(entry.getValue().contains("Transfer from"), "Custom data should have expected format"); - } - } - - @And("the token should have {int} transfers in history") - public void theTokenShouldHaveTransfersInHistory(int expectedTransfers) { - int actualTransfers = context.getChainToken().getTransactions().size(); // Subtract mint transaction - assertEquals(expectedTransfers, actualTransfers, "Token should have expected number of transfers"); - } - - private TokenCoinData createCoinDataWithValue(BigInteger totalValue) { - CoinId coinId = new CoinId(TestUtils.generateRandomBytes(32)); - return new TokenCoinData(java.util.Map.of(coinId, totalValue)); - } -} \ No newline at end of file +//package org.unicitylabs.sdk.e2e.steps; +// +//import org.unicitylabs.sdk.address.ProxyAddress; +//import org.unicitylabs.sdk.e2e.config.CucumberConfiguration; +//import org.unicitylabs.sdk.e2e.context.TestContext; +//import org.unicitylabs.sdk.e2e.steps.shared.StepHelper; +//import org.unicitylabs.sdk.token.fungible.CoinId; +//import org.unicitylabs.sdk.utils.TestUtils; +//import org.unicitylabs.sdk.signing.SigningService; +//import org.unicitylabs.sdk.token.Token; +//import org.unicitylabs.sdk.token.TokenId; +//import org.unicitylabs.sdk.token.TokenType; +//import org.unicitylabs.sdk.token.fungible.TokenCoinData; +//import io.cucumber.java.en.And; +//import io.cucumber.java.en.Given; +//import io.cucumber.java.en.Then; +//import io.cucumber.java.en.When; +// +//import java.math.BigInteger; +//import java.util.ArrayList; +//import java.util.List; +//import java.util.Map; +//import java.util.concurrent.*; +//import java.util.stream.Collectors; +// +// +//import static org.junit.jupiter.api.Assertions.*; +// +///** +// * Refactored step definitions that use TestContext and SharedStepDefinitions. +// * These steps handle specific scenarios that aren't covered by the shared steps. +// */ +//public class StepDefinitions { +// +// private final TestContext context; +// +// public StepDefinitions() { +// this.context = CucumberConfiguration.getTestContext(); +// } +// +// StepHelper helper = new StepHelper(); +// +// // Token Properties Validation +// @And("the token should maintain its original ID and type") +// public void theTokenShouldMaintainItsOriginalIdAndType() { +// String currentUser = context.getCurrentUser(); +// if (currentUser == null) currentUser = "Alice"; // fallback +// +// // Get the first user's token (original) and current user's token +// List userNames = new ArrayList<>(context.getUserTokens().keySet()); +// if (userNames.size() >= 2) { +// Token originalToken = context.getUserToken(userNames.get(0)); +// Token currentToken = context.getUserToken(currentUser); +// +// if (originalToken != null && currentToken != null) { +// assertEquals(originalToken.getId(), currentToken.getId(), "Token ID should remain the same"); +// assertEquals(originalToken.getType(), currentToken.getType(), "Token type should remain the same"); +// } +// } +// } +// +// @And("the token should have {int} transactions in its history") +// public void theTokenShouldHaveTransactionsInItsHistory(int expectedTransactionCount) { +// String currentUser = context.getCurrentUser(); +// if (currentUser == null) { +// // Find the last user who received a token +// List userNames = new ArrayList<>(context.getUserTokens().keySet()); +// currentUser = userNames.get(userNames.size() - 1); +// } +// +// Token token = context.getUserToken(currentUser); +// assertNotNull(token, "Token should exist for validation"); +// assertEquals(expectedTransactionCount, token.getTransactions().size(), // Subtract mint transaction +// "Token should have the expected number of transactions"); +// } +// +// // Minting with Parameters +// @Given("user {string} with nonce of {int} bytes") +// public void userWithNonceOfBytes(String userName, int nonceLength) { +// byte[] nonce = TestUtils.generateRandomBytes(nonceLength); +// SigningService signingService = TestUtils.createSigningServiceForUser(userName, nonce); +// +// context.getUserSigningServices().put(userName, signingService); +// context.getUserNonces().put(userName, nonce); +// context.getUserTokens().put(userName, new ArrayList<>()); +// context.setCurrentUser(userName); +// } +// +// @When("the user mints a token of type {string} with coin data containing {int} coins") +// public void theUserMintsATokenOfTypeWithCoinDataContainingCoins(String tokenType, int coinCount) throws Exception { +// String user = context.getCurrentUser(); +// TokenId tokenId = TestUtils.generateRandomTokenId(); +// TokenType type = TestUtils.createTokenTypeFromString(tokenType); +// TokenCoinData coinData = TestUtils.createRandomCoinData(coinCount); +// +// Token token = TestUtils.mintTokenForUser( +// context.getClient(), +// context.getUserSigningServices().get(user), +// context.getUserNonces().get(user), +// tokenId, +// type, +// coinData +// ); +// context.addUserToken(user, token); +// } +// +// @Then("the token should be minted successfully") +// public void theTokenShouldBeMintedSuccessfully() { +// String user = context.getCurrentUser(); +// Token token = context.getUserToken(user); +// assertNotNull(token, "Token should be minted"); +// } +// +// @And("the token should be verified successfully") +// public void theTokenShouldBeVerifiedSuccessfully() { +// String user = context.getCurrentUser(); +// Token token = context.getUserToken(user); +// assertTrue(token.verify().isSuccessful(), "Token should be verified successfully"); +// } +// +// @And("the token should belong to the user") +// public void theTokenShouldBelongToTheUser() { +// String user = context.getCurrentUser(); +// Token token = context.getUserToken(user); +// SigningService signingService = context.getUserSigningServices().get(user); +// +// assertTrue(TestUtils.validateTokenOwnership(token, signingService), +// "Token should belong to the user"); +// } +// +// @Then("the name tag token should be created successfully") +// public void theNameTagTokenShouldBeCreatedSuccessfully() { +// String user = context.getCurrentUser(); +// Token nametagToken = context.getNameTagToken(user); +// assertNotNull(nametagToken, "Name tag token should be created"); +// assertTrue(nametagToken.verify().isSuccessful(), "Name tag token should be valid"); +// } +// +// @And("the name tag should be usable for proxy addressing") +// public void theNameTagShouldBeUsableForProxyAddressing() { +// String user = context.getCurrentUser(); +// Token nametagToken = context.getNameTagToken(user); +// ProxyAddress proxyAddress = ProxyAddress.create(nametagToken.getId()); +// assertNotNull(proxyAddress, "Proxy address should be creatable from name tag"); +// } +// +// // Bulk Operations +// @Given("{int} users are configured for bulk operations") +// public void usersAreConfiguredForBulkOperations(int userCount) { +// context.setConfiguredUserCount(userCount); +// +// // Setup additional users if needed +// for (int i = 0; i < userCount; i++) { +// String userName = "BulkUser" + i; +// TestUtils.setupUser(userName, context.getUserSigningServices(), context.getUserNonces(), context.getUserSecret()); +// context.getUserTokens().put(userName, new ArrayList<>()); +// } +// } +// +// @When("each user mints {int} tokens simultaneously") +// public void eachUserMintsTokensSimultaneously(int tokensPerUser) throws Exception { +// context.setConfiguredTokensPerUser(tokensPerUser); +// +// //Lower the thread pool size to avoid overload +// int poolSize = Math.min(500, context.getConfiguredUserCount() * 2); +// ExecutorService executor = Executors.newFixedThreadPool(poolSize); +// +// //ExecutorService executor = Executors.newFixedThreadPool(context.getConfiguredUserCount() * 2); +// List> futures = new ArrayList<>(); +// Map, String> futureOwners = new ConcurrentHashMap<>(); +// +// long startTime = System.currentTimeMillis(); +// +// // Create minting tasks for each user +// for (int userIndex = 0; userIndex < context.getConfiguredUserCount(); userIndex++) { +// String userName = "BulkUser" + userIndex; +// SigningService signingService = context.getUserSigningServices().get(userName); +// byte[] nonce = context.getUserNonces().get(userName); +// +// for (int tokenIndex = 0; tokenIndex < tokensPerUser; tokenIndex++) { +// String requestId = userName + "-token" + tokenIndex; // helpful identifier +// +// CompletableFuture future = CompletableFuture.supplyAsync(() -> { +// try { +// TokenId tokenId = TestUtils.generateRandomTokenId(); +// TokenType tokenType = TestUtils.generateRandomTokenType(); +// TokenCoinData coinData = TestUtils.createRandomCoinData(2); +// +// Token token = TestUtils.mintTokenForUser(context.getClient(), signingService, nonce, tokenId, tokenType, coinData); +// System.out.println(token.getGenesis().getData().getSourceState()); +// // do post-processing here (still in parallel) +// for (var entry : context.getUserSigningServices().entrySet()) { +// if (TestUtils.validateTokenOwnership(token, entry.getValue())) { +// context.addUserToken(entry.getKey(), token); +// break; +// } +// } +// System.out.println("[Collector] Got result from " + requestId + +// " on thread " + Thread.currentThread().getName()); +// return TestUtils.TokenOperationResult.success("Token minted successfully (" + requestId + ")", token); +// } catch (Exception e) { +// e.printStackTrace(); +// System.out.println("[Collector] Failed " + requestId + " on thread " + Thread.currentThread().getName() + +// " with " + e.getMessage()); +// return TestUtils.TokenOperationResult.failure("Failed to mint token (" + requestId + ")", e); +// } +// }, executor).orTimeout(30, TimeUnit.SECONDS) +// .exceptionally(ex -> TestUtils.TokenOperationResult.failure("Timeout (" + requestId + ")", (Exception) ex));; +// +// futures.add(future); +// futureOwners.put(future, requestId); +// } +// } +// +// // Start monitoring thread +// ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor(); +// monitor.scheduleAtFixedRate(() -> { +// long doneCount = futures.stream().filter(CompletableFuture::isDone).count(); +// long total = futures.size(); +// long pending = total - doneCount; +// +// System.out.println("[Monitor] " + doneCount + "/" + total + " completed, " + pending + " still pending"); +// +// if (pending == 0) { +// System.out.println("[Monitor] All requests completed. Stopping monitor."); +// monitor.shutdown(); // ✅ stop the monitor here +// } +// +// // After 15s, dump details of stuck ones +// if (System.currentTimeMillis() - startTime > 15_000 && pending > 0) { +// List pendingRequests = futures.stream() +// .filter(f -> !f.isDone()) +// .map(futureOwners::get) +// .collect(Collectors.toList()); +// System.out.println("[Monitor] Still waiting for requests: " + pendingRequests); +// } +// }, 5, 5, TimeUnit.SECONDS); +// +// // Wait for all operations to complete and collect results +// List results = futures.stream() +// .map(CompletableFuture::join) +// .collect(Collectors.toList()); +// +// long successes = results.stream().filter(TestUtils.TokenOperationResult::isSuccess).count(); +// long failures = results.size() - successes; +// +// System.out.println("[Summary] Successes: " + successes + ", Failures: " + failures); +// +// long endTime = System.currentTimeMillis(); +// context.setBulkResults(results); +// context.setBulkOperationDuration(endTime - startTime); +// +// executor.shutdown(); +// } +// +// @And("all tokens are verified in parallel") +// public void allTokensAreVerifiedInParallel() { +// // Verification is already done during token creation +// long successfulTokens = context.getBulkResults().stream() +// .mapToLong(result -> result.isSuccess() ? 1 : 0) +// .sum(); +// +// System.out.println("Successfully created tokens: " + successfulTokens); +// System.out.println("Total operation time: " + context.getBulkOperationDuration() + " ms"); +// } +// +// @Then("all {int} tokens should be created successfully") +// public void allTokensShouldBeCreatedSuccessfully(int expectedTotalTokens) { +// long successfulTokens = context.getBulkResults().stream() +// .mapToLong(result -> result.isSuccess() ? 1 : 0) +// .sum(); +// +// assertEquals(expectedTotalTokens, successfulTokens, +// "All tokens should be created successfully"); +// } +// +// @And("the operation should complete within {int} seconds") +// public void theOperationShouldCompleteWithinSeconds(int maxSeconds) { +// long maxMilliseconds = maxSeconds * 1000L; +// TestUtils.PerformanceValidator.validateDuration( +// context.getBulkOperationDuration(), +// maxMilliseconds, +// "Bulk token creation" +// ); +// } +// +// @And("the success rate should be at least {int}%") +// public void theSuccessRateShouldBeAtLeast(int minSuccessRate) { +// long successful = context.getBulkResults().stream() +// .mapToLong(result -> result.isSuccess() ? 1 : 0) +// .sum(); +// long total = context.getBulkResults().size(); +// +// TestUtils.PerformanceValidator.validateSuccessRate( +// successful, +// total, +// minSuccessRate / 100.0, +// "Bulk operations" +// ); +// } +// +// // Transfer Chain Operations +// @Given("{string} mints a token with {int} coin value") +// public void userMintsATokenWithCoinValue(String userName, int coinValue) throws Exception { +// TokenId tokenId = TestUtils.generateRandomTokenId(); +// TokenType tokenType = TestUtils.generateRandomTokenType(); +// +// // Create coin data with specified value +// TokenCoinData coinData = createCoinDataWithValue(BigInteger.valueOf(coinValue)); +// +// Token token = TestUtils.mintTokenForUser( +// context.getClient(), +// context.getUserSigningServices().get(userName), +// context.getUserNonces().get(userName), +// tokenId, +// tokenType, +// coinData +// ); +// +// context.setChainToken(token); +// context.getTransferChain().add(userName); +// context.setCurrentUser(userName); +// } +// +// @And("each transfer includes custom data validation") +// public void eachTransferIncludesCustomDataValidation() { +// // Validation is included in the transfer process +// for (Map.Entry entry : context.getTransferCustomData().entrySet()) { +// assertNotNull(entry.getValue(), "Custom data should be present for " + entry.getKey()); +// assertTrue(entry.getValue().contains("Transfer from"), "Custom data should have expected format"); +// } +// } +// +// @And("the token should have {int} transfers in history") +// public void theTokenShouldHaveTransfersInHistory(int expectedTransfers) { +// int actualTransfers = context.getChainToken().getTransactions().size(); // Subtract mint transaction +// assertEquals(expectedTransfers, actualTransfers, "Token should have expected number of transfers"); +// } +// +// private TokenCoinData createCoinDataWithValue(BigInteger totalValue) { +// CoinId coinId = new CoinId(TestUtils.generateRandomBytes(32)); +// return new TokenCoinData(java.util.Map.of(coinId, totalValue)); +// } +//} \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/e2e/steps/shared/SharedStepDefinitions.java b/src/test/java/org/unicitylabs/sdk/e2e/steps/shared/SharedStepDefinitions.java index 8c84257..0961bef 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/steps/shared/SharedStepDefinitions.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/steps/shared/SharedStepDefinitions.java @@ -1,616 +1,616 @@ -package org.unicitylabs.sdk.e2e.steps.shared; - -import org.unicitylabs.sdk.StateTransitionClient; -import org.unicitylabs.sdk.address.DirectAddress; -import org.unicitylabs.sdk.address.ProxyAddress; -import org.unicitylabs.sdk.api.*; -import org.unicitylabs.sdk.e2e.config.CucumberConfiguration; -import org.unicitylabs.sdk.e2e.context.TestContext; -import org.unicitylabs.sdk.predicate.UnmaskedPredicate; -import org.unicitylabs.sdk.transaction.*; -import org.unicitylabs.sdk.utils.TestUtils; -import org.unicitylabs.sdk.hash.DataHash; -import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.signing.SigningService; -import org.unicitylabs.sdk.token.Token; -import org.unicitylabs.sdk.token.TokenId; -import org.unicitylabs.sdk.token.TokenType; -import org.unicitylabs.sdk.token.fungible.TokenCoinData; -import io.cucumber.datatable.DataTable; -import io.cucumber.java.en.And; -import io.cucumber.java.en.Given; -import io.cucumber.java.en.Then; -import io.cucumber.java.en.When; - -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.*; -import java.util.stream.Collectors; - -import static org.unicitylabs.sdk.utils.TestUtils.randomCoinData; -import static org.junit.jupiter.api.Assertions.*; - -import org.unicitylabs.sdk.utils.helpers.CommitmentResult; - - -/** - * Shared step definitions that can be reused across multiple feature files. - * These steps use TestContext to maintain state and avoid duplication. - */ -public class SharedStepDefinitions { - - private final TestContext context; - - public SharedStepDefinitions() { // ✅ Public zero-argument constructor - this.context = CucumberConfiguration.getTestContext(); - } - - StepHelper helper = new StepHelper(); - - // Setup Steps - @Given("the aggregator URL is configured") - public void theAggregatorUrlIsConfigured() { - String aggregatorUrl = System.getenv("AGGREGATOR_URL"); - assertNotNull(aggregatorUrl, "AGGREGATOR_URL environment variable must be set"); - context.setAggregatorClient(new AggregatorClient(aggregatorUrl)); - } - - @And("the aggregator client is initialized") - public void theAggregatorClientIsInitialized() { - assertNotNull(context.getAggregatorClient(), "Aggregator client should be initialized"); - } - - @And("the state transition client is initialized") - public void theStateTransitionClientIsInitialized() { - context.setClient(new StateTransitionClient(context.getAggregatorClient())); - assertNotNull(context.getClient(), "State transition client should be initialized"); - } - - @And("the following users are set up with their signing services") - public void usersAreSetUpWithTheirSigningServices(DataTable dataTable) { - List users = dataTable.asList(); - for (String user : users) { - TestUtils.setupUser(user, context.getUserSigningServices(), context.getUserNonces(), context.getUserSecret()); - context.getUserTokens().put(user, new ArrayList<>()); - } - } - - // Aggregator Operations - @When("I request the current block height") - public void iRequestTheCurrentBlockHeight() throws Exception { - Long blockHeight = context.getAggregatorClient().getBlockHeight().get(); - context.setBlockHeight(blockHeight); - } - - @Then("the block height should be returned") - public void theBlockHeightShouldBeReturned() { - assertNotNull(context.getBlockHeight(), "Block height should not be null"); - } - - @And("the block height should be greater than {int}") - public void theBlockHeightShouldBeGreaterThan(int minHeight) { - assertTrue(context.getBlockHeight() > minHeight, "Block height should be greater than " + minHeight); - } - - // Commitment Operations - @Given("a random secret of {int} bytes") - public void aRandomSecretOfBytes(int secretLength) { - byte[] randomSecret = TestUtils.generateRandomBytes(secretLength); - context.setRandomSecret(randomSecret); - assertNotNull(randomSecret); - assertEquals(secretLength, randomSecret.length); - } - - @And("a state hash from {int} bytes of random data") - public void aStateHashFromBytesOfRandomData(int stateLength) { - byte[] stateBytes = TestUtils.generateRandomBytes(stateLength); - DataHash stateHash = TestUtils.hashData(stateBytes); - context.setStateBytes(stateBytes); - context.setStateHash(stateHash); - assertNotNull(stateHash); - } - - @And("transaction data {string}") - public void transactionData(String txData) { - DataHash txDataHash = TestUtils.hashData(txData.getBytes(StandardCharsets.UTF_8)); - context.setTxDataHash(txDataHash); - assertNotNull(txDataHash); - } - - @When("I submit a commitment with the generated data") - public void iSubmitACommitmentWithTheGeneratedData() throws Exception { - long startTime = System.currentTimeMillis(); - - SigningService signingService = SigningService.createFromSecret(context.getRandomSecret()); - var requestId = TestUtils.createRequestId(signingService, context.getStateHash()); - var authenticator = TestUtils.createAuthenticator(signingService, context.getTxDataHash(), context.getStateHash()); - - SubmitCommitmentResponse response = context.getAggregatorClient() - .submitCommitment(requestId, context.getTxDataHash(), authenticator).get(); - context.setCommitmentResponse(response); - - long endTime = System.currentTimeMillis(); - context.setSubmissionDuration(endTime - startTime); - } - - @Then("the commitment should be submitted successfully") - public void theCommitmentShouldBeSubmittedSuccessfully() { - assertNotNull(context.getCommitmentResponse(), "Commitment response should not be null"); - assertEquals(SubmitCommitmentStatus.SUCCESS, context.getCommitmentResponse().getStatus(), - "Commitment should be submitted successfully"); - } - - @And("the submission should complete in less than {int} milliseconds") - public void theSubmissionShouldCompleteInLessThanMilliseconds(int maxDuration) { - assertTrue(context.getSubmissionDuration() < maxDuration, - String.format("Submission took %d ms, should be less than %d ms", - context.getSubmissionDuration(), maxDuration)); - } - - // Multi-threaded Operations - @Given("I configure {int} threads with {int} commitments each") - public void iConfigureThreadsWithCommitmentsEach(int threadCount, int commitmentsPerThread) { - context.setConfiguredThreadCount(threadCount); - context.setConfiguredCommitmentsPerThread(commitmentsPerThread); - - // Reuse existing user setup to create users - context.setConfiguredUserCount(threadCount); - - // Setup additional users if needed - for (int i = 0; i < threadCount; i++) { - String userName = "BulkUser" + i; - TestUtils.setupUser(userName, context.getUserSigningServices(), context.getUserNonces(), context.getUserSecret()); - context.getUserTokens().put(userName, new ArrayList<>()); - } - } - - @When("I submit all mint commitments concurrently") - public void iSubmitAllMintCommitmentsConcurrently() throws Exception { - int threadsCount = context.getConfiguredThreadCount(); - int commitmentsPerThread = context.getConfiguredCommitmentsPerThread(); - - Map userSigningServices = context.getUserSigningServices(); - ExecutorService executor = Executors.newFixedThreadPool(threadsCount); - - List> futures = new ArrayList<>(); - - for (Map.Entry entry : userSigningServices.entrySet()) { - String userName = entry.getKey(); - SigningService signingService = entry.getValue(); - - for (int i = 0; i < commitmentsPerThread; i++) { - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - long start = System.nanoTime(); - byte[] stateBytes = TestUtils.generateRandomBytes(32); - byte[] txData = TestUtils.generateRandomBytes(32); - - DataHash stateHash = TestUtils.hashData(stateBytes); - DataHash txDataHash = TestUtils.hashData(txData); - RequestId requestId = TestUtils.createRequestId(signingService, stateHash); - - try { - Authenticator authenticator = TestUtils.createAuthenticator(signingService, txDataHash, stateHash); - - SubmitCommitmentResponse response = context.getAggregatorClient() - .submitCommitment(requestId, txDataHash, authenticator).get(); - - boolean success = response.getStatus() == SubmitCommitmentStatus.SUCCESS; - long end = System.nanoTime(); - - return new CommitmentResult(userName, Thread.currentThread().getName(), - requestId, success, start, end); - } catch (Exception e) { - long end = System.nanoTime(); - return new CommitmentResult(userName, Thread.currentThread().getName(), - requestId, false, start, end); - } - }, executor); - - futures.add(future); - } - } - - context.setCommitmentFutures(futures); - - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); - executor.shutdown(); - } - - // Token Operations - @Given("{string} mints a token with random coin data") - public void userMintsATokenWithRandomCoinData(String username) throws Exception { - TokenId tokenId = TestUtils.generateRandomTokenId(); - TokenType tokenType = context.getNameTagToken(context.getCurrentUser()).getType(); - TokenCoinData coinData = randomCoinData(2); - - Token token = TestUtils.mintTokenForUser( - context.getClient(), - context.getUserSigningServices().get(username), - context.getUserNonces().get(username), - tokenId, - tokenType, - coinData - ); - - context.addUserToken(username, token); - context.setCurrentUser(username); - } - - @When("{string} transfers the token to {string} using a proxy address") - public void userTransfersTheTokenToUserUsingAProxyAddress(String fromUser, String toUser) throws Exception { - Token sourceToken = context.getUserToken(fromUser); - ProxyAddress proxyAddress = ProxyAddress.create(context.getNameTagToken(toUser).getId()); - helper.transferToken(fromUser, toUser, sourceToken, proxyAddress, null); - } - - @When("{string} transfers the token to {string} using an unmasked predicate") - public void userTransfersTheTokenToUserUsingAnUnmaskedPredicate(String fromUser, String toUser) throws Exception { - Token sourceToken = context.getUserToken(fromUser); - SigningService toSigningService = context.getUserSigningServices().get(toUser); - - UnmaskedPredicate userPredicate = UnmaskedPredicate.create( - toSigningService, - HashAlgorithm.SHA256, - context.getUserNonces().get(toUser) - ); - context.getUserPredicate().put(toUser, userPredicate); - - DirectAddress toAddress = userPredicate.getReference(sourceToken.getType()).toAddress(); - - helper.transferToken(fromUser, toUser, sourceToken, toAddress, null); - } - - @Then("{string} should own the token successfully") - public void userShouldOwnTheTokenSuccessfully(String username) { - Token token = context.getUserToken(username); - context.setCurrentUser(username); - SigningService signingService = context.getUserSigningServices().get(username); - assertTrue(token.verify().isSuccessful(), "Token should be valid"); - assertTrue(token.getState().getUnlockPredicate().isOwner(signingService.getPublicKey()), - username + " should own the token"); - } - - @Then("all mint commitments should receive inclusion proofs within {int} seconds") - public void allMintCommitmentsShouldReceiveInclusionProofs(int timeoutSeconds) throws Exception { - List results = helper.collectCommitmentResults(); - helper.verifyAllInclusionProofsInParallel(timeoutSeconds); - - long verifiedCount = results.stream() - .filter(CommitmentResult::isVerified) - .count(); - - System.out.println("Verified commitments: " + verifiedCount + " / " + results.size()); - // Print failed ones (not verified) - results.stream() - .filter(r -> !r.isVerified()) - .forEach(r -> System.out.println( - "❌ Commitment failed: requestId=" + r.getRequestId().toString() + ", status=" + r.getStatus() - )); - - assertEquals(results.size(), verifiedCount, "All commitments should be verified"); - } - - @Given("user {string} create a nametag token with custom data {string}") - public void userCreateANametagTokenWithCustomData(String username, String customData) throws Exception { - Token nametagToken = helper.createNameTagTokenForUser( - username, - TestUtils.generateRandomTokenType(), - java.util.UUID.randomUUID().toString(), - customData - ); - assertNotNull(nametagToken, "Name tag token should be created"); - assertTrue(nametagToken.verify().isSuccessful(), "Name tag token should be valid"); - context.addNameTagToken(username, nametagToken); - context.setCurrentUser(username); - } - - @Given("the aggregator URLs are configured") - public void theAggregatorURLsAreConfigured() { - // You can either use environment variables or hardcode the URLs - List aggregatorUrls = Arrays.asList( - System.getenv("AGGREGATOR_URL") - ); - - assertNotNull(aggregatorUrls, "Aggregator URLs must be configured"); - assertFalse(aggregatorUrls.isEmpty(), "At least one aggregator URL must be provided"); - - List clients = new ArrayList<>(); - for (String url : aggregatorUrls) { - clients.add(new AggregatorClient(url.trim())); - } - - context.setAggregatorClients(clients); - } - - @And("the aggregator clients are initialized") - public void theAggregatorClientsAreInitialized() { - List clients = context.getAggregatorClients(); - assertNotNull(clients, "Aggregator clients should be initialized"); - assertFalse(clients.isEmpty(), "At least one aggregator client should be initialized"); - } - - @When("I submit all mint commitments concurrently to all aggregators") - public void iSubmitAllMintCommitmentsConcurrentlyToAllAggregators() { - int threadsCount = context.getConfiguredThreadCount(); - int commitmentsPerThread = context.getConfiguredCommitmentsPerThread(); - List aggregatorClients = context.getAggregatorClients(); - - Map userSigningServices = context.getUserSigningServices(); - - // Calculate total thread pool size: threads * aggregators - int totalThreadPoolSize = threadsCount * aggregatorClients.size(); - ExecutorService executor = Executors.newFixedThreadPool(totalThreadPoolSize); - - List> futures = new ArrayList<>(); - - for (Map.Entry entry : userSigningServices.entrySet()) { - String userName = entry.getKey(); - SigningService signingService = entry.getValue(); - - for (int i = 0; i < commitmentsPerThread; i++) { - // Generate the commitment data once for this iteration - byte[] stateBytes = TestUtils.generateRandomBytes(32); - byte[] txData = TestUtils.generateRandomBytes(32); - DataHash stateHash = TestUtils.hashData(stateBytes); - DataHash txDataHash = TestUtils.hashData(txData); - RequestId requestId = TestUtils.createRequestId(signingService, stateHash); - - // Submit the same commitment to all aggregators concurrently - for (int aggIndex = 0; aggIndex < aggregatorClients.size(); aggIndex++) { - AggregatorClient aggregatorClient = aggregatorClients.get(aggIndex); - String aggregatorId = "Aggregator" + aggIndex; - - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - long start = System.nanoTime(); - - try { - Authenticator authenticator = TestUtils.createAuthenticator(signingService, txDataHash, stateHash); - - SubmitCommitmentResponse response = aggregatorClient - .submitCommitment(requestId, txDataHash, authenticator).get(); - - boolean success = response.getStatus() == SubmitCommitmentStatus.SUCCESS; - long end = System.nanoTime(); - - return new CommitmentResult(userName + "-" + aggregatorId, - Thread.currentThread().getName(), - requestId, success, start, end); - } catch (Exception e) { - long end = System.nanoTime(); - return new CommitmentResult(userName + "-" + aggregatorId, - Thread.currentThread().getName(), - requestId, false, start, end); - } - }, executor); - - futures.add(future); - } - } - } - - context.setCommitmentFutures(futures); - - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); - executor.shutdown(); - } - - @Then("all commitments should be processed successfully") - public void allCommitmentsShouldBeProcessedSuccessfully() { - int threadsCount = context.getConfiguredThreadCount(); - int commitmentsPerThread = context.getConfiguredCommitmentsPerThread(); - List aggregatorClients = context.getAggregatorClients(); - - Map userSigningServices = context.getUserSigningServices(); - ExecutorService executor = Executors.newFixedThreadPool(threadsCount); - - List>> futures = new ArrayList<>(); - - for (Map.Entry entry : userSigningServices.entrySet()) { - String userName = entry.getKey(); - SigningService signingService = entry.getValue(); - - for (int i = 0; i < commitmentsPerThread; i++) { - CompletableFuture> future = CompletableFuture.supplyAsync(() -> { - List results = new ArrayList<>(); - - // Generate commitment data once - byte[] stateBytes = TestUtils.generateRandomBytes(32); - byte[] txData = TestUtils.generateRandomBytes(32); - DataHash stateHash = TestUtils.hashData(stateBytes); - DataHash txDataHash = TestUtils.hashData(txData); - RequestId requestId = TestUtils.createRequestId(signingService, stateHash); - - // Submit to all aggregators with the same data - for (int aggIndex = 0; aggIndex < aggregatorClients.size(); aggIndex++) { - AggregatorClient aggregatorClient = aggregatorClients.get(aggIndex); - String aggregatorId = "Aggregator" + aggIndex; - - long start = System.nanoTime(); - try { - Authenticator authenticator = TestUtils.createAuthenticator(signingService, txDataHash, stateHash); - - SubmitCommitmentResponse response = aggregatorClient - .submitCommitment(requestId, txDataHash, authenticator).get(); - - boolean success = response.getStatus() == SubmitCommitmentStatus.SUCCESS; - long end = System.nanoTime(); - - results.add(new CommitmentResult(userName + "-" + aggregatorId, - Thread.currentThread().getName(), - requestId, success, start, end)); - } catch (Exception e) { - long end = System.nanoTime(); - results.add(new CommitmentResult(userName + "-" + aggregatorId, - Thread.currentThread().getName(), - requestId, false, start, end)); - } - } - - return results; - }, executor); - - futures.add(future); - } - } - - // Flatten the results - List> flattenedFutures = new ArrayList<>(); - for (CompletableFuture> future : futures) { - CompletableFuture flattened = future.thenCompose(results -> { - // Return the first result (or you could return all) - return CompletableFuture.completedFuture(results.get(0)); - }); - flattenedFutures.add(flattened); - } - - context.setCommitmentFutures(flattenedFutures); - - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); - executor.shutdown(); - } - - @Then("all mint commitments should receive inclusion proofs from all aggregators within {int} seconds") - public void allMintCommitmentsShouldReceiveInclusionProofsFromAllAggregatorsWithinSeconds(int timeoutSeconds) throws Exception { - List results = helper.collectCommitmentResults(); - - // Verify inclusion proofs for all aggregators in parallel - helper.verifyAllInclusionProofsInParallelForMultipleAggregators(timeoutSeconds, context.getAggregatorClients()); - - long verifiedCount = results.stream() - .filter(CommitmentResult::isVerified) - .count(); - - System.out.println("=== Inclusion Proof Verification Results ==="); - System.out.println("Total commitments: " + results.size()); - System.out.println("Verified commitments: " + verifiedCount + " / " + results.size()); - - // Group results by aggregator for detailed reporting - Map> resultsByAggregator = results.stream() - .collect(Collectors.groupingBy(r -> helper.extractAggregatorFromUserName(r.getUserName()))); - - for (Map.Entry> entry : resultsByAggregator.entrySet()) { - String aggregatorId = entry.getKey(); - List aggregatorResults = entry.getValue(); - - long aggregatorVerifiedCount = aggregatorResults.stream() - .filter(CommitmentResult::isVerified) - .count(); - - System.out.println("\n" + aggregatorId + ":"); - System.out.println(" Verified: " + aggregatorVerifiedCount + " / " + aggregatorResults.size()); - - // Print failed ones for this aggregator - aggregatorResults.stream() - .filter(r -> !r.isVerified()) - .forEach(r -> System.out.println( - " ❌ Failed: requestId=" + r.getRequestId().toString() + - ", status=" + (r.getStatus() != null ? r.getStatus() : "Unknown") + - ", user=" + r.getUserName() - )); - - // Print successful ones (optional, for debugging) - if (aggregatorVerifiedCount > 0) { - System.out.println(" ✅ Successfully verified " + aggregatorVerifiedCount + " commitments"); - } - } - - assertEquals(results.size(), verifiedCount, "All commitments should be verified"); - } - - @Then("all mint commitments should receive inclusion proofs within {int} seconds with {int}% success rate") - public void allMintCommitmentsShouldReceiveInclusionProofsWithSuccessRate(int timeoutSeconds, int expectedSuccessRate) throws Exception { - List results = helper.collectCommitmentResults(); - - // Verify inclusion proofs for all aggregators in parallel - helper.verifyAllInclusionProofsInParallelForMultipleAggregators(timeoutSeconds, context.getAggregatorClients()); - - long verifiedCount = results.stream() - .filter(CommitmentResult::isVerified) - .count(); - - double actualSuccessRate = (double) verifiedCount / results.size() * 100; - - System.out.println("=== Inclusion Proof Verification Results ==="); - System.out.println("Total commitments: " + results.size()); - System.out.println("Verified commitments: " + verifiedCount + " / " + results.size()); - System.out.println("Actual success rate: " + String.format("%.2f%%", actualSuccessRate)); - System.out.println("Expected success rate: " + expectedSuccessRate + "%"); - - // Detailed reporting by aggregator - Map> resultsByAggregator = results.stream() - .collect(Collectors.groupingBy(r -> helper.extractAggregatorFromUserName(r.getUserName()))); - - for (Map.Entry> entry : resultsByAggregator.entrySet()) { - String aggregatorId = entry.getKey(); - List aggregatorResults = entry.getValue(); - - long aggregatorVerifiedCount = aggregatorResults.stream() - .filter(CommitmentResult::isVerified) - .count(); - - double aggregatorSuccessRate = (double) aggregatorVerifiedCount / aggregatorResults.size() * 100; - - System.out.println("\n" + aggregatorId + ":"); - System.out.println(" Success rate: " + String.format("%.2f%%", aggregatorSuccessRate) + - " (" + aggregatorVerifiedCount + " / " + aggregatorResults.size() + ")"); - } - - assertTrue(actualSuccessRate >= expectedSuccessRate, - String.format("Expected success rate of at least %d%%, but got %.2f%%", - expectedSuccessRate, actualSuccessRate)); - } - - @Then("I should see performance metrics for each aggregator") - public void iShouldSeePerformanceMetricsForEachAggregator() { - List results = helper.collectCommitmentResults(); - List aggregatorClients = context.getAggregatorClients(); - - System.out.println("\n=== 📊 AGGREGATOR PERFORMANCE COMPARISON ==="); - - // Print detailed breakdown - helper.printDetailedResultsByAggregator(results, aggregatorClients.size()); - - // Additional performance analysis - helper.printPerformanceComparison(results, aggregatorClients.size()); - } - - @Then("aggregator performance should meet minimum thresholds") - public void aggregatorPerformanceShouldMeetMinimumThresholds() { - List results = helper.collectCommitmentResults(); - List aggregatorClients = context.getAggregatorClients(); - - Map> resultsByAggregator = results.stream() - .collect(Collectors.groupingBy(r -> helper.extractAggregatorFromUserName(r.getUserName()))); - - for (int i = 0; i < aggregatorClients.size(); i++) { - String aggregatorId = "-Aggregator" + i; - List aggregatorResults = resultsByAggregator.getOrDefault(aggregatorId, new ArrayList<>()); - - if (aggregatorResults.isEmpty()) continue; - - long verifiedCount = aggregatorResults.stream() - .filter(CommitmentResult::isVerified) - .count(); - - double successRate = (double) verifiedCount / aggregatorResults.size() * 100; - - // Assert minimum success rate (configurable) - assertTrue(successRate >= 90.0, - String.format("Aggregator%d success rate (%.2f%%) should be at least 90%%", i, successRate)); - - // Assert reasonable average inclusion time - OptionalDouble avgInclusionTime = aggregatorResults.stream() - .filter(CommitmentResult::isVerified) - .mapToDouble(CommitmentResult::getInclusionDurationMillis) - .average(); - - if (avgInclusionTime.isPresent()) { - assertTrue(avgInclusionTime.getAsDouble() <= 30000, // 30 seconds max - String.format("Aggregator%d average inclusion time (%.2fms) should be under 30 seconds", - i, avgInclusionTime.getAsDouble())); - } - - System.out.println("✅ Aggregator" + i + " meets performance thresholds"); - } - } -} \ No newline at end of file +//package org.unicitylabs.sdk.e2e.steps.shared; +// +//import org.unicitylabs.sdk.StateTransitionClient; +//import org.unicitylabs.sdk.address.DirectAddress; +//import org.unicitylabs.sdk.address.ProxyAddress; +//import org.unicitylabs.sdk.api.*; +//import org.unicitylabs.sdk.e2e.config.CucumberConfiguration; +//import org.unicitylabs.sdk.e2e.context.TestContext; +//import org.unicitylabs.sdk.predicate.UnmaskedPredicate; +//import org.unicitylabs.sdk.transaction.*; +//import org.unicitylabs.sdk.utils.TestUtils; +//import org.unicitylabs.sdk.hash.DataHash; +//import org.unicitylabs.sdk.hash.HashAlgorithm; +//import org.unicitylabs.sdk.signing.SigningService; +//import org.unicitylabs.sdk.token.Token; +//import org.unicitylabs.sdk.token.TokenId; +//import org.unicitylabs.sdk.token.TokenType; +//import org.unicitylabs.sdk.token.fungible.TokenCoinData; +//import io.cucumber.datatable.DataTable; +//import io.cucumber.java.en.And; +//import io.cucumber.java.en.Given; +//import io.cucumber.java.en.Then; +//import io.cucumber.java.en.When; +// +//import java.nio.charset.StandardCharsets; +//import java.util.*; +//import java.util.concurrent.*; +//import java.util.stream.Collectors; +// +//import static org.unicitylabs.sdk.utils.TestUtils.randomCoinData; +//import static org.junit.jupiter.api.Assertions.*; +// +//import org.unicitylabs.sdk.utils.helpers.CommitmentResult; +// +// +///** +// * Shared step definitions that can be reused across multiple feature files. +// * These steps use TestContext to maintain state and avoid duplication. +// */ +//public class SharedStepDefinitions { +// +// private final TestContext context; +// +// public SharedStepDefinitions() { // ✅ Public zero-argument constructor +// this.context = CucumberConfiguration.getTestContext(); +// } +// +// StepHelper helper = new StepHelper(); +// +// // Setup Steps +// @Given("the aggregator URL is configured") +// public void theAggregatorUrlIsConfigured() { +// String aggregatorUrl = System.getenv("AGGREGATOR_URL"); +// assertNotNull(aggregatorUrl, "AGGREGATOR_URL environment variable must be set"); +// context.setAggregatorClient(new AggregatorClient(aggregatorUrl)); +// } +// +// @And("the aggregator client is initialized") +// public void theAggregatorClientIsInitialized() { +// assertNotNull(context.getAggregatorClient(), "Aggregator client should be initialized"); +// } +// +// @And("the state transition client is initialized") +// public void theStateTransitionClientIsInitialized() { +// context.setClient(new StateTransitionClient(context.getAggregatorClient())); +// assertNotNull(context.getClient(), "State transition client should be initialized"); +// } +// +// @And("the following users are set up with their signing services") +// public void usersAreSetUpWithTheirSigningServices(DataTable dataTable) { +// List users = dataTable.asList(); +// for (String user : users) { +// TestUtils.setupUser(user, context.getUserSigningServices(), context.getUserNonces(), context.getUserSecret()); +// context.getUserTokens().put(user, new ArrayList<>()); +// } +// } +// +// // Aggregator Operations +// @When("I request the current block height") +// public void iRequestTheCurrentBlockHeight() throws Exception { +// Long blockHeight = context.getAggregatorClient().getBlockHeight().get(); +// context.setBlockHeight(blockHeight); +// } +// +// @Then("the block height should be returned") +// public void theBlockHeightShouldBeReturned() { +// assertNotNull(context.getBlockHeight(), "Block height should not be null"); +// } +// +// @And("the block height should be greater than {int}") +// public void theBlockHeightShouldBeGreaterThan(int minHeight) { +// assertTrue(context.getBlockHeight() > minHeight, "Block height should be greater than " + minHeight); +// } +// +// // Commitment Operations +// @Given("a random secret of {int} bytes") +// public void aRandomSecretOfBytes(int secretLength) { +// byte[] randomSecret = TestUtils.generateRandomBytes(secretLength); +// context.setRandomSecret(randomSecret); +// assertNotNull(randomSecret); +// assertEquals(secretLength, randomSecret.length); +// } +// +// @And("a state hash from {int} bytes of random data") +// public void aStateHashFromBytesOfRandomData(int stateLength) { +// byte[] stateBytes = TestUtils.generateRandomBytes(stateLength); +// DataHash stateHash = TestUtils.hashData(stateBytes); +// context.setStateBytes(stateBytes); +// context.setStateHash(stateHash); +// assertNotNull(stateHash); +// } +// +// @And("transaction data {string}") +// public void transactionData(String txData) { +// DataHash txDataHash = TestUtils.hashData(txData.getBytes(StandardCharsets.UTF_8)); +// context.setTxDataHash(txDataHash); +// assertNotNull(txDataHash); +// } +// +// @When("I submit a commitment with the generated data") +// public void iSubmitACommitmentWithTheGeneratedData() throws Exception { +// long startTime = System.currentTimeMillis(); +// +// SigningService signingService = SigningService.createFromSecret(context.getRandomSecret()); +// var requestId = TestUtils.createRequestId(signingService, context.getStateHash()); +// var authenticator = TestUtils.createAuthenticator(signingService, context.getTxDataHash(), context.getStateHash()); +// +// SubmitCommitmentResponse response = context.getAggregatorClient() +// .submitCommitment(requestId, context.getTxDataHash(), authenticator).get(); +// context.setCommitmentResponse(response); +// +// long endTime = System.currentTimeMillis(); +// context.setSubmissionDuration(endTime - startTime); +// } +// +// @Then("the commitment should be submitted successfully") +// public void theCommitmentShouldBeSubmittedSuccessfully() { +// assertNotNull(context.getCommitmentResponse(), "Commitment response should not be null"); +// assertEquals(SubmitCommitmentStatus.SUCCESS, context.getCommitmentResponse().getStatus(), +// "Commitment should be submitted successfully"); +// } +// +// @And("the submission should complete in less than {int} milliseconds") +// public void theSubmissionShouldCompleteInLessThanMilliseconds(int maxDuration) { +// assertTrue(context.getSubmissionDuration() < maxDuration, +// String.format("Submission took %d ms, should be less than %d ms", +// context.getSubmissionDuration(), maxDuration)); +// } +// +// // Multi-threaded Operations +// @Given("I configure {int} threads with {int} commitments each") +// public void iConfigureThreadsWithCommitmentsEach(int threadCount, int commitmentsPerThread) { +// context.setConfiguredThreadCount(threadCount); +// context.setConfiguredCommitmentsPerThread(commitmentsPerThread); +// +// // Reuse existing user setup to create users +// context.setConfiguredUserCount(threadCount); +// +// // Setup additional users if needed +// for (int i = 0; i < threadCount; i++) { +// String userName = "BulkUser" + i; +// TestUtils.setupUser(userName, context.getUserSigningServices(), context.getUserNonces(), context.getUserSecret()); +// context.getUserTokens().put(userName, new ArrayList<>()); +// } +// } +// +// @When("I submit all mint commitments concurrently") +// public void iSubmitAllMintCommitmentsConcurrently() throws Exception { +// int threadsCount = context.getConfiguredThreadCount(); +// int commitmentsPerThread = context.getConfiguredCommitmentsPerThread(); +// +// Map userSigningServices = context.getUserSigningServices(); +// ExecutorService executor = Executors.newFixedThreadPool(threadsCount); +// +// List> futures = new ArrayList<>(); +// +// for (Map.Entry entry : userSigningServices.entrySet()) { +// String userName = entry.getKey(); +// SigningService signingService = entry.getValue(); +// +// for (int i = 0; i < commitmentsPerThread; i++) { +// CompletableFuture future = CompletableFuture.supplyAsync(() -> { +// long start = System.nanoTime(); +// byte[] stateBytes = TestUtils.generateRandomBytes(32); +// byte[] txData = TestUtils.generateRandomBytes(32); +// +// DataHash stateHash = TestUtils.hashData(stateBytes); +// DataHash txDataHash = TestUtils.hashData(txData); +// RequestId requestId = TestUtils.createRequestId(signingService, stateHash); +// +// try { +// Authenticator authenticator = TestUtils.createAuthenticator(signingService, txDataHash, stateHash); +// +// SubmitCommitmentResponse response = context.getAggregatorClient() +// .submitCommitment(requestId, txDataHash, authenticator).get(); +// +// boolean success = response.getStatus() == SubmitCommitmentStatus.SUCCESS; +// long end = System.nanoTime(); +// +// return new CommitmentResult(userName, Thread.currentThread().getName(), +// requestId, success, start, end); +// } catch (Exception e) { +// long end = System.nanoTime(); +// return new CommitmentResult(userName, Thread.currentThread().getName(), +// requestId, false, start, end); +// } +// }, executor); +// +// futures.add(future); +// } +// } +// +// context.setCommitmentFutures(futures); +// +// CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); +// executor.shutdown(); +// } +// +// // Token Operations +// @Given("{string} mints a token with random coin data") +// public void userMintsATokenWithRandomCoinData(String username) throws Exception { +// TokenId tokenId = TestUtils.generateRandomTokenId(); +// TokenType tokenType = context.getNameTagToken(context.getCurrentUser()).getType(); +// TokenCoinData coinData = randomCoinData(2); +// +// Token token = TestUtils.mintTokenForUser( +// context.getClient(), +// context.getUserSigningServices().get(username), +// context.getUserNonces().get(username), +// tokenId, +// tokenType, +// coinData +// ); +// +// context.addUserToken(username, token); +// context.setCurrentUser(username); +// } +// +// @When("{string} transfers the token to {string} using a proxy address") +// public void userTransfersTheTokenToUserUsingAProxyAddress(String fromUser, String toUser) throws Exception { +// Token sourceToken = context.getUserToken(fromUser); +// ProxyAddress proxyAddress = ProxyAddress.create(context.getNameTagToken(toUser).getId()); +// helper.transferToken(fromUser, toUser, sourceToken, proxyAddress, null); +// } +// +// @When("{string} transfers the token to {string} using an unmasked predicate") +// public void userTransfersTheTokenToUserUsingAnUnmaskedPredicate(String fromUser, String toUser) throws Exception { +// Token sourceToken = context.getUserToken(fromUser); +// SigningService toSigningService = context.getUserSigningServices().get(toUser); +// +// UnmaskedPredicate userPredicate = UnmaskedPredicate.create( +// toSigningService, +// HashAlgorithm.SHA256, +// context.getUserNonces().get(toUser) +// ); +// context.getUserPredicate().put(toUser, userPredicate); +// +// DirectAddress toAddress = userPredicate.getReference(sourceToken.getType()).toAddress(); +// +// helper.transferToken(fromUser, toUser, sourceToken, toAddress, null); +// } +// +// @Then("{string} should own the token successfully") +// public void userShouldOwnTheTokenSuccessfully(String username) { +// Token token = context.getUserToken(username); +// context.setCurrentUser(username); +// SigningService signingService = context.getUserSigningServices().get(username); +// assertTrue(token.verify().isSuccessful(), "Token should be valid"); +// assertTrue(token.getState().getUnlockPredicate().isOwner(signingService.getPublicKey()), +// username + " should own the token"); +// } +// +// @Then("all mint commitments should receive inclusion proofs within {int} seconds") +// public void allMintCommitmentsShouldReceiveInclusionProofs(int timeoutSeconds) throws Exception { +// List results = helper.collectCommitmentResults(); +// helper.verifyAllInclusionProofsInParallel(timeoutSeconds); +// +// long verifiedCount = results.stream() +// .filter(CommitmentResult::isVerified) +// .count(); +// +// System.out.println("Verified commitments: " + verifiedCount + " / " + results.size()); +// // Print failed ones (not verified) +// results.stream() +// .filter(r -> !r.isVerified()) +// .forEach(r -> System.out.println( +// "❌ Commitment failed: requestId=" + r.getRequestId().toString() + ", status=" + r.getStatus() +// )); +// +// assertEquals(results.size(), verifiedCount, "All commitments should be verified"); +// } +// +// @Given("user {string} create a nametag token with custom data {string}") +// public void userCreateANametagTokenWithCustomData(String username, String customData) throws Exception { +// Token nametagToken = helper.createNameTagTokenForUser( +// username, +// TestUtils.generateRandomTokenType(), +// java.util.UUID.randomUUID().toString(), +// customData +// ); +// assertNotNull(nametagToken, "Name tag token should be created"); +// assertTrue(nametagToken.verify().isSuccessful(), "Name tag token should be valid"); +// context.addNameTagToken(username, nametagToken); +// context.setCurrentUser(username); +// } +// +// @Given("the aggregator URLs are configured") +// public void theAggregatorURLsAreConfigured() { +// // You can either use environment variables or hardcode the URLs +// List aggregatorUrls = Arrays.asList( +// System.getenv("AGGREGATOR_URL") +// ); +// +// assertNotNull(aggregatorUrls, "Aggregator URLs must be configured"); +// assertFalse(aggregatorUrls.isEmpty(), "At least one aggregator URL must be provided"); +// +// List clients = new ArrayList<>(); +// for (String url : aggregatorUrls) { +// clients.add(new AggregatorClient(url.trim())); +// } +// +// context.setAggregatorClients(clients); +// } +// +// @And("the aggregator clients are initialized") +// public void theAggregatorClientsAreInitialized() { +// List clients = context.getAggregatorClients(); +// assertNotNull(clients, "Aggregator clients should be initialized"); +// assertFalse(clients.isEmpty(), "At least one aggregator client should be initialized"); +// } +// +// @When("I submit all mint commitments concurrently to all aggregators") +// public void iSubmitAllMintCommitmentsConcurrentlyToAllAggregators() { +// int threadsCount = context.getConfiguredThreadCount(); +// int commitmentsPerThread = context.getConfiguredCommitmentsPerThread(); +// List aggregatorClients = context.getAggregatorClients(); +// +// Map userSigningServices = context.getUserSigningServices(); +// +// // Calculate total thread pool size: threads * aggregators +// int totalThreadPoolSize = threadsCount * aggregatorClients.size(); +// ExecutorService executor = Executors.newFixedThreadPool(totalThreadPoolSize); +// +// List> futures = new ArrayList<>(); +// +// for (Map.Entry entry : userSigningServices.entrySet()) { +// String userName = entry.getKey(); +// SigningService signingService = entry.getValue(); +// +// for (int i = 0; i < commitmentsPerThread; i++) { +// // Generate the commitment data once for this iteration +// byte[] stateBytes = TestUtils.generateRandomBytes(32); +// byte[] txData = TestUtils.generateRandomBytes(32); +// DataHash stateHash = TestUtils.hashData(stateBytes); +// DataHash txDataHash = TestUtils.hashData(txData); +// RequestId requestId = TestUtils.createRequestId(signingService, stateHash); +// +// // Submit the same commitment to all aggregators concurrently +// for (int aggIndex = 0; aggIndex < aggregatorClients.size(); aggIndex++) { +// AggregatorClient aggregatorClient = aggregatorClients.get(aggIndex); +// String aggregatorId = "Aggregator" + aggIndex; +// +// CompletableFuture future = CompletableFuture.supplyAsync(() -> { +// long start = System.nanoTime(); +// +// try { +// Authenticator authenticator = TestUtils.createAuthenticator(signingService, txDataHash, stateHash); +// +// SubmitCommitmentResponse response = aggregatorClient +// .submitCommitment(requestId, txDataHash, authenticator).get(); +// +// boolean success = response.getStatus() == SubmitCommitmentStatus.SUCCESS; +// long end = System.nanoTime(); +// +// return new CommitmentResult(userName + "-" + aggregatorId, +// Thread.currentThread().getName(), +// requestId, success, start, end); +// } catch (Exception e) { +// long end = System.nanoTime(); +// return new CommitmentResult(userName + "-" + aggregatorId, +// Thread.currentThread().getName(), +// requestId, false, start, end); +// } +// }, executor); +// +// futures.add(future); +// } +// } +// } +// +// context.setCommitmentFutures(futures); +// +// CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); +// executor.shutdown(); +// } +// +// @Then("all commitments should be processed successfully") +// public void allCommitmentsShouldBeProcessedSuccessfully() { +// int threadsCount = context.getConfiguredThreadCount(); +// int commitmentsPerThread = context.getConfiguredCommitmentsPerThread(); +// List aggregatorClients = context.getAggregatorClients(); +// +// Map userSigningServices = context.getUserSigningServices(); +// ExecutorService executor = Executors.newFixedThreadPool(threadsCount); +// +// List>> futures = new ArrayList<>(); +// +// for (Map.Entry entry : userSigningServices.entrySet()) { +// String userName = entry.getKey(); +// SigningService signingService = entry.getValue(); +// +// for (int i = 0; i < commitmentsPerThread; i++) { +// CompletableFuture> future = CompletableFuture.supplyAsync(() -> { +// List results = new ArrayList<>(); +// +// // Generate commitment data once +// byte[] stateBytes = TestUtils.generateRandomBytes(32); +// byte[] txData = TestUtils.generateRandomBytes(32); +// DataHash stateHash = TestUtils.hashData(stateBytes); +// DataHash txDataHash = TestUtils.hashData(txData); +// RequestId requestId = TestUtils.createRequestId(signingService, stateHash); +// +// // Submit to all aggregators with the same data +// for (int aggIndex = 0; aggIndex < aggregatorClients.size(); aggIndex++) { +// AggregatorClient aggregatorClient = aggregatorClients.get(aggIndex); +// String aggregatorId = "Aggregator" + aggIndex; +// +// long start = System.nanoTime(); +// try { +// Authenticator authenticator = TestUtils.createAuthenticator(signingService, txDataHash, stateHash); +// +// SubmitCommitmentResponse response = aggregatorClient +// .submitCommitment(requestId, txDataHash, authenticator).get(); +// +// boolean success = response.getStatus() == SubmitCommitmentStatus.SUCCESS; +// long end = System.nanoTime(); +// +// results.add(new CommitmentResult(userName + "-" + aggregatorId, +// Thread.currentThread().getName(), +// requestId, success, start, end)); +// } catch (Exception e) { +// long end = System.nanoTime(); +// results.add(new CommitmentResult(userName + "-" + aggregatorId, +// Thread.currentThread().getName(), +// requestId, false, start, end)); +// } +// } +// +// return results; +// }, executor); +// +// futures.add(future); +// } +// } +// +// // Flatten the results +// List> flattenedFutures = new ArrayList<>(); +// for (CompletableFuture> future : futures) { +// CompletableFuture flattened = future.thenCompose(results -> { +// // Return the first result (or you could return all) +// return CompletableFuture.completedFuture(results.get(0)); +// }); +// flattenedFutures.add(flattened); +// } +// +// context.setCommitmentFutures(flattenedFutures); +// +// CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); +// executor.shutdown(); +// } +// +// @Then("all mint commitments should receive inclusion proofs from all aggregators within {int} seconds") +// public void allMintCommitmentsShouldReceiveInclusionProofsFromAllAggregatorsWithinSeconds(int timeoutSeconds) throws Exception { +// List results = helper.collectCommitmentResults(); +// +// // Verify inclusion proofs for all aggregators in parallel +// helper.verifyAllInclusionProofsInParallelForMultipleAggregators(timeoutSeconds, context.getAggregatorClients()); +// +// long verifiedCount = results.stream() +// .filter(CommitmentResult::isVerified) +// .count(); +// +// System.out.println("=== Inclusion Proof Verification Results ==="); +// System.out.println("Total commitments: " + results.size()); +// System.out.println("Verified commitments: " + verifiedCount + " / " + results.size()); +// +// // Group results by aggregator for detailed reporting +// Map> resultsByAggregator = results.stream() +// .collect(Collectors.groupingBy(r -> helper.extractAggregatorFromUserName(r.getUserName()))); +// +// for (Map.Entry> entry : resultsByAggregator.entrySet()) { +// String aggregatorId = entry.getKey(); +// List aggregatorResults = entry.getValue(); +// +// long aggregatorVerifiedCount = aggregatorResults.stream() +// .filter(CommitmentResult::isVerified) +// .count(); +// +// System.out.println("\n" + aggregatorId + ":"); +// System.out.println(" Verified: " + aggregatorVerifiedCount + " / " + aggregatorResults.size()); +// +// // Print failed ones for this aggregator +// aggregatorResults.stream() +// .filter(r -> !r.isVerified()) +// .forEach(r -> System.out.println( +// " ❌ Failed: requestId=" + r.getRequestId().toString() + +// ", status=" + (r.getStatus() != null ? r.getStatus() : "Unknown") + +// ", user=" + r.getUserName() +// )); +// +// // Print successful ones (optional, for debugging) +// if (aggregatorVerifiedCount > 0) { +// System.out.println(" ✅ Successfully verified " + aggregatorVerifiedCount + " commitments"); +// } +// } +// +// assertEquals(results.size(), verifiedCount, "All commitments should be verified"); +// } +// +// @Then("all mint commitments should receive inclusion proofs within {int} seconds with {int}% success rate") +// public void allMintCommitmentsShouldReceiveInclusionProofsWithSuccessRate(int timeoutSeconds, int expectedSuccessRate) throws Exception { +// List results = helper.collectCommitmentResults(); +// +// // Verify inclusion proofs for all aggregators in parallel +// helper.verifyAllInclusionProofsInParallelForMultipleAggregators(timeoutSeconds, context.getAggregatorClients()); +// +// long verifiedCount = results.stream() +// .filter(CommitmentResult::isVerified) +// .count(); +// +// double actualSuccessRate = (double) verifiedCount / results.size() * 100; +// +// System.out.println("=== Inclusion Proof Verification Results ==="); +// System.out.println("Total commitments: " + results.size()); +// System.out.println("Verified commitments: " + verifiedCount + " / " + results.size()); +// System.out.println("Actual success rate: " + String.format("%.2f%%", actualSuccessRate)); +// System.out.println("Expected success rate: " + expectedSuccessRate + "%"); +// +// // Detailed reporting by aggregator +// Map> resultsByAggregator = results.stream() +// .collect(Collectors.groupingBy(r -> helper.extractAggregatorFromUserName(r.getUserName()))); +// +// for (Map.Entry> entry : resultsByAggregator.entrySet()) { +// String aggregatorId = entry.getKey(); +// List aggregatorResults = entry.getValue(); +// +// long aggregatorVerifiedCount = aggregatorResults.stream() +// .filter(CommitmentResult::isVerified) +// .count(); +// +// double aggregatorSuccessRate = (double) aggregatorVerifiedCount / aggregatorResults.size() * 100; +// +// System.out.println("\n" + aggregatorId + ":"); +// System.out.println(" Success rate: " + String.format("%.2f%%", aggregatorSuccessRate) + +// " (" + aggregatorVerifiedCount + " / " + aggregatorResults.size() + ")"); +// } +// +// assertTrue(actualSuccessRate >= expectedSuccessRate, +// String.format("Expected success rate of at least %d%%, but got %.2f%%", +// expectedSuccessRate, actualSuccessRate)); +// } +// +// @Then("I should see performance metrics for each aggregator") +// public void iShouldSeePerformanceMetricsForEachAggregator() { +// List results = helper.collectCommitmentResults(); +// List aggregatorClients = context.getAggregatorClients(); +// +// System.out.println("\n=== 📊 AGGREGATOR PERFORMANCE COMPARISON ==="); +// +// // Print detailed breakdown +// helper.printDetailedResultsByAggregator(results, aggregatorClients.size()); +// +// // Additional performance analysis +// helper.printPerformanceComparison(results, aggregatorClients.size()); +// } +// +// @Then("aggregator performance should meet minimum thresholds") +// public void aggregatorPerformanceShouldMeetMinimumThresholds() { +// List results = helper.collectCommitmentResults(); +// List aggregatorClients = context.getAggregatorClients(); +// +// Map> resultsByAggregator = results.stream() +// .collect(Collectors.groupingBy(r -> helper.extractAggregatorFromUserName(r.getUserName()))); +// +// for (int i = 0; i < aggregatorClients.size(); i++) { +// String aggregatorId = "-Aggregator" + i; +// List aggregatorResults = resultsByAggregator.getOrDefault(aggregatorId, new ArrayList<>()); +// +// if (aggregatorResults.isEmpty()) continue; +// +// long verifiedCount = aggregatorResults.stream() +// .filter(CommitmentResult::isVerified) +// .count(); +// +// double successRate = (double) verifiedCount / aggregatorResults.size() * 100; +// +// // Assert minimum success rate (configurable) +// assertTrue(successRate >= 90.0, +// String.format("Aggregator%d success rate (%.2f%%) should be at least 90%%", i, successRate)); +// +// // Assert reasonable average inclusion time +// OptionalDouble avgInclusionTime = aggregatorResults.stream() +// .filter(CommitmentResult::isVerified) +// .mapToDouble(CommitmentResult::getInclusionDurationMillis) +// .average(); +// +// if (avgInclusionTime.isPresent()) { +// assertTrue(avgInclusionTime.getAsDouble() <= 30000, // 30 seconds max +// String.format("Aggregator%d average inclusion time (%.2fms) should be under 30 seconds", +// i, avgInclusionTime.getAsDouble())); +// } +// +// System.out.println("✅ Aggregator" + i + " meets performance thresholds"); +// } +// } +//} \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/e2e/steps/shared/StepHelper.java b/src/test/java/org/unicitylabs/sdk/e2e/steps/shared/StepHelper.java index dd8ddad..1d4994e 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/steps/shared/StepHelper.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/steps/shared/StepHelper.java @@ -1,478 +1,478 @@ -package org.unicitylabs.sdk.e2e.steps.shared; - -import org.unicitylabs.sdk.address.Address; -import org.unicitylabs.sdk.address.DirectAddress; -import org.unicitylabs.sdk.address.ProxyAddress; -import org.unicitylabs.sdk.api.AggregatorClient; -import org.unicitylabs.sdk.api.SubmitCommitmentResponse; -import org.unicitylabs.sdk.api.SubmitCommitmentStatus; -import org.unicitylabs.sdk.e2e.config.CucumberConfiguration; -import org.unicitylabs.sdk.e2e.context.TestContext; -import org.unicitylabs.sdk.hash.DataHash; -import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.MaskedPredicate; -import org.unicitylabs.sdk.predicate.Predicate; -import org.unicitylabs.sdk.predicate.UnmaskedPredicate; -import org.unicitylabs.sdk.predicate.UnmaskedPredicateReference; -import org.unicitylabs.sdk.signing.SigningService; -import org.unicitylabs.sdk.token.Token; -import org.unicitylabs.sdk.token.TokenState; -import org.unicitylabs.sdk.token.TokenType; -import org.unicitylabs.sdk.transaction.*; -import org.unicitylabs.sdk.utils.TestUtils; -import org.unicitylabs.sdk.utils.helpers.CommitmentResult; - -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.*; -import java.util.stream.Collectors; - -import static org.unicitylabs.sdk.util.InclusionProofUtils.waitInclusionProof; -import static org.unicitylabs.sdk.utils.TestUtils.randomBytes; - - -public class StepHelper { - - private final TestContext context; - - public StepHelper() { // ✅ Public zero-argument constructor - this.context = CucumberConfiguration.getTestContext(); - } - - public Token createNameTagTokenForUser(String userName, TokenType type, String nametag, String nametagData) throws Exception { - byte[] nametagNonce = TestUtils.generateRandomBytes(32); - - MaskedPredicate nametagPredicate = MaskedPredicate.create( - SigningService.createFromMaskedSecret(context.getUserSecret().get(userName), nametagNonce), - HashAlgorithm.SHA256, - nametagNonce - ); - - TokenType nametagTokenType = TestUtils.generateRandomTokenType(); - DirectAddress nametagAddress = nametagPredicate.getReference(nametagTokenType).toAddress(); - - DirectAddress userAddress = UnmaskedPredicateReference.create( - nametagTokenType, - SigningService.createFromSecret(context.getUserSecret().get(userName)), - HashAlgorithm.SHA256 - ).toAddress(); - - var nametagMintCommitment = org.unicitylabs.sdk.transaction.MintCommitment.create( - new NametagMintTransactionData<>( - nametag, - nametagTokenType, - nametagAddress, - TestUtils.generateRandomBytes(32), - userAddress - ) - ); - - SubmitCommitmentResponse response = context.getClient().submitCommitment(nametagMintCommitment).get(); - if (response.getStatus() != SubmitCommitmentStatus.SUCCESS) { - throw new Exception("Failed to submit nametag mint commitment: " + response.getStatus()); - } - - InclusionProof inclusionProof = waitInclusionProof(context.getClient(), nametagMintCommitment).get(); - Transaction> nametagGenesis = nametagMintCommitment.toTransaction(inclusionProof); - - return new Token( - new org.unicitylabs.sdk.token.TokenState(nametagPredicate, null), - nametagGenesis - ); - } - - public void transferToken(String fromUser, String toUser, Token token, Address toAddress, String customData) throws Exception { - SigningService fromSigningService = context.getUserSigningServices().get(fromUser); - - // Create data hash and state data if custom data provided - DataHash dataHash = null; - byte[] stateData = null; - if (customData != null && !customData.isEmpty()) { - stateData = customData.getBytes(StandardCharsets.UTF_8); - dataHash = TestUtils.hashData(stateData); - } - - // Submit transfer commitment - TransferCommitment transferCommitment = TransferCommitment.create( - token, - toAddress, - randomBytes(32), - dataHash, - null, - fromSigningService - ); - - SubmitCommitmentResponse response = context.getClient().submitCommitment(token, transferCommitment).get(); - if (response.getStatus() != SubmitCommitmentStatus.SUCCESS) { - throw new Exception("Failed to submit transfer commitment: " + response.getStatus()); - } - - // Wait for inclusion proof - InclusionProof inclusionProof = waitInclusionProof( - context.getClient(), - transferCommitment - ).get(); - Transaction transferTransaction = transferCommitment.toTransaction( - token, - inclusionProof - ); - - context.savePendingTransfer(toUser, token, transferTransaction); - } - - public void finalizeTransfer(String username, Token token, Transaction tx) throws Exception { - - byte[] secret = context.getUserSecret().get(username); - - Token currentNameTagToken = context.getNameTagToken(username); - List nametagTokens = context.getNameTagTokens().get(username); - if (nametagTokens != null && !nametagTokens.isEmpty()) { - for (Token t : nametagTokens) { - String actualNametagAddress = tx.getData().getRecipient().getAddress(); - String expectedProxyAddress = ProxyAddress.create(t.getId()).getAddress(); - - if (actualNametagAddress.equalsIgnoreCase(expectedProxyAddress)) { - currentNameTagToken = t; - break; - } - } - } - - List> additionalTokens = new ArrayList<>(); - if (currentNameTagToken != null) { - additionalTokens.add(currentNameTagToken); - } - - Predicate unlockPredicate = context.getUserPredicate().get(username); - if (unlockPredicate == null){ - context.getUserSigningServices().put(username, SigningService.createFromSecret(secret)); - unlockPredicate = UnmaskedPredicate.create( - context.getUserSigningServices().get(username), - HashAlgorithm.SHA256, - tx.getData().getSalt() - ); - } - - TokenState recipientState = new TokenState( - unlockPredicate, - null - ); - - Token finalizedToken = context.getClient().finalizeTransaction( - token, - recipientState, - tx, - additionalTokens - ); - - context.addUserToken(username, finalizedToken); - } - - public boolean submitSingleCommitment() { - try { - byte[] randomSecret = TestUtils.generateRandomBytes(32); - byte[] stateBytes = TestUtils.generateRandomBytes(32); - byte[] txData = TestUtils.generateRandomBytes(32); - - DataHash stateHash = TestUtils.hashData(stateBytes); - DataHash txDataHash = TestUtils.hashData(txData); - SigningService signingService = SigningService.createFromSecret(randomSecret); - var requestId = TestUtils.createRequestId(signingService, stateHash); - var authenticator = TestUtils.createAuthenticator(signingService, txDataHash, stateHash); - - SubmitCommitmentResponse response = context.getAggregatorClient() - .submitCommitment(requestId, txDataHash, authenticator).get(); - return response.getStatus() == SubmitCommitmentStatus.SUCCESS; - } catch (Exception e) { - return false; - } - } - - public void verifyAllInclusionProofsInParallel(int timeoutSeconds) - throws InterruptedException { - List results = collectCommitmentResults(); - ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - CountDownLatch latch = new CountDownLatch(results.size()); - - long startAll = System.nanoTime(); - long globalTimeout = startAll + TimeUnit.SECONDS.toNanos(timeoutSeconds); - - for (CommitmentResult result : results) { - executor.submit(() -> { - long inclStart = System.nanoTime(); - boolean verified = false; - String errorMessage = "Global timeout reached"; - - try { - while (System.nanoTime() < globalTimeout && !verified) { - try { - InclusionProof proof = context.getAggregatorClient() - .getInclusionProof(result.getRequestId()) - .get(calculateRemainingTimeout(globalTimeout), TimeUnit.MILLISECONDS); - - if (proof != null && proof.verify(result.getRequestId()) - == InclusionProofVerificationStatus.OK) { - result.markVerified(inclStart, System.nanoTime()); - verified = true; - } else { - // Неуспешная верификация, но продолжаем пытаться - InclusionProofVerificationStatus status = proof.verify(result.getRequestId()); - errorMessage = status.toString(); - Thread.sleep(1000); // Небольшая пауза перед повторной попыткой - } - } catch (TimeoutException e) { - // Таймаут отдельной операции, продолжаем цикл - errorMessage = "Individual operation timeout: " + e.getMessage(); - } catch (ExecutionException e) { - // Ошибка выполнения, продолжаем цикл - errorMessage = "Execution error: " + e.getMessage(); - Thread.sleep(1000); // Пауза перед повторной попыткой - } - } - - if (!verified) { - result.markFailedVerification(inclStart, System.nanoTime(), errorMessage); - } - - } catch (Exception e) { - result.markFailedVerification(inclStart, System.nanoTime(), - "Unexpected error: " + e.getMessage()); - } finally { - latch.countDown(); - } - }); - } - - // Wait for all tasks to complete or timeout - boolean finished = latch.await(timeoutSeconds, TimeUnit.SECONDS); - executor.shutdownNow(); - - long endAll = System.nanoTime(); - System.out.println("All inclusion proofs completed in: " + ((endAll - startAll) / 1_000_000) + " ms"); - - if (!finished) { - System.err.println("Timeout reached before all inclusion proofs were verified"); - } - } - - private long calculateRemainingTimeout(long globalTimeoutNanos) { - long remaining = globalTimeoutNanos - System.nanoTime(); - return TimeUnit.NANOSECONDS.toMillis(Math.max(remaining, 100)); // Минимум 100мс - } - - public List collectCommitmentResults() { - return context.getCommitmentFutures().stream() - .map(f -> { - try { - return f.get(); // wait for completion - } catch (Exception e) { - e.printStackTrace(); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - // Helper method to extract aggregator info from username - public String extractAggregatorFromUserName(String userName) { - if (userName.contains("-Aggregator")) { - return userName.substring(userName.indexOf("-Aggregator")); - } - return "Unknown-Aggregator"; - } - - // Updated helper method for your existing CommitmentResult class - public void verifyAllInclusionProofsInParallelForMultipleAggregators(int timeoutSeconds, List aggregatorClients) throws Exception { - List results = collectCommitmentResults(); - - // Group results by aggregator - Map> resultsByAggregator = results.stream() - .collect(Collectors.groupingBy(r -> extractAggregatorFromUserName(r.getUserName()))); - - ExecutorService executor = Executors.newFixedThreadPool(aggregatorClients.size()); - List> verificationFutures = new ArrayList<>(); - - for (int i = 0; i < aggregatorClients.size(); i++) { - AggregatorClient aggregatorClient = aggregatorClients.get(i); - String aggregatorId = "Aggregator" + i; - List aggregatorResults = resultsByAggregator.getOrDefault("-" + aggregatorId, new ArrayList<>()); - - CompletableFuture future = CompletableFuture.runAsync(() -> { - try { - verifyInclusionProofsForAggregator(aggregatorClient, aggregatorResults, timeoutSeconds); - } catch (Exception e) { - throw new RuntimeException("Failed to verify inclusion proofs for " + aggregatorId, e); - } - }, executor); - - verificationFutures.add(future); - } - - try { - CompletableFuture.allOf(verificationFutures.toArray(new CompletableFuture[0])) - .get(timeoutSeconds + 10, TimeUnit.SECONDS); // Add buffer time for processing - } finally { - executor.shutdown(); - } - } - - private void verifyInclusionProofsForAggregator(AggregatorClient aggregatorClient, - List results, - int timeoutSeconds) throws Exception { - long globalStartTime = System.currentTimeMillis(); - long timeoutMillis = timeoutSeconds * 1000L; - - for (CommitmentResult result : results) { - long inclusionStartTime = System.nanoTime(); - - if (!result.isSuccess()) { - long inclusionEndTime = System.nanoTime(); - result.markFailedVerification(inclusionStartTime, inclusionEndTime, "Commitment submission failed"); - continue; - } - - boolean verified = false; - String statusMessage = "Timeout waiting for inclusion proof"; - - // Poll for inclusion proof with timeout - while (System.currentTimeMillis() - globalStartTime < timeoutMillis) { - try { - // Check if inclusion proof is available - InclusionProof proofResponse = aggregatorClient - .getInclusionProof(result.getRequestId()).get(5, TimeUnit.SECONDS); - if (proofResponse != null && proofResponse.verify(result.getRequestId()) - == InclusionProofVerificationStatus.OK) { - System.out.println("InclusionProofVerificationStatus.OK"); - result.markVerified(inclusionStartTime, System.nanoTime()); - verified = true; - break; - } else { - InclusionProofVerificationStatus status = proofResponse.verify(result.getRequestId()); - System.out.println(status.toString()); - statusMessage = status.toString(); - } - Thread.sleep(1000); - } catch (TimeoutException e) { - // Continue polling - statusMessage = "Timeout during proof retrieval"; - } catch (Exception e) { - statusMessage = "Error retrieving proof: " + e.getMessage(); - break; - } - } - - long inclusionEndTime = System.nanoTime(); - - // Use your existing methods to mark verification result - if (verified) { - result.markVerified(inclusionStartTime, inclusionEndTime); - } else { - result.markFailedVerification(inclusionStartTime, inclusionEndTime, statusMessage); - } - } - } - - // Method to print detailed results by aggregator - public void printDetailedResultsByAggregator(List results, int aggregatorCount) { - System.out.println("\n=== Detailed Results by Aggregator ==="); - - Map> resultsByAggregator = results.stream() - .collect(Collectors.groupingBy(r -> extractAggregatorFromUserName(r.getUserName()))); - - for (int i = 0; i < aggregatorCount; i++) { - String aggregatorId = "-Aggregator" + i; - List aggregatorResults = resultsByAggregator.getOrDefault(aggregatorId, new ArrayList<>()); - - long verifiedCount = aggregatorResults.stream() - .filter(CommitmentResult::isVerified) - .count(); - - double successRate = aggregatorResults.isEmpty() ? 0 : - (double) verifiedCount / aggregatorResults.size() * 100; - - // Calculate average inclusion proof time for verified commitments - OptionalDouble avgInclusionTime = aggregatorResults.stream() - .filter(CommitmentResult::isVerified) - .mapToDouble(CommitmentResult::getInclusionDurationMillis) - .average(); - - System.out.println("Aggregator" + i + " (localhost:" + (3000 + i * 5080) + "):"); - System.out.println(" Total commitments: " + aggregatorResults.size()); - System.out.println(" Verified: " + verifiedCount + " / " + aggregatorResults.size()); - System.out.println(" Success rate: " + String.format("%.2f%%", successRate)); - - if (avgInclusionTime.isPresent()) { - System.out.println(" Average inclusion time: " + String.format("%.2f ms", avgInclusionTime.getAsDouble())); - } - - // Print failed verifications - List failed = aggregatorResults.stream() - .filter(r -> !r.isVerified()) - .collect(Collectors.toList()); - - if (!failed.isEmpty()) { - System.out.println(" Failed verifications (" + failed.size() + "):"); - failed.forEach(r -> System.out.println(" ❌ " + r.getRequestId() + - " - " + (r.getStatus() != null ? r.getStatus() : "Unknown error"))); - } else { - System.out.println(" ✅ All commitments verified successfully!"); - } - - System.out.println(); - } - } - - public void printPerformanceComparison(List results, int aggregatorCount) { - Map> resultsByAggregator = results.stream() - .collect(Collectors.groupingBy(r -> extractAggregatorFromUserName(r.getUserName()))); - - System.out.println("=== 🏆 PERFORMANCE WINNER ANALYSIS ==="); - - // Find best success rate - double bestSuccessRate = 0; - String bestSuccessAggregator = ""; - - // Find fastest average inclusion time - double fastestAvgTime = Double.MAX_VALUE; - String fastestAggregator = ""; - - for (int i = 0; i < aggregatorCount; i++) { - String aggregatorId = "-Aggregator" + i; - List aggregatorResults = resultsByAggregator.getOrDefault(aggregatorId, new ArrayList<>()); - - if (aggregatorResults.isEmpty()) continue; - - long verifiedCount = aggregatorResults.stream() - .filter(CommitmentResult::isVerified) - .count(); - - double successRate = (double) verifiedCount / aggregatorResults.size() * 100; - - if (successRate > bestSuccessRate) { - bestSuccessRate = successRate; - bestSuccessAggregator = "Aggregator" + i; - } - - OptionalDouble avgInclusionTime = aggregatorResults.stream() - .filter(CommitmentResult::isVerified) - .mapToDouble(CommitmentResult::getInclusionDurationMillis) - .average(); - - if (avgInclusionTime.isPresent() && avgInclusionTime.getAsDouble() < fastestAvgTime) { - fastestAvgTime = avgInclusionTime.getAsDouble(); - fastestAggregator = "Aggregator" + i; - } - } - - System.out.println("🥇 Highest Success Rate: " + bestSuccessAggregator + - " (" + String.format("%.2f%%", bestSuccessRate) + ")"); - - if (fastestAvgTime != Double.MAX_VALUE) { - System.out.println("⚡ Fastest Inclusion Time: " + fastestAggregator + - " (" + String.format("%.2f ms", fastestAvgTime) + ")"); - } - - System.out.println("=====================================\n"); - } -} \ No newline at end of file +//package org.unicitylabs.sdk.e2e.steps.shared; +// +//import org.unicitylabs.sdk.address.Address; +//import org.unicitylabs.sdk.address.DirectAddress; +//import org.unicitylabs.sdk.address.ProxyAddress; +//import org.unicitylabs.sdk.api.AggregatorClient; +//import org.unicitylabs.sdk.api.SubmitCommitmentResponse; +//import org.unicitylabs.sdk.api.SubmitCommitmentStatus; +//import org.unicitylabs.sdk.e2e.config.CucumberConfiguration; +//import org.unicitylabs.sdk.e2e.context.TestContext; +//import org.unicitylabs.sdk.hash.DataHash; +//import org.unicitylabs.sdk.hash.HashAlgorithm; +//import org.unicitylabs.sdk.predicate.MaskedPredicate; +//import org.unicitylabs.sdk.predicate.Predicate; +//import org.unicitylabs.sdk.predicate.UnmaskedPredicate; +//import org.unicitylabs.sdk.predicate.UnmaskedPredicateReference; +//import org.unicitylabs.sdk.signing.SigningService; +//import org.unicitylabs.sdk.token.Token; +//import org.unicitylabs.sdk.token.TokenState; +//import org.unicitylabs.sdk.token.TokenType; +//import org.unicitylabs.sdk.transaction.*; +//import org.unicitylabs.sdk.utils.TestUtils; +//import org.unicitylabs.sdk.utils.helpers.CommitmentResult; +// +//import java.nio.charset.StandardCharsets; +//import java.util.*; +//import java.util.concurrent.*; +//import java.util.stream.Collectors; +// +//import static org.unicitylabs.sdk.util.InclusionProofUtils.waitInclusionProof; +//import static org.unicitylabs.sdk.utils.TestUtils.randomBytes; +// +// +//public class StepHelper { +// +// private final TestContext context; +// +// public StepHelper() { // ✅ Public zero-argument constructor +// this.context = CucumberConfiguration.getTestContext(); +// } +// +// public Token createNameTagTokenForUser(String userName, TokenType type, String nametag, String nametagData) throws Exception { +// byte[] nametagNonce = TestUtils.generateRandomBytes(32); +// +// MaskedPredicate nametagPredicate = MaskedPredicate.create( +// SigningService.createFromMaskedSecret(context.getUserSecret().get(userName), nametagNonce), +// HashAlgorithm.SHA256, +// nametagNonce +// ); +// +// TokenType nametagTokenType = TestUtils.generateRandomTokenType(); +// DirectAddress nametagAddress = nametagPredicate.getReference(nametagTokenType).toAddress(); +// +// DirectAddress userAddress = UnmaskedPredicateReference.create( +// nametagTokenType, +// SigningService.createFromSecret(context.getUserSecret().get(userName)), +// HashAlgorithm.SHA256 +// ).toAddress(); +// +// var nametagMintCommitment = org.unicitylabs.sdk.transaction.MintCommitment.create( +// new NametagMintTransactionData<>( +// nametag, +// nametagTokenType, +// nametagAddress, +// TestUtils.generateRandomBytes(32), +// userAddress +// ) +// ); +// +// SubmitCommitmentResponse response = context.getClient().submitCommitment(nametagMintCommitment).get(); +// if (response.getStatus() != SubmitCommitmentStatus.SUCCESS) { +// throw new Exception("Failed to submit nametag mint commitment: " + response.getStatus()); +// } +// +// InclusionProof inclusionProof = waitInclusionProof(context.getClient(), nametagMintCommitment).get(); +// Transaction> nametagGenesis = nametagMintCommitment.toTransaction(inclusionProof); +// +// return new Token( +// new org.unicitylabs.sdk.token.TokenState(nametagPredicate, null), +// nametagGenesis +// ); +// } +// +// public void transferToken(String fromUser, String toUser, Token token, Address toAddress, String customData) throws Exception { +// SigningService fromSigningService = context.getUserSigningServices().get(fromUser); +// +// // Create data hash and state data if custom data provided +// DataHash dataHash = null; +// byte[] stateData = null; +// if (customData != null && !customData.isEmpty()) { +// stateData = customData.getBytes(StandardCharsets.UTF_8); +// dataHash = TestUtils.hashData(stateData); +// } +// +// // Submit transfer commitment +// TransferCommitment transferCommitment = TransferCommitment.create( +// token, +// toAddress, +// randomBytes(32), +// dataHash, +// null, +// fromSigningService +// ); +// +// SubmitCommitmentResponse response = context.getClient().submitCommitment(token, transferCommitment).get(); +// if (response.getStatus() != SubmitCommitmentStatus.SUCCESS) { +// throw new Exception("Failed to submit transfer commitment: " + response.getStatus()); +// } +// +// // Wait for inclusion proof +// InclusionProof inclusionProof = waitInclusionProof( +// context.getClient(), +// transferCommitment +// ).get(); +// Transaction transferTransaction = transferCommitment.toTransaction( +// token, +// inclusionProof +// ); +// +// context.savePendingTransfer(toUser, token, transferTransaction); +// } +// +// public void finalizeTransfer(String username, Token token, Transaction tx) throws Exception { +// +// byte[] secret = context.getUserSecret().get(username); +// +// Token currentNameTagToken = context.getNameTagToken(username); +// List nametagTokens = context.getNameTagTokens().get(username); +// if (nametagTokens != null && !nametagTokens.isEmpty()) { +// for (Token t : nametagTokens) { +// String actualNametagAddress = tx.getData().getRecipient().getAddress(); +// String expectedProxyAddress = ProxyAddress.create(t.getId()).getAddress(); +// +// if (actualNametagAddress.equalsIgnoreCase(expectedProxyAddress)) { +// currentNameTagToken = t; +// break; +// } +// } +// } +// +// List> additionalTokens = new ArrayList<>(); +// if (currentNameTagToken != null) { +// additionalTokens.add(currentNameTagToken); +// } +// +// Predicate unlockPredicate = context.getUserPredicate().get(username); +// if (unlockPredicate == null){ +// context.getUserSigningServices().put(username, SigningService.createFromSecret(secret)); +// unlockPredicate = UnmaskedPredicate.create( +// context.getUserSigningServices().get(username), +// HashAlgorithm.SHA256, +// tx.getData().getSalt() +// ); +// } +// +// TokenState recipientState = new TokenState( +// unlockPredicate, +// null +// ); +// +// Token finalizedToken = context.getClient().finalizeTransaction( +// token, +// recipientState, +// tx, +// additionalTokens +// ); +// +// context.addUserToken(username, finalizedToken); +// } +// +// public boolean submitSingleCommitment() { +// try { +// byte[] randomSecret = TestUtils.generateRandomBytes(32); +// byte[] stateBytes = TestUtils.generateRandomBytes(32); +// byte[] txData = TestUtils.generateRandomBytes(32); +// +// DataHash stateHash = TestUtils.hashData(stateBytes); +// DataHash txDataHash = TestUtils.hashData(txData); +// SigningService signingService = SigningService.createFromSecret(randomSecret); +// var requestId = TestUtils.createRequestId(signingService, stateHash); +// var authenticator = TestUtils.createAuthenticator(signingService, txDataHash, stateHash); +// +// SubmitCommitmentResponse response = context.getAggregatorClient() +// .submitCommitment(requestId, txDataHash, authenticator).get(); +// return response.getStatus() == SubmitCommitmentStatus.SUCCESS; +// } catch (Exception e) { +// return false; +// } +// } +// +// public void verifyAllInclusionProofsInParallel(int timeoutSeconds) +// throws InterruptedException { +// List results = collectCommitmentResults(); +// ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); +// CountDownLatch latch = new CountDownLatch(results.size()); +// +// long startAll = System.nanoTime(); +// long globalTimeout = startAll + TimeUnit.SECONDS.toNanos(timeoutSeconds); +// +// for (CommitmentResult result : results) { +// executor.submit(() -> { +// long inclStart = System.nanoTime(); +// boolean verified = false; +// String errorMessage = "Global timeout reached"; +// +// try { +// while (System.nanoTime() < globalTimeout && !verified) { +// try { +// InclusionProof proof = context.getAggregatorClient() +// .getInclusionProof(result.getRequestId()) +// .get(calculateRemainingTimeout(globalTimeout), TimeUnit.MILLISECONDS); +// +// if (proof != null && proof.verify(result.getRequestId()) +// == InclusionProofVerificationStatus.OK) { +// result.markVerified(inclStart, System.nanoTime()); +// verified = true; +// } else { +// // Неуспешная верификация, но продолжаем пытаться +// InclusionProofVerificationStatus status = proof.verify(result.getRequestId()); +// errorMessage = status.toString(); +// Thread.sleep(1000); // Небольшая пауза перед повторной попыткой +// } +// } catch (TimeoutException e) { +// // Таймаут отдельной операции, продолжаем цикл +// errorMessage = "Individual operation timeout: " + e.getMessage(); +// } catch (ExecutionException e) { +// // Ошибка выполнения, продолжаем цикл +// errorMessage = "Execution error: " + e.getMessage(); +// Thread.sleep(1000); // Пауза перед повторной попыткой +// } +// } +// +// if (!verified) { +// result.markFailedVerification(inclStart, System.nanoTime(), errorMessage); +// } +// +// } catch (Exception e) { +// result.markFailedVerification(inclStart, System.nanoTime(), +// "Unexpected error: " + e.getMessage()); +// } finally { +// latch.countDown(); +// } +// }); +// } +// +// // Wait for all tasks to complete or timeout +// boolean finished = latch.await(timeoutSeconds, TimeUnit.SECONDS); +// executor.shutdownNow(); +// +// long endAll = System.nanoTime(); +// System.out.println("All inclusion proofs completed in: " + ((endAll - startAll) / 1_000_000) + " ms"); +// +// if (!finished) { +// System.err.println("Timeout reached before all inclusion proofs were verified"); +// } +// } +// +// private long calculateRemainingTimeout(long globalTimeoutNanos) { +// long remaining = globalTimeoutNanos - System.nanoTime(); +// return TimeUnit.NANOSECONDS.toMillis(Math.max(remaining, 100)); // Минимум 100мс +// } +// +// public List collectCommitmentResults() { +// return context.getCommitmentFutures().stream() +// .map(f -> { +// try { +// return f.get(); // wait for completion +// } catch (Exception e) { +// e.printStackTrace(); +// return null; +// } +// }) +// .filter(Objects::nonNull) +// .collect(Collectors.toList()); +// } +// +// // Helper method to extract aggregator info from username +// public String extractAggregatorFromUserName(String userName) { +// if (userName.contains("-Aggregator")) { +// return userName.substring(userName.indexOf("-Aggregator")); +// } +// return "Unknown-Aggregator"; +// } +// +// // Updated helper method for your existing CommitmentResult class +// public void verifyAllInclusionProofsInParallelForMultipleAggregators(int timeoutSeconds, List aggregatorClients) throws Exception { +// List results = collectCommitmentResults(); +// +// // Group results by aggregator +// Map> resultsByAggregator = results.stream() +// .collect(Collectors.groupingBy(r -> extractAggregatorFromUserName(r.getUserName()))); +// +// ExecutorService executor = Executors.newFixedThreadPool(aggregatorClients.size()); +// List> verificationFutures = new ArrayList<>(); +// +// for (int i = 0; i < aggregatorClients.size(); i++) { +// AggregatorClient aggregatorClient = aggregatorClients.get(i); +// String aggregatorId = "Aggregator" + i; +// List aggregatorResults = resultsByAggregator.getOrDefault("-" + aggregatorId, new ArrayList<>()); +// +// CompletableFuture future = CompletableFuture.runAsync(() -> { +// try { +// verifyInclusionProofsForAggregator(aggregatorClient, aggregatorResults, timeoutSeconds); +// } catch (Exception e) { +// throw new RuntimeException("Failed to verify inclusion proofs for " + aggregatorId, e); +// } +// }, executor); +// +// verificationFutures.add(future); +// } +// +// try { +// CompletableFuture.allOf(verificationFutures.toArray(new CompletableFuture[0])) +// .get(timeoutSeconds + 10, TimeUnit.SECONDS); // Add buffer time for processing +// } finally { +// executor.shutdown(); +// } +// } +// +// private void verifyInclusionProofsForAggregator(AggregatorClient aggregatorClient, +// List results, +// int timeoutSeconds) throws Exception { +// long globalStartTime = System.currentTimeMillis(); +// long timeoutMillis = timeoutSeconds * 1000L; +// +// for (CommitmentResult result : results) { +// long inclusionStartTime = System.nanoTime(); +// +// if (!result.isSuccess()) { +// long inclusionEndTime = System.nanoTime(); +// result.markFailedVerification(inclusionStartTime, inclusionEndTime, "Commitment submission failed"); +// continue; +// } +// +// boolean verified = false; +// String statusMessage = "Timeout waiting for inclusion proof"; +// +// // Poll for inclusion proof with timeout +// while (System.currentTimeMillis() - globalStartTime < timeoutMillis) { +// try { +// // Check if inclusion proof is available +// InclusionProof proofResponse = aggregatorClient +// .getInclusionProof(result.getRequestId()).get(5, TimeUnit.SECONDS); +// if (proofResponse != null && proofResponse.verify(result.getRequestId()) +// == InclusionProofVerificationStatus.OK) { +// System.out.println("InclusionProofVerificationStatus.OK"); +// result.markVerified(inclusionStartTime, System.nanoTime()); +// verified = true; +// break; +// } else { +// InclusionProofVerificationStatus status = proofResponse.verify(result.getRequestId()); +// System.out.println(status.toString()); +// statusMessage = status.toString(); +// } +// Thread.sleep(1000); +// } catch (TimeoutException e) { +// // Continue polling +// statusMessage = "Timeout during proof retrieval"; +// } catch (Exception e) { +// statusMessage = "Error retrieving proof: " + e.getMessage(); +// break; +// } +// } +// +// long inclusionEndTime = System.nanoTime(); +// +// // Use your existing methods to mark verification result +// if (verified) { +// result.markVerified(inclusionStartTime, inclusionEndTime); +// } else { +// result.markFailedVerification(inclusionStartTime, inclusionEndTime, statusMessage); +// } +// } +// } +// +// // Method to print detailed results by aggregator +// public void printDetailedResultsByAggregator(List results, int aggregatorCount) { +// System.out.println("\n=== Detailed Results by Aggregator ==="); +// +// Map> resultsByAggregator = results.stream() +// .collect(Collectors.groupingBy(r -> extractAggregatorFromUserName(r.getUserName()))); +// +// for (int i = 0; i < aggregatorCount; i++) { +// String aggregatorId = "-Aggregator" + i; +// List aggregatorResults = resultsByAggregator.getOrDefault(aggregatorId, new ArrayList<>()); +// +// long verifiedCount = aggregatorResults.stream() +// .filter(CommitmentResult::isVerified) +// .count(); +// +// double successRate = aggregatorResults.isEmpty() ? 0 : +// (double) verifiedCount / aggregatorResults.size() * 100; +// +// // Calculate average inclusion proof time for verified commitments +// OptionalDouble avgInclusionTime = aggregatorResults.stream() +// .filter(CommitmentResult::isVerified) +// .mapToDouble(CommitmentResult::getInclusionDurationMillis) +// .average(); +// +// System.out.println("Aggregator" + i + " (localhost:" + (3000 + i * 5080) + "):"); +// System.out.println(" Total commitments: " + aggregatorResults.size()); +// System.out.println(" Verified: " + verifiedCount + " / " + aggregatorResults.size()); +// System.out.println(" Success rate: " + String.format("%.2f%%", successRate)); +// +// if (avgInclusionTime.isPresent()) { +// System.out.println(" Average inclusion time: " + String.format("%.2f ms", avgInclusionTime.getAsDouble())); +// } +// +// // Print failed verifications +// List failed = aggregatorResults.stream() +// .filter(r -> !r.isVerified()) +// .collect(Collectors.toList()); +// +// if (!failed.isEmpty()) { +// System.out.println(" Failed verifications (" + failed.size() + "):"); +// failed.forEach(r -> System.out.println(" ❌ " + r.getRequestId() + +// " - " + (r.getStatus() != null ? r.getStatus() : "Unknown error"))); +// } else { +// System.out.println(" ✅ All commitments verified successfully!"); +// } +// +// System.out.println(); +// } +// } +// +// public void printPerformanceComparison(List results, int aggregatorCount) { +// Map> resultsByAggregator = results.stream() +// .collect(Collectors.groupingBy(r -> extractAggregatorFromUserName(r.getUserName()))); +// +// System.out.println("=== 🏆 PERFORMANCE WINNER ANALYSIS ==="); +// +// // Find best success rate +// double bestSuccessRate = 0; +// String bestSuccessAggregator = ""; +// +// // Find fastest average inclusion time +// double fastestAvgTime = Double.MAX_VALUE; +// String fastestAggregator = ""; +// +// for (int i = 0; i < aggregatorCount; i++) { +// String aggregatorId = "-Aggregator" + i; +// List aggregatorResults = resultsByAggregator.getOrDefault(aggregatorId, new ArrayList<>()); +// +// if (aggregatorResults.isEmpty()) continue; +// +// long verifiedCount = aggregatorResults.stream() +// .filter(CommitmentResult::isVerified) +// .count(); +// +// double successRate = (double) verifiedCount / aggregatorResults.size() * 100; +// +// if (successRate > bestSuccessRate) { +// bestSuccessRate = successRate; +// bestSuccessAggregator = "Aggregator" + i; +// } +// +// OptionalDouble avgInclusionTime = aggregatorResults.stream() +// .filter(CommitmentResult::isVerified) +// .mapToDouble(CommitmentResult::getInclusionDurationMillis) +// .average(); +// +// if (avgInclusionTime.isPresent() && avgInclusionTime.getAsDouble() < fastestAvgTime) { +// fastestAvgTime = avgInclusionTime.getAsDouble(); +// fastestAggregator = "Aggregator" + i; +// } +// } +// +// System.out.println("🥇 Highest Success Rate: " + bestSuccessAggregator + +// " (" + String.format("%.2f%%", bestSuccessRate) + ")"); +// +// if (fastestAvgTime != Double.MAX_VALUE) { +// System.out.println("⚡ Fastest Inclusion Time: " + fastestAggregator + +// " (" + String.format("%.2f ms", fastestAvgTime) + ")"); +// } +// +// System.out.println("=====================================\n"); +// } +//} \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java b/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java index 232edfc..7f06aae 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java @@ -3,7 +3,7 @@ import org.junit.jupiter.api.BeforeEach; import org.unicitylabs.sdk.StateTransitionClient; import org.unicitylabs.sdk.TestAggregatorClient; -import org.unicitylabs.sdk.e2e.CommonTestFlow; +import org.unicitylabs.sdk.common.CommonTestFlow; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.utils.RootTrustBaseUtils; diff --git a/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java b/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java index e31a906..e210fd9 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java @@ -101,10 +101,10 @@ private Token receiveToken(String[] tokenInfo, byte[] secret) throws Exceptio ); return this.client.finalizeTransaction( + this.trustBase, token, state, transaction, - this.trustBase, List.of() ); } diff --git a/src/test/java/org/unicitylabs/sdk/utils/TestUtils.java b/src/test/java/org/unicitylabs/sdk/utils/TestUtils.java index 6b61a4c..d2b5ea1 100644 --- a/src/test/java/org/unicitylabs/sdk/utils/TestUtils.java +++ b/src/test/java/org/unicitylabs/sdk/utils/TestUtils.java @@ -1,31 +1,10 @@ package org.unicitylabs.sdk.utils; -import org.unicitylabs.sdk.api.Authenticator; -import org.unicitylabs.sdk.api.RequestId; -import org.unicitylabs.sdk.token.fungible.CoinId; -import org.unicitylabs.sdk.token.fungible.TokenCoinData; - -import org.unicitylabs.sdk.StateTransitionClient; -import org.unicitylabs.sdk.address.Address; -import org.unicitylabs.sdk.api.SubmitCommitmentResponse; -import org.unicitylabs.sdk.api.SubmitCommitmentStatus; -import org.unicitylabs.sdk.hash.DataHash; -import org.unicitylabs.sdk.hash.DataHasher; -import org.unicitylabs.sdk.hash.HashAlgorithm; -import org.unicitylabs.sdk.predicate.MaskedPredicate; -import org.unicitylabs.sdk.signing.SigningService; -import org.unicitylabs.sdk.token.Token; -import org.unicitylabs.sdk.token.TokenId; -import org.unicitylabs.sdk.token.TokenState; -import org.unicitylabs.sdk.token.TokenType; -import org.unicitylabs.sdk.transaction.*; -import org.unicitylabs.sdk.util.InclusionProofUtils; - import java.math.BigInteger; -import java.nio.charset.StandardCharsets; import java.security.SecureRandom; -import java.util.List; import java.util.Map; +import org.unicitylabs.sdk.token.fungible.CoinId; +import org.unicitylabs.sdk.token.fungible.TokenCoinData; /** * Utility methods for tests. @@ -64,260 +43,260 @@ public static TokenCoinData randomCoinData(int numCoins) { private static final SecureRandom SECURE_RANDOM = new SecureRandom(); - /** - * Creates a token mint commitment and submits it to the client - */ - public static Token mintTokenForUser( - StateTransitionClient client, - SigningService signingService, - byte[] nonce, - TokenId tokenId, - TokenType tokenType, - TokenCoinData coinData) throws Exception { - - MaskedPredicate predicate = MaskedPredicate.create(signingService, HashAlgorithm.SHA256, nonce); - Address address = predicate.getReference(tokenType).toAddress(); - TokenState tokenState = new TokenState(predicate, null); - - MintCommitment mintCommitment = MintCommitment.create( - new MintTransactionData( - tokenId, - tokenType, - new TestTokenData(randomBytes(32)).getData(), - coinData, - address, - randomBytes(5), - null, - null - ) - ); - - SubmitCommitmentResponse response = client.submitCommitment(mintCommitment).get(); - if (response.getStatus() != SubmitCommitmentStatus.SUCCESS) { - throw new Exception("Failed to submit mint commitment: " + response.getStatus()); - } - - InclusionProof inclusionProof = InclusionProofUtils.waitInclusionProof(client, mintCommitment).get(); - return new Token(tokenState, mintCommitment.toTransaction(inclusionProof)); - } - - /** - * Transfers a token from one user to another - */ - public static Token transferToken( - StateTransitionClient client, - Token sourceToken, - SigningService fromSigningService, - SigningService toSigningService, - byte[] toNonce, - Address toAddress, - byte[] customData, - List> additionalTokens) throws Exception { - - // Create data hash if custom data provided - DataHash dataHash = null; - if (customData != null) { - dataHash = hashData(customData); - } - - // Submit transfer commitment - TransferCommitment transferCommitment = TransferCommitment.create( - sourceToken, - toAddress, - randomBytes(32), - dataHash, - null, - fromSigningService - ); - - SubmitCommitmentResponse response = client.submitCommitment(sourceToken, transferCommitment).get(); - if (response.getStatus() != SubmitCommitmentStatus.SUCCESS) { - throw new Exception("Failed to submit transfer commitment: " + response.getStatus()); - } - - // Wait for inclusion proof - InclusionProof inclusionProof = InclusionProofUtils.waitInclusionProof(client, transferCommitment).get(); - Transaction transferTransaction = transferCommitment.toTransaction(sourceToken, inclusionProof); - - - //Bob steps - // Create predicate for recipient - MaskedPredicate toPredicate = MaskedPredicate.create(toSigningService, HashAlgorithm.SHA256, toNonce); - - // Finalize transaction - return client.finalizeTransaction( - sourceToken, - new TokenState(toPredicate, customData), - transferTransaction, - additionalTokens != null ? additionalTokens : List.of() - ); - } - - /** - * Creates random coin data with specified number of coins - */ - public static TokenCoinData createRandomCoinData(int coinCount) { - Map coins = new java.util.HashMap<>(); - for (int i = 0; i < coinCount; i++) { - CoinId coinId = new CoinId(randomBytes(32)); - BigInteger value = BigInteger.valueOf(SECURE_RANDOM.nextInt(1000) + 100); // Random value between 100-1099 - coins.put(coinId, value); - } - return new TokenCoinData(coins); - } - - /** - * Generates random bytes of specified length - */ - public static byte[] generateRandomBytes(int length) { - byte[] bytes = new byte[length]; - SECURE_RANDOM.nextBytes(bytes); - return bytes; - } - - /** - * Creates a hash of the provided data - */ - public static DataHash hashData(byte[] data) { - return new DataHasher(HashAlgorithm.SHA256).update(data).digest(); - } - - /** - * Creates a hash of string data - */ - public static DataHash hashData(String data) { - return hashData(data.getBytes(StandardCharsets.UTF_8)); - } - - /** - * Creates a signing service from a user name and optional nonce - */ - public static SigningService createSigningServiceForUser(String userName, byte[] nonce) { - byte[] secret = userName.getBytes(StandardCharsets.UTF_8); - return SigningService.createFromMaskedSecret(secret, nonce); - } - - /** - * Sets up a user with signing service and nonce in the provided maps - */ - public static void setupUser(String userName, - Map userSigningServices, - Map userNonces, - Map userSecret) { - byte[] secret = userName.getBytes(StandardCharsets.UTF_8); - byte[] nonce = generateRandomBytes(32); - SigningService signingService = SigningService.createFromMaskedSecret(secret, nonce); - - userSigningServices.put(userName, signingService); - userNonces.put(userName, nonce); - userSecret.put(userName,secret); - } - - /** - * Validates that a token is properly owned by a signing service - */ - public static boolean validateTokenOwnership(Token token, SigningService signingService) { - if (!token.verify().isSuccessful()) { - return false; - } - return token.getState().getUnlockPredicate().isOwner(signingService.getPublicKey()); - } - - public static RequestId createRequestId(SigningService signingService, DataHash stateHash) { - return RequestId.createFromImprint(signingService.getPublicKey(), stateHash.getImprint()); - } - - public static Authenticator createAuthenticator(SigningService signingService, DataHash txDataHash, DataHash stateHash) { - return Authenticator.create(signingService, txDataHash, stateHash); - } - - /** - * Waits for a commitment to be included and returns the inclusion proof - */ - public static InclusionProof waitForInclusionProof(StateTransitionClient client, Commitment commitment) throws Exception { - return InclusionProofUtils.waitInclusionProof(client, commitment).get(); - } - - /** - * Generates a random token ID - */ - public static TokenId generateRandomTokenId() { - return new TokenId(randomBytes(32)); - } - - /** - * Generates a random token type - */ - public static TokenType generateRandomTokenType() { - return new TokenType(randomBytes(32)); - } - - /** - * Creates a token type from a string identifier - */ - public static TokenType createTokenTypeFromString(String identifier) { - return new TokenType(identifier.getBytes(StandardCharsets.UTF_8)); - } - - /** - * Validates performance metrics - */ - public static class PerformanceValidator { - public static void validateDuration(long actualDuration, long maxDuration, String operation) { - if (actualDuration >= maxDuration) { - throw new AssertionError(String.format( - "%s took %d ms, should be less than %d ms", - operation, actualDuration, maxDuration)); - } - } - - public static void validateSuccessRate(long successful, long total, double minSuccessRate, String operation) { - double actualRate = (double) successful / total; - if (actualRate < minSuccessRate) { - throw new AssertionError(String.format( - "%s success rate %.2f%% is below required %.2f%%", - operation, actualRate * 100, minSuccessRate * 100)); - } - } - } - - /** - * Token operation result wrapper - */ - public static class TokenOperationResult { - private final boolean success; - private final String message; - private final Token token; - private final Exception error; - - public TokenOperationResult(boolean success, String message, Token token, Exception error) { - this.success = success; - this.message = message; - this.token = token; - this.error = error; - } - - public static TokenOperationResult success(String message, Token token) { - return new TokenOperationResult(true, message, token, null); - } - - public static TokenOperationResult failure(String message, Exception error) { - return new TokenOperationResult(false, message, null, error); - } - - public boolean isSuccess() { return success; } - public String getMessage() { return message; } - public Token getToken() { return token; } - public Exception getError() { return error; } - } - - public static String generateRandomString(int length) { - StringBuilder sb = new StringBuilder(length); - for (int i = 0; i < length; i++) { - sb.append(CHARACTERS.charAt(RANDOM.nextInt(CHARACTERS.length()))); - } - return sb.toString(); - } +// /** +// * Creates a token mint commitment and submits it to the client +// */ +// public static Token mintTokenForUser( +// StateTransitionClient client, +// SigningService signingService, +// byte[] nonce, +// TokenId tokenId, +// TokenType tokenType, +// TokenCoinData coinData) throws Exception { +// +// MaskedPredicate predicate = MaskedPredicate.create(signingService, HashAlgorithm.SHA256, nonce); +// Address address = predicate.getReference(tokenType).toAddress(); +// TokenState tokenState = new TokenState(predicate, null); +// +// MintCommitment mintCommitment = MintCommitment.create( +// new MintTransactionData( +// tokenId, +// tokenType, +// new TestTokenData(randomBytes(32)).getData(), +// coinData, +// address, +// randomBytes(5), +// null, +// null +// ) +// ); +// +// SubmitCommitmentResponse response = client.submitCommitment(mintCommitment).get(); +// if (response.getStatus() != SubmitCommitmentStatus.SUCCESS) { +// throw new Exception("Failed to submit mint commitment: " + response.getStatus()); +// } +// +// InclusionProof inclusionProof = InclusionProofUtils.waitInclusionProof(client, mintCommitment).get(); +// return new Token(tokenState, mintCommitment.toTransaction(inclusionProof)); +// } +// +// /** +// * Transfers a token from one user to another +// */ +// public static Token transferToken( +// StateTransitionClient client, +// Token sourceToken, +// SigningService fromSigningService, +// SigningService toSigningService, +// byte[] toNonce, +// Address toAddress, +// byte[] customData, +// List> additionalTokens) throws Exception { +// +// // Create data hash if custom data provided +// DataHash dataHash = null; +// if (customData != null) { +// dataHash = hashData(customData); +// } +// +// // Submit transfer commitment +// TransferCommitment transferCommitment = TransferCommitment.create( +// sourceToken, +// toAddress, +// randomBytes(32), +// dataHash, +// null, +// fromSigningService +// ); +// +// SubmitCommitmentResponse response = client.submitCommitment(sourceToken, transferCommitment).get(); +// if (response.getStatus() != SubmitCommitmentStatus.SUCCESS) { +// throw new Exception("Failed to submit transfer commitment: " + response.getStatus()); +// } +// +// // Wait for inclusion proof +// InclusionProof inclusionProof = InclusionProofUtils.waitInclusionProof(client, transferCommitment).get(); +// Transaction transferTransaction = transferCommitment.toTransaction(sourceToken, inclusionProof); +// +// +// //Bob steps +// // Create predicate for recipient +// MaskedPredicate toPredicate = MaskedPredicate.create(toSigningService, HashAlgorithm.SHA256, toNonce); +// +// // Finalize transaction +// return client.finalizeTransaction( +// sourceToken, +// new TokenState(toPredicate, customData), +// transferTransaction, +// additionalTokens != null ? additionalTokens : List.of() +// ); +// } +// +// /** +// * Creates random coin data with specified number of coins +// */ +// public static TokenCoinData createRandomCoinData(int coinCount) { +// Map coins = new java.util.HashMap<>(); +// for (int i = 0; i < coinCount; i++) { +// CoinId coinId = new CoinId(randomBytes(32)); +// BigInteger value = BigInteger.valueOf(SECURE_RANDOM.nextInt(1000) + 100); // Random value between 100-1099 +// coins.put(coinId, value); +// } +// return new TokenCoinData(coins); +// } +// +// /** +// * Generates random bytes of specified length +// */ +// public static byte[] generateRandomBytes(int length) { +// byte[] bytes = new byte[length]; +// SECURE_RANDOM.nextBytes(bytes); +// return bytes; +// } +// +// /** +// * Creates a hash of the provided data +// */ +// public static DataHash hashData(byte[] data) { +// return new DataHasher(HashAlgorithm.SHA256).update(data).digest(); +// } +// +// /** +// * Creates a hash of string data +// */ +// public static DataHash hashData(String data) { +// return hashData(data.getBytes(StandardCharsets.UTF_8)); +// } +// +// /** +// * Creates a signing service from a user name and optional nonce +// */ +// public static SigningService createSigningServiceForUser(String userName, byte[] nonce) { +// byte[] secret = userName.getBytes(StandardCharsets.UTF_8); +// return SigningService.createFromMaskedSecret(secret, nonce); +// } +// +// /** +// * Sets up a user with signing service and nonce in the provided maps +// */ +// public static void setupUser(String userName, +// Map userSigningServices, +// Map userNonces, +// Map userSecret) { +// byte[] secret = userName.getBytes(StandardCharsets.UTF_8); +// byte[] nonce = generateRandomBytes(32); +// SigningService signingService = SigningService.createFromMaskedSecret(secret, nonce); +// +// userSigningServices.put(userName, signingService); +// userNonces.put(userName, nonce); +// userSecret.put(userName,secret); +// } +// +// /** +// * Validates that a token is properly owned by a signing service +// */ +// public static boolean validateTokenOwnership(Token token, SigningService signingService) { +// if (!token.verify().isSuccessful()) { +// return false; +// } +// return token.getState().getUnlockPredicate().isOwner(signingService.getPublicKey()); +// } +// +// public static RequestId createRequestId(SigningService signingService, DataHash stateHash) { +// return RequestId.createFromImprint(signingService.getPublicKey(), stateHash.getImprint()); +// } +// +// public static Authenticator createAuthenticator(SigningService signingService, DataHash txDataHash, DataHash stateHash) { +// return Authenticator.create(signingService, txDataHash, stateHash); +// } +// +// /** +// * Waits for a commitment to be included and returns the inclusion proof +// */ +// public static InclusionProof waitForInclusionProof(StateTransitionClient client, Commitment commitment) throws Exception { +// return InclusionProofUtils.waitInclusionProof(client, commitment).get(); +// } +// +// /** +// * Generates a random token ID +// */ +// public static TokenId generateRandomTokenId() { +// return new TokenId(randomBytes(32)); +// } +// +// /** +// * Generates a random token type +// */ +// public static TokenType generateRandomTokenType() { +// return new TokenType(randomBytes(32)); +// } +// +// /** +// * Creates a token type from a string identifier +// */ +// public static TokenType createTokenTypeFromString(String identifier) { +// return new TokenType(identifier.getBytes(StandardCharsets.UTF_8)); +// } +// +// /** +// * Validates performance metrics +// */ +// public static class PerformanceValidator { +// public static void validateDuration(long actualDuration, long maxDuration, String operation) { +// if (actualDuration >= maxDuration) { +// throw new AssertionError(String.format( +// "%s took %d ms, should be less than %d ms", +// operation, actualDuration, maxDuration)); +// } +// } +// +// public static void validateSuccessRate(long successful, long total, double minSuccessRate, String operation) { +// double actualRate = (double) successful / total; +// if (actualRate < minSuccessRate) { +// throw new AssertionError(String.format( +// "%s success rate %.2f%% is below required %.2f%%", +// operation, actualRate * 100, minSuccessRate * 100)); +// } +// } +// } +// +// /** +// * Token operation result wrapper +// */ +// public static class TokenOperationResult { +// private final boolean success; +// private final String message; +// private final Token token; +// private final Exception error; +// +// public TokenOperationResult(boolean success, String message, Token token, Exception error) { +// this.success = success; +// this.message = message; +// this.token = token; +// this.error = error; +// } +// +// public static TokenOperationResult success(String message, Token token) { +// return new TokenOperationResult(true, message, token, null); +// } +// +// public static TokenOperationResult failure(String message, Exception error) { +// return new TokenOperationResult(false, message, null, error); +// } +// +// public boolean isSuccess() { return success; } +// public String getMessage() { return message; } +// public Token getToken() { return token; } +// public Exception getError() { return error; } +// } +// +// public static String generateRandomString(int length) { +// StringBuilder sb = new StringBuilder(length); +// for (int i = 0; i < length; i++) { +// sb.append(CHARACTERS.charAt(RANDOM.nextInt(CHARACTERS.length()))); +// } +// return sb.toString(); +// } } \ No newline at end of file From a524f17ebe69f74bbc79f9b5a7b6f72d4b10995c Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Fri, 26 Sep 2025 12:02:47 +0400 Subject: [PATCH 16/16] Enable e2e tests --- .../sdk/transaction/InclusionProof.java | 34 ++++++------ .../sdk/e2e/E2EEscrowSwapTest.java | 29 +++++++---- .../org/unicitylabs/sdk/e2e/TokenE2ETest.java | 52 +++++++++++-------- src/test/resources/trust-base.json | 20 +++++++ 4 files changed, 85 insertions(+), 50 deletions(-) create mode 100644 src/test/resources/trust-base.json diff --git a/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java b/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java index a27dd3f..3bc215d 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/InclusionProof.java @@ -62,6 +62,24 @@ public Optional getTransactionHash() { } public InclusionProofVerificationStatus verify(RequestId requestId, RootTrustBase trustBase) { + // Check if path is valid and signed by a trusted authority + if (!new UnicityCertificateVerificationRule().verify( + new UnicityCertificateVerificationContext( + this.merkleTreePath.getRootHash(), + this.unicityCertificate, + trustBase + ) + ).isSuccessful()) { + return InclusionProofVerificationStatus.NOT_AUTHENTICATED; + } + + MerkleTreePathVerificationResult result = this.merkleTreePath.verify( + requestId.toBitString().toBigInteger()); + if (!result.isPathValid()) { + return InclusionProofVerificationStatus.PATH_INVALID; + } + + if (this.authenticator != null && this.transactionHash != null) { if (!this.authenticator.verify(this.transactionHash)) { return InclusionProofVerificationStatus.NOT_AUTHENTICATED; @@ -79,22 +97,6 @@ public InclusionProofVerificationStatus verify(RequestId requestId, RootTrustBas } } - if (!new UnicityCertificateVerificationRule().verify( - new UnicityCertificateVerificationContext( - this.merkleTreePath.getRootHash(), - this.unicityCertificate, - trustBase - ) - ).isSuccessful()) { - return InclusionProofVerificationStatus.NOT_AUTHENTICATED; - } - - MerkleTreePathVerificationResult result = this.merkleTreePath.verify( - requestId.toBitString().toBigInteger()); - if (!result.isPathValid()) { - return InclusionProofVerificationStatus.PATH_INVALID; - } - if (!result.isPathIncluded()) { return InclusionProofVerificationStatus.PATH_NOT_INCLUDED; } diff --git a/src/test/java/org/unicitylabs/sdk/e2e/E2EEscrowSwapTest.java b/src/test/java/org/unicitylabs/sdk/e2e/E2EEscrowSwapTest.java index 5a4fb44..778636a 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/E2EEscrowSwapTest.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/E2EEscrowSwapTest.java @@ -1,22 +1,29 @@ package org.unicitylabs.sdk.e2e; +import java.io.IOException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.unicitylabs.sdk.StateTransitionClient; import org.unicitylabs.sdk.api.AggregatorClient; +import org.unicitylabs.sdk.bft.RootTrustBase; import org.unicitylabs.sdk.common.BaseEscrowSwapTest; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; -//@Tag("integration") -//@EnabledIfEnvironmentVariable(named = "AGGREGATOR_URL", matches = ".+") -//public class E2EEscrowSwapTest extends BaseEscrowSwapTest { -// @BeforeEach -// void setUp() { -// String aggregatorUrl = System.getenv("AGGREGATOR_URL"); -// Assertions.assertNotNull(aggregatorUrl, "AGGREGATOR_URL environment variable must be set"); -// -// this.client = new StateTransitionClient(new AggregatorClient(aggregatorUrl)); -// } -//} +@Tag("integration") +@EnabledIfEnvironmentVariable(named = "AGGREGATOR_URL", matches = ".+") +public class E2EEscrowSwapTest extends BaseEscrowSwapTest { + @BeforeEach + void setUp() throws IOException { + String aggregatorUrl = System.getenv("AGGREGATOR_URL"); + Assertions.assertNotNull(aggregatorUrl, "AGGREGATOR_URL environment variable must be set"); + + this.client = new StateTransitionClient(new AggregatorClient(aggregatorUrl)); + this.trustBase = UnicityObjectMapper.JSON.readValue( + getClass().getResourceAsStream("/trust-base.json"), + RootTrustBase.class + ); + } +} diff --git a/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java b/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java index 57d5b41..ab618fc 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java @@ -1,12 +1,15 @@ package org.unicitylabs.sdk.e2e; +import java.io.IOException; import org.unicitylabs.sdk.StateTransitionClient; import org.unicitylabs.sdk.api.AggregatorClient; 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.bft.RootTrustBase; import org.unicitylabs.sdk.common.CommonTestFlow; +import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -15,26 +18,29 @@ * End-to-end tests for token operations using CommonTestFlow. Matches TypeScript SDK's test * structure. */ -// TODO: Need to load trustbase from real aggregator but currently it has to be predefined -//@Tag("integration") -//@EnabledIfEnvironmentVariable(named = "AGGREGATOR_URL", matches = ".+") -//public class TokenE2ETest extends CommonTestFlow { -// private AggregatorClient aggregatorClient; -// -// @BeforeEach -// void setUp() { -// String aggregatorUrl = System.getenv("AGGREGATOR_URL"); -// assertNotNull(aggregatorUrl, "AGGREGATOR_URL environment variable must be set"); -// -// this.aggregatorClient = new AggregatorClient(aggregatorUrl); -// this.client = new StateTransitionClient(this.aggregatorClient); -// this.trustBase = null; -// } -// -// @Test -// void testGetBlockHeight() throws Exception { -// Long blockHeight = aggregatorClient.getBlockHeight().get(); -// assertNotNull(blockHeight); -// assertTrue(blockHeight > 0); -// } -//} \ No newline at end of file + +@Tag("integration") +@EnabledIfEnvironmentVariable(named = "AGGREGATOR_URL", matches = ".+") +public class TokenE2ETest extends CommonTestFlow { + private AggregatorClient aggregatorClient; + + @BeforeEach + void setUp() throws IOException { + String aggregatorUrl = System.getenv("AGGREGATOR_URL"); + assertNotNull(aggregatorUrl, "AGGREGATOR_URL environment variable must be set"); + + this.aggregatorClient = new AggregatorClient(aggregatorUrl); + this.client = new StateTransitionClient(this.aggregatorClient); + this.trustBase = UnicityObjectMapper.JSON.readValue( + getClass().getResourceAsStream("/trust-base.json"), + RootTrustBase.class + ); + } + + @Test + void testGetBlockHeight() throws Exception { + Long blockHeight = aggregatorClient.getBlockHeight().get(); + assertNotNull(blockHeight); + assertTrue(blockHeight > 0); + } +} \ No newline at end of file diff --git a/src/test/resources/trust-base.json b/src/test/resources/trust-base.json new file mode 100644 index 0000000..c45ac37 --- /dev/null +++ b/src/test/resources/trust-base.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "networkId": 3, + "epoch": 1, + "epochStartRound": 1, + "rootNodes": [ + { + "nodeId": "16Uiu2HAmEbr7f25WfmJeEyhq7uDDQdc9SnedqfyEXZpGNGNBawAn", + "sigKey": "0x03d3a3a53f1ebcdd7520a977035310186f889c727c4edb7685523ec5097c4a747f", + "stake": 1 + } + ], + "quorumThreshold": 1, + "stateHash": "", + "changeRecordHash": "", + "previousEntryHash": "", + "signatures": { + "16Uiu2HAmEbr7f25WfmJeEyhq7uDDQdc9SnedqfyEXZpGNGNBawAn": "0x77ac35416564607288c0290f99396070c3e905858299bc09e653d8a305545a6d38f0b832dfae8fb82a902556678603b648103f288534984a343ba1c6850650fa01" + } +} \ No newline at end of file