Skip to content

Commit b9427b8

Browse files
authored
Merge pull request #74 from unicitynetwork/issue-73
Change sum tree hashing to match new spec
2 parents 2a16b24 + c89e954 commit b9427b8

33 files changed

Lines changed: 623 additions & 690 deletions

package-lock.json

Lines changed: 206 additions & 200 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,19 @@
3737
"uuid": "13.0.0"
3838
},
3939
"devDependencies": {
40-
"@babel/preset-env": "7.28.3",
41-
"@babel/preset-typescript": "7.27.1",
42-
"@eslint/js": "9.37.0",
40+
"@babel/preset-env": "7.28.5",
41+
"@babel/preset-typescript": "7.28.5",
42+
"@eslint/js": "9.39.1",
4343
"@types/jest": "30.0.0",
4444
"babel-jest": "30.2.0",
45-
"eslint": "9.37.0",
45+
"eslint": "9.39.1",
4646
"eslint-config-prettier": "10.1.8",
4747
"eslint-plugin-import": "2.32.0",
4848
"eslint-plugin-prettier": "5.5.4",
49-
"globals": "16.4.0",
49+
"globals": "16.5.0",
5050
"jest": "30.2.0",
51-
"testcontainers": "11.7.1",
51+
"testcontainers": "11.8.1",
5252
"typescript": "5.9.3",
53-
"typescript-eslint": "8.46.1"
53+
"typescript-eslint": "8.46.4"
5454
}
5555
}

src/StateTransitionClient.ts

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { RequestId } from './api/RequestId.js';
44
import { SubmitCommitmentResponse } from './api/SubmitCommitmentResponse.js';
55
import { RootTrustBase } from './bft/RootTrustBase.js';
66
import { PredicateEngineService } from './predicate/PredicateEngineService.js';
7+
import { MintSigningService } from './sign/MintSigningService.js';
78
import { Token } from './token/Token.js';
9+
import { TokenId } from './token/TokenId.js';
810
import { TokenState } from './token/TokenState.js';
911
import { Commitment } from './transaction/Commitment.js';
1012
import { IMintTransactionReason } from './transaction/IMintTransactionReason.js';
1113
import { InclusionProofVerificationStatus } from './transaction/InclusionProof.js';
1214
import { MintCommitment } from './transaction/MintCommitment.js';
13-
import { MintTransactionData } from './transaction/MintTransactionData.js';
15+
import { MintTransactionState } from './transaction/MintTransactionState.js';
1416
import { TransferTransaction } from './transaction/TransferTransaction.js';
1517
import { TransferTransactionData } from './transaction/TransferTransactionData.js';
1618

@@ -71,11 +73,11 @@ export class StateTransitionClient {
7173
* Finalizes a transaction by updating the token state based on the provided transaction data and
7274
* nametags.
7375
*
74-
* @param trustBase The root trust base for inclusion proof verification.
75-
* @param token The token to be updated.
76-
* @param state The current state of the token.
77-
* @param transaction The transaction containing transfer data.
78-
* @param nametags A list of tokens used as nametags in the transaction.
76+
* @param {RootTrustBase} trustBase The root trust base for inclusion proof verification.
77+
* @param {Token} token The token to be updated.
78+
* @param {TokenState} state The current state of the token.
79+
* @param {TransferTransaction} transaction The transaction containing transfer data.
80+
* @param {Token} nametags A list of tokens used as nametags in the transaction.
7981
* @return The updated token after applying the transaction.
8082
*/
8183
public finalizeTransaction<R extends IMintTransactionReason>(
@@ -89,34 +91,70 @@ export class StateTransitionClient {
8991
}
9092

9193
/**
92-
* Retrieves the inclusion proof for a token and verifies its status against the provided public
93-
* key and trust base.
94+
* Retrieves the inclusion proof for a given commitment.
9495
*
95-
* @param token The token for which to retrieve the inclusion proof.
96-
* @param publicKey The public key associated with the token.
97-
* @param trustBase The root trust base for verification.
98-
* @return inclusion proof verification status.
96+
* @param {RequestId} requestId The request ID of inclusion proof to retrieve.
97+
* @return inclusion proof response from the aggregator.
9998
*/
100-
public async getTokenStatus(
99+
public getInclusionProof(requestId: RequestId): Promise<InclusionProofResponse> {
100+
return this.client.getInclusionProof(requestId);
101+
}
102+
103+
/**
104+
* Check if state is already spent for given request id.
105+
*
106+
* @param {RootTrustBase} trustBase root trust base
107+
* @param {RequestId} requestId request id
108+
* @return true if state is spent, false otherwise.
109+
*/
110+
public async isStateSpent(trustBase: RootTrustBase, requestId: RequestId): Promise<boolean> {
111+
const response = await this.getInclusionProof(requestId);
112+
const result = await response.inclusionProof.verify(trustBase, requestId);
113+
switch (result) {
114+
case InclusionProofVerificationStatus.OK:
115+
return true;
116+
case InclusionProofVerificationStatus.PATH_NOT_INCLUDED:
117+
return false;
118+
default:
119+
throw new Error(`Inclusion proof verification failed with status ${result}`);
120+
}
121+
}
122+
123+
/**
124+
* Check if token state is already spent.
125+
* @param {RootTrustBase} trustBase trustBase
126+
* @param {Token} token token
127+
* @param {Uint8Array} publicKey public key
128+
* @return true if token state is spent, false otherwise
129+
*/
130+
public async isTokenStateSpent(
101131
trustBase: RootTrustBase,
102132
token: Token<IMintTransactionReason>,
103133
publicKey: Uint8Array,
104-
): Promise<InclusionProofVerificationStatus> {
105-
const requestId = await RequestId.create(publicKey, await token.state.calculateHash());
106-
return this.client
107-
.getInclusionProof(requestId)
108-
.then((response) => response.inclusionProof.verify(trustBase, requestId));
134+
): Promise<boolean> {
135+
const pk = new Uint8Array(publicKey);
136+
const predicate = await PredicateEngineService.createPredicate(token.state.predicate);
137+
if (!(await predicate.isOwner(pk))) {
138+
throw new Error('Given key is not owner of the token.');
139+
}
140+
141+
return this.isStateSpent(trustBase, await RequestId.create(pk, await token.state.calculateHash()));
109142
}
110143

111144
/**
112-
* Retrieves the inclusion proof for a given commitment.
145+
* Check if token id is already minted.
113146
*
114-
* @param commitment The commitment for which to retrieve the inclusion proof.
115-
* @return inclusion proof response from the aggregator.
147+
* @param {RootTrustBase} trustBase root trust base
148+
* @param {TokenId} tokenId token id
149+
* @return true if token id is spent, false otherwise.
116150
*/
117-
public getInclusionProof(
118-
commitment: Commitment<TransferTransactionData | MintTransactionData<IMintTransactionReason>>,
119-
): Promise<InclusionProofResponse> {
120-
return this.client.getInclusionProof(commitment.requestId);
151+
public async isMinted(trustBase: RootTrustBase, tokenId: TokenId): Promise<boolean> {
152+
return this.isStateSpent(
153+
trustBase,
154+
await RequestId.create(
155+
await MintSigningService.create(tokenId).then((signingService) => signingService.publicKey),
156+
await MintTransactionState.create(tokenId),
157+
),
158+
);
121159
}
122160
}

src/mtree/plain/Branch.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LeafBranch } from './LeafBranch.js';
2-
import { NodeBranch } from './NodeBranch.js';
1+
import { FinalizedLeafBranch } from './FinalizedLeafBranch.js';
2+
import { FinalizedNodeBranch } from './FinalizedNodeBranch.js';
33

4-
export type Branch = NodeBranch | LeafBranch;
4+
export type Branch = FinalizedNodeBranch | FinalizedLeafBranch;
Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,27 @@ import { DataHash } from '../../hash/DataHash.js';
22
import { HexConverter } from '../../util/HexConverter.js';
33
import { dedent } from '../../util/StringUtils.js';
44

5-
export class LeafBranch {
5+
export class FinalizedLeafBranch {
66
public constructor(
77
public readonly path: bigint,
8-
private readonly _value: Uint8Array,
8+
private readonly _data: Uint8Array,
99
public readonly hash: DataHash,
10-
) {}
10+
) {
11+
this._data = new Uint8Array(_data);
12+
}
1113

12-
public get value(): Uint8Array {
13-
return new Uint8Array(this._value);
14+
public get data(): Uint8Array {
15+
return new Uint8Array(this._data);
1416
}
1517

16-
public finalize(): Promise<LeafBranch> {
18+
public finalize(): Promise<FinalizedLeafBranch> {
1719
return Promise.resolve(this);
1820
}
1921

2022
public toString(): string {
2123
return dedent`
2224
Leaf[${this.path.toString(2)}]
23-
Value: ${HexConverter.encode(this._value)}
25+
Data: ${HexConverter.encode(this._data)}
2426
Hash: ${this.hash.toString()}`;
2527
}
2628
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import { Branch } from './Branch.js';
22
import { DataHash } from '../../hash/DataHash.js';
33
import { dedent } from '../../util/StringUtils.js';
44

5-
export class NodeBranch {
5+
export class FinalizedNodeBranch {
66
public constructor(
77
public readonly path: bigint,
88
public readonly left: Branch,
99
public readonly right: Branch,
1010
public readonly hash: DataHash,
1111
) {}
1212

13-
public finalize(): Promise<NodeBranch> {
13+
public finalize(): Promise<FinalizedNodeBranch> {
1414
return Promise.resolve(this);
1515
}
1616

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LeafBranch } from './LeafBranch.js';
1+
import { FinalizedLeafBranch } from './FinalizedLeafBranch.js';
22
import { IDataHasher } from '../../hash/IDataHasher.js';
33
import { IDataHasherFactory } from '../../hash/IDataHasherFactory.js';
44
import { CborSerializer } from '../../serializer/cbor/CborSerializer.js';
@@ -7,19 +7,25 @@ import { BigintConverter } from '../../util/BigintConverter.js';
77
export class PendingLeafBranch {
88
public constructor(
99
public readonly path: bigint,
10-
public readonly value: Uint8Array,
11-
) {}
10+
private readonly _data: Uint8Array,
11+
) {
12+
this._data = new Uint8Array(_data);
13+
}
14+
15+
public get data(): Uint8Array {
16+
return new Uint8Array(this._data);
17+
}
1218

13-
public async finalize(factory: IDataHasherFactory<IDataHasher>): Promise<LeafBranch> {
19+
public async finalize(factory: IDataHasherFactory<IDataHasher>): Promise<FinalizedLeafBranch> {
1420
const hash = await factory
1521
.create()
1622
.update(
1723
CborSerializer.encodeArray(
1824
CborSerializer.encodeByteString(BigintConverter.encode(this.path)),
19-
CborSerializer.encodeByteString(this.value),
25+
CborSerializer.encodeByteString(this._data),
2026
),
2127
)
2228
.digest();
23-
return new LeafBranch(this.path, this.value, hash);
29+
return new FinalizedLeafBranch(this.path, this._data, hash);
2430
}
2531
}

0 commit comments

Comments
 (0)