Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 68 additions & 22 deletions src/main/java/org/unicitylabs/sdk/StateTransitionClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@
import org.unicitylabs.sdk.api.RequestId;
import org.unicitylabs.sdk.api.SubmitCommitmentResponse;
import org.unicitylabs.sdk.bft.RootTrustBase;
import org.unicitylabs.sdk.predicate.Predicate;
import org.unicitylabs.sdk.predicate.PredicateEngineService;
import org.unicitylabs.sdk.signing.MintSigningService;
import org.unicitylabs.sdk.token.Token;
import org.unicitylabs.sdk.token.TokenId;
import org.unicitylabs.sdk.token.TokenState;
import org.unicitylabs.sdk.transaction.Commitment;
import org.unicitylabs.sdk.transaction.InclusionProofVerificationStatus;
import org.unicitylabs.sdk.transaction.MintCommitment;
import org.unicitylabs.sdk.transaction.MintTransactionReason;
import org.unicitylabs.sdk.transaction.MintTransactionState;
import org.unicitylabs.sdk.transaction.TransferCommitment;
import org.unicitylabs.sdk.transaction.TransferTransaction;
import org.unicitylabs.sdk.verification.VerificationException;

/**
* Client for handling state transitions of tokens, including submitting commitments and finalizing
* transactions.
* Client for handling state transitions of tokens, including submitting commitments and finalizing transactions.
*/
public class StateTransitionClient {

Expand Down Expand Up @@ -80,8 +82,7 @@ public CompletableFuture<SubmitCommitmentResponse> submitCommitment(
}

/**
* Finalizes a transaction by updating the token state based on the provided transaction data
* without nametags.
* Finalizes a transaction by updating the token state based on the provided transaction data without nametags.
*
* @param trustBase The root trust base for inclusion proof verification.
* @param token The token to be updated.
Expand All @@ -101,8 +102,7 @@ public <R extends MintTransactionReason> Token<R> finalizeTransaction(
}

/**
* Finalizes a transaction by updating the token state based on the provided transaction data and
* nametags.
* Finalizes a transaction by updating the token state based on the provided transaction data and nametags.
*
* @param trustBase The root trust base for inclusion proof verification.
* @param token The token to be updated.
Expand All @@ -126,31 +126,77 @@ public <R extends MintTransactionReason> Token<R> finalizeTransaction(
}

/**
* Retrieves the inclusion proof for a token and verifies its status against the provided public
* key and trust base.
* Retrieves the inclusion proof for a given request id.
*
* @param token The token for which to retrieve the inclusion proof.
* @param publicKey The public key associated with the token.
* @param trustBase The root trust base for verification.
* @return A CompletableFuture that resolves to the inclusion proof verification status.
* @param requestId The request ID of inclusion proof to retrieve.
* @return A CompletableFuture that resolves to the inclusion proof response from the aggregator.
*/
public CompletableFuture<InclusionProofResponse> getInclusionProof(RequestId requestId) {
return this.client.getInclusionProof(requestId);
}

/**
* Check if state is already spent for given request id.
*
* @param requestId request id
* @param trustBase root trust base
* @return A CompletableFuture that resolves to true if state is spent, false otherwise.
*/
public CompletableFuture<InclusionProofVerificationStatus> getTokenStatus(
public CompletableFuture<Boolean> isStateSpent(RequestId requestId, RootTrustBase trustBase) {
return this.getInclusionProof(requestId)
.thenApply(inclusionProof -> {
InclusionProofVerificationStatus result = inclusionProof.getInclusionProof().verify(requestId, trustBase);
switch (result) {
case OK:
return true;
case PATH_NOT_INCLUDED:
return false;
default:
throw new RuntimeException(
String.format("Inclusion proof verification failed with status %s", result)
);
}
});
}


/**
* Get inclusion proof for current token state.
*
* @param token token
* @param publicKey public key
* @param trustBase trustBase
* @return A CompletableFuture that resolves to the inclusion proof response from the aggregator.
*/
public CompletableFuture<Boolean> isStateSpent(
Token<?> token,
byte[] publicKey,
RootTrustBase trustBase
) {
RequestId requestId = RequestId.create(publicKey, token.getState().calculateHash());
return this.client.getInclusionProof(requestId)
.thenApply(response -> response.getInclusionProof().verify(requestId, trustBase));
Predicate predicate = PredicateEngineService.createPredicate(token.getState().getPredicate());
if (!predicate.isOwner(publicKey)) {
throw new IllegalArgumentException("Given key is not owner of the token.");
}

return this.isStateSpent(RequestId.create(publicKey, token.getState()), trustBase);
}

/**
* Retrieves the inclusion proof for a given commitment.
* Check if token id is already minted.
*
* @param commitment The commitment for which to retrieve the inclusion proof.
* @return A CompletableFuture that resolves to the inclusion proof response from the aggregator.
* @param tokenId token id
* @param trustBase root trust base
* @return A CompletableFuture that resolves to true if token id is spent, false otherwise.
*/
public CompletableFuture<InclusionProofResponse> getInclusionProof(Commitment<?> commitment) {
return this.client.getInclusionProof(commitment.getRequestId());
public CompletableFuture<Boolean> isMinted(TokenId tokenId, RootTrustBase trustBase) {
return this.isStateSpent(
RequestId.create(
MintSigningService.create(tokenId).getPublicKey(),
MintTransactionState.create(tokenId)
),
trustBase
);
}


}
8 changes: 6 additions & 2 deletions src/main/java/org/unicitylabs/sdk/api/Authenticator.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,12 @@ public static Authenticator create(
DataHash transactionHash,
DataHash stateHash
) {
return new Authenticator(signingService.getAlgorithm(), signingService.getPublicKey(),
signingService.sign(transactionHash), stateHash);
return new Authenticator(
signingService.getAlgorithm(),
signingService.getPublicKey(),
signingService.sign(transactionHash),
stateHash
);
}

/**
Expand Down
37 changes: 25 additions & 12 deletions src/main/java/org/unicitylabs/sdk/api/RequestId.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.unicitylabs.sdk.hash.HashAlgorithm;
import org.unicitylabs.sdk.serializer.UnicityObjectMapper;
import org.unicitylabs.sdk.serializer.json.JsonSerializationException;
import org.unicitylabs.sdk.token.TokenState;
import org.unicitylabs.sdk.transaction.MintTransaction;
import org.unicitylabs.sdk.util.BitString;
import org.unicitylabs.sdk.util.HexConverter;

Expand All @@ -26,27 +28,38 @@ protected RequestId(DataHash hash) {
}

/**
* Creates a RequestId from a public key and state hash.
* Creates a RequestId from public key and state.
*
* @param id The public key as a byte array.
* @param stateHash The state hash.
* @return A CompletableFuture resolving to a RequestId instance.
* @param publicKey public key as a byte array.
* @param state token state.
* @return request id
*/
public static RequestId create(byte[] publicKey, TokenState state) {
return RequestId.create(publicKey, state.calculateHash());
}

/**
* Creates a RequestId from public key and hash.
*
* @param publicKey public key as a byte array.
* @param hash hash.
* @return request id
*/
public static RequestId create(byte[] id, DataHash stateHash) {
return createFromImprint(id, stateHash.getImprint());
public static RequestId create(byte[] publicKey, DataHash hash) {
return RequestId.create(publicKey, hash.getImprint());
}

/**
* Creates a RequestId from a public key and hash imprint.
* Creates a RequestId from identifier bytes and hash imprint.
*
* @param id The public key as a byte array.
* @param hashImprint The hash imprint as a byte array.
* @return A CompletableFuture resolving to a RequestId instance.
* @param id id bytes.
* @param stateBytes state bytes.
* @return request id.
*/
public static RequestId createFromImprint(byte[] id, byte[] hashImprint) {
public static RequestId create(byte[] id, byte[] stateBytes) {
DataHasher hasher = new DataHasher(HashAlgorithm.SHA256);
hasher.update(id);
hasher.update(hashImprint);
hasher.update(stateBytes);

return new RequestId(hasher.digest());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package org.unicitylabs.sdk.bft.verification.rule;

import com.google.common.primitives.UnsignedBytes;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

import com.google.common.primitives.UnsignedBytes;
import org.unicitylabs.sdk.bft.UnicityCertificate;
import org.unicitylabs.sdk.bft.UnicityTreeCertificate;
import org.unicitylabs.sdk.bft.verification.UnicityCertificateVerificationContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ public MerkleTreePathVerificationResult verify(BigInteger requestId) {

SparseMerkleTreePathStep step = this.steps.get(0);
byte[] currentData;
BigInteger currentPath = BigInteger.ONE;
if (step.getPath().compareTo(BigInteger.ZERO) > 0) {
BigInteger currentPath = step.getPath();
if (step.getPath().compareTo(BigInteger.ONE) > 0) {
DataHash hash = new DataHasher(this.rootHash.getAlgorithm())
.update(
CborSerializer.encodeArray(
Expand All @@ -86,17 +86,15 @@ public MerkleTreePathVerificationResult verify(BigInteger requestId) {
.digest();

currentData = hash.getData();

int length = step.getPath().bitLength() - 1;
currentPath = currentPath.shiftLeft(length)
.or(step.getPath().and(BigInteger.ONE.shiftLeft(length).subtract(BigInteger.ONE)));
} else {
currentPath = BigInteger.ONE;
currentData = step.getData().orElse(null);
}

SparseMerkleTreePathStep previousStep = step;
for (int i = 1; i < this.steps.size(); i++) {
step = this.steps.get(i);
boolean isRight = currentPath.testBit(0);
boolean isRight = previousStep.getPath().testBit(0);

byte[] left = isRight ? step.getData().orElse(null) : currentData;
byte[] right = isRight ? currentData : step.getData().orElse(null);
Expand All @@ -114,8 +112,12 @@ public MerkleTreePathVerificationResult verify(BigInteger requestId) {
currentData = hash.getData();

int length = step.getPath().bitLength() - 1;
if (length < 0) {
return new MerkleTreePathVerificationResult(false, false);
}
currentPath = currentPath.shiftLeft(length)
.or(step.getPath().and(BigInteger.ONE.shiftLeft(length).subtract(BigInteger.ONE)));
previousStep = step;
}

boolean pathValid = currentData != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
*/
public class SparseMerkleTreeRootNode {

private final BigInteger path = BigInteger.ONE; // Root path is always 0
private final FinalizedNodeBranch root;

private SparseMerkleTreeRootNode(FinalizedNodeBranch root) {
Expand Down Expand Up @@ -58,12 +57,12 @@ public boolean equals(Object o) {
return false;
}
SparseMerkleTreeRootNode that = (SparseMerkleTreeRootNode) o;
return Objects.equals(this.path, that.path) && Objects.equals(this.root, that.root);
return Objects.equals(this.root, that.root);
}

@Override
public int hashCode() {
return Objects.hash(this.path, this.root);
return Objects.hash(this.root);
}

private static List<SparseMerkleTreePathStep> generatePath(
Expand All @@ -83,16 +82,16 @@ private static List<SparseMerkleTreePathStep> generatePath(
|| remainingPath.compareTo(BigInteger.ONE) == 0) {
return List.of(
new SparseMerkleTreePathStep(
BigInteger.ONE,
node.getRight() == null
BigInteger.ZERO,
node.getLeft() == null
? null
: node.getRight().getHash().getData()
: node.getLeft().getHash().getData()
),
new SparseMerkleTreePathStep(
node.getPath(),
node.getLeft() == null
node.getRight() == null
? null
: node.getLeft().getHash().getData()
: node.getRight().getHash().getData()
)
);
}
Expand All @@ -101,30 +100,26 @@ private static List<SparseMerkleTreePathStep> generatePath(
FinalizedBranch branch = isRight ? node.getRight() : node.getLeft();
FinalizedBranch siblingBranch = isRight ? node.getLeft() : node.getRight();

SparseMerkleTreePathStep step = new SparseMerkleTreePathStep(
node.getPath(),
siblingBranch == null ? null : siblingBranch.getHash().getData()
);

if (branch == null) {
return List.of(
new SparseMerkleTreePathStep(
BigInteger.ZERO,
node.getRight() == null
? null
: node.getRight().getHash().getData()),
new SparseMerkleTreePathStep(
BigInteger.ONE,
node.getLeft() == null
? null
: node.getLeft().getHash().getData())
isRight ? BigInteger.ONE : BigInteger.ZERO,
null
),
step
);
}

ArrayList<SparseMerkleTreePathStep> list = new ArrayList<>(
List<SparseMerkleTreePathStep> list = new ArrayList<>(
SparseMerkleTreeRootNode.generatePath(remainingPath, branch)
);
list.add(
new SparseMerkleTreePathStep(
parent.getPath(),
siblingBranch == null ? null : siblingBranch.getHash().getData()
)
);

list.add(step);

return List.copyOf(list);
}
Expand Down
Loading
Loading