diff --git a/src/main/java/org/unicitylabs/sdk/address/ProxyAddress.java b/src/main/java/org/unicitylabs/sdk/address/ProxyAddress.java index 4ed42bd..c95de61 100644 --- a/src/main/java/org/unicitylabs/sdk/address/ProxyAddress.java +++ b/src/main/java/org/unicitylabs/sdk/address/ProxyAddress.java @@ -64,12 +64,12 @@ public static Address resolve(Address inputAddress, List> nametags) { Address targetAddress = inputAddress; while (targetAddress.getScheme() != AddressScheme.DIRECT) { Token nametag = nametagMap.get(targetAddress); - if (nametag == null || nametag.getState().getData().isEmpty()) { + if (nametag == null || nametag.getData().isEmpty()) { return null; } targetAddress = AddressFactory.createAddress( - new String(nametag.getState().getData().get(), StandardCharsets.UTF_8)); + new String(nametag.getData().get(), StandardCharsets.UTF_8)); } return targetAddress; diff --git a/src/main/java/org/unicitylabs/sdk/predicate/BurnPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/BurnPredicate.java index 8367963..416fa9a 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/BurnPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/BurnPredicate.java @@ -2,17 +2,19 @@ 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; -import java.util.Arrays; -import java.util.Objects; public class BurnPredicate implements Predicate { private final DataHash burnReason; @@ -47,8 +49,10 @@ public boolean isOwner(byte[] publicKey) { } @Override - public boolean verify(Transaction transaction, TokenId tokenId, - TokenType tokenType) { + public boolean verify( + List> transactions, + Token token + ) { return false; } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/DefaultPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/DefaultPredicate.java index afaa01b..5ce857a 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/DefaultPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/DefaultPredicate.java @@ -2,20 +2,22 @@ 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; 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.token.Token; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenType; import org.unicitylabs.sdk.transaction.InclusionProofVerificationStatus; import org.unicitylabs.sdk.transaction.Transaction; import org.unicitylabs.sdk.transaction.TransferTransactionData; import org.unicitylabs.sdk.util.HexConverter; -import java.util.Arrays; -import java.util.Objects; /** * Base class for unmasked and masked predicates @@ -91,8 +93,12 @@ public boolean isOwner(byte[] publicKey) { } @Override - public boolean verify(Transaction transaction, TokenId tokenId, - TokenType tokenType) { + public boolean verify( + List> transactions, + Token token + ) { + Transaction transaction = transactions.get(transactions.size() - 1); + Authenticator authenticator = transaction.getInclusionProof().getAuthenticator().orElse(null); DataHash transactionHash = transaction.getInclusionProof().getTransactionHash().orElse(null); @@ -104,12 +110,12 @@ public boolean verify(Transaction transaction, TokenId return false; } - if (!authenticator.verify(transaction.getData().calculateHash(tokenId, tokenType))) { + if (!authenticator.verify(transaction.getData().calculateHash(token.getId(), token.getType()))) { return false; } RequestId requestId = RequestId.create(this.publicKey, - transaction.getData().getSourceState().calculateHash(tokenId, tokenType)); + transaction.getData().getSourceState().calculateHash(token.getId(), token.getType())); return transaction.getInclusionProof().verify(requestId) == InclusionProofVerificationStatus.OK; } diff --git a/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java b/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java index d72df36..1aacdc9 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/Predicate.java @@ -1,6 +1,8 @@ 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; @@ -12,6 +14,8 @@ public interface Predicate { IPredicateReference getReference(TokenType tokenType); byte[] getNonce(); boolean isOwner(byte[] publicKey); - boolean verify(Transaction transaction, TokenId tokenId, - TokenType tokenType); + boolean verify( + List> transactions, + Token token + ); } \ No newline at end of file diff --git a/src/main/java/org/unicitylabs/sdk/predicate/UnmaskedPredicate.java b/src/main/java/org/unicitylabs/sdk/predicate/UnmaskedPredicate.java index cbb1e6b..2d2f182 100644 --- a/src/main/java/org/unicitylabs/sdk/predicate/UnmaskedPredicate.java +++ b/src/main/java/org/unicitylabs/sdk/predicate/UnmaskedPredicate.java @@ -1,17 +1,15 @@ package org.unicitylabs.sdk.predicate; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.node.ArrayNode; -import org.unicitylabs.sdk.hash.DataHash; +import java.util.List; 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.signing.Signature; import org.unicitylabs.sdk.signing.SigningService; -import org.unicitylabs.sdk.token.TokenId; +import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenType; +import org.unicitylabs.sdk.transaction.Transaction; +import org.unicitylabs.sdk.transaction.TransferTransactionData; public class UnmaskedPredicate extends DefaultPredicate { @@ -39,21 +37,21 @@ public static UnmaskedPredicate create( } @Override - public DataHash calculateHash(TokenId tokenId, TokenType tokenType) { - IPredicateReference reference = this.getReference(tokenType); - - ArrayNode node = UnicityObjectMapper.CBOR.createArrayNode(); - node.addPOJO(reference.getHash()); - node.addPOJO(tokenId); - node.addPOJO(this.getNonce()); - - try { - return new DataHasher(HashAlgorithm.SHA256) - .update(UnicityObjectMapper.CBOR.writeValueAsBytes(reference.getHash())) - .digest(); - } catch (JsonProcessingException e) { - throw new CborSerializationException(e); - } + public boolean verify( + List> transactions, + Token token + ) { + return super.verify(transactions, token) && SigningService.verifyWithPublicKey( + new DataHasher(HashAlgorithm.SHA256) + .update( + transactions.size() > 1 + ? transactions.get(transactions.size() - 2).getData().getSalt() + : token.getGenesis().getData().getSalt() + ) + .digest(), + this.getNonce(), + this.getPublicKey() + ); } public UnmaskedPredicateReference getReference(TokenType tokenType) { 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 index dc40041..7779edd 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/MaskedPredicateJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/MaskedPredicateJson.java @@ -52,7 +52,7 @@ public static class Deserializer extends @Override public MaskedPredicate deserialize(JsonParser p, DeserializationContext ctx) throws IOException { - PredicateType type = null; + PredicateType type; byte[] publicKey = null; String algorithm = null; HashAlgorithm hashAlgorithm = null; 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 index 441075f..9d04710 100644 --- a/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/UnmaskedPredicateJson.java +++ b/src/main/java/org/unicitylabs/sdk/serializer/json/predicate/UnmaskedPredicateJson.java @@ -53,7 +53,7 @@ public static class Deserializer extends @Override public UnmaskedPredicate deserialize(JsonParser p, DeserializationContext ctx) throws IOException { - PredicateType type = null; + PredicateType type; byte[] publicKey = null; String algorithm = null; HashAlgorithm hashAlgorithm = null; diff --git a/src/main/java/org/unicitylabs/sdk/signing/SigningService.java b/src/main/java/org/unicitylabs/sdk/signing/SigningService.java index e8c6471..1d4bc9c 100644 --- a/src/main/java/org/unicitylabs/sdk/signing/SigningService.java +++ b/src/main/java/org/unicitylabs/sdk/signing/SigningService.java @@ -79,9 +79,16 @@ public static byte[] generatePrivateKey() { } /** - * Create signing service from secret and optional nonce. + * Create signing service from secret. */ - public static SigningService createFromSecret(byte[] secret, byte[] nonce) { + public static SigningService createFromSecret(byte[] secret) { + return SigningService.createFromMaskedSecret(secret, null); + } + + /** + * Create signing service from secret and nonce. + */ + public static SigningService createFromMaskedSecret(byte[] secret, byte[] nonce) { DataHasher hasher = new DataHasher(HashAlgorithm.SHA256); hasher.update(secret); if (nonce != null) { diff --git a/src/main/java/org/unicitylabs/sdk/token/NameTagTokenState.java b/src/main/java/org/unicitylabs/sdk/token/NameTagTokenState.java deleted file mode 100644 index 03bfe4e..0000000 --- a/src/main/java/org/unicitylabs/sdk/token/NameTagTokenState.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.unicitylabs.sdk.token; - -import org.unicitylabs.sdk.address.Address; -import org.unicitylabs.sdk.predicate.Predicate; -import java.nio.charset.StandardCharsets; - -public class NameTagTokenState extends TokenState { - private final Address address; - - public NameTagTokenState(Predicate unlockPredicate, Address address) { - super(unlockPredicate, address == null ? null : address.getAddress().getBytes(StandardCharsets.UTF_8)); - - this.address = address; - } - - public Address getAddress() { - return address; - } -} diff --git a/src/main/java/org/unicitylabs/sdk/token/Token.java b/src/main/java/org/unicitylabs/sdk/token/Token.java index c44faa5..78f7035 100644 --- a/src/main/java/org/unicitylabs/sdk/token/Token.java +++ b/src/main/java/org/unicitylabs/sdk/token/Token.java @@ -1,5 +1,11 @@ package org.unicitylabs.sdk.token; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import org.unicitylabs.sdk.address.Address; import org.unicitylabs.sdk.address.ProxyAddress; import org.unicitylabs.sdk.api.RequestId; @@ -10,17 +16,12 @@ import org.unicitylabs.sdk.token.fungible.TokenCoinData; import org.unicitylabs.sdk.transaction.InclusionProofVerificationStatus; import org.unicitylabs.sdk.transaction.MintCommitment; -import org.unicitylabs.sdk.transaction.MintTransactionState; 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; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; public class Token> { @@ -31,8 +32,12 @@ public class Token> { private final List> transactions; private final List> nametags; - public Token(TokenState state, Transaction genesis, List> transactions, - List> nametags) { + public Token( + TokenState state, + Transaction genesis, + List> transactions, + List> nametags + ) { Objects.requireNonNull(state, "State cannot be null"); Objects.requireNonNull(genesis, "Genesis cannot be null"); Objects.requireNonNull(transactions, "Transactions list cannot be null"); @@ -97,8 +102,9 @@ public Token update( Objects.requireNonNull(transaction, "Transaction is null"); Objects.requireNonNull(transactionNametags, "Nametag tokens are null"); - if (!transaction.getData().getSourceState().getUnlockPredicate() - .verify(transaction, this.getId(), this.getType())) { + LinkedList> verificationTransactions = new LinkedList<>(this.transactions); + verificationTransactions.add(transaction); + if (!transaction.getData().getSourceState().getUnlockPredicate().verify(verificationTransactions, this)) { throw new RuntimeException("Predicate verification failed"); } @@ -130,7 +136,8 @@ public VerificationResult verify() { ); Transaction> previousTransaction = this.genesis; - for (Transaction transaction : this.transactions) { + for (int i = 0; i < this.transactions.size(); i++) { + Transaction transaction = this.transactions.get(i); Address recipient = previousTransaction.getData().getRecipient(); results.add( @@ -138,7 +145,7 @@ public VerificationResult verify() { "Transaction verification", List.of( this.verifyTransaction( - transaction, + this.transactions.subList(0, i + 1), previousTransaction.getData().getDataHash().orElse(null), recipient ) @@ -189,8 +196,11 @@ public VerificationResult verify() { } private VerificationResult verifyTransaction( - Transaction transaction, DataHash dataHash, Address recipient) { - + List> transactions, + DataHash dataHash, + Address recipient + ) { + Transaction transaction = transactions.get(transactions.size() - 1); for (Token nametag : transaction.getData().getNametags()) { if (!nametag.verify().isSuccessful()) { return VerificationResult.fail( @@ -212,8 +222,7 @@ private VerificationResult verifyTransaction( return VerificationResult.fail("data mismatch"); } - if (!transaction.getData().getSourceState().getUnlockPredicate() - .verify(transaction, this.getId(), this.getType())) { + if (!transaction.getData().getSourceState().getUnlockPredicate().verify(transactions, this)) { return VerificationResult.fail("predicate verification failed"); } diff --git a/src/main/java/org/unicitylabs/sdk/transaction/MintCommitment.java b/src/main/java/org/unicitylabs/sdk/transaction/MintCommitment.java index 374456d..76d6e52 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/MintCommitment.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/MintCommitment.java @@ -40,7 +40,7 @@ public static > MintCommitment create( } public static SigningService createSigningService(MintTransactionData transactionData) { - return SigningService.createFromSecret(MINTER_SECRET, transactionData.getTokenId().getBytes()); + return SigningService.createFromMaskedSecret(MINTER_SECRET, transactionData.getTokenId().getBytes()); } public Transaction toTransaction(InclusionProof inclusionProof) { diff --git a/src/main/java/org/unicitylabs/sdk/transaction/NametagMintTransactionData.java b/src/main/java/org/unicitylabs/sdk/transaction/NametagMintTransactionData.java index 8985828..6c42436 100644 --- a/src/main/java/org/unicitylabs/sdk/transaction/NametagMintTransactionData.java +++ b/src/main/java/org/unicitylabs/sdk/transaction/NametagMintTransactionData.java @@ -1,12 +1,9 @@ package org.unicitylabs.sdk.transaction; +import java.nio.charset.StandardCharsets; import org.unicitylabs.sdk.address.Address; -import org.unicitylabs.sdk.hash.DataHasher; -import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenType; -import org.unicitylabs.sdk.token.fungible.TokenCoinData; -import java.nio.charset.StandardCharsets; public class NametagMintTransactionData extends MintTransactionData { @@ -14,8 +11,6 @@ public class NametagMintTransactionData extends public NametagMintTransactionData( String name, TokenType tokenType, - byte[] tokenData, - TokenCoinData coinData, Address recipient, byte[] salt, Address targetAddress @@ -23,15 +18,11 @@ public NametagMintTransactionData( super( TokenId.fromNameTag(name), tokenType, - tokenData, - coinData, + targetAddress.getAddress().getBytes(StandardCharsets.UTF_8), + null, recipient, salt, - targetAddress == null - ? null - : new DataHasher(HashAlgorithm.SHA256) - .update(targetAddress.getAddress().getBytes(StandardCharsets.UTF_8)) - .digest(), + null, null ); } diff --git a/src/test/java/org/unicitylabs/sdk/AndroidCompatibilityTest.java b/src/test/java/org/unicitylabs/sdk/AndroidCompatibilityTest.java index a590b2c..9d00a69 100644 --- a/src/test/java/org/unicitylabs/sdk/AndroidCompatibilityTest.java +++ b/src/test/java/org/unicitylabs/sdk/AndroidCompatibilityTest.java @@ -27,7 +27,7 @@ void testCoreSDKFeaturesWorkOnAndroid() throws Exception { // Test 2: Signing Service (uses Bouncy Castle) byte[] secret = "test secret".getBytes(StandardCharsets.UTF_8); byte[] nonce = new byte[32]; - var signingService = SigningService.createFromSecret(secret, nonce); + var signingService = SigningService.createFromMaskedSecret(secret, nonce); assertNotNull(signingService.getPublicKey()); // Test 3: Token IDs and Types diff --git a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java index 6c3f9f8..f59b780 100644 --- a/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java +++ b/src/test/java/org/unicitylabs/sdk/TestAggregatorClient.java @@ -39,7 +39,7 @@ public CompletableFuture submitCommitment(RequestId re new SubmitCommitmentResponse(SubmitCommitmentStatus.SUCCESS) ); } catch (Exception e) { - throw new RuntimeException(e); + throw new RuntimeException("Aggregator commitment failed", e); } } diff --git a/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java b/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java index cb87a1a..f561448 100644 --- a/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java +++ b/src/test/java/org/unicitylabs/sdk/common/BaseEscrowSwapTest.java @@ -4,18 +4,15 @@ import java.nio.charset.StandardCharsets; import java.util.List; - 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.ProxyAddress; import org.unicitylabs.sdk.api.SubmitCommitmentResponse; import org.unicitylabs.sdk.api.SubmitCommitmentStatus; -import org.unicitylabs.sdk.hash.DataHasher; 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.serializer.UnicityObjectMapper; import org.unicitylabs.sdk.signing.SigningService; import org.unicitylabs.sdk.token.Token; @@ -30,18 +27,20 @@ import org.unicitylabs.sdk.utils.TokenUtils; /** - * Alice has a nametag and acts as an escrow for the swap - * Bob transfers token to Alice - * Carol transfers token to Alice + * Alice has a nametag and acts as an escrow for the swap Bob transfers token to Alice Carol + * transfers token to Alice *

- * Alice transfers Bob's token to Carol - * Alice transfers Carol's token to Bob + * Alice transfers Bob's token to Carol Alice transfers Carol's token to Bob *

* Everyone's happy :) */ public abstract class BaseEscrowSwapTest { protected StateTransitionClient client; + private final TokenType tokenType = new TokenType(HexConverter.decode( + "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509")); + + private final byte[] ALICE_SECRET = "ALICE_SECRET".getBytes(StandardCharsets.UTF_8); private final byte[] BOB_SECRET = "BOB_SECRET".getBytes(StandardCharsets.UTF_8); private final byte[] CAROL_SECRET = "CAROL_SECRET".getBytes(StandardCharsets.UTF_8); @@ -50,14 +49,14 @@ 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, byte[] secret, String nametag) throws Exception { + private String[] transferToken(Token token, SigningService signingService, String nametag) throws Exception { TransferCommitment commitment = TransferCommitment.create( token, ProxyAddress.create(nametag), randomBytes(32), null, null, - SigningService.createFromSecret(secret, token.getState().getUnlockPredicate().getNonce()) + signingService ); SubmitCommitmentResponse response = this.client.submitCommitment(token, commitment).get(); @@ -77,8 +76,7 @@ private Token mintToken(byte[] secret) throws Exception { this.client, secret, new TokenId(randomBytes(32)), - new TokenType(HexConverter.decode( - "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509")), + this.tokenType, randomBytes(32), null, randomBytes(32), @@ -87,157 +85,119 @@ private Token mintToken(byte[] secret) throws Exception { ); } - private Token receiveToken(String[] tokenInfo, byte[] secret, - NametagWrapper nametagToken) throws Exception { + private Token receiveToken(String[] tokenInfo, SigningService signingService, + Token nametagToken) throws Exception { Token token = UnicityObjectMapper.JSON.readValue(tokenInfo[0], Token.class); Transaction transaction = UnicityObjectMapper.JSON.readValue( tokenInfo[1], UnicityObjectMapper.JSON.getTypeFactory() .constructParametricType(Transaction.class, TransferTransactionData.class)); - byte[] nonce = randomBytes(32); TokenState state = new TokenState( - MaskedPredicate.create( - SigningService.createFromSecret(secret, nonce), + UnmaskedPredicate.create( + signingService, HashAlgorithm.SHA256, - nonce + transaction.getData().getSalt() ), null ); - nametagToken.updateNameTag(this.client, secret, - state.getUnlockPredicate().getReference(token.getType()).toAddress()); - return this.client.finalizeTransaction( token, state, transaction, - List.of(nametagToken.getNametagToken()) + List.of(nametagToken) ); } @Test void testEscrow() throws Exception { // Make nametags unique for each test run + Token bobToken = mintToken(BOB_SECRET); String[] bobSerializedData = transferToken( - mintToken(BOB_SECRET), - BOB_SECRET, + bobToken, + SigningService.createFromMaskedSecret(BOB_SECRET, bobToken.getState().getUnlockPredicate().getNonce()), ALICE_NAMETAG ); + + Token carolToken = mintToken(CAROL_SECRET); String[] carolSerializedData = transferToken( - mintToken(CAROL_SECRET), - CAROL_SECRET, + carolToken, + SigningService.createFromMaskedSecret(CAROL_SECRET, carolToken.getState().getUnlockPredicate().getNonce()), ALICE_NAMETAG ); - NametagWrapper aliceNametagToken = new NametagWrapper( - TokenUtils.mintNametagToken( - this.client, - ALICE_SECRET, - new TokenType(HexConverter.decode( - "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509")), - randomBytes(32), - null, - ALICE_NAMETAG, - null, - randomBytes(32), - randomBytes(32) - ) + Token aliceNametagToken = TokenUtils.mintNametagToken( + this.client, + ALICE_SECRET, + this.tokenType, + ALICE_NAMETAG, + UnmaskedPredicateReference.create( + this.tokenType, + SigningService.createFromSecret(ALICE_SECRET), + HashAlgorithm.SHA256 + ).toAddress(), + randomBytes(32), + randomBytes(32) ); - Token aliceBobToken = receiveToken(bobSerializedData, ALICE_SECRET, aliceNametagToken); + Token aliceBobToken = receiveToken( + bobSerializedData, + SigningService.createFromSecret(ALICE_SECRET), + aliceNametagToken + ); Assertions.assertTrue(aliceBobToken.verify().isSuccessful()); - Token aliceCarolToken = receiveToken(carolSerializedData, ALICE_SECRET, aliceNametagToken); + Token aliceCarolToken = receiveToken( + carolSerializedData, + SigningService.createFromSecret(ALICE_SECRET), + aliceNametagToken + ); Assertions.assertTrue(aliceCarolToken.verify().isSuccessful()); Token aliceToCarolToken = receiveToken( - transferToken(aliceBobToken, ALICE_SECRET, CAROL_NAMETAG), - CAROL_SECRET, - new NametagWrapper( - TokenUtils.mintNametagToken( - this.client, - CAROL_SECRET, - new TokenType(HexConverter.decode( - "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509")), - randomBytes(32), - null, - CAROL_NAMETAG, - null, - randomBytes(32), - randomBytes(32) - ) - )); + transferToken( + aliceBobToken, + SigningService.createFromSecret(ALICE_SECRET), + CAROL_NAMETAG + ), + SigningService.createFromSecret(CAROL_SECRET), + TokenUtils.mintNametagToken( + this.client, + CAROL_SECRET, + this.tokenType, + CAROL_NAMETAG, + UnmaskedPredicateReference.create( + this.tokenType, + SigningService.createFromSecret(CAROL_SECRET), + HashAlgorithm.SHA256 + ).toAddress(), + randomBytes(32), + randomBytes(32) + ) + ); Assertions.assertTrue(aliceToCarolToken.verify().isSuccessful()); Token aliceToBobToken = receiveToken( - transferToken(aliceCarolToken, ALICE_SECRET, BOB_NAMETAG), - BOB_SECRET, - new NametagWrapper( - TokenUtils.mintNametagToken( - this.client, - BOB_SECRET, - new TokenType(HexConverter.decode( - "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509")), - randomBytes(32), - null, - BOB_NAMETAG, - null, - randomBytes(32), - randomBytes(32) - ) - )); + transferToken( + aliceCarolToken, + SigningService.createFromSecret(ALICE_SECRET), + BOB_NAMETAG + ), + SigningService.createFromSecret(BOB_SECRET), + TokenUtils.mintNametagToken( + this.client, + BOB_SECRET, + this.tokenType, + BOB_NAMETAG, + UnmaskedPredicateReference.create( + this.tokenType, + SigningService.createFromSecret(BOB_SECRET), + HashAlgorithm.SHA256 + ).toAddress(), + randomBytes(32), + randomBytes(32) + ) + ); Assertions.assertTrue(aliceToBobToken.verify().isSuccessful()); } - - static class NametagWrapper { - - private Token nametagToken; - - public NametagWrapper(Token nametagToken) { - this.nametagToken = nametagToken; - } - - public Token getNametagToken() { - return this.nametagToken; - } - - public void updateNameTag(StateTransitionClient client, byte[] secret, Address address) - throws Exception { - byte[] nonce = randomBytes(32); - Predicate predicate = MaskedPredicate.create( - SigningService.createFromSecret(secret, nonce), - HashAlgorithm.SHA256, - nonce - ); - - TransferCommitment commitment = TransferCommitment.create( - this.nametagToken, - predicate.getReference(this.nametagToken.getType()).toAddress(), - randomBytes(32), - new DataHasher(HashAlgorithm.SHA256) - .update(address.getAddress().getBytes(StandardCharsets.UTF_8)) - .digest(), - null, - SigningService.createFromSecret( - secret, - this.nametagToken.getState().getUnlockPredicate().getNonce() - ) - ); - - SubmitCommitmentResponse response = client.submitCommitment(this.nametagToken, commitment) - .get(); - if (response.getStatus() != SubmitCommitmentStatus.SUCCESS) { - throw new RuntimeException("Failed to submit transfer commitment: " + response); - } - - this.nametagToken = client.finalizeTransaction( - this.nametagToken, - new TokenState(predicate, address.getAddress().getBytes(StandardCharsets.UTF_8)), - commitment.toTransaction( - this.nametagToken, - 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 aa317d4..5c1a221 100644 --- a/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java +++ b/src/test/java/org/unicitylabs/sdk/common/split/BaseTokenSplitTest.java @@ -2,17 +2,20 @@ import static org.unicitylabs.sdk.utils.TestUtils.randomBytes; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +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.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.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.signing.SigningService; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; @@ -26,14 +29,9 @@ 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.TokenUtils; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; public abstract class BaseTokenSplitTest { @@ -41,13 +39,15 @@ public abstract class BaseTokenSplitTest { @Test void testTokenSplitFullAmounts() throws Exception { + TokenType tokenType = new TokenType(HexConverter.decode( + "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509")); byte[] secret = "SECRET".getBytes(StandardCharsets.UTF_8); Token token = TokenUtils.mintToken( this.client, secret, new TokenId(randomBytes(32)), - new TokenType(randomBytes(32)), + tokenType, randomBytes(32), new TokenCoinData(Map.of( new CoinId("test_eur".getBytes(StandardCharsets.UTF_8)), @@ -66,14 +66,18 @@ void testTokenSplitFullAmounts() throws Exception { this.client, secret, nametag, - null + UnmaskedPredicateReference.create( + tokenType, + SigningService.createFromSecret(secret), + HashAlgorithm.SHA256 + ).toAddress() ); TokenSplitBuilder builder = new TokenSplitBuilder(); TokenSplit split = builder .createToken( new TokenId(randomBytes(32)), - new TokenType(randomBytes(32)), + tokenType, null, new TokenCoinData(Map.of( new CoinId("test_eur".getBytes(StandardCharsets.UTF_8)), @@ -85,7 +89,7 @@ void testTokenSplitFullAmounts() throws Exception { ) .createToken( new TokenId(randomBytes(32)), - new TokenType(randomBytes(32)), + tokenType, null, new TokenCoinData(Map.of( new CoinId("test_usd".getBytes(StandardCharsets.UTF_8)), @@ -99,7 +103,7 @@ void testTokenSplitFullAmounts() throws Exception { TransferCommitment burnCommitment = split.createBurnCommitment( randomBytes(32), - SigningService.createFromSecret(secret, token.getState().getUnlockPredicate().getNonce()) + SigningService.createFromMaskedSecret(secret, token.getState().getUnlockPredicate().getNonce()) ); SubmitCommitmentResponse burnCommitmentResponse = this.client @@ -128,61 +132,14 @@ void testTokenSplitFullAmounts() throws Exception { response.getStatus())); } - byte[] nonce = randomBytes(32); TokenState state = new TokenState( - MaskedPredicate.create( - SigningService.createFromSecret(secret, nonce), + UnmaskedPredicate.create( + SigningService.createFromSecret(secret), HashAlgorithm.SHA256, - nonce + commitment.getTransactionData().getSalt() ), null ); - Address address = state.getUnlockPredicate() - .getReference( - commitment.getTransactionData().getTokenType() - ) - .toAddress(); - - byte[] nametagNonce = randomBytes(32); - TokenState nametagTokenState = new TokenState( - MaskedPredicate.create( - SigningService.createFromSecret(secret, nametagNonce), - HashAlgorithm.SHA256, - nametagNonce - ), - address.getAddress().getBytes(StandardCharsets.UTF_8) - ); - - TransferCommitment nametagCommitment = TransferCommitment.create( - nametagToken, - nametagTokenState.getUnlockPredicate().getReference(nametagToken.getType()).toAddress(), - randomBytes(32), - new DataHasher(HashAlgorithm.SHA256) - .update(address.getAddress().getBytes(StandardCharsets.UTF_8)) - .digest(), - null, - SigningService.createFromSecret( - secret, - nametagToken.getState().getUnlockPredicate().getNonce() - ) - ); - - SubmitCommitmentResponse nametagTransferResponse = this.client - .submitCommitment(nametagToken, nametagCommitment) - .get(); - if (nametagTransferResponse.getStatus() != SubmitCommitmentStatus.SUCCESS) { - throw new Exception(String.format("Failed to submit nametag transfer commitment: %s", - response.getStatus())); - } - - nametagToken = this.client.finalizeTransaction( - nametagToken, - nametagTokenState, - nametagCommitment.toTransaction( - nametagToken, - InclusionProofUtils.waitInclusionProof(this.client, nametagCommitment).get() - ) - ); Token> splitToken = new Token<>( state, diff --git a/src/test/java/org/unicitylabs/sdk/e2e/BasicE2ETest.java b/src/test/java/org/unicitylabs/sdk/e2e/BasicE2ETest.java index 7c38e92..475c21e 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/BasicE2ETest.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/BasicE2ETest.java @@ -53,7 +53,7 @@ void testCommitmentPerformance() throws Exception { sr.nextBytes(stateBytes); DataHash stateHash = new DataHasher(HashAlgorithm.SHA256).update(stateBytes).digest(); DataHash txDataHash = new DataHasher(HashAlgorithm.SHA256).update("test commitment performance".getBytes()).digest(); - SigningService signingService = SigningService.createFromSecret(randomSecret, null); + SigningService signingService = SigningService.createFromSecret(randomSecret); RequestId requestId = RequestId.createFromImprint(signingService.getPublicKey(), stateHash.getImprint()); Authenticator auth = Authenticator.create(signingService, txDataHash, stateHash); SubmitCommitmentResponse response = aggregatorClient.submitCommitment(requestId, txDataHash, auth).get(); @@ -98,7 +98,7 @@ void testCommitmentPerformanceMultiThreaded() throws Exception { DataHash stateHash = new DataHasher(HashAlgorithm.SHA256).update(stateBytes).digest(); DataHash txDataHash = new DataHasher(HashAlgorithm.SHA256).update(txData).digest(); - SigningService signingService = SigningService.createFromSecret(randomSecret, null); + SigningService signingService = SigningService.createFromSecret(randomSecret); RequestId requestId = RequestId.createFromImprint(signingService.getPublicKey(), stateHash.getImprint()); Authenticator auth = Authenticator.create(signingService, txDataHash, stateHash); SubmitCommitmentResponse response = aggregatorClient.submitCommitment(requestId, txDataHash, auth).get(); diff --git a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java b/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java index 5895c0f..4572ed8 100644 --- a/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java +++ b/src/test/java/org/unicitylabs/sdk/e2e/CommonTestFlow.java @@ -1,10 +1,18 @@ package org.unicitylabs.sdk.e2e; -import static org.unicitylabs.sdk.utils.TestUtils.randomBytes; -import static org.unicitylabs.sdk.utils.TestUtils.randomCoinData; 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; +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; import org.unicitylabs.sdk.address.Address; import org.unicitylabs.sdk.address.DirectAddress; @@ -18,34 +26,25 @@ import org.unicitylabs.sdk.predicate.UnmaskedPredicate; import org.unicitylabs.sdk.predicate.UnmaskedPredicateReference; import org.unicitylabs.sdk.signing.SigningService; -import org.unicitylabs.sdk.token.NameTagTokenState; 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.token.fungible.CoinId; -import org.unicitylabs.sdk.transaction.MintTransactionReason; -import org.unicitylabs.sdk.transaction.NametagMintTransactionData; -import org.unicitylabs.sdk.transaction.split.SplitMintReason; import org.unicitylabs.sdk.token.fungible.TokenCoinData; 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; +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.InclusionProofUtils; import org.unicitylabs.sdk.utils.TestTokenData; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -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; /** * Common test flows for token operations, matching TypeScript SDK's CommonTestFlow. @@ -66,7 +65,8 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti // Alice mints a token byte[] aliceNonce = randomBytes(32); - SigningService aliceSigningService = SigningService.createFromSecret(ALICE_SECRET, aliceNonce); + SigningService aliceSigningService = SigningService.createFromMaskedSecret(ALICE_SECRET, + aliceNonce); MaskedPredicate alicePredicate = MaskedPredicate.create( aliceSigningService, @@ -149,17 +149,17 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti ); // Bob prepares to receive the token - byte[] bobNonce = randomBytes(32); - SigningService bobSigningService = SigningService.createFromSecret(BOB_SECRET, bobNonce); - MaskedPredicate bobPredicate = MaskedPredicate.create(bobSigningService, HashAlgorithm.SHA256, - bobNonce); - DirectAddress bobAddress = bobPredicate.getReference(tokenType).toAddress(); + DirectAddress bobAddress = UnmaskedPredicateReference.create( + tokenType, + SigningService.createFromSecret(BOB_SECRET), + HashAlgorithm.SHA256 + ).toAddress(); // Bob mints a name tag tokens byte[] bobNametagNonce = randomBytes(32); MaskedPredicate bobNametagPredicate = MaskedPredicate.create( - SigningService.createFromSecret(BOB_SECRET, bobNametagNonce), + SigningService.createFromMaskedSecret(BOB_SECRET, bobNametagNonce), HashAlgorithm.SHA256, bobNametagNonce ); @@ -170,8 +170,6 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti new NametagMintTransactionData<>( bobNameTag, bobNametagTokenType, - new byte[10], - null, bobNametagAddress, randomBytes(32), bobAddress @@ -191,28 +189,37 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti ).get() ); Token bobNametagToken = new Token<>( - new NameTagTokenState(bobNametagPredicate, bobAddress), + new TokenState(bobNametagPredicate, null), bobNametagGenesis ); // Bob finalizes the token Token bobToken = client.finalizeTransaction( aliceToken, - new TokenState(bobPredicate, bobStateData), + new TokenState( + UnmaskedPredicate.create( + SigningService.createFromSecret(BOB_SECRET), + HashAlgorithm.SHA256, + aliceToBobTransferTransaction.getData().getSalt() + ), + bobStateData + ), aliceToBobTransferTransaction, List.of(bobNametagToken) ); // Verify Bob is now the owner assertTrue(bobToken.verify().isSuccessful()); - assertTrue(bobToken.getState().getUnlockPredicate().isOwner(bobSigningService.getPublicKey())); + assertTrue(bobToken.getState().getUnlockPredicate() + .isOwner(SigningService.createFromSecret(BOB_SECRET).getPublicKey()) + ); assertEquals(aliceToken.getId(), bobToken.getId()); assertEquals(aliceToken.getType(), bobToken.getType()); // Transfer to Carol with UnmaskedPredicate - byte[] carolNonce = randomBytes(32); - SigningService carolSigningService = SigningService.createFromSecret(CAROL_SECRET, carolNonce); - DirectAddress carolAddress = UnmaskedPredicateReference.create(tokenType, carolSigningService, + DirectAddress carolAddress = UnmaskedPredicateReference.create( + tokenType, + SigningService.createFromSecret(CAROL_SECRET), HashAlgorithm.SHA256).toAddress(); // Bob transfers to Carol (no custom data) @@ -223,7 +230,7 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti randomBytes(32), null, null, - bobSigningService + SigningService.createFromSecret(BOB_SECRET) ); SubmitCommitmentResponse bobToCarolTransferSubmitResponse = client.submitCommitment( bobToken, @@ -246,9 +253,9 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti // Carol creates UnmaskedPredicate and finalizes UnmaskedPredicate carolPredicate = UnmaskedPredicate.create( - carolSigningService, + SigningService.createFromSecret(CAROL_SECRET), HashAlgorithm.SHA256, - carolNonce + bobToCarolTransaction.getData().getSalt() ); Token carolToken = client.finalizeTransaction( @@ -261,63 +268,13 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti assertEquals(2, carolToken.getTransactions().size()); // Bob receives carol token with nametag - byte[] carolToBobNonce = randomBytes(32); - UnmaskedPredicate carolToBobPredicate = UnmaskedPredicate.create( - SigningService.createFromSecret(BOB_SECRET, carolToBobNonce), - HashAlgorithm.SHA256, - carolToBobNonce - ); - - byte[] bobNametagSecondUseNonce = randomBytes(32); - NameTagTokenState nametagSecondUseTokenState = new NameTagTokenState( - MaskedPredicate.create( - SigningService.createFromSecret(BOB_SECRET, bobNametagSecondUseNonce), - HashAlgorithm.SHA256, - bobNametagSecondUseNonce - ), - carolToBobPredicate.getReference(carolToken.getType()).toAddress() - ); - - TransferCommitment nametagSecondUseCommitment = TransferCommitment.create( - bobNametagToken, - nametagSecondUseTokenState.getUnlockPredicate().getReference(bobNametagToken.getType()) - .toAddress(), - randomBytes(32), - new DataHasher(HashAlgorithm.SHA256) - .update( - nametagSecondUseTokenState.getData() - .orElseThrow( - () -> new RuntimeException("Invalid nametag, address data missing") - ) - ) - .digest(), - null, - SigningService.createFromSecret(BOB_SECRET, bobNametagNonce) - ); - SubmitCommitmentResponse nametagSecondUseResponse = client.submitCommitment( - bobNametagToken, - nametagSecondUseCommitment - ).get(); - - if (nametagSecondUseResponse.getStatus() != SubmitCommitmentStatus.SUCCESS) { - throw new Exception(String.format("Failed to submit nametag transfer commitment: %s", - nametagMintResponse.getStatus())); - } - - Token bobSecondUseNametag = client.finalizeTransaction( - bobNametagToken, - nametagSecondUseTokenState, - nametagSecondUseCommitment.toTransaction(bobNametagToken, - InclusionProofUtils.waitInclusionProof(client, nametagSecondUseCommitment).get()) - ); - TransferCommitment carolToBobTransferCommitment = TransferCommitment.create( carolToken, - ProxyAddress.create(bobNametagToken.getId()), + ProxyAddress.create(bobNameTag), randomBytes(32), null, null, - carolSigningService + SigningService.createFromSecret(CAROL_SECRET) ); SubmitCommitmentResponse carolToBobTransferSubmitResponse = client.submitCommitment( carolToken, @@ -341,9 +298,16 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti Token carolToBobToken = client.finalizeTransaction( carolToken, - new TokenState(carolToBobPredicate, null), + new TokenState( + UnmaskedPredicate.create( + SigningService.createFromSecret(BOB_SECRET), + HashAlgorithm.SHA256, + carolToBobTransaction.getData().getSalt() + ), + null + ), carolToBobTransaction, - List.of(bobSecondUseNametag) + List.of(bobNametagToken) ); assertTrue(carolToBobToken.verify().isSuccessful()); @@ -355,7 +319,7 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti TokenType splitTokenType = new TokenType(randomBytes(32)); byte[] splitTokenNonce = randomBytes(32); MaskedPredicate splitTokenPredicate = MaskedPredicate.create( - SigningService.createFromSecret(BOB_SECRET, splitTokenNonce), + SigningService.createFromMaskedSecret(BOB_SECRET, splitTokenNonce), HashAlgorithm.SHA256, splitTokenNonce ); @@ -381,8 +345,10 @@ public static void testTransferFlow(StateTransitionClient client) throws Excepti ) .build(carolToBobToken); - TransferCommitment burnCommitment = split.createBurnCommitment(randomBytes(32), - SigningService.createFromSecret(BOB_SECRET, carolToBobNonce)); + TransferCommitment burnCommitment = split.createBurnCommitment( + randomBytes(32), + SigningService.createFromSecret(BOB_SECRET) + ); if (client.submitCommitment(carolToBobToken, burnCommitment).get().getStatus() != SubmitCommitmentStatus.SUCCESS) { diff --git a/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java b/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java new file mode 100644 index 0000000..dce9cbf --- /dev/null +++ b/src/test/java/org/unicitylabs/sdk/functional/FunctionalCommonFlowTest.java @@ -0,0 +1,22 @@ +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; + +public class FunctionalCommonFlowTest { + + private StateTransitionClient client; + + @BeforeEach + void setUp() { + this.client = new StateTransitionClient(new TestAggregatorClient()); + } + + @Test + void testTransferFlow() throws Exception { + CommonTestFlow.testTransferFlow(this.client); + } +} \ 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 new file mode 100644 index 0000000..4a2abf0 --- /dev/null +++ b/src/test/java/org/unicitylabs/sdk/functional/FunctionalUnsignedPredicateDoubleSpendPreventionTest.java @@ -0,0 +1,121 @@ +package org.unicitylabs.sdk.functional; + +import static org.unicitylabs.sdk.utils.TestUtils.randomBytes; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import org.junit.jupiter.api.Assertions; +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.SubmitCommitmentResponse; +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.serializer.UnicityObjectMapper; +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.Transaction; +import org.unicitylabs.sdk.transaction.TransferCommitment; +import org.unicitylabs.sdk.transaction.TransferTransactionData; +import org.unicitylabs.sdk.util.HexConverter; +import org.unicitylabs.sdk.util.InclusionProofUtils; +import org.unicitylabs.sdk.utils.TokenUtils; + +public class FunctionalUnsignedPredicateDoubleSpendPreventionTest { + 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 { + TransferCommitment commitment = TransferCommitment.create( + token, + address, + randomBytes(32), + null, + null, + SigningService.createFromMaskedSecret(secret, token.getState().getUnlockPredicate().getNonce()) + ); + + SubmitCommitmentResponse response = this.client.submitCommitment(token, 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())) + }; + } + + private Token mintToken(byte[] secret) throws Exception { + return TokenUtils.mintToken( + this.client, + secret, + new TokenId(randomBytes(32)), + new TokenType(HexConverter.decode( + "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509")), + randomBytes(32), + null, + randomBytes(32), + randomBytes(32), + null + ); + } + + private Token receiveToken(String[] tokenInfo, byte[] secret) throws Exception { + Token token = UnicityObjectMapper.JSON.readValue(tokenInfo[0], Token.class); + Transaction transaction = UnicityObjectMapper.JSON.readValue( + tokenInfo[1], + UnicityObjectMapper.JSON.getTypeFactory() + .constructParametricType(Transaction.class, TransferTransactionData.class)); + + TokenState state = new TokenState( + UnmaskedPredicate.create( + SigningService.createFromSecret(secret), + HashAlgorithm.SHA256, + transaction.getData().getSalt() + ), + null + ); + + return this.client.finalizeTransaction( + token, + state, + transaction, + List.of() + ); + } + + @Test + void testDoubleSpend() throws Exception { + Token token = mintToken(BOB_SECRET); + + UnmaskedPredicateReference reference = UnmaskedPredicateReference.create( + token.getType(), + SigningService.createFromSecret(BOB_SECRET), + HashAlgorithm.SHA256 + ); + + Assertions.assertTrue( + receiveToken( + transferToken(token, BOB_SECRET, reference.toAddress()), + BOB_SECRET + ).verify().isSuccessful()); + RuntimeException ex = Assertions.assertThrows( + RuntimeException.class, + () -> receiveToken( + transferToken(token, BOB_SECRET, reference.toAddress()), + BOB_SECRET + ).verify() + ); + + Assertions.assertInstanceOf(BranchExistsException.class, ex.getCause()); + } +} diff --git a/src/test/java/org/unicitylabs/sdk/hash/DataHashTest.java b/src/test/java/org/unicitylabs/sdk/hash/DataHashTest.java index 702551b..36947b0 100644 --- a/src/test/java/org/unicitylabs/sdk/hash/DataHashTest.java +++ b/src/test/java/org/unicitylabs/sdk/hash/DataHashTest.java @@ -33,9 +33,9 @@ public void testDataHashJsonSerialization() throws JsonProcessingException { objectMapper.readValue( "\"00000000000000000000000000000000000000000000000000000000000000000000\"", DataHash.class)); - JsonMappingException exception = Assertions.assertThrows(JsonMappingException.class, + Assertions.assertThrows(JsonMappingException.class, () -> objectMapper.readValue("[]", DataHash.class)); - exception = Assertions.assertThrows(JsonMappingException.class, + Assertions.assertThrows(JsonMappingException.class, () -> objectMapper.readValue("\"AABBGG\"", DataHash.class)); } } diff --git a/src/test/java/org/unicitylabs/sdk/mtree/MerkleTreePathStepTest.java b/src/test/java/org/unicitylabs/sdk/mtree/MerkleTreePathStepTest.java index e7eaad2..b1f0486 100644 --- a/src/test/java/org/unicitylabs/sdk/mtree/MerkleTreePathStepTest.java +++ b/src/test/java/org/unicitylabs/sdk/mtree/MerkleTreePathStepTest.java @@ -18,9 +18,8 @@ public class MerkleTreePathStepTest { @Test public void testConstructorThrowsOnNullArguments() { - Exception exception = assertThrows(NullPointerException.class, () -> { - new SparseMerkleTreePathStep(null, null, null); - }); + Exception exception = assertThrows(NullPointerException.class, + () -> new SparseMerkleTreePathStep(null, null, null)); assertEquals("path cannot be null", exception.getMessage()); } diff --git a/src/test/java/org/unicitylabs/sdk/mtree/MerkleTreePathTest.java b/src/test/java/org/unicitylabs/sdk/mtree/MerkleTreePathTest.java index 0c5e21c..85a5e40 100644 --- a/src/test/java/org/unicitylabs/sdk/mtree/MerkleTreePathTest.java +++ b/src/test/java/org/unicitylabs/sdk/mtree/MerkleTreePathTest.java @@ -21,13 +21,13 @@ public class MerkleTreePathTest { @Test public void testConstructorThrowsOnNullArguments() { - Exception exception = assertThrows(NullPointerException.class, () -> { - new SparseMerkleTreePath(null, null); - }); + Exception exception = assertThrows(NullPointerException.class, + () -> new SparseMerkleTreePath(null, null) + ); assertEquals("rootHash cannot be null", exception.getMessage()); - exception = assertThrows(NullPointerException.class, () -> { - new SparseMerkleTreePath(new DataHash(HashAlgorithm.SHA256, new byte[32]), null); - }); + exception = assertThrows(NullPointerException.class, + () -> new SparseMerkleTreePath(new DataHash(HashAlgorithm.SHA256, new byte[32]), null) + ); assertEquals("steps cannot be null", exception.getMessage()); } diff --git a/src/test/java/org/unicitylabs/sdk/signing/SigningServiceTest.java b/src/test/java/org/unicitylabs/sdk/signing/SigningServiceTest.java index b2e8397..a80136d 100644 --- a/src/test/java/org/unicitylabs/sdk/signing/SigningServiceTest.java +++ b/src/test/java/org/unicitylabs/sdk/signing/SigningServiceTest.java @@ -28,7 +28,7 @@ public void testCreateFromSecret() throws Exception { byte[] secret = "test secret".getBytes(StandardCharsets.UTF_8); byte[] nonce = "test nonce".getBytes(StandardCharsets.UTF_8); - SigningService signingService = SigningService.createFromSecret(secret, nonce); + SigningService signingService = SigningService.createFromMaskedSecret(secret, nonce); assertNotNull(signingService); assertNotNull(signingService.getPublicKey()); diff --git a/src/test/java/org/unicitylabs/sdk/token/TokenTest.java b/src/test/java/org/unicitylabs/sdk/token/TokenTest.java index 84d7c29..4b739b4 100644 --- a/src/test/java/org/unicitylabs/sdk/token/TokenTest.java +++ b/src/test/java/org/unicitylabs/sdk/token/TokenTest.java @@ -41,25 +41,21 @@ public void testJsonSerialization() throws IOException { ); byte[] nametagNonce = TestUtils.randomBytes(32); - NameTagTokenState nametagTokenState = new NameTagTokenState( - MaskedPredicate.create( - SigningService.createFromSecret(TestUtils.randomBytes(32), nametagNonce), - HashAlgorithm.SHA256, - nametagNonce), - DirectAddress.create(new DataHash(HashAlgorithm.SHA256, TestUtils.randomBytes(32))) - ); MintTransactionData nametagGenesisData = new NametagMintTransactionData<>( UUID.randomUUID().toString(), new TokenType(TestUtils.randomBytes(32)), - TestUtils.randomBytes(5), - null, DirectAddress.create(new DataHash(HashAlgorithm.SHA256, TestUtils.randomBytes(32))), TestUtils.randomBytes(32), - nametagTokenState.getAddress() + DirectAddress.create(new DataHash(HashAlgorithm.SHA256, TestUtils.randomBytes(32))) ); Token nametagToken = new Token<>( - nametagTokenState, + new TokenState( + MaskedPredicate.create( + SigningService.createFromMaskedSecret(TestUtils.randomBytes(32), nametagNonce), + HashAlgorithm.SHA256, + nametagNonce), + null), new Transaction<>( nametagGenesisData, new InclusionProof( @@ -76,8 +72,10 @@ public void testJsonSerialization() throws IOException { Token token = new Token<>( new TokenState( MaskedPredicate.create( - SigningService.createFromSecret(TestUtils.randomBytes(32), - genesisData.getTokenId().getBytes()), + SigningService.createFromMaskedSecret( + TestUtils.randomBytes(32), + genesisData.getTokenId().getBytes() + ), HashAlgorithm.SHA256, TestUtils.randomBytes(24)), null 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 5459d71..cd6818e 100644 --- a/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java +++ b/src/test/java/org/unicitylabs/sdk/transaction/split/TokenSplitBuilderTest.java @@ -82,17 +82,17 @@ public void testTokenSplitIntoMultipleTokens() TokenSplitBuilder builder = new TokenSplitBuilder(); - Assertions.assertThrows(IllegalArgumentException.class, () -> { - builder.createToken( - new TokenId(UUID.randomUUID().toString().getBytes()), - token.getType(), - null, - new TokenCoinData(Map.of()), - predicate.getReference(token.getType()).toAddress(), - new byte[20], - null - ); - }); + Assertions.assertThrows(IllegalArgumentException.class, + () -> builder.createToken( + new TokenId(UUID.randomUUID().toString().getBytes()), + token.getType(), + null, + new TokenCoinData(Map.of()), + predicate.getReference(token.getType()).toAddress(), + new byte[20], + null + ) + ); builder.createToken( new TokenId(UUID.randomUUID().toString().getBytes()), diff --git a/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java b/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java index 4f68142..e9fcd50 100644 --- a/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java +++ b/src/test/java/org/unicitylabs/sdk/utils/TokenUtils.java @@ -11,7 +11,6 @@ import org.unicitylabs.sdk.hash.HashAlgorithm; import org.unicitylabs.sdk.predicate.MaskedPredicate; import org.unicitylabs.sdk.signing.SigningService; -import org.unicitylabs.sdk.token.NameTagTokenState; import org.unicitylabs.sdk.token.Token; import org.unicitylabs.sdk.token.TokenId; import org.unicitylabs.sdk.token.TokenState; @@ -50,7 +49,7 @@ public static Token mintToken( byte[] salt, DataHash dataHash ) throws Exception { - SigningService signingService = SigningService.createFromSecret(secret, nonce); + SigningService signingService = SigningService.createFromMaskedSecret(secret, nonce); MaskedPredicate predicate = MaskedPredicate.create( signingService, @@ -106,8 +105,6 @@ public static Token mintNametagToken( client, secret, new TokenType(randomBytes(32)), - randomBytes(32), - randomCoinData(2), nametag, targetAddress, randomBytes(32), @@ -119,14 +116,12 @@ public static Token mintNametagToken( StateTransitionClient client, byte[] secret, TokenType tokenType, - byte[] tokenData, - TokenCoinData coinData, String nametag, Address targetAddress, byte[] nonce, byte[] salt ) throws Exception { - SigningService signingService = SigningService.createFromSecret(secret, nonce); + SigningService signingService = SigningService.createFromMaskedSecret(secret, nonce); MaskedPredicate predicate = MaskedPredicate.create( signingService, @@ -135,14 +130,11 @@ public static Token mintNametagToken( ); Address address = predicate.getReference(tokenType).toAddress(); - TokenState tokenState = new NameTagTokenState(predicate, targetAddress); MintCommitment> commitment = MintCommitment.create( new NametagMintTransactionData<>( nametag, tokenType, - tokenData, - coinData, address, salt, targetAddress @@ -166,7 +158,7 @@ public static Token mintNametagToken( // Create mint transaction return new Token<>( - tokenState, + new TokenState(predicate, null), commitment.toTransaction(inclusionProof) ); }