diff --git a/src/main/java/org/unicitylabs/sdk/api/AggregatorClient.java b/src/main/java/org/unicitylabs/sdk/api/AggregatorClient.java index 772a1c9..9ae3886 100644 --- a/src/main/java/org/unicitylabs/sdk/api/AggregatorClient.java +++ b/src/main/java/org/unicitylabs/sdk/api/AggregatorClient.java @@ -25,9 +25,9 @@ public interface AggregatorClient { CompletableFuture getInclusionProof(StateId stateId); /** - * Get block height. + * Get the latest block number. * - * @return block height + * @return latest block number */ - CompletableFuture getBlockHeight(); + CompletableFuture getLatestBlockNumber(); } diff --git a/src/main/java/org/unicitylabs/sdk/api/CertificationStatus.java b/src/main/java/org/unicitylabs/sdk/api/CertificationStatus.java index bc917e1..d062de4 100644 --- a/src/main/java/org/unicitylabs/sdk/api/CertificationStatus.java +++ b/src/main/java/org/unicitylabs/sdk/api/CertificationStatus.java @@ -9,10 +9,6 @@ public enum CertificationStatus { */ SUCCESS("SUCCESS"), - /** - * The certification request failed because the state ID already exists. - */ - STATE_ID_EXISTS("STATE_ID_EXISTS"), /** * The certification request failed because the state ID does not match the expected format. */ diff --git a/src/main/java/org/unicitylabs/sdk/api/JsonRpcAggregatorClient.java b/src/main/java/org/unicitylabs/sdk/api/JsonRpcAggregatorClient.java index fd298fb..affbfd1 100644 --- a/src/main/java/org/unicitylabs/sdk/api/JsonRpcAggregatorClient.java +++ b/src/main/java/org/unicitylabs/sdk/api/JsonRpcAggregatorClient.java @@ -87,12 +87,12 @@ public CompletableFuture getInclusionProof(StateId state } /** - * Get block height. + * Get the latest block number. * - * @return block height + * @return latest block number */ @Override - public CompletableFuture getBlockHeight() { + public CompletableFuture getLatestBlockNumber() { return this.transport.request("get_block_height", Map.of(), BlockHeightResponse.class) .thenApply(BlockHeightResponse::getBlockNumber); } diff --git a/src/main/java/org/unicitylabs/sdk/api/NetworkId.java b/src/main/java/org/unicitylabs/sdk/api/NetworkId.java new file mode 100644 index 0000000..e5812e8 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/api/NetworkId.java @@ -0,0 +1,79 @@ +package org.unicitylabs.sdk.api; + +/** + * Unicity network identifier ({@code α}). Used to scope token ids and other + * network-bound values so they cannot be replayed across networks. + */ +public final class NetworkId { + + public static final NetworkId MAINNET = new NetworkId((short) 1, "MAINNET"); + public static final NetworkId TESTNET = new NetworkId((short) 2, "TESTNET"); + public static final NetworkId LOCAL = new NetworkId((short) 3, "LOCAL"); + + private final short id; + private final String name; + + private NetworkId(short id, String name) { + this.id = id; + this.name = name; + } + + private NetworkId(short id) { + this(id, null); + } + + /** + * Resolve a NetworkId from its numeric identifier. Returns the registered + * singleton for known ids; constructs a new (unnamed) instance for any + * other 16-bit value. + * + * @param id numeric network identifier + * @return NetworkId for the given identifier + */ + public static NetworkId fromId(short id) { + if (id <= 0) { + throw new IllegalArgumentException( + "Network identifier out of allowed 16-bit unsigned range: " + id + "."); + } + if (id == MAINNET.id) { + return MAINNET; + } + if (id == TESTNET.id) { + return TESTNET; + } + if (id == LOCAL.id) { + return LOCAL; + } + return new NetworkId(id); + } + + /** + * Get the numeric network identifier. + * + * @return numeric identifier + */ + public short getId() { + return this.id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof NetworkId)) { + return false; + } + return this.id == ((NetworkId) o).id; + } + + @Override + public int hashCode() { + return Short.hashCode(this.id); + } + + @Override + public String toString() { + return "NetworkId[" + (this.name != null ? this.name : this.id) + "]"; + } +} \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/RootTrustBase.java b/src/main/java/org/unicitylabs/sdk/api/bft/RootTrustBase.java index b4fe31d..c489410 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/RootTrustBase.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/RootTrustBase.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.unicitylabs.sdk.api.NetworkId; import org.unicitylabs.sdk.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.serializer.json.JsonSerializationException; import org.unicitylabs.sdk.serializer.json.LongAsStringSerializer; @@ -20,7 +21,7 @@ public class RootTrustBase { private final long version; - private final int networkId; + private final NetworkId networkId; private final long epoch; private final long epochStartRound; private final Set rootNodes; @@ -33,7 +34,7 @@ public class RootTrustBase { @JsonCreator RootTrustBase( @JsonProperty("version") long version, - @JsonProperty("networkId") int networkId, + @JsonProperty("networkId") NetworkId networkId, @JsonProperty("epoch") long epoch, @JsonProperty("epochStartRound") long epochStartRound, @JsonProperty("rootNodes") Set rootNodes, @@ -78,7 +79,7 @@ public long getVersion() { * * @return network id */ - public int getNetworkId() { + public NetworkId getNetworkId() { return this.networkId; } diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/UnicitySeal.java b/src/main/java/org/unicitylabs/sdk/api/bft/UnicitySeal.java index 3759ffe..8726334 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/UnicitySeal.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/UnicitySeal.java @@ -1,5 +1,6 @@ package org.unicitylabs.sdk.api.bft; +import org.unicitylabs.sdk.api.NetworkId; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer.CborTag; import org.unicitylabs.sdk.serializer.cbor.CborSerializationException; @@ -17,7 +18,7 @@ public class UnicitySeal { public static final long CBOR_TAG = 39005; private static final int VERSION = 1; - private final short networkId; + private final NetworkId networkId; private final long rootChainRoundNumber; private final long epoch; private final long timestamp; @@ -26,7 +27,7 @@ public class UnicitySeal { private final Set signatures; UnicitySeal( - short networkId, + NetworkId networkId, long rootChainRoundNumber, long epoch, long timestamp, @@ -74,7 +75,7 @@ public int getVersion() { * * @return network ID */ - public short getNetworkId() { + public NetworkId getNetworkId() { return this.networkId; } @@ -152,7 +153,7 @@ public static UnicitySeal fromCbor(byte[] bytes) { } return new UnicitySeal( - CborDeserializer.decodeUnsignedInteger(data.get(1)).asShort(), + NetworkId.fromId(CborDeserializer.decodeUnsignedInteger(data.get(1)).asShort()), CborDeserializer.decodeUnsignedInteger(data.get(2)).asLong(), CborDeserializer.decodeUnsignedInteger(data.get(3)).asLong(), CborDeserializer.decodeUnsignedInteger(data.get(4)).asLong(), @@ -177,7 +178,7 @@ public byte[] toCbor() { UnicitySeal.CBOR_TAG, CborSerializer.encodeArray( CborSerializer.encodeUnsignedInteger(UnicitySeal.VERSION), - CborSerializer.encodeUnsignedInteger(this.networkId), + CborSerializer.encodeUnsignedInteger(this.networkId.getId()), CborSerializer.encodeUnsignedInteger(this.rootChainRoundNumber), CborSerializer.encodeUnsignedInteger(this.epoch), CborSerializer.encodeUnsignedInteger(this.timestamp), diff --git a/src/main/java/org/unicitylabs/sdk/api/bft/verification/UnicityCertificateVerification.java b/src/main/java/org/unicitylabs/sdk/api/bft/verification/UnicityCertificateVerification.java index 4b8daad..963908f 100644 --- a/src/main/java/org/unicitylabs/sdk/api/bft/verification/UnicityCertificateVerification.java +++ b/src/main/java/org/unicitylabs/sdk/api/bft/verification/UnicityCertificateVerification.java @@ -28,6 +28,13 @@ private UnicityCertificateVerification() { public static UnicityCertificateVerificationResult verify(RootTrustBase trustBase, InclusionProof inclusionProof) { ArrayList> results = new ArrayList<>(); + + if (!inclusionProof.getUnicityCertificate().getUnicitySeal().getNetworkId().equals(trustBase.getNetworkId())) { + results.add(new VerificationResult<>("UnicitySealNetworkMatchesTrustBaseRule", VerificationStatus.FAIL)); + return UnicityCertificateVerificationResult.fail(results); + } + results.add(new VerificationResult<>("UnicitySealNetworkMatchesTrustBaseRule", VerificationStatus.OK)); + VerificationResult result = UnicitySealHashMatchesWithRootHashRule.verify(inclusionProof.getUnicityCertificate()); results.add(result); if (result.getStatus() != VerificationStatus.OK) { diff --git a/src/main/java/org/unicitylabs/sdk/payment/DuplicateSplitTokenIdException.java b/src/main/java/org/unicitylabs/sdk/payment/DuplicateSplitTokenIdException.java new file mode 100644 index 0000000..3d42880 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/payment/DuplicateSplitTokenIdException.java @@ -0,0 +1,16 @@ +package org.unicitylabs.sdk.payment; + +/** + * Thrown when two split requests derive the same token id, which would collide in the sum trees. + */ +public class DuplicateSplitTokenIdException extends RuntimeException { + + /** + * Create exception indicating that the given token id is shared by multiple split requests. + * + * @param tokenId duplicated token id description + */ + public DuplicateSplitTokenIdException(String tokenId) { + super("Duplicate token id across split requests: " + tokenId + "."); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/payment/SplitMintJustification.java b/src/main/java/org/unicitylabs/sdk/payment/SplitMintJustification.java index 6f68714..c8d582b 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/SplitMintJustification.java +++ b/src/main/java/org/unicitylabs/sdk/payment/SplitMintJustification.java @@ -5,9 +5,9 @@ import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.transaction.Token; +import java.util.HashSet; import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.stream.Collectors; /** @@ -54,7 +54,7 @@ public List getProofs() { * * @return split mint justification */ - public static SplitMintJustification create(Token token, Set proofs) { + public static SplitMintJustification create(Token token, List proofs) { Objects.requireNonNull(token, "token cannot be null"); Objects.requireNonNull(proofs, "proofs cannot be null"); @@ -62,6 +62,10 @@ public static SplitMintJustification create(Token token, Set pr throw new IllegalArgumentException("proofs cannot be empty"); } + if (new HashSet<>(proofs).size() != proofs.size()) { + throw new IllegalArgumentException("proofs contain duplicate asset ids"); + } + return new SplitMintJustification(token, List.copyOf(proofs)); } @@ -80,7 +84,9 @@ public static SplitMintJustification fromCbor(byte[] bytes) { List data = CborDeserializer.decodeArray(tag.getData(), 2); return SplitMintJustification.create( Token.fromCbor(data.get(0)), - CborDeserializer.decodeArray(data.get(1)).stream().map(SplitAssetProof::fromCbor).collect(Collectors.toSet()) + CborDeserializer.decodeArray(data.get(1)).stream() + .map(SplitAssetProof::fromCbor) + .collect(Collectors.toList()) ); } diff --git a/src/main/java/org/unicitylabs/sdk/payment/SplitMintJustificationVerifier.java b/src/main/java/org/unicitylabs/sdk/payment/SplitMintJustificationVerifier.java index a78a9db..864620f 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/SplitMintJustificationVerifier.java +++ b/src/main/java/org/unicitylabs/sdk/payment/SplitMintJustificationVerifier.java @@ -1,5 +1,6 @@ package org.unicitylabs.sdk.payment; +import org.unicitylabs.sdk.api.NetworkId; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.payment.asset.Asset; @@ -69,6 +70,19 @@ public VerificationResult verify(CertifiedMintTransaction tr ); } + NetworkId sourceNetworkId = justification.getToken().getGenesis().getNetworkId(); + if (!transaction.getNetworkId().equals(sourceNetworkId)) { + return new VerificationResult<>( + "SplitMintJustificationVerificationRule", + VerificationStatus.FAIL, + String.format( + "Network identifier mismatch: mint is on %s, source token is on %s.", + transaction.getNetworkId(), + sourceNetworkId + ) + ); + } + VerificationResult verificationResult = justification.getToken() .verify(trustBase, predicateVerifier, mintJustificationVerifier); if (verificationResult.getStatus() != VerificationStatus.OK) { diff --git a/src/main/java/org/unicitylabs/sdk/payment/SplitResult.java b/src/main/java/org/unicitylabs/sdk/payment/SplitResult.java index ac2afb0..8098149 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/SplitResult.java +++ b/src/main/java/org/unicitylabs/sdk/payment/SplitResult.java @@ -1,29 +1,20 @@ package org.unicitylabs.sdk.payment; -import org.unicitylabs.sdk.transaction.TokenId; import org.unicitylabs.sdk.transaction.TransferTransaction; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Collectors; /** - * Result of token split generation containing burn transaction and per-token proofs. + * Result of token split generation containing burn transaction and per-output split tokens. */ public class SplitResult { private final TransferTransaction burnTransaction; - private final Map> proofs; + private final List tokens; - SplitResult(TransferTransaction burnTransaction, Map> proofs) { + SplitResult(TransferTransaction burnTransaction, List tokens) { this.burnTransaction = burnTransaction; - this.proofs = Map.copyOf( - proofs.entrySet().stream() - .collect( - Collectors.toMap(Entry::getKey, value -> List.copyOf(value.getValue())) - ) - ); + this.tokens = List.copyOf(tokens); } /** @@ -36,11 +27,11 @@ public TransferTransaction getBurnTransaction() { } /** - * Get proofs grouped by resulting token id. + * Get the split tokens ready to be minted. * - * @return split proofs map + * @return immutable list of split tokens */ - public Map> getProofs() { - return this.proofs; + public List getTokens() { + return this.tokens; } -} +} \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/payment/SplitToken.java b/src/main/java/org/unicitylabs/sdk/payment/SplitToken.java new file mode 100644 index 0000000..eb55d6c --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/payment/SplitToken.java @@ -0,0 +1,63 @@ +package org.unicitylabs.sdk.payment; + +import org.unicitylabs.sdk.api.NetworkId; +import org.unicitylabs.sdk.payment.asset.Asset; +import org.unicitylabs.sdk.predicate.Predicate; +import org.unicitylabs.sdk.transaction.TokenSalt; +import org.unicitylabs.sdk.transaction.TokenType; + +import java.util.List; +import java.util.Set; + +/** + * Realized split output: all data needed to mint the new token. + */ +public class SplitToken { + + private final NetworkId networkId; + private final Predicate recipient; + private final TokenType tokenType; + private final TokenSalt salt; + private final Set assets; + private final List proofs; + + SplitToken( + NetworkId networkId, + Predicate recipient, + TokenType tokenType, + TokenSalt salt, + Set assets, + List proofs + ) { + this.networkId = networkId; + this.recipient = recipient; + this.tokenType = tokenType; + this.salt = salt; + this.assets = Set.copyOf(assets); + this.proofs = List.copyOf(proofs); + } + + public NetworkId getNetworkId() { + return this.networkId; + } + + public Predicate getRecipient() { + return this.recipient; + } + + public TokenType getTokenType() { + return this.tokenType; + } + + public TokenSalt getSalt() { + return this.salt; + } + + public Set getAssets() { + return this.assets; + } + + public List getProofs() { + return this.proofs; + } +} \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/payment/SplitTokenRequest.java b/src/main/java/org/unicitylabs/sdk/payment/SplitTokenRequest.java new file mode 100644 index 0000000..ca5199a --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/payment/SplitTokenRequest.java @@ -0,0 +1,92 @@ +package org.unicitylabs.sdk.payment; + +import org.unicitylabs.sdk.payment.asset.Asset; +import org.unicitylabs.sdk.predicate.Predicate; +import org.unicitylabs.sdk.transaction.TokenSalt; +import org.unicitylabs.sdk.transaction.TokenType; + +import java.util.Objects; +import java.util.Set; + +/** + * Request to mint one new token as part of a token split. + */ +public class SplitTokenRequest { + + private final Predicate recipient; + private final TokenType tokenType; + private final Set assets; + private final TokenSalt salt; + + private SplitTokenRequest(Predicate recipient, TokenType tokenType, Set assets, TokenSalt salt) { + this.recipient = recipient; + this.tokenType = tokenType; + this.assets = Set.copyOf(assets); + this.salt = salt; + } + + /** + * Create a split token request. + * + * @param recipient predicate that will lock the new token + * @param assets assets the new token will receive + * @param tokenType token type for the new token + * @param salt salt for the new token + * + * @return split token request + */ + public static SplitTokenRequest create( + Predicate recipient, + Set assets, + TokenType tokenType, + TokenSalt salt + ) { + Objects.requireNonNull(recipient, "Recipient cannot be null"); + Objects.requireNonNull(assets, "Assets cannot be null"); + Objects.requireNonNull(tokenType, "Token type cannot be null"); + Objects.requireNonNull(salt, "Salt cannot be null"); + + return new SplitTokenRequest(recipient, tokenType, assets, salt); + } + + /** + * Create a split token request with a random salt. + * + * @param recipient predicate that will lock the new token + * @param assets assets the new token will receive + * @param tokenType token type for the new token + * + * @return split token request + */ + public static SplitTokenRequest create(Predicate recipient, Set assets, TokenType tokenType) { + return SplitTokenRequest.create(recipient, assets, tokenType, TokenSalt.generate()); + } + + /** + * Create a split token request with a random token type and salt. + * + * @param recipient predicate that will lock the new token + * @param assets assets the new token will receive + * + * @return split token request + */ + public static SplitTokenRequest create(Predicate recipient, Set assets) { + return SplitTokenRequest.create(recipient, assets, TokenType.generate(), TokenSalt.generate()); + } + + public Predicate getRecipient() { + return this.recipient; + } + + public TokenType getTokenType() { + return this.tokenType; + } + + public Set getAssets() { + return this.assets; + } + + public TokenSalt getSalt() { + return this.salt; + } +} \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/payment/TokenSplit.java b/src/main/java/org/unicitylabs/sdk/payment/TokenSplit.java index 39cdda3..19bb74f 100644 --- a/src/main/java/org/unicitylabs/sdk/payment/TokenSplit.java +++ b/src/main/java/org/unicitylabs/sdk/payment/TokenSplit.java @@ -1,5 +1,6 @@ package org.unicitylabs.sdk.payment; +import org.unicitylabs.sdk.api.NetworkId; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import org.unicitylabs.sdk.payment.asset.Asset; import org.unicitylabs.sdk.payment.asset.AssetId; @@ -15,11 +16,19 @@ import org.unicitylabs.sdk.transaction.TokenId; import org.unicitylabs.sdk.transaction.TransferTransaction; +import java.math.BigInteger; import java.security.SecureRandom; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; + /** * Utilities for creating and verifying token split proofs. */ @@ -31,13 +40,13 @@ private TokenSplit() { } /** - * Create split proofs and burn transaction for provided target token distributions. + * Create split proofs and burn transaction for the provided split token requests. * * @param token source token being split * @param paymentDataDeserializer payment data decoder for source token payload - * @param splitTokens destination token ids and their asset allocations + * @param requests per-output mint requests * - * @return split result containing burn transaction and proof map + * @return split result containing burn transaction and split tokens * * @throws LeafOutOfBoundsException if a leaf path is invalid for merkle tree insertion * @throws BranchExistsException if duplicate branches are inserted into a merkle tree @@ -45,27 +54,34 @@ private TokenSplit() { public static SplitResult split( Token token, PaymentDataDeserializer paymentDataDeserializer, - Map> splitTokens + List requests ) throws LeafOutOfBoundsException, BranchExistsException { Objects.requireNonNull(token, "Token cannot be null"); Objects.requireNonNull(paymentDataDeserializer, "Payment data deserializer cannot be null"); - Objects.requireNonNull(splitTokens, "Split tokens cannot be null"); + Objects.requireNonNull(requests, "Requests cannot be null"); byte[] paymentDataBytes = token.getGenesis().getData().orElse(null); if (paymentDataBytes == null) { throw new IllegalArgumentException("Token genesis data must be present"); } - HashMap trees = new HashMap(); - for (Entry> entry : splitTokens.entrySet()) { - Objects.requireNonNull(entry, "Split token entry cannot be null"); - Objects.requireNonNull(entry.getKey(), "Split token id cannot be null"); - for (Asset asset : entry.getValue()) { - Objects.requireNonNull(asset, "Split token asset cannot be null"); + NetworkId networkId = token.getGenesis().getNetworkId(); + HashMap trees = new HashMap<>(); + LinkedHashMap requestsByTokenId = new LinkedHashMap<>(); + for (SplitTokenRequest request : requests) { + Objects.requireNonNull(request, "Split token request cannot be null"); + TokenId tokenId = TokenId.fromSalt(networkId, request.getSalt()); + if (requestsByTokenId.containsKey(tokenId)) { + throw new DuplicateSplitTokenIdException(tokenId.toString()); + } + requestsByTokenId.put(tokenId, request); + BigInteger tokenIdPath = tokenId.toBitString().toBigInteger(); + for (Asset asset : request.getAssets()) { + Objects.requireNonNull(asset, "Split token asset cannot be null"); SparseMerkleSumTree tree = trees.computeIfAbsent(asset.getId(), v -> new SparseMerkleSumTree(HashAlgorithm.SHA256)); tree.addLeaf( - entry.getKey().toBitString().toBigInteger(), + tokenIdPath, new SparseMerkleSumTree.LeafValue(asset.getId().getBytes(), asset.getValue()) ); } @@ -88,7 +104,7 @@ public static SplitResult split( } SparseMerkleTree aggregationTree = new SparseMerkleTree(HashAlgorithm.SHA256); - HashMap assetTreeRoots = new HashMap(); + HashMap assetTreeRoots = new HashMap<>(); for (Entry entry : trees.entrySet()) { Asset tokenAsset = assets.get(entry.getKey()); if (tokenAsset == null) { @@ -123,22 +139,29 @@ public static SplitResult split( CborSerializer.encodeNull() ); - HashMap> proofs = new HashMap>(); - for (Entry> entry : splitTokens.entrySet()) { - proofs.put( - entry.getKey(), - List.copyOf( - entry.getValue().stream().map(asset -> SplitAssetProof.create( - asset.getId(), - aggregationRoot.getPath(asset.getId().toBitString().toBigInteger()), - assetTreeRoots.get(asset.getId()).getPath(entry.getKey().toBitString().toBigInteger()) - ) - ).collect(Collectors.toList()) - ) - ); + List tokens = new ArrayList<>(requestsByTokenId.size()); + for (Entry entry : requestsByTokenId.entrySet()) { + SplitTokenRequest request = entry.getValue(); + BigInteger tokenIdPath = entry.getKey().toBitString().toBigInteger(); + Set requestAssets = request.getAssets(); + List proofs = requestAssets.stream() + .map(asset -> SplitAssetProof.create( + asset.getId(), + aggregationRoot.getPath(asset.getId().toBitString().toBigInteger()), + assetTreeRoots.get(asset.getId()).getPath(tokenIdPath) + )) + .collect(Collectors.toList()); + tokens.add(new SplitToken( + networkId, + request.getRecipient(), + request.getTokenType(), + request.getSalt(), + requestAssets, + proofs + )); } - return new SplitResult(burnTransaction, proofs); + return new SplitResult(burnTransaction, tokens); } -} +} \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java index 50e9fce..596c8bb 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/UnicityObjectMapper.java @@ -3,7 +3,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import org.unicitylabs.sdk.api.NetworkId; import org.unicitylabs.sdk.serializer.json.ByteArrayJson; +import org.unicitylabs.sdk.serializer.json.NetworkIdJson; /** @@ -23,6 +25,8 @@ private static ObjectMapper createJsonObjectMapper() { SimpleModule module = new SimpleModule(); module.addSerializer(byte[].class, new ByteArrayJson.Serializer()); module.addDeserializer(byte[].class, new ByteArrayJson.Deserializer()); + module.addSerializer(NetworkId.class, new NetworkIdJson.Serializer()); + module.addDeserializer(NetworkId.class, new NetworkIdJson.Deserializer()); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new Jdk8Module()); diff --git a/src/main/java/org/unicitylabs/sdk/serializer/json/NetworkIdJson.java b/src/main/java/org/unicitylabs/sdk/serializer/json/NetworkIdJson.java new file mode 100644 index 0000000..6a25f4a --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/NetworkIdJson.java @@ -0,0 +1,73 @@ +package org.unicitylabs.sdk.serializer.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.unicitylabs.sdk.api.NetworkId; + +import java.io.IOException; + +/** + * {@link NetworkId} serializer and deserializer implementation that maps to/from a numeric identifier. + */ +public class NetworkIdJson { + + private NetworkIdJson() { + } + + /** + * Network id serializer. + */ + public static class Serializer extends StdSerializer { + + /** + * Create serializer. + */ + public Serializer() { + super(NetworkId.class); + } + + /** + * Serialize network id. + * + * @param value network id + * @param gen json generator + * @param serializers serializer provider + * @throws IOException on serialization failure + */ + @Override + public void serialize(NetworkId value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeNumber(value.getId()); + } + } + + /** + * Network id deserializer. + */ + public static class Deserializer extends StdDeserializer { + + /** + * Create deserializer. + */ + public Deserializer() { + super(NetworkId.class); + } + + /** + * Deserialize network id. + * + * @param p Parser used for reading JSON content + * @param ctx Context that can be used to access information about this deserialization activity. + * @return network id + * @throws IOException on deserialization failure + */ + @Override + public NetworkId deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + return NetworkId.fromId(p.getShortValue()); + } + } +} diff --git a/src/main/java/org/unicitylabs/sdk/transaction/CertifiedMintTransaction.java b/src/main/java/org/unicitylabs/sdk/transaction/CertifiedMintTransaction.java index 9c9e777..a644018 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/CertifiedMintTransaction.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/CertifiedMintTransaction.java @@ -1,6 +1,7 @@ package org.unicitylabs.sdk.transaction; import org.unicitylabs.sdk.api.InclusionProof; +import org.unicitylabs.sdk.api.NetworkId; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.predicate.EncodedPredicate; @@ -49,6 +50,24 @@ public DataHash getSourceStateHash() { return this.transaction.getSourceStateHash(); } + /** + * Returns the network identifier. + * + * @return network id + */ + public NetworkId getNetworkId() { + return this.transaction.getNetworkId(); + } + + /** + * Returns the mint-transaction salt. + * + * @return token salt + */ + public TokenSalt getSalt() { + return this.transaction.getSalt(); + } + /** * Returns the token identifier. * diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java b/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java index 80eaf3a..b707d3a 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintTransaction.java @@ -1,6 +1,7 @@ package org.unicitylabs.sdk.transaction; import org.unicitylabs.sdk.api.InclusionProof; +import org.unicitylabs.sdk.api.NetworkId; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.MintSigningService; import org.unicitylabs.sdk.crypto.hash.DataHash; @@ -34,26 +35,32 @@ public class MintTransaction implements Transaction { private final MintTransactionState sourceStateHash; private final EncodedPredicate lockScript; + private final NetworkId networkId; private final EncodedPredicate recipient; - private final TokenId tokenId; + private final TokenSalt salt; private final TokenType tokenType; + private final TokenId tokenId; private final byte[] justification; private final byte[] data; private MintTransaction( MintTransactionState sourceStateHash, EncodedPredicate lockScript, + NetworkId networkId, EncodedPredicate recipient, - TokenId tokenId, + TokenSalt salt, TokenType tokenType, + TokenId tokenId, byte[] justification, byte[] data ) { this.sourceStateHash = sourceStateHash; this.lockScript = lockScript; + this.networkId = networkId; this.recipient = recipient; - this.tokenId = tokenId; + this.salt = salt; this.tokenType = tokenType; + this.tokenId = tokenId; this.justification = justification; this.data = data; } @@ -78,6 +85,24 @@ public EncodedPredicate getRecipient() { return this.recipient; } + /** + * Retrieves the network identifier. + * + * @return the network identifier as a {@code NetworkId}. + */ + public NetworkId getNetworkId() { + return this.networkId; + } + + /** + * Retrieves the mint-transaction salt. + * + * @return the salt as a {@code TokenSalt}. + */ + public TokenSalt getSalt() { + return this.salt; + } + /** * Retrieves the unique token identifier. * @@ -118,37 +143,180 @@ public byte[] getStateMask() { /** * Create a mint transaction. * + * @param networkId network identifier * @param recipient recipient predicate - * @param tokenId token identifier + * @param data payload bytes, may be null * @param tokenType token type identifier + * @param salt mint-transaction salt * @param justification mint justification bytes, may be null - * @param data payload bytes, may be null * * @return mint transaction */ public static MintTransaction create( + NetworkId networkId, Predicate recipient, - TokenId tokenId, + byte[] data, TokenType tokenType, - byte[] justification, - byte[] data + TokenSalt salt, + byte[] justification ) { + Objects.requireNonNull(networkId, "Network id cannot be null"); Objects.requireNonNull(recipient, "Recipient cannot be null"); - Objects.requireNonNull(tokenId, "Token ID cannot be null"); Objects.requireNonNull(tokenType, "Token type cannot be null"); + Objects.requireNonNull(salt, "Salt cannot be null"); + TokenId tokenId = TokenId.fromSalt(networkId, salt); SigningService signingService = MintSigningService.create(tokenId); return new MintTransaction( MintTransactionState.create(tokenId), EncodedPredicate.fromPredicate(SignaturePredicate.fromSigningService(signingService)), + networkId, EncodedPredicate.fromPredicate(recipient), - tokenId, + salt, tokenType, + tokenId, justification != null ? Arrays.copyOf(justification, justification.length) : null, data != null ? Arrays.copyOf(data, data.length) : null ); } + /** + * Create a mint transaction without a justification. + * + * @param networkId network identifier + * @param recipient recipient predicate + * @param data payload bytes, may be null + * @param tokenType token type identifier + * @param salt mint-transaction salt + * + * @return mint transaction + */ + public static MintTransaction create( + NetworkId networkId, + Predicate recipient, + byte[] data, + TokenType tokenType, + TokenSalt salt + ) { + return MintTransaction.create(networkId, recipient, data, tokenType, salt, null); + } + + /** + * Create a mint transaction with a fresh random salt. + * + * @param networkId network identifier + * @param recipient recipient predicate + * @param data payload bytes, may be null + * @param tokenType token type identifier + * + * @return mint transaction + */ + public static MintTransaction create( + NetworkId networkId, + Predicate recipient, + byte[] data, + TokenType tokenType + ) { + return MintTransaction.create(networkId, recipient, data, tokenType, TokenSalt.generate()); + } + + /** + * Create a mint transaction with a generated token type. + * + * @param networkId network identifier + * @param recipient recipient predicate + * @param data payload bytes, may be null + * @param salt mint-transaction salt + * + * @return mint transaction + */ + public static MintTransaction create( + NetworkId networkId, + Predicate recipient, + byte[] data, + TokenSalt salt + ) { + return MintTransaction.create(networkId, recipient, data, TokenType.generate(), salt); + } + + /** + * Create a mint transaction with no data. + * + * @param networkId network identifier + * @param recipient recipient predicate + * @param tokenType token type identifier + * @param salt mint-transaction salt + * + * @return mint transaction + */ + public static MintTransaction create( + NetworkId networkId, + Predicate recipient, + TokenType tokenType, + TokenSalt salt + ) { + return MintTransaction.create(networkId, recipient, (byte[]) null, tokenType, salt); + } + + /** + * Create a mint transaction with a generated token type and salt. + * + * @param networkId network identifier + * @param recipient recipient predicate + * @param data payload bytes, may be null + * + * @return mint transaction + */ + public static MintTransaction create(NetworkId networkId, Predicate recipient, byte[] data) { + return MintTransaction.create(networkId, recipient, data, TokenType.generate()); + } + + /** + * Create a mint transaction with no data and a generated salt. + * + * @param networkId network identifier + * @param recipient recipient predicate + * @param tokenType token type identifier + * + * @return mint transaction + */ + public static MintTransaction create( + NetworkId networkId, + Predicate recipient, + TokenType tokenType + ) { + return MintTransaction.create(networkId, recipient, (byte[]) null, tokenType); + } + + /** + * Create a mint transaction with no data and a generated token type. + * + * @param networkId network identifier + * @param recipient recipient predicate + * @param salt mint-transaction salt + * + * @return mint transaction + */ + public static MintTransaction create( + NetworkId networkId, + Predicate recipient, + TokenSalt salt + ) { + return MintTransaction.create(networkId, recipient, TokenType.generate(), salt); + } + + /** + * Create a mint transaction with no data, generated token type and salt. + * + * @param networkId network identifier + * @param recipient recipient predicate + * + * @return mint transaction + */ + public static MintTransaction create(NetworkId networkId, Predicate recipient) { + return MintTransaction.create(networkId, recipient, (byte[]) null); + } + /** * Deserialize mint transaction from CBOR bytes. * @@ -161,7 +329,7 @@ public static MintTransaction fromCbor(byte[] bytes) { if (tag.getTag() != MintTransaction.CBOR_TAG) { throw new CborSerializationException(String.format("Invalid CBOR tag: %s", tag.getTag())); } - List data = CborDeserializer.decodeArray(tag.getData(), 6); + List data = CborDeserializer.decodeArray(tag.getData(), 7); int version = CborDeserializer.decodeUnsignedInteger(data.get(0)).asInt(); if (version != MintTransaction.VERSION) { @@ -169,10 +337,11 @@ public static MintTransaction fromCbor(byte[] bytes) { } return MintTransaction.create( - EncodedPredicate.fromCbor(data.get(1)), - TokenId.fromCbor(data.get(2)), - TokenType.fromCbor(data.get(3)), - CborDeserializer.decodeNullable(data.get(4), CborDeserializer::decodeByteString), + NetworkId.fromId(CborDeserializer.decodeUnsignedInteger(data.get(1)).asShort()), + EncodedPredicate.fromCbor(data.get(2)), + CborDeserializer.decodeNullable(data.get(6), CborDeserializer::decodeByteString), + TokenType.fromCbor(data.get(4)), + TokenSalt.fromCbor(data.get(3)), CborDeserializer.decodeNullable(data.get(5), CborDeserializer::decodeByteString) ); } @@ -215,8 +384,9 @@ public byte[] toCbor() { MintTransaction.CBOR_TAG, CborSerializer.encodeArray( CborSerializer.encodeUnsignedInteger(MintTransaction.VERSION), + CborSerializer.encodeUnsignedInteger(this.networkId.getId()), this.recipient.toCbor(), - this.tokenId.toCbor(), + this.salt.toCbor(), this.tokenType.toCbor(), CborSerializer.encodeNullable(this.justification, CborSerializer::encodeByteString), CborSerializer.encodeNullable(this.data, CborSerializer::encodeByteString) @@ -245,8 +415,8 @@ public CertifiedMintTransaction toCertifiedTransaction( @Override public String toString() { return String.format( - "MintTransaction{sourceStateHash=%s, lockScript=%s, recipient=%s, tokenId=%s, tokenType=%s, data=%s}", - this.sourceStateHash, this.lockScript, this.recipient, this.tokenId, this.tokenType, - HexConverter.encode(this.data)); + "MintTransaction{sourceStateHash=%s, lockScript=%s, networkId=%s, recipient=%s, salt=%s, tokenType=%s, tokenId=%s, data=%s}", + this.sourceStateHash, this.lockScript, this.networkId, this.recipient, this.salt, + this.tokenType, this.tokenId, HexConverter.encode(this.data)); } } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/TokenId.java b/src/main/java/org/unicitylabs/sdk/transaction/TokenId.java index fd44370..404bb39 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/TokenId.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/TokenId.java @@ -1,11 +1,13 @@ package org.unicitylabs.sdk.transaction; +import org.unicitylabs.sdk.api.NetworkId; +import org.unicitylabs.sdk.crypto.hash.DataHasher; +import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; import org.unicitylabs.sdk.serializer.cbor.CborSerializer; import org.unicitylabs.sdk.util.BitString; import org.unicitylabs.sdk.util.HexConverter; -import java.security.SecureRandom; import java.util.Arrays; import java.util.Objects; @@ -15,7 +17,6 @@ */ public class TokenId { - private static final SecureRandom RANDOM = new SecureRandom(); private final byte[] bytes; /** @@ -30,14 +31,28 @@ public TokenId(byte[] bytes) { } /** - * Generate a random token id. + * Derive a token id from a network identifier and salt. * - * @return token id + * @param networkId network identifier + * @param salt mint-transaction salt + * + * @return derived token id */ - public static TokenId generate() { - byte[] bytes = new byte[32]; - RANDOM.nextBytes(bytes); - return new TokenId(bytes); + public static TokenId fromSalt(NetworkId networkId, TokenSalt salt) { + Objects.requireNonNull(networkId, "Network id cannot be null"); + Objects.requireNonNull(salt, "Token salt cannot be null"); + + return new TokenId( + new DataHasher(HashAlgorithm.SHA256) + .update( + CborSerializer.encodeArray( + salt.toCbor(), + CborSerializer.encodeUnsignedInteger(networkId.getId()) + ) + ) + .digest() + .getData() + ); } /** diff --git a/src/main/java/org/unicitylabs/sdk/transaction/TokenSalt.java b/src/main/java/org/unicitylabs/sdk/transaction/TokenSalt.java new file mode 100644 index 0000000..8e36310 --- /dev/null +++ b/src/main/java/org/unicitylabs/sdk/transaction/TokenSalt.java @@ -0,0 +1,100 @@ +package org.unicitylabs.sdk.transaction; + +import org.unicitylabs.sdk.serializer.cbor.CborDeserializer; +import org.unicitylabs.sdk.serializer.cbor.CborSerializer; +import org.unicitylabs.sdk.util.HexConverter; + +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Objects; + + +/** + * 32-byte salt mixed with a network identifier to derive a {@link TokenId}. + */ +public class TokenSalt { + + public static final int LENGTH = 32; + + private static final SecureRandom RANDOM = new SecureRandom(); + private final byte[] bytes; + + private TokenSalt(byte[] bytes) { + this.bytes = bytes; + } + + /** + * Wrap an existing 32-byte salt. + * + * @param bytes salt bytes; must be exactly 32 bytes + * + * @return token salt + */ + public static TokenSalt fromBytes(byte[] bytes) { + Objects.requireNonNull(bytes, "Token salt cannot be null"); + if (bytes.length != TokenSalt.LENGTH) { + throw new IllegalArgumentException( + "Token salt must be " + TokenSalt.LENGTH + " bytes long, got " + bytes.length); + } + return new TokenSalt(Arrays.copyOf(bytes, bytes.length)); + } + + /** + * Deserialize a token salt from CBOR bytes. + * + * @param bytes CBOR encoded token salt bytes + * + * @return token salt + */ + public static TokenSalt fromCbor(byte[] bytes) { + return TokenSalt.fromBytes(CborDeserializer.decodeByteString(bytes)); + } + + /** + * Generate a fresh random 32-byte token salt. + * + * @return token salt + */ + public static TokenSalt generate() { + byte[] bytes = new byte[TokenSalt.LENGTH]; + RANDOM.nextBytes(bytes); + return new TokenSalt(bytes); + } + + /** + * Get token salt bytes. + * + * @return token salt bytes + */ + public byte[] getBytes() { + return Arrays.copyOf(this.bytes, this.bytes.length); + } + + /** + * Serialize token salt to CBOR bytes. + * + * @return CBOR bytes + */ + public byte[] toCbor() { + return CborSerializer.encodeByteString(this.bytes); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TokenSalt)) { + return false; + } + TokenSalt tokenSalt = (TokenSalt) o; + return Arrays.equals(this.bytes, tokenSalt.bytes); + } + + @Override + public int hashCode() { + return Arrays.hashCode(this.bytes); + } + + @Override + public String toString() { + return String.format("TokenSalt[%s]", HexConverter.encode(this.bytes)); + } +} diff --git a/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedMintTransactionVerificationRule.java b/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedMintTransactionVerificationRule.java index 4c041dc..a60d1a3 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedMintTransactionVerificationRule.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/verification/CertifiedMintTransactionVerificationRule.java @@ -44,6 +44,13 @@ public static VerificationResult verify( ) { List> results = new ArrayList<>(); + if (!transaction.getNetworkId().equals(trustBase.getNetworkId())) { + results.add(new VerificationResult<>("MintNetworkMatchesTrustBaseRule", VerificationStatus.FAIL)); + return new VerificationResult<>("CertifiedMintTransactionVerificationRule", + VerificationStatus.FAIL, "Mint network does not match trust base.", results); + } + results.add(new VerificationResult<>("MintNetworkMatchesTrustBaseRule", VerificationStatus.OK)); + SigningService signingService = MintSigningService.create(transaction.getTokenId()); EncodedPredicate expectedLockScript = EncodedPredicate.fromPredicate(SignaturePredicate.fromSigningService(signingService)); VerificationResult result = expectedLockScript diff --git a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java index 93be061..7e8149b 100644 --- a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java +++ b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java @@ -103,7 +103,7 @@ public CompletableFuture getInclusionProof(StateId state } @Override - public CompletableFuture getBlockHeight() { + public CompletableFuture getLatestBlockNumber() { return CompletableFuture.completedFuture(1L); } } diff --git a/src/test/java/org/unicitylabs/sdk/TestApiKeyIntegration.java b/src/test/java/org/unicitylabs/sdk/TestApiKeyIntegration.java index c3bc5c2..1ecafbf 100644 --- a/src/test/java/org/unicitylabs/sdk/TestApiKeyIntegration.java +++ b/src/test/java/org/unicitylabs/sdk/TestApiKeyIntegration.java @@ -9,8 +9,6 @@ import org.unicitylabs.sdk.crypto.secp256k1.SigningService; import org.unicitylabs.sdk.predicate.builtin.SignaturePredicate; import org.unicitylabs.sdk.transaction.MintTransaction; -import org.unicitylabs.sdk.transaction.TokenId; -import org.unicitylabs.sdk.transaction.TokenType; import org.unicitylabs.sdk.util.HexConverter; import java.util.concurrent.CompletableFuture; @@ -43,11 +41,8 @@ void setUp() throws Exception { HexConverter.decode("0000000000000000000000000000000000000000000000000000000000000001")); MintTransaction transaction = MintTransaction.create( - SignaturePredicate.fromSigningService(signingService), - TokenId.generate(), - TokenType.generate(), - null, - null + NetworkId.LOCAL, + SignaturePredicate.fromSigningService(signingService) ); certificationData = CertificationData.fromMintTransaction(transaction); } @@ -131,7 +126,7 @@ public void testRateLimitExceeded() { @Test public void testGetBlockHeightWorksWithoutApiKey() throws Exception { - CompletableFuture future = clientWithoutApiKey.getBlockHeight(); + CompletableFuture future = clientWithoutApiKey.getLatestBlockNumber(); Long blockHeight = future.get(5, TimeUnit.SECONDS); assertNotNull(blockHeight); @@ -140,7 +135,7 @@ public void testGetBlockHeightWorksWithoutApiKey() throws Exception { @Test public void testGetBlockHeightAlsoWorksWithApiKey() throws Exception { - CompletableFuture future = clientWithApiKey.getBlockHeight(); + CompletableFuture future = clientWithApiKey.getLatestBlockNumber(); Long blockHeight = future.get(5, TimeUnit.SECONDS); assertNotNull(blockHeight); diff --git a/src/test/java/org/unicitylabs/sdk/api/InclusionProofTest.java b/src/test/java/org/unicitylabs/sdk/api/InclusionProofTest.java index faa0064..8ead4eb 100644 --- a/src/test/java/org/unicitylabs/sdk/api/InclusionProofTest.java +++ b/src/test/java/org/unicitylabs/sdk/api/InclusionProofTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.unicitylabs.sdk.api.NetworkId; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.api.bft.RootTrustBaseUtils; import org.unicitylabs.sdk.api.bft.ShardId; @@ -18,8 +19,6 @@ import org.unicitylabs.sdk.smt.radix.FinalizedNodeBranch; import org.unicitylabs.sdk.smt.radix.SparseMerkleTree; import org.unicitylabs.sdk.transaction.MintTransaction; -import org.unicitylabs.sdk.transaction.TokenId; -import org.unicitylabs.sdk.transaction.TokenType; import org.unicitylabs.sdk.transaction.verification.InclusionProofVerificationRule; import org.unicitylabs.sdk.transaction.verification.InclusionProofVerificationStatus; import org.unicitylabs.sdk.util.HexConverter; @@ -42,11 +41,8 @@ public void createMerkleTreePath() throws Exception { transaction = MintTransaction.create( - SignaturePredicate.fromSigningService(signingService), - TokenId.generate(), - TokenType.generate(), - null, - null + NetworkId.LOCAL, + SignaturePredicate.fromSigningService(signingService) ); certificationData = CertificationData.fromMintTransaction(transaction); diff --git a/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseTest.java b/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseTest.java index 8487d82..6a2041a 100644 --- a/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseTest.java +++ b/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.unicitylabs.sdk.api.NetworkId; public class RootTrustBaseTest { @@ -12,7 +13,7 @@ public void testRootTrustBaseDeserializationFromJson() { ); Assertions.assertEquals(1, trustBase.getVersion()); - Assertions.assertEquals(3, trustBase.getNetworkId()); + Assertions.assertEquals(NetworkId.LOCAL, trustBase.getNetworkId()); Assertions.assertEquals(1, trustBase.getEpoch()); Assertions.assertEquals(1, trustBase.getEpochStartRound()); Assertions.assertEquals(4, trustBase.getRootNodes().size()); diff --git a/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseUtils.java b/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseUtils.java index 3ad587a..87364c8 100644 --- a/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseUtils.java +++ b/src/test/java/org/unicitylabs/sdk/api/bft/RootTrustBaseUtils.java @@ -1,5 +1,7 @@ package org.unicitylabs.sdk.api.bft; +import org.unicitylabs.sdk.api.NetworkId; + import java.util.Map; import java.util.Set; @@ -7,9 +9,9 @@ public class RootTrustBaseUtils { public static RootTrustBase generateRootTrustBase(byte[] publicKey) { return new RootTrustBase( 0, - 0, - 0, - 0, + NetworkId.LOCAL, + 0L, + 0L, Set.of( new RootTrustBase.NodeInfo( "NODE", diff --git a/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateTest.java b/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateTest.java index dd5d36d..ae0800e 100644 --- a/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateTest.java +++ b/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateTest.java @@ -9,11 +9,11 @@ public class UnicityCertificateTest { @Test public void testUnicityCertificateDeserializationFromCbor() { byte[] data = HexConverter.decode( - "d998598701d9985a8a010000f65820747276e851904305a7457d31edd9d374cdedbc7f72c4f5674da410cddb23b6c14a00000000000000000000004a00000000000000000000004a000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058200000000000000000000000000000000000000000000000000000000000000000d9985b8301418080d9985c83010080d9985d880100000000f65820bd991b4e822f177252fa79aebdec0a24546519b813fdd54e6e48fc67d12c454da1644e4f44455841a3ed5d3e5ea04d39297d5db9cc69bbd97eabf2351e6f96660b8fc3e8d106daf06e0f6336c0862a8068ebdd326474b5a0f52f7942ccf010219a619c650ea4a2a301"); + "d998598701d9985a8a010000f65820747276e851904305a7457d31edd9d374cdedbc7f72c4f5674da410cddb23b6c14a00000000000000000000004a00000000000000000000004a000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058200000000000000000000000000000000000000000000000000000000000000000d9985b8301418080d9985c83010080d9985d880103000000f65820bd991b4e822f177252fa79aebdec0a24546519b813fdd54e6e48fc67d12c454da1644e4f44455841a3ed5d3e5ea04d39297d5db9cc69bbd97eabf2351e6f96660b8fc3e8d106daf06e0f6336c0862a8068ebdd326474b5a0f52f7942ccf010219a619c650ea4a2a301"); UnicityCertificate unicityCertificate = UnicityCertificate.fromCbor(data); Assertions.assertEquals( - "d998598701d9985a8a010000f65820747276e851904305a7457d31edd9d374cdedbc7f72c4f5674da410cddb23b6c14a00000000000000000000004a00000000000000000000004a000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058200000000000000000000000000000000000000000000000000000000000000000d9985b8301418080d9985c83010080d9985d880100000000f65820bd991b4e822f177252fa79aebdec0a24546519b813fdd54e6e48fc67d12c454da1644e4f44455841a3ed5d3e5ea04d39297d5db9cc69bbd97eabf2351e6f96660b8fc3e8d106daf06e0f6336c0862a8068ebdd326474b5a0f52f7942ccf010219a619c650ea4a2a301", + "d998598701d9985a8a010000f65820747276e851904305a7457d31edd9d374cdedbc7f72c4f5674da410cddb23b6c14a00000000000000000000004a00000000000000000000004a000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058200000000000000000000000000000000000000000000000000000000000000000d9985b8301418080d9985c83010080d9985d880103000000f65820bd991b4e822f177252fa79aebdec0a24546519b813fdd54e6e48fc67d12c454da1644e4f44455841a3ed5d3e5ea04d39297d5db9cc69bbd97eabf2351e6f96660b8fc3e8d106daf06e0f6336c0862a8068ebdd326474b5a0f52f7942ccf010219a619c650ea4a2a301", HexConverter.encode(unicityCertificate.toCbor()) ); } diff --git a/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateUtils.java b/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateUtils.java index 5a92481..24d397a 100644 --- a/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateUtils.java +++ b/src/test/java/org/unicitylabs/sdk/api/bft/UnicityCertificateUtils.java @@ -1,5 +1,6 @@ package org.unicitylabs.sdk.api.bft; +import org.unicitylabs.sdk.api.NetworkId; import org.unicitylabs.sdk.crypto.hash.DataHash; import org.unicitylabs.sdk.crypto.hash.DataHasher; import org.unicitylabs.sdk.crypto.hash.HashAlgorithm; @@ -72,7 +73,7 @@ public static UnicityCertificate generateCertificate( .digest(); UnicitySeal seal = new UnicitySeal( - (short) 0, + NetworkId.LOCAL, 0L, 0L, 0L, diff --git a/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java b/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java index d829eb0..e0d4594 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/TokenE2ETest.java @@ -45,7 +45,7 @@ void setUp() throws IOException { @Test void testGetBlockHeight() throws Exception { - Long blockHeight = aggregatorClient.getBlockHeight().get(); + Long blockHeight = aggregatorClient.getLatestBlockNumber().get(); assertNotNull(blockHeight); assertTrue(blockHeight > 0); } diff --git a/src/test/java/org/unicitylabs/sdk/functional/payment/SplitBuilderTest.java b/src/test/java/org/unicitylabs/sdk/functional/payment/SplitBuilderTest.java index bad56fb..35e9973 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/payment/SplitBuilderTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/payment/SplitBuilderTest.java @@ -9,6 +9,8 @@ import org.unicitylabs.sdk.payment.SplitMintJustification; import org.unicitylabs.sdk.payment.SplitMintJustificationVerifier; import org.unicitylabs.sdk.payment.SplitResult; +import org.unicitylabs.sdk.payment.SplitToken; +import org.unicitylabs.sdk.payment.SplitTokenRequest; import org.unicitylabs.sdk.payment.TokenSplit; import org.unicitylabs.sdk.payment.asset.Asset; import org.unicitylabs.sdk.payment.asset.AssetId; @@ -16,16 +18,13 @@ import org.unicitylabs.sdk.predicate.builtin.SignaturePredicateUnlockScript; import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; import org.unicitylabs.sdk.transaction.Token; -import org.unicitylabs.sdk.transaction.TokenId; -import org.unicitylabs.sdk.transaction.TokenType; import org.unicitylabs.sdk.transaction.verification.MintJustificationVerifierService; import org.unicitylabs.sdk.util.verification.VerificationStatus; import org.unicitylabs.sdk.utils.TokenUtils; import java.math.BigInteger; import java.nio.charset.StandardCharsets; -import java.util.LinkedHashSet; -import java.util.Map; +import java.util.List; import java.util.Set; /** @@ -60,15 +59,13 @@ public void buildAndVerifySplitToken() throws Exception { predicateVerifier, mintJustificationVerifier, ownerPredicate, - null, new TestPaymentData(assets).encode() ); - TokenId outputTokenId = TokenId.generate(); SplitResult split = TokenSplit.split( sourceToken, TestPaymentData::decode, - Map.of(outputTokenId, assets) + List.of(SplitTokenRequest.create(ownerPredicate, assets)) ); Token burnToken = TokenUtils.transferToken( @@ -80,9 +77,10 @@ public void buildAndVerifySplitToken() throws Exception { SignaturePredicateUnlockScript.create(split.getBurnTransaction(), signingService) ); + SplitToken splitResult = split.getTokens().get(0); SplitMintJustification justification = SplitMintJustification.create( burnToken, - new LinkedHashSet<>(split.getProofs().get(outputTokenId)) + splitResult.getProofs() ); Token splitToken = TokenUtils.mintToken( @@ -90,11 +88,12 @@ public void buildAndVerifySplitToken() throws Exception { trustBase, predicateVerifier, mintJustificationVerifier, - outputTokenId, - TokenType.generate(), - ownerPredicate, - justification.toCbor(), - new TestPaymentData(assets).encode() + splitResult.getRecipient(), + new TestPaymentData(assets).encode(), + splitResult.getNetworkId(), + splitResult.getTokenType(), + splitResult.getSalt(), + justification.toCbor() ); Assertions.assertEquals( @@ -102,4 +101,4 @@ public void buildAndVerifySplitToken() throws Exception { splitToken.verify(trustBase, predicateVerifier, mintJustificationVerifier).getStatus() ); } -} +} \ No newline at end of file diff --git a/src/test/java/org/unicitylabs/sdk/functional/payment/SplitMintJustificationVerifierTest.java b/src/test/java/org/unicitylabs/sdk/functional/payment/SplitMintJustificationVerifierTest.java index 1cae00d..3cac11d 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/payment/SplitMintJustificationVerifierTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/payment/SplitMintJustificationVerifierTest.java @@ -14,6 +14,8 @@ import org.unicitylabs.sdk.payment.SplitMintJustificationVerifier; import org.unicitylabs.sdk.payment.SplitAssetProof; import org.unicitylabs.sdk.payment.SplitResult; +import org.unicitylabs.sdk.payment.SplitToken; +import org.unicitylabs.sdk.payment.SplitTokenRequest; import org.unicitylabs.sdk.payment.TokenSplit; import org.unicitylabs.sdk.payment.asset.Asset; import org.unicitylabs.sdk.payment.asset.AssetId; @@ -27,8 +29,6 @@ import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTree; import org.unicitylabs.sdk.smt.sum.SparseMerkleSumTreeRootNode; import org.unicitylabs.sdk.transaction.Token; -import org.unicitylabs.sdk.transaction.TokenId; -import org.unicitylabs.sdk.transaction.TokenType; import org.unicitylabs.sdk.transaction.verification.MintJustificationVerifierService; import org.unicitylabs.sdk.util.verification.VerificationResult; import org.unicitylabs.sdk.util.verification.VerificationStatus; @@ -40,9 +40,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -91,15 +89,13 @@ public void setupFixture() throws Exception { this.predicateVerifier, this.mintJustificationVerifier, ownerPredicate, - null, new TestPaymentData(assets).encode() ); - TokenId outputTokenId = TokenId.generate(); SplitResult split = TokenSplit.split( sourceToken, TestPaymentData::decode, - Map.of(outputTokenId, assets) + List.of(SplitTokenRequest.create(ownerPredicate, assets)) ); Token burnToken = TokenUtils.transferToken( @@ -111,9 +107,10 @@ public void setupFixture() throws Exception { SignaturePredicateUnlockScript.create(split.getBurnTransaction(), signingService) ); + SplitToken splitResult = split.getTokens().get(0); this.splitJustification = SplitMintJustification.create( burnToken, - new LinkedHashSet<>(split.getProofs().get(outputTokenId)) + splitResult.getProofs() ); this.splitToken = TokenUtils.mintToken( @@ -121,11 +118,12 @@ public void setupFixture() throws Exception { this.trustBase, this.predicateVerifier, this.mintJustificationVerifier, - outputTokenId, - TokenType.generate(), - ownerPredicate, - this.splitJustification.toCbor(), - new TestPaymentData(assets).encode() + splitResult.getRecipient(), + new TestPaymentData(assets).encode(), + splitResult.getNetworkId(), + splitResult.getTokenType(), + splitResult.getSalt(), + this.splitJustification.toCbor() ); } @@ -219,7 +217,7 @@ public void verifyFailsWhenAggregationPathVerificationFails() throws Exception { ); SplitMintJustification mutated = SplitMintJustification.create( - this.splitJustification.getToken(), new LinkedHashSet<>(proofs)); + this.splitJustification.getToken(), proofs); VerificationResult result = verifyWith(mutated.toCbor(), originalDataBytes()); assertFailWithMessage(result, @@ -241,7 +239,7 @@ public void verifyFailsWhenAssetTreePathVerificationFails() throws Exception { proofs.set(0, mutatedProof); SplitMintJustification mutated = SplitMintJustification.create( - this.splitJustification.getToken(), new LinkedHashSet<>(proofs)); + this.splitJustification.getToken(), proofs); VerificationResult result = verifyWith(mutated.toCbor(), originalDataBytes()); assertFailWithMessage(result, @@ -268,7 +266,7 @@ public void verifyFailsWhenProofsUseDifferentAssetTrees() throws Exception { )); SplitMintJustification mutated = SplitMintJustification.create( - this.splitJustification.getToken(), new LinkedHashSet<>(proofs)); + this.splitJustification.getToken(), proofs); VerificationResult result = verifyWith(mutated.toCbor(), originalDataBytes()); assertFailWithMessage(result, "Current proof is not derived from the same asset tree as other proofs."); @@ -296,7 +294,7 @@ public void verifyFailsWhenAssetTreeRootDoesNotMatchAggregationLeaf() throws Exc proofs.set(0, mutatedProof); SplitMintJustification mutated = SplitMintJustification.create( - this.splitJustification.getToken(), new LinkedHashSet<>(proofs)); + this.splitJustification.getToken(), proofs); VerificationResult result = verifyWith(mutated.toCbor(), originalDataBytes()); assertFailWithMessage(result, "Asset tree root does not match aggregation path leaf."); @@ -311,7 +309,7 @@ public void verifyFailsWhenProofAssetIdIsMissingFromAssetData() { .collect(Collectors.toSet()); SplitMintJustification mutated = SplitMintJustification.create( - this.splitJustification.getToken(), new LinkedHashSet<>(proofs)); + this.splitJustification.getToken(), proofs); byte[] data = new TestPaymentData(assets).encode(); VerificationResult result = verifyWith(mutated.toCbor(), data); @@ -361,7 +359,7 @@ public void verifyFailsWhenAggregationRootDoesNotMatchBurnPredicate() throws Exc } SplitMintJustification mutated = SplitMintJustification.create( - this.splitJustification.getToken(), new LinkedHashSet<>(mutatedProofs)); + this.splitJustification.getToken(), mutatedProofs); VerificationResult result = verifyWith(mutated.toCbor(), originalDataBytes()); assertFailWithMessage(result, "Aggregation path root does not match burn predicate."); @@ -393,8 +391,8 @@ private Token withJustificationAndData(Token token, byte[] justification, byte[] CborDeserializer.CborTag mintTag = CborDeserializer.decodeTag(certifiedGenesis.get(0)); List mint = CborDeserializer.decodeArray(mintTag.getData()); - mint.set(4, CborSerializer.encodeNullable(justification, CborSerializer::encodeByteString)); - mint.set(5, CborSerializer.encodeNullable(data, CborSerializer::encodeByteString)); + mint.set(5, CborSerializer.encodeNullable(justification, CborSerializer::encodeByteString)); + mint.set(6, CborSerializer.encodeNullable(data, CborSerializer::encodeByteString)); certifiedGenesis.set(0, CborSerializer.encodeTag(mintTag.getTag(), encodeArray(mint))); tokenData.set(1, encodeArray(certifiedGenesis)); diff --git a/src/test/java/org/unicitylabs/sdk/functional/payment/TokenSplitTest.java b/src/test/java/org/unicitylabs/sdk/functional/payment/TokenSplitTest.java index cf56c85..cbe31cd 100644 --- a/src/test/java/org/unicitylabs/sdk/functional/payment/TokenSplitTest.java +++ b/src/test/java/org/unicitylabs/sdk/functional/payment/TokenSplitTest.java @@ -9,19 +9,19 @@ import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; import org.unicitylabs.sdk.payment.SplitMintJustificationVerifier; +import org.unicitylabs.sdk.payment.SplitTokenRequest; import org.unicitylabs.sdk.payment.TokenSplit; import org.unicitylabs.sdk.payment.asset.Asset; import org.unicitylabs.sdk.payment.asset.AssetId; import org.unicitylabs.sdk.predicate.builtin.SignaturePredicate; import org.unicitylabs.sdk.predicate.verification.PredicateVerifierService; import org.unicitylabs.sdk.transaction.Token; -import org.unicitylabs.sdk.transaction.TokenId; import org.unicitylabs.sdk.transaction.verification.MintJustificationVerifierService; import org.unicitylabs.sdk.utils.TokenUtils; import java.math.BigInteger; import java.nio.charset.StandardCharsets; -import java.util.Map; +import java.util.List; import java.util.Set; /** @@ -45,8 +45,7 @@ public void setupFixture() throws Exception { mintJustificationVerifier.register(new SplitMintJustificationVerifier( trustBase, predicateVerifier, TestPaymentData::decode)); - SigningService signingService = SigningService.generate(); - SignaturePredicate ownerPredicate = SignaturePredicate.fromSigningService(signingService); + SignaturePredicate ownerPredicate = SignaturePredicate.fromSigningService(SigningService.generate()); this.asset1 = new Asset(new AssetId("ASSET_1".getBytes(StandardCharsets.UTF_8)), BigInteger.valueOf(500)); this.asset2 = new Asset(new AssetId("ASSET_2".getBytes(StandardCharsets.UTF_8)), BigInteger.valueOf(500)); @@ -57,7 +56,6 @@ public void setupFixture() throws Exception { predicateVerifier, mintJustificationVerifier, ownerPredicate, - null, new TestPaymentData(Set.of(this.asset1, this.asset2)).encode() ); } @@ -69,7 +67,10 @@ public void splitFailsWhenAssetCountsDiffer() { () -> TokenSplit.split( this.sourceToken, TestPaymentData::decode, - Map.of(TokenId.generate(), Set.of(this.asset1)) + List.of(SplitTokenRequest.create( + SignaturePredicate.fromSigningService(SigningService.generate()), + Set.of(this.asset1) + )) ) ); Assertions.assertEquals("Token and split tokens asset counts differ.", exception.getMessage()); @@ -82,10 +83,10 @@ public void splitFailsWhenAssetTreeAmountIsLess() { () -> TokenSplit.split( this.sourceToken, TestPaymentData::decode, - Map.of( - TokenId.generate(), + List.of(SplitTokenRequest.create( + SignaturePredicate.fromSigningService(SigningService.generate()), Set.of(this.asset1, new Asset(this.asset2.getId(), BigInteger.valueOf(400))) - ) + )) ) ); Assertions.assertEquals("Token contained 500 AssetId{bytes=41535345545f32} assets, but tree has 400", @@ -99,10 +100,10 @@ public void splitFailsWhenAssetTreeAmountIsMore() { () -> TokenSplit.split( this.sourceToken, TestPaymentData::decode, - Map.of( - TokenId.generate(), + List.of(SplitTokenRequest.create( + SignaturePredicate.fromSigningService(SigningService.generate()), Set.of(this.asset1, new Asset(this.asset2.getId(), BigInteger.valueOf(1500))) - ) + )) ) ); Assertions.assertEquals("Token contained 500 AssetId{bytes=41535345545f32} assets, but tree has 1500", diff --git a/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java b/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java index 1fa7845..c10f9cf 100644 --- a/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java +++ b/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java @@ -5,6 +5,7 @@ import org.unicitylabs.sdk.api.CertificationData; import org.unicitylabs.sdk.api.CertificationResponse; import org.unicitylabs.sdk.api.CertificationStatus; +import org.unicitylabs.sdk.api.NetworkId; import org.unicitylabs.sdk.api.bft.RootTrustBase; import org.unicitylabs.sdk.crypto.secp256k1.SigningService; import org.unicitylabs.sdk.predicate.Predicate; @@ -31,17 +32,8 @@ public static Token mintToken( MintJustificationVerifierService mintJustificationVerifier, Predicate recipient ) throws Exception { - return TokenUtils.mintToken( - client, - trustBase, - predicateVerifier, - mintJustificationVerifier, - TokenId.generate(), - TokenType.generate(), - recipient, - null, - null - ); + return TokenUtils.mintToken(client, trustBase, predicateVerifier, mintJustificationVerifier, + recipient, (byte[]) null); } public static Token mintToken( @@ -50,20 +42,23 @@ public static Token mintToken( PredicateVerifierService predicateVerifier, MintJustificationVerifierService mintJustificationVerifier, Predicate recipient, - byte[] justification, byte[] data ) throws Exception { - return TokenUtils.mintToken( - client, - trustBase, - predicateVerifier, - mintJustificationVerifier, - TokenId.generate(), - TokenType.generate(), - recipient, - justification, - data - ); + return TokenUtils.mintToken(client, trustBase, predicateVerifier, mintJustificationVerifier, + recipient, data, NetworkId.LOCAL); + } + + public static Token mintToken( + StateTransitionClient client, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + MintJustificationVerifierService mintJustificationVerifier, + Predicate recipient, + byte[] data, + NetworkId networkId + ) throws Exception { + return TokenUtils.mintToken(client, trustBase, predicateVerifier, mintJustificationVerifier, + recipient, data, networkId, TokenType.generate()); } public static Token mintToken( @@ -71,18 +66,49 @@ public static Token mintToken( RootTrustBase trustBase, PredicateVerifierService predicateVerifier, MintJustificationVerifierService mintJustificationVerifier, - TokenId tokenId, + Predicate recipient, + byte[] data, + NetworkId networkId, + TokenType tokenType + ) throws Exception { + return TokenUtils.mintToken(client, trustBase, predicateVerifier, mintJustificationVerifier, + recipient, data, networkId, tokenType, TokenSalt.generate()); + } + + public static Token mintToken( + StateTransitionClient client, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + MintJustificationVerifierService mintJustificationVerifier, + Predicate recipient, + byte[] data, + NetworkId networkId, TokenType tokenType, + TokenSalt salt + ) throws Exception { + return TokenUtils.mintToken(client, trustBase, predicateVerifier, mintJustificationVerifier, + recipient, data, networkId, tokenType, salt, null); + } + + public static Token mintToken( + StateTransitionClient client, + RootTrustBase trustBase, + PredicateVerifierService predicateVerifier, + MintJustificationVerifierService mintJustificationVerifier, Predicate recipient, - byte[] justification, - byte[] data + byte[] data, + NetworkId networkId, + TokenType tokenType, + TokenSalt salt, + byte[] justification ) throws Exception { MintTransaction transaction = MintTransaction.create( + networkId, recipient, - tokenId, + data, tokenType, - justification, - data + salt, + justification ); CertificationData certificationData = CertificationData.fromMintTransaction(transaction); @@ -139,7 +165,7 @@ public static Token transferToken( token, recipient, x, - CborSerializer.encodeArray() + null ); return TokenUtils.transferToken(