From b87b8c624d86d62cf50db91bcde385d20f892581 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Tue, 27 Jan 2026 18:32:50 +0200 Subject: [PATCH 01/15] #91 WIP: Payment module --- src/api/CertificationData.ts | 14 +- src/api/StateId.ts | 4 +- src/payment/IPaymentData.ts | 9 + src/payment/IReason.ts | 3 + src/payment/ISplitPaymentData.ts | 6 + src/payment/ISplitReason.ts | 14 ++ src/payment/PaymentData.ts | 171 ++++++++++++++++++ src/payment/asset/Asset.ts | 29 +++ src/payment/asset/AssetId.ts | 42 +++++ src/payment/asset/PaymentAssetCollection.ts | 50 +++++ .../BuiltInPredicateVerifierFactory.ts | 20 ++ .../predicate/builtin/BurnPredicate.ts | 63 +++++++ .../PayToPublicKeyPredicateVerifier.ts | 51 ++++++ .../BuiltInPredicateVerifierFactory.ts | 8 + .../builtin/PayToPublicKeyPredicate.ts | 2 +- src/transaction/CertifiedMintTransaction.ts | 4 + src/transaction/ITransaction.ts | 2 + src/transaction/MintTransaction.ts | 14 +- src/transaction/TransferTransaction.ts | 4 + src/util/InclusionProofUtils.ts | 1 + tests/functional/TestAggregatorClient.ts | 5 +- tests/functional/TransitionFlowTest.ts | 30 ++- 22 files changed, 526 insertions(+), 20 deletions(-) create mode 100644 src/payment/IPaymentData.ts create mode 100644 src/payment/IReason.ts create mode 100644 src/payment/ISplitPaymentData.ts create mode 100644 src/payment/ISplitReason.ts create mode 100644 src/payment/PaymentData.ts create mode 100644 src/payment/asset/Asset.ts create mode 100644 src/payment/asset/AssetId.ts create mode 100644 src/payment/asset/PaymentAssetCollection.ts create mode 100644 src/payment/predicate/builtin/BuiltInPredicateVerifierFactory.ts create mode 100644 src/payment/predicate/builtin/BurnPredicate.ts create mode 100644 src/payment/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts diff --git a/src/api/CertificationData.ts b/src/api/CertificationData.ts index c5f26589..7cf45715 100644 --- a/src/api/CertificationData.ts +++ b/src/api/CertificationData.ts @@ -89,15 +89,19 @@ export class CertificationData { public static async fromMintTransaction(transaction: MintTransaction): Promise { const signingService = await MintSigningService.create(transaction.tokenId); - const sourceStateHash = await transaction.calculateStateHash(); const transactionHash = await transaction.calculateTransactionHash(); const signatureDataHash = await new DataHasher(HashAlgorithm.SHA256) - .update(CborSerializer.encodeArray(sourceStateHash.toCBOR(), transactionHash.toCBOR())) + .update(CborSerializer.encodeArray(transaction.sourceStateHash.toCBOR(), transactionHash.toCBOR())) .digest(); const unlockScript = await signingService.sign(signatureDataHash); - return CertificationData.create(transaction.lockScript, sourceStateHash, transactionHash, unlockScript.encode()); + return CertificationData.create( + transaction.lockScript, + transaction.sourceStateHash, + transactionHash, + unlockScript.encode(), + ); } public static async fromTransferTransaction( @@ -105,11 +109,9 @@ export class CertificationData { unlockScript: Uint8Array, ): Promise { unlockScript = new Uint8Array(unlockScript); - - const sourceStateHash = await transaction.calculateStateHash(); const transactionHash = await transaction.calculateTransactionHash(); - return CertificationData.create(transaction.lockScript, sourceStateHash, transactionHash, unlockScript); + return CertificationData.create(transaction.lockScript, transaction.sourceStateHash, transactionHash, unlockScript); } /** diff --git a/src/api/StateId.ts b/src/api/StateId.ts index 7df36193..face9f69 100644 --- a/src/api/StateId.ts +++ b/src/api/StateId.ts @@ -43,8 +43,8 @@ export class StateId { return new StateId(DataHash.fromJSON(data)); } - public static async fromTransaction(transaction: ITransaction): Promise { - return StateId.create(transaction.lockScript, await transaction.calculateStateHash()); + public static fromTransaction(transaction: ITransaction): Promise { + return StateId.create(transaction.lockScript, transaction.sourceStateHash); } /** diff --git a/src/payment/IPaymentData.ts b/src/payment/IPaymentData.ts new file mode 100644 index 00000000..5a7a91e1 --- /dev/null +++ b/src/payment/IPaymentData.ts @@ -0,0 +1,9 @@ +import { PaymentAssetCollection } from './asset/PaymentAssetCollection.js'; +import { IReason } from './IReason.js'; + +export interface IPaymentData { + readonly assets: PaymentAssetCollection; + readonly reason: IReason; + + toCBOR(): Uint8Array; +} diff --git a/src/payment/IReason.ts b/src/payment/IReason.ts new file mode 100644 index 00000000..fadad6fa --- /dev/null +++ b/src/payment/IReason.ts @@ -0,0 +1,3 @@ +export interface IReason { + toCBOR(): Uint8Array; +} diff --git a/src/payment/ISplitPaymentData.ts b/src/payment/ISplitPaymentData.ts new file mode 100644 index 00000000..4dc03d78 --- /dev/null +++ b/src/payment/ISplitPaymentData.ts @@ -0,0 +1,6 @@ +import { IPaymentData } from './IPaymentData.js'; +import { ISplitReason } from './ISplitReason.js'; + +export interface ISplitPaymentData extends IPaymentData { + readonly reason: ISplitReason; +} diff --git a/src/payment/ISplitReason.ts b/src/payment/ISplitReason.ts new file mode 100644 index 00000000..aeb2837a --- /dev/null +++ b/src/payment/ISplitReason.ts @@ -0,0 +1,14 @@ +import { IReason } from './IReason.js'; +import { SparseMerkleTreePath } from '../smt/plain/SparseMerkleTreePath.js'; +import { AssetId } from './asset/AssetId.js'; +import { SparseMerkleSumTreePath } from '../smt/sum/SparseMerkleSumTreePath.js'; +import { Token } from '../transaction/Token.js'; + +export interface ISplitReason extends IReason { + readonly proofs: { + readonly aggregationPath: SparseMerkleTreePath; + readonly assetId: AssetId; + readonly coinTreePath: SparseMerkleSumTreePath; + }; + readonly token: Token; +} diff --git a/src/payment/PaymentData.ts b/src/payment/PaymentData.ts new file mode 100644 index 00000000..02137b94 --- /dev/null +++ b/src/payment/PaymentData.ts @@ -0,0 +1,171 @@ +import { IPaymentData } from './IPaymentData.js'; +import { ISplitPaymentData } from './ISplitPaymentData.js'; +import { RootTrustBase } from '../api/bft/RootTrustBase.js'; +import { CertificationData } from '../api/CertificationData.js'; +import { DataHasher } from '../crypto/hash/DataHasher.js'; +import { DataHasherFactory } from '../crypto/hash/DataHasherFactory.js'; +import { HashAlgorithm } from '../crypto/hash/HashAlgorithm.js'; +import { IPredicate } from '../predicate/IPredicate.js'; +import { PredicateVerifier } from '../predicate/verification/PredicateVerifier.js'; +import { CborSerializer } from '../serialization/cbor/CborSerializer.js'; +import { HexConverter } from '../serialization/HexConverter.js'; +import { SparseMerkleTree } from '../smt/plain/SparseMerkleTree.js'; +import { SparseMerkleTreeRootNode } from '../smt/plain/SparseMerkleTreeRootNode.js'; +import { SparseMerkleSumTree } from '../smt/sum/SparseMerkleSumTree.js'; +import { SparseMerkleSumTreeRootNode } from '../smt/sum/SparseMerkleSumTreeRootNode.js'; +import { CertifiedTransferTransaction } from '../transaction/CertifiedTransferTransaction.js'; +import { MintTransaction } from '../transaction/MintTransaction.js'; +import { PayToScriptHash } from '../transaction/PayToScriptHash.js'; +import { Token } from '../transaction/Token.js'; +import { TokenId } from '../transaction/TokenId.js'; +import { TokenType } from '../transaction/TokenType.js'; +import { TransferTransaction } from '../transaction/TransferTransaction.js'; +import { AssetId } from './asset/AssetId.js'; +import { PaymentAssetCollection } from './asset/PaymentAssetCollection.js'; +import { BurnPredicate } from './predicate/builtin/BurnPredicate.js'; + +/** + * New token request for generating it out of burnt token. + */ +export class SplitToken { + public constructor( + public readonly id: TokenId, + public readonly assets: PaymentAssetCollection, + ) { + if (assets.size() == 0) { + throw new Error('Token must have at least one asset'); + } + } +} + +interface ISplit { + burnTransaction: TransferTransaction; + tokens: SplitToken[]; +} + +/** + * Token splitting builder. + */ +export class SplitBuilder { + private readonly hasher = new DataHasherFactory(HashAlgorithm.SHA256, DataHasher); + private readonly tokens = new Map(); + + /** + * Split old token to new tokens. + * + * @param token token to be used for split + * @param ownerPredicate + * @param parsePaymentData + * @param splitTokens + * @return token split object for submitting info + */ + public async split( + token: Token, + ownerPredicate: IPredicate, + parsePaymentData: (bytes: Uint8Array) => IPaymentData, + splitTokens: SplitToken[], + ): Promise { + const trees = new Map(); + + for (const splitToken of splitTokens) { + for (const asset of splitToken.assets.toArray()) { + const key = HexConverter.encode(asset.id.bytes); + let tree = trees.get(key)?.[1]; + if (!tree) { + tree = new SparseMerkleSumTree(this.hasher); + trees.set(key, [asset.id, tree]); + } + + await tree.addLeaf(splitToken.id.toBitString().toBigInt(), asset.id.bytes, asset.value); + } + } + + // Parse this from user object + const assets = parsePaymentData(token.genesis.data).assets; + + if (trees.size !== assets.size()) { + throw new Error('Token has different number of coins than expected'); + } + + const aggregationTree = new SparseMerkleTree(this.hasher); + const assetTreeRoots = new Map(); + for (const [assetId, tree] of trees.values()) { + const assetsInToken = assets.get(assetId); + const root = await tree.calculateRoot(); + if (root.value !== assetsInToken?.value) { + throw new Error( + `Token contained ${assetsInToken?.value} ${assetId.toString()} assets, but tree has ${root.value}`, + ); + } + + assetTreeRoots.set(HexConverter.encode(assetId.bytes), root); + await aggregationTree.addLeaf(assetId.toBitString().toBigInt(), root.hash.imprint); + } + + const aggregationRoot = await aggregationTree.calculateRoot(); + const burnPredicate = BurnPredicate.create(aggregationRoot.hash.imprint); + const burnTransaction = await TransferTransaction.create( + token, + ownerPredicate, + await PayToScriptHash.create(burnPredicate), + crypto.getRandomValues(new Uint8Array(32)), + CborSerializer.encodeArray(), + ); + + return { + burnTransaction, + tokens: Array.from(this.tokens.values()), + }; + } +} + +/** + * Token split request object. + */ +class Split { + public constructor( + private readonly token: Token, + private readonly aggregationRoot: SparseMerkleTreeRootNode, + private readonly assetRoots: Map, + private readonly splitTokens: SplitToken[], + ) {} + + public async burn(ownerPredicate: IPredicate, witness: Uint8Array): Promise { + const burnPredicate = BurnPredicate.create(this.aggregationRoot.hash.imprint); + const burnTransaction = await TransferTransaction.create( + this.token, + ownerPredicate, + await PayToScriptHash.create(burnPredicate), + crypto.getRandomValues(new Uint8Array(32)), + CborSerializer.encodeArray(), + ); + + return CertificationData.fromTransferTransaction(burnTransaction, witness); + } + + /** + * Create split mint commitments after burn transaction is received. + * + * @param trustBase trust base for burn transaction verification + * @param predicateVerifier + * @param burnTransaction burn transaction + * @return list of mint commitments for sending to unicity service + * @throws VerificationException if token verification fails + */ + public async createSplitMintCommitments( + trustBase: RootTrustBase, + predicateVerifier: PredicateVerifier, + burnTransaction: CertifiedTransferTransaction, + ): Promise { + const burnedToken = await this.token.transfer(trustBase, predicateVerifier, burnTransaction); + + return Promise.all( + this.splitTokens.map((request) => + MintTransaction.create(request.recipient, request.id, request.type, request.data.toCBOR()).then((data) => + MintCommitment.create(data), + ), + ), + ); + } +} +// TODO: What if one token id is taken while minting new tokens? diff --git a/src/payment/asset/Asset.ts b/src/payment/asset/Asset.ts new file mode 100644 index 00000000..59bca898 --- /dev/null +++ b/src/payment/asset/Asset.ts @@ -0,0 +1,29 @@ +import { AssetId } from './AssetId.js'; +import { BigintConverter } from '../../serialization/BigintConverter.js'; +import { CborDeserializer } from '../../serialization/cbor/CborDeserializer.js'; +import { CborSerializer } from '../../serialization/cbor/CborSerializer.js'; + +export class Asset { + public constructor( + public readonly id: AssetId, + private readonly _value: bigint, + ) { + } + + public get value(): bigint { + return this._value; + } + + public static fromCBOR(bytes: Uint8Array): Asset { + const data = CborDeserializer.decodeArray(bytes); + + return new Asset(AssetId.fromCBOR(data[0]), BigintConverter.decode(CborDeserializer.decodeByteString(data[1]))); + } + + public toCBOR(): Uint8Array { + return CborSerializer.encodeArray( + this.id.toCBOR(), + CborSerializer.encodeByteString(BigintConverter.encode(this._value)), + ); + } +} diff --git a/src/payment/asset/AssetId.ts b/src/payment/asset/AssetId.ts new file mode 100644 index 00000000..8820d74d --- /dev/null +++ b/src/payment/asset/AssetId.ts @@ -0,0 +1,42 @@ +import { CborDeserializer } from '../../serialization/cbor/CborDeserializer.js'; +import { CborSerializer } from '../../serialization/cbor/CborSerializer.js'; +import { HexConverter } from '../../serialization/HexConverter.js'; +import { BitString } from '../../util/BitString.js'; + +/** Identifier for a fungible coin type. */ +export class AssetId { + /** + * @param _bytes Raw byte representation + */ + public constructor(private readonly _bytes: Uint8Array) { + this._bytes = new Uint8Array(_bytes); + } + + public get bytes(): Uint8Array { + return new Uint8Array(this._bytes); + } + + /** + * Creates a CoinId from a byte array encoded in CBOR. + * @param bytes + */ + public static fromCBOR(bytes: Uint8Array): AssetId { + return new AssetId(CborDeserializer.decodeByteString(bytes)); + } + + /** + * Converts the CoinId to a bitstring representation. + */ + public toBitString(): BitString { + return new BitString(this._bytes); + } + + /** CBOR serialization. */ + public toCBOR(): Uint8Array { + return CborSerializer.encodeByteString(this._bytes); + } + + public toString(): string { + return HexConverter.encode(this._bytes); + } +} diff --git a/src/payment/asset/PaymentAssetCollection.ts b/src/payment/asset/PaymentAssetCollection.ts new file mode 100644 index 00000000..f738b41a --- /dev/null +++ b/src/payment/asset/PaymentAssetCollection.ts @@ -0,0 +1,50 @@ +import { Asset } from './Asset.js'; +import { AssetId } from './AssetId.js'; +import { CborDeserializer } from '../../serialization/cbor/CborDeserializer.js'; +import { CborSerializer } from '../../serialization/cbor/CborSerializer.js'; +import { HexConverter } from '../../serialization/HexConverter.js'; + +export class PaymentAssetCollection { + private constructor(private readonly _assets: Map) {} + + public static create(...data: Asset[]): PaymentAssetCollection { + const assets = new Map(); + for (const asset of data) { + const name = HexConverter.encode(asset.id.bytes); + if (assets.has(name)) { + throw new Error('Invalid payment asset collection. Duplicate assets found.'); + } + assets.set(name, asset); + } + + return new PaymentAssetCollection(assets); + } + + // TODO: decode and encode? + public static fromCBOR(bytes: Uint8Array): PaymentAssetCollection { + const data = CborDeserializer.decodeArray(bytes); + + const assets: Asset[] = []; + for (const asset of data) { + assets.push(Asset.fromCBOR(asset)); + } + + return PaymentAssetCollection.create(...assets); + } + + public get(id: AssetId): Asset | null { + return this._assets.get(HexConverter.encode(id.bytes)) ?? null; + } + + public size(): number { + return this._assets.size; + } + + public toArray(): Asset[] { + return Array.from(this._assets.values()); + } + + public toCBOR(): Uint8Array { + return CborSerializer.encodeArray(...this.toArray().map((asset) => asset.toCBOR())); + } +} diff --git a/src/payment/predicate/builtin/BuiltInPredicateVerifierFactory.ts b/src/payment/predicate/builtin/BuiltInPredicateVerifierFactory.ts new file mode 100644 index 00000000..8911f305 --- /dev/null +++ b/src/payment/predicate/builtin/BuiltInPredicateVerifierFactory.ts @@ -0,0 +1,20 @@ +import { BurnPredicate } from './BurnPredicate.js'; +import { BuiltInPredicateVerifierFactory } from '../../../predicate/builtin/BuiltInPredicateVerifierFactory.js'; +import { PayToPublicKeyPredicate } from '../../../predicate/builtin/PayToPublicKeyPredicate.js'; +import { PayToPublicKeyPredicateVerifier } from '../../../predicate/builtin/verification/PayToPublicKeyPredicateVerifier.js'; +import { IPredicateVerifier } from '../../../predicate/verification/IPredicateVerifier.js'; + +export class PaymentPredicateVerifierFactory extends BuiltInPredicateVerifierFactory { + public constructor(factories: Map) { + super(factories); + } + + public static create(): PaymentPredicateVerifierFactory { + return new BuiltInPredicateVerifierFactory( + new Map([ + [PayToPublicKeyPredicate.TYPE, new PayToPublicKeyPredicateVerifier()], + [BurnPredicate.TYPE, new PayToPublicKeyPredicateVerifier()], + ]), + ); + } +} diff --git a/src/payment/predicate/builtin/BurnPredicate.ts b/src/payment/predicate/builtin/BurnPredicate.ts new file mode 100644 index 00000000..00d59856 --- /dev/null +++ b/src/payment/predicate/builtin/BurnPredicate.ts @@ -0,0 +1,63 @@ +import { IPredicate } from '../../../predicate/IPredicate.js'; +import { PredicateEngine } from '../../../predicate/PredicateEngine.js'; +import { CborDeserializer } from '../../../serialization/cbor/CborDeserializer.js'; +import { CborSerializer } from '../../../serialization/cbor/CborSerializer.js'; +import { dedent } from '../../../util/StringUtils.js'; +import { HexConverter } from '../../../serialization/HexConverter.js'; + +export class BurnPredicate implements IPredicate { + public static readonly TYPE: bigint = 0x02n; + + private constructor(private readonly _reason: Uint8Array) { + this._reason = new Uint8Array(_reason); + } + + public get engine(): PredicateEngine { + return PredicateEngine.BUILT_IN; + } + + public get publicKey(): Uint8Array { + return new Uint8Array(this._reason); + } + + public get type(): bigint { + return BurnPredicate.TYPE; + } + + public static create(reason: Uint8Array): BurnPredicate { + return new BurnPredicate(reason); + } + + public static decode(bytes: Uint8Array): BurnPredicate { + const data = CborDeserializer.decodeArray(bytes); + const engine = CborDeserializer.decodeUnsignedInteger(data[0]); + if (engine !== BigInt(PredicateEngine.BUILT_IN)) { + throw new Error('Invalid predicate engine for BurnPredicate.'); + } + + const type = CborDeserializer.decodeUnsignedInteger(CborDeserializer.decodeByteString(data[1])); + if (type !== BurnPredicate.TYPE) { + throw new Error('Invalid predicate type for BurnPredicate.'); + } + + return new BurnPredicate(CborDeserializer.decodeByteString(data[2])); + } + + public static generateUnlockScript(): Promise { + return Promise.resolve(new Uint8Array(0)); + } + + public toCBOR(): Uint8Array { + return CborSerializer.encodeArray( + CborSerializer.encodeUnsignedInteger(BigInt(this.engine)), + CborSerializer.encodeByteString(CborSerializer.encodeUnsignedInteger(this.type)), + CborSerializer.encodeByteString(this._reason), + ); + } + + public toString(): string { + return dedent` + BurnPredicate + Reason: ${HexConverter.encode(this._reason)}`; + } +} diff --git a/src/payment/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts b/src/payment/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts new file mode 100644 index 00000000..9802f8d4 --- /dev/null +++ b/src/payment/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts @@ -0,0 +1,51 @@ +import { CertificationData } from '../../../api/CertificationData.js'; +import { DataHasher } from '../../../crypto/hash/DataHasher.js'; +import { HashAlgorithm } from '../../../crypto/hash/HashAlgorithm.js'; +import { Signature } from '../../../crypto/secp256k1/Signature.js'; +import { SigningService } from '../../../crypto/secp256k1/SigningService.js'; +import { CborSerializer } from '../../../serialization/cbor/CborSerializer.js'; +import { VerificationResult } from '../../../verification/VerificationResult.js'; +import { VerificationStatus } from '../../../verification/VerificationStatus.js'; +import { IPredicate } from '../../IPredicate.js'; +import { IPredicateVerifier } from '../../verification/IPredicateVerifier.js'; +import { PayToPublicKeyPredicate } from '../PayToPublicKeyPredicate.js'; + +export class PayToPublicKeyPredicateVerifier implements IPredicateVerifier { + public async verify( + encodedPredicate: IPredicate, + certificationData: CertificationData, + ): Promise> { + const predicate = PayToPublicKeyPredicate.decode(encodedPredicate.toCBOR()); + + if (certificationData === null) { + return new VerificationResult( + 'PayToPublicKeyPredicateVerifier', + VerificationStatus.FAIL, + 'Certification data is missing.', + ); + } + + const result = await SigningService.verifyWithPublicKey( + await new DataHasher(HashAlgorithm.SHA256) + .update( + CborSerializer.encodeArray( + certificationData.sourceStateHash.toCBOR(), + certificationData.transactionHash.toCBOR(), + ), + ) + .digest(), + Signature.decode(certificationData.unlockScript).bytes, + predicate.publicKey, + ); + + if (!result) { + return new VerificationResult( + 'PayToPublicKeyPredicateVerifier', + VerificationStatus.FAIL, + 'Signature verification failed.', + ); + } + + return new VerificationResult('PayToPublicKeyPredicateVerifier', VerificationStatus.OK); + } +} diff --git a/src/predicate/builtin/BuiltInPredicateVerifierFactory.ts b/src/predicate/builtin/BuiltInPredicateVerifierFactory.ts index 293d6361..0b3f674d 100644 --- a/src/predicate/builtin/BuiltInPredicateVerifierFactory.ts +++ b/src/predicate/builtin/BuiltInPredicateVerifierFactory.ts @@ -1,3 +1,4 @@ +import { PayToPublicKeyPredicate } from './PayToPublicKeyPredicate.js'; import { CertificationData } from '../../api/CertificationData.js'; import { CborDeserializer } from '../../serialization/cbor/CborDeserializer.js'; import { VerificationResult } from '../../verification/VerificationResult.js'; @@ -5,10 +6,17 @@ import { VerificationStatus } from '../../verification/VerificationStatus.js'; import { IPredicate } from '../IPredicate.js'; import { IPredicateVerifier } from '../verification/IPredicateVerifier.js'; import { IPredicateVerifierFactory } from '../verification/IPredicateVerifierFactory.js'; +import { PayToPublicKeyPredicateVerifier } from './verification/PayToPublicKeyPredicateVerifier.js'; export class BuiltInPredicateVerifierFactory implements IPredicateVerifierFactory { public constructor(private readonly factories: Map) {} + public static create(): BuiltInPredicateVerifierFactory { + return new BuiltInPredicateVerifierFactory( + new Map([[PayToPublicKeyPredicate.TYPE, new PayToPublicKeyPredicateVerifier()]]), + ); + } + public verify( predicate: IPredicate, certificationData: CertificationData, diff --git a/src/predicate/builtin/PayToPublicKeyPredicate.ts b/src/predicate/builtin/PayToPublicKeyPredicate.ts index f717a2bf..337a098f 100644 --- a/src/predicate/builtin/PayToPublicKeyPredicate.ts +++ b/src/predicate/builtin/PayToPublicKeyPredicate.ts @@ -54,7 +54,7 @@ export class PayToPublicKeyPredicate implements IPredicate { const hash = await new DataHasher(HashAlgorithm.SHA256) .update( CborSerializer.encodeArray( - await transaction.calculateStateHash().then((hash) => hash.toCBOR()), + transaction.sourceStateHash.toCBOR(), await transaction.calculateTransactionHash().then((hash) => hash.toCBOR()), ), ) diff --git a/src/transaction/CertifiedMintTransaction.ts b/src/transaction/CertifiedMintTransaction.ts index 885b27b3..20ce1570 100644 --- a/src/transaction/CertifiedMintTransaction.ts +++ b/src/transaction/CertifiedMintTransaction.ts @@ -28,6 +28,10 @@ export class CertifiedMintTransaction implements ITransaction { return this.transaction.recipient; } + public get sourceStateHash(): DataHash { + return this.transaction.sourceStateHash; + } + public get tokenId(): TokenId { return this.transaction.tokenId; } diff --git a/src/transaction/ITransaction.ts b/src/transaction/ITransaction.ts index d572f547..ff77b8c1 100644 --- a/src/transaction/ITransaction.ts +++ b/src/transaction/ITransaction.ts @@ -9,6 +9,8 @@ export interface ITransaction { readonly recipient: PayToScriptHash; + readonly sourceStateHash: DataHash; + readonly x: Uint8Array; calculateStateHash(): Promise; diff --git a/src/transaction/MintTransaction.ts b/src/transaction/MintTransaction.ts index 3369321b..c5b21da6 100644 --- a/src/transaction/MintTransaction.ts +++ b/src/transaction/MintTransaction.ts @@ -25,6 +25,7 @@ import { dedent } from '../util/StringUtils.js'; export class MintTransaction implements ITransaction { private constructor( + public readonly sourceStateHash: MintTransactionState, public readonly lockScript: IPredicate, public readonly recipient: PayToScriptHash, public readonly tokenId: TokenId, @@ -49,7 +50,14 @@ export class MintTransaction implements ITransaction { data = new Uint8Array(data); const signingService = await MintSigningService.create(tokenId); - return new MintTransaction(PayToPublicKeyPredicate.create(signingService), recipient, tokenId, tokenType, data); + return new MintTransaction( + await MintTransactionState.create(tokenId), + PayToPublicKeyPredicate.create(signingService), + recipient, + tokenId, + tokenType, + data, + ); } public static async fromCBOR(bytes: Uint8Array): Promise { @@ -65,7 +73,9 @@ export class MintTransaction implements ITransaction { } public calculateStateHash(): Promise { - return MintTransactionState.create(this.tokenId); + return new DataHasher(HashAlgorithm.SHA256) + .update(CborSerializer.encodeArray(this.sourceStateHash.toCBOR(), CborSerializer.encodeByteString(this.x))) + .digest(); } public calculateTransactionHash(): Promise { diff --git a/src/transaction/TransferTransaction.ts b/src/transaction/TransferTransaction.ts index 87784b00..74863ce8 100644 --- a/src/transaction/TransferTransaction.ts +++ b/src/transaction/TransferTransaction.ts @@ -48,6 +48,10 @@ export class TransferTransaction implements ITransaction { data: Uint8Array, ): Promise { const transaction = token.transactions.at(-1) ?? token.genesis; + if (!transaction.recipient.equals(await PayToScriptHash.create(owner))) { + throw new Error('Predicate does not match pay to script hash.'); + } + const sourceStateHash = await transaction.calculateStateHash(); return new TransferTransaction(sourceStateHash, owner, recipient, x, data); } diff --git a/src/util/InclusionProofUtils.ts b/src/util/InclusionProofUtils.ts index a247cb02..bd1c4747 100644 --- a/src/util/InclusionProofUtils.ts +++ b/src/util/InclusionProofUtils.ts @@ -49,6 +49,7 @@ export async function waitInclusionProof( inclusionProof, stateId, ); + switch (verificationStatus.status) { case InclusionProofVerificationStatus.OK: return inclusionProof; diff --git a/tests/functional/TestAggregatorClient.ts b/tests/functional/TestAggregatorClient.ts index f53d310d..1d9377c2 100644 --- a/tests/functional/TestAggregatorClient.ts +++ b/tests/functional/TestAggregatorClient.ts @@ -10,6 +10,7 @@ import { DataHasherFactory } from '../../src/crypto/hash/DataHasherFactory.js'; import { HashAlgorithm } from '../../src/crypto/hash/HashAlgorithm.js'; import { SigningService } from '../../src/crypto/secp256k1/SigningService.js'; import { BuiltInPredicateVerifierFactory } from '../../src/predicate/builtin/BuiltInPredicateVerifierFactory.js'; +import { PayToPublicKeyPredicate } from '../../src/predicate/builtin/PayToPublicKeyPredicate.js'; import { PayToPublicKeyPredicateVerifier } from '../../src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.js'; import { EncodedPredicate } from '../../src/predicate/EncodedPredicate.js'; import { PredicateEngine } from '../../src/predicate/PredicateEngine.js'; @@ -24,7 +25,9 @@ export class TestAggregatorClient implements IAggregatorClient { new Map([ [ PredicateEngine.BUILT_IN, - new BuiltInPredicateVerifierFactory(new Map([[1n, new PayToPublicKeyPredicateVerifier()]])), + new BuiltInPredicateVerifierFactory( + new Map([[PayToPublicKeyPredicate.TYPE, new PayToPublicKeyPredicateVerifier()]]), + ), ], ]), ); diff --git a/tests/functional/TransitionFlowTest.ts b/tests/functional/TransitionFlowTest.ts index 0b118b6b..d7a38691 100644 --- a/tests/functional/TransitionFlowTest.ts +++ b/tests/functional/TransitionFlowTest.ts @@ -4,7 +4,6 @@ import { CertificationStatus } from '../../src/api/CertificationResponse.js'; import { SigningService } from '../../src/crypto/secp256k1/SigningService.js'; import { BuiltInPredicateVerifierFactory } from '../../src/predicate/builtin/BuiltInPredicateVerifierFactory.js'; import { PayToPublicKeyPredicate } from '../../src/predicate/builtin/PayToPublicKeyPredicate.js'; -import { PayToPublicKeyPredicateVerifier } from '../../src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.js'; import { PredicateEngine } from '../../src/predicate/PredicateEngine.js'; import { PredicateVerifier } from '../../src/predicate/verification/PredicateVerifier.js'; import { CborSerializer } from '../../src/serialization/cbor/CborSerializer.js'; @@ -24,12 +23,7 @@ describe('Transition', () => { const trustBase = aggregatorClient.rootTrustBase; const client = new StateTransitionClient(aggregatorClient); const predicateVerifier = new PredicateVerifier( - new Map([ - [ - PredicateEngine.BUILT_IN, - new BuiltInPredicateVerifierFactory(new Map([[1n, new PayToPublicKeyPredicateVerifier()]])), - ], - ]), + new Map([[PredicateEngine.BUILT_IN, BuiltInPredicateVerifierFactory.create()]]), ); const signingService = new SigningService(SigningService.generatePrivateKey()); @@ -56,6 +50,7 @@ describe('Transition', () => { ), ); + // TODO: Sending to another person besides myself const transferTransaction = await TransferTransaction.create( token, predicate, @@ -72,6 +67,25 @@ describe('Transition', () => { response = await client.submitCertificationRequest(certificationData); expect(response.status).toEqual(CertificationStatus.SUCCESS); + // Test double spend attempt + const doubleSpendTransferTransaction = await TransferTransaction.create( + token, + predicate, + await PayToScriptHash.create(predicate), + crypto.getRandomValues(new Uint8Array(32)), + CborSerializer.encodeArray(), + ); + + await expect(async () => + client.submitCertificationRequest( + await CertificationData.fromTransferTransaction( + doubleSpendTransferTransaction, + await PayToPublicKeyPredicate.generateUnlockScript(doubleSpendTransferTransaction, signingService), + ), + ), + ).rejects.toThrow(); + + // Finish initial transfer token = await token.transfer( trustBase, predicateVerifier, @@ -87,6 +101,6 @@ describe('Transition', () => { VerificationStatus.OK, ); - console.log(importedToken.toString()); + // console.log(importedToken.toString()); }, 30000); }); From 72bd096cda4919c6723be4351d7d6f4535679d4c Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Wed, 28 Jan 2026 10:10:39 +0200 Subject: [PATCH 02/15] Write test for split, fix verification --- src/payment/IPaymentData.ts | 4 +- src/payment/IReason.ts | 3 - src/payment/ISplitPaymentData.ts | 4 +- src/payment/ISplitReason.ts | 14 - src/payment/PaymentData.ts | 171 ----------- src/payment/SplitReason.ts | 41 +++ src/payment/SplitReasonProof.ts | 41 +++ src/payment/TokenSplit.ts | 272 ++++++++++++++++++ .../error/TokenAssetCountMismatchError.ts | 5 + src/payment/error/TokenAssetMissingError.ts | 7 + .../error/TokenAssetValueMismatchError.ts | 7 + .../BuiltInPredicateVerifierFactory.ts | 20 -- .../predicate/builtin/BurnPredicate.ts | 2 +- .../PayToPublicKeyPredicateVerifier.ts | 51 ---- src/transaction/Token.ts | 10 + src/transaction/TokenId.ts | 15 - tests/functional/payment/SplitBuilderTest.ts | 158 ++++++++++ tests/functional/payment/TestPaymentData.ts | 15 + .../payment/TestSplitPaymentData.ts | 24 ++ 19 files changed, 584 insertions(+), 280 deletions(-) delete mode 100644 src/payment/IReason.ts delete mode 100644 src/payment/ISplitReason.ts delete mode 100644 src/payment/PaymentData.ts create mode 100644 src/payment/SplitReason.ts create mode 100644 src/payment/SplitReasonProof.ts create mode 100644 src/payment/TokenSplit.ts create mode 100644 src/payment/error/TokenAssetCountMismatchError.ts create mode 100644 src/payment/error/TokenAssetMissingError.ts create mode 100644 src/payment/error/TokenAssetValueMismatchError.ts delete mode 100644 src/payment/predicate/builtin/BuiltInPredicateVerifierFactory.ts delete mode 100644 src/payment/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts create mode 100644 tests/functional/payment/SplitBuilderTest.ts create mode 100644 tests/functional/payment/TestPaymentData.ts create mode 100644 tests/functional/payment/TestSplitPaymentData.ts diff --git a/src/payment/IPaymentData.ts b/src/payment/IPaymentData.ts index 5a7a91e1..c347e506 100644 --- a/src/payment/IPaymentData.ts +++ b/src/payment/IPaymentData.ts @@ -1,9 +1,7 @@ import { PaymentAssetCollection } from './asset/PaymentAssetCollection.js'; -import { IReason } from './IReason.js'; export interface IPaymentData { readonly assets: PaymentAssetCollection; - readonly reason: IReason; - toCBOR(): Uint8Array; + toCBOR(): Promise; } diff --git a/src/payment/IReason.ts b/src/payment/IReason.ts deleted file mode 100644 index fadad6fa..00000000 --- a/src/payment/IReason.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface IReason { - toCBOR(): Uint8Array; -} diff --git a/src/payment/ISplitPaymentData.ts b/src/payment/ISplitPaymentData.ts index 4dc03d78..62c9f985 100644 --- a/src/payment/ISplitPaymentData.ts +++ b/src/payment/ISplitPaymentData.ts @@ -1,6 +1,6 @@ import { IPaymentData } from './IPaymentData.js'; -import { ISplitReason } from './ISplitReason.js'; +import { SplitReason } from './SplitReason.js'; export interface ISplitPaymentData extends IPaymentData { - readonly reason: ISplitReason; + readonly reason: SplitReason; } diff --git a/src/payment/ISplitReason.ts b/src/payment/ISplitReason.ts deleted file mode 100644 index aeb2837a..00000000 --- a/src/payment/ISplitReason.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IReason } from './IReason.js'; -import { SparseMerkleTreePath } from '../smt/plain/SparseMerkleTreePath.js'; -import { AssetId } from './asset/AssetId.js'; -import { SparseMerkleSumTreePath } from '../smt/sum/SparseMerkleSumTreePath.js'; -import { Token } from '../transaction/Token.js'; - -export interface ISplitReason extends IReason { - readonly proofs: { - readonly aggregationPath: SparseMerkleTreePath; - readonly assetId: AssetId; - readonly coinTreePath: SparseMerkleSumTreePath; - }; - readonly token: Token; -} diff --git a/src/payment/PaymentData.ts b/src/payment/PaymentData.ts deleted file mode 100644 index 02137b94..00000000 --- a/src/payment/PaymentData.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { IPaymentData } from './IPaymentData.js'; -import { ISplitPaymentData } from './ISplitPaymentData.js'; -import { RootTrustBase } from '../api/bft/RootTrustBase.js'; -import { CertificationData } from '../api/CertificationData.js'; -import { DataHasher } from '../crypto/hash/DataHasher.js'; -import { DataHasherFactory } from '../crypto/hash/DataHasherFactory.js'; -import { HashAlgorithm } from '../crypto/hash/HashAlgorithm.js'; -import { IPredicate } from '../predicate/IPredicate.js'; -import { PredicateVerifier } from '../predicate/verification/PredicateVerifier.js'; -import { CborSerializer } from '../serialization/cbor/CborSerializer.js'; -import { HexConverter } from '../serialization/HexConverter.js'; -import { SparseMerkleTree } from '../smt/plain/SparseMerkleTree.js'; -import { SparseMerkleTreeRootNode } from '../smt/plain/SparseMerkleTreeRootNode.js'; -import { SparseMerkleSumTree } from '../smt/sum/SparseMerkleSumTree.js'; -import { SparseMerkleSumTreeRootNode } from '../smt/sum/SparseMerkleSumTreeRootNode.js'; -import { CertifiedTransferTransaction } from '../transaction/CertifiedTransferTransaction.js'; -import { MintTransaction } from '../transaction/MintTransaction.js'; -import { PayToScriptHash } from '../transaction/PayToScriptHash.js'; -import { Token } from '../transaction/Token.js'; -import { TokenId } from '../transaction/TokenId.js'; -import { TokenType } from '../transaction/TokenType.js'; -import { TransferTransaction } from '../transaction/TransferTransaction.js'; -import { AssetId } from './asset/AssetId.js'; -import { PaymentAssetCollection } from './asset/PaymentAssetCollection.js'; -import { BurnPredicate } from './predicate/builtin/BurnPredicate.js'; - -/** - * New token request for generating it out of burnt token. - */ -export class SplitToken { - public constructor( - public readonly id: TokenId, - public readonly assets: PaymentAssetCollection, - ) { - if (assets.size() == 0) { - throw new Error('Token must have at least one asset'); - } - } -} - -interface ISplit { - burnTransaction: TransferTransaction; - tokens: SplitToken[]; -} - -/** - * Token splitting builder. - */ -export class SplitBuilder { - private readonly hasher = new DataHasherFactory(HashAlgorithm.SHA256, DataHasher); - private readonly tokens = new Map(); - - /** - * Split old token to new tokens. - * - * @param token token to be used for split - * @param ownerPredicate - * @param parsePaymentData - * @param splitTokens - * @return token split object for submitting info - */ - public async split( - token: Token, - ownerPredicate: IPredicate, - parsePaymentData: (bytes: Uint8Array) => IPaymentData, - splitTokens: SplitToken[], - ): Promise { - const trees = new Map(); - - for (const splitToken of splitTokens) { - for (const asset of splitToken.assets.toArray()) { - const key = HexConverter.encode(asset.id.bytes); - let tree = trees.get(key)?.[1]; - if (!tree) { - tree = new SparseMerkleSumTree(this.hasher); - trees.set(key, [asset.id, tree]); - } - - await tree.addLeaf(splitToken.id.toBitString().toBigInt(), asset.id.bytes, asset.value); - } - } - - // Parse this from user object - const assets = parsePaymentData(token.genesis.data).assets; - - if (trees.size !== assets.size()) { - throw new Error('Token has different number of coins than expected'); - } - - const aggregationTree = new SparseMerkleTree(this.hasher); - const assetTreeRoots = new Map(); - for (const [assetId, tree] of trees.values()) { - const assetsInToken = assets.get(assetId); - const root = await tree.calculateRoot(); - if (root.value !== assetsInToken?.value) { - throw new Error( - `Token contained ${assetsInToken?.value} ${assetId.toString()} assets, but tree has ${root.value}`, - ); - } - - assetTreeRoots.set(HexConverter.encode(assetId.bytes), root); - await aggregationTree.addLeaf(assetId.toBitString().toBigInt(), root.hash.imprint); - } - - const aggregationRoot = await aggregationTree.calculateRoot(); - const burnPredicate = BurnPredicate.create(aggregationRoot.hash.imprint); - const burnTransaction = await TransferTransaction.create( - token, - ownerPredicate, - await PayToScriptHash.create(burnPredicate), - crypto.getRandomValues(new Uint8Array(32)), - CborSerializer.encodeArray(), - ); - - return { - burnTransaction, - tokens: Array.from(this.tokens.values()), - }; - } -} - -/** - * Token split request object. - */ -class Split { - public constructor( - private readonly token: Token, - private readonly aggregationRoot: SparseMerkleTreeRootNode, - private readonly assetRoots: Map, - private readonly splitTokens: SplitToken[], - ) {} - - public async burn(ownerPredicate: IPredicate, witness: Uint8Array): Promise { - const burnPredicate = BurnPredicate.create(this.aggregationRoot.hash.imprint); - const burnTransaction = await TransferTransaction.create( - this.token, - ownerPredicate, - await PayToScriptHash.create(burnPredicate), - crypto.getRandomValues(new Uint8Array(32)), - CborSerializer.encodeArray(), - ); - - return CertificationData.fromTransferTransaction(burnTransaction, witness); - } - - /** - * Create split mint commitments after burn transaction is received. - * - * @param trustBase trust base for burn transaction verification - * @param predicateVerifier - * @param burnTransaction burn transaction - * @return list of mint commitments for sending to unicity service - * @throws VerificationException if token verification fails - */ - public async createSplitMintCommitments( - trustBase: RootTrustBase, - predicateVerifier: PredicateVerifier, - burnTransaction: CertifiedTransferTransaction, - ): Promise { - const burnedToken = await this.token.transfer(trustBase, predicateVerifier, burnTransaction); - - return Promise.all( - this.splitTokens.map((request) => - MintTransaction.create(request.recipient, request.id, request.type, request.data.toCBOR()).then((data) => - MintCommitment.create(data), - ), - ), - ); - } -} -// TODO: What if one token id is taken while minting new tokens? diff --git a/src/payment/SplitReason.ts b/src/payment/SplitReason.ts new file mode 100644 index 00000000..af0e7929 --- /dev/null +++ b/src/payment/SplitReason.ts @@ -0,0 +1,41 @@ +import { SplitReasonProof } from './SplitReasonProof.js'; +import { CborDeserializer } from '../serialization/cbor/CborDeserializer.js'; +import { CborSerializer } from '../serialization/cbor/CborSerializer.js'; +import { Token } from '../transaction/Token.js'; + +export class SplitReason { + private constructor( + public readonly token: Token, + private readonly _proofs: SplitReasonProof[], + ) { + this._proofs = _proofs.slice(); + } + + public get proofs(): SplitReasonProof[] { + return this._proofs.slice(); + } + + public static create(token: Token, proofs: SplitReasonProof[]): SplitReason { + if (proofs.length === 0) { + throw new Error('proofs cannot be empty.'); + } + + return new SplitReason(token, proofs); + } + + public static async fromCBOR(bytes: Uint8Array): Promise { + const data = CborDeserializer.decodeArray(bytes); + + return new SplitReason( + await Token.fromCBOR(data[0]), + CborDeserializer.decodeArray(data[1]).map((proof) => SplitReasonProof.fromCBOR(proof)), + ); + } + + public toCBOR(): Uint8Array { + return CborSerializer.encodeArray( + this.token.toCBOR(), + CborSerializer.encodeArray(...this._proofs.map((proof) => proof.toCBOR())), + ); + } +} diff --git a/src/payment/SplitReasonProof.ts b/src/payment/SplitReasonProof.ts new file mode 100644 index 00000000..088bad46 --- /dev/null +++ b/src/payment/SplitReasonProof.ts @@ -0,0 +1,41 @@ +import { AssetId } from './asset/AssetId.js'; +import { CborDeserializer } from '../serialization/cbor/CborDeserializer.js'; +import { CborSerializer } from '../serialization/cbor/CborSerializer.js'; +import { SparseMerkleTreePath } from '../smt/plain/SparseMerkleTreePath.js'; +import { SparseMerkleSumTreePath } from '../smt/sum/SparseMerkleSumTreePath.js'; + +export class SplitReasonProof { + private constructor( + public readonly assetId: AssetId, + public readonly aggregationPath: SparseMerkleTreePath, + public readonly coinTreePath: SparseMerkleSumTreePath, + ) {} + + public static create( + assetId: AssetId, + aggregationPath: SparseMerkleTreePath, + coinTreePath: SparseMerkleSumTreePath, + ): SplitReasonProof { + return new SplitReasonProof(assetId, aggregationPath, coinTreePath); + } + + /** + * Create split mint reason from CBOR bytes. + * + * @param bytes CBOR bytes + * @return split mint reason proof + */ + public static fromCBOR(bytes: Uint8Array): SplitReasonProof { + const data = CborDeserializer.decodeArray(bytes); + + return new SplitReasonProof( + AssetId.fromCBOR(data[0]), + SparseMerkleTreePath.fromCBOR(data[1]), + SparseMerkleSumTreePath.fromCBOR(data[2]), + ); + } + + public toCBOR(): Uint8Array { + return CborSerializer.encodeArray(this.assetId.toCBOR(), this.aggregationPath.toCBOR(), this.coinTreePath.toCBOR()); + } +} diff --git a/src/payment/TokenSplit.ts b/src/payment/TokenSplit.ts new file mode 100644 index 00000000..a5939c97 --- /dev/null +++ b/src/payment/TokenSplit.ts @@ -0,0 +1,272 @@ +import { TokenAssetMissingError } from './error/TokenAssetMissingError.js'; +import { IPaymentData } from './IPaymentData.js'; +import { ISplitPaymentData } from './ISplitPaymentData.js'; +import { RootTrustBase } from '../api/bft/RootTrustBase.js'; +import { DataHasher } from '../crypto/hash/DataHasher.js'; +import { DataHasherFactory } from '../crypto/hash/DataHasherFactory.js'; +import { HashAlgorithm } from '../crypto/hash/HashAlgorithm.js'; +import { IPredicate } from '../predicate/IPredicate.js'; +import { PredicateVerifier } from '../predicate/verification/PredicateVerifier.js'; +import { CborSerializer } from '../serialization/cbor/CborSerializer.js'; +import { HexConverter } from '../serialization/HexConverter.js'; +import { TokenAssetValueMismatchError } from './error/TokenAssetValueMismatchError.js'; +import { SparseMerkleTree } from '../smt/plain/SparseMerkleTree.js'; +import { SparseMerkleSumTree } from '../smt/sum/SparseMerkleSumTree.js'; +import { SparseMerkleSumTreeRootNode } from '../smt/sum/SparseMerkleSumTreeRootNode.js'; +import { PayToScriptHash } from '../transaction/PayToScriptHash.js'; +import { Token } from '../transaction/Token.js'; +import { TokenId } from '../transaction/TokenId.js'; +import { TransferTransaction } from '../transaction/TransferTransaction.js'; +import { AssetId } from './asset/AssetId.js'; +import { PaymentAssetCollection } from './asset/PaymentAssetCollection.js'; +import { TokenAssetCountMismatchError } from './error/TokenAssetCountMismatchError.js'; +import { BurnPredicate } from './predicate/builtin/BurnPredicate.js'; +import { SplitReasonProof } from './SplitReasonProof.js'; +import { areUint8ArraysEqual } from '../util/TypedArrayUtils.js'; +import { VerificationResult } from '../verification/VerificationResult.js'; +import { VerificationStatus } from '../verification/VerificationStatus.js'; + +class ProofMapEntry { + private constructor( + public readonly tokenId: TokenId, + private readonly _proofs: SplitReasonProof[], + ) { + this._proofs = _proofs.slice(); + } + + public get proofs(): SplitReasonProof[] { + return this._proofs.slice(); + } + + public static create(tokenId: TokenId, proofs: SplitReasonProof[]): ProofMapEntry { + return new ProofMapEntry(tokenId, proofs); + } +} + +class ProofMap { + private constructor(private readonly _proofs: Map) {} + + public static create(data: [TokenId, SplitReasonProof[]][]): ProofMap { + return new ProofMap( + new Map( + data.map(([tokenId, proofs]) => [HexConverter.encode(tokenId.bytes), ProofMapEntry.create(tokenId, proofs)]), + ), + ); + } + + public get(id: TokenId): ProofMapEntry | null { + return this._proofs.get(HexConverter.encode(id.bytes)) ?? null; + } + + public size(): number { + return this._proofs.size; + } +} + +interface ISplit { + readonly burn: { + readonly ownerPredicate: BurnPredicate; + readonly transaction: TransferTransaction; + }; + + readonly proofs: ProofMap; +} + +/** + * Token splitting. + */ +export class TokenSplit { + /** + * Split old token to new tokens. + * + * @param token token to be used for split + * @param ownerPredicate + * @param parsePaymentData + * @param splitTokens + * @return token split object for submitting info + */ + public static async split( + token: Token, + ownerPredicate: IPredicate, + parsePaymentData: (bytes: Uint8Array) => Promise, + splitTokens: [TokenId, PaymentAssetCollection][], + ): Promise { + const hasher = new DataHasherFactory(HashAlgorithm.SHA256, DataHasher); + const trees = new Map(); + + for (const [tokenId, assets] of splitTokens) { + for (const asset of assets.toArray()) { + const key = HexConverter.encode(asset.id.bytes); + let tree = trees.get(key)?.[1]; + if (!tree) { + tree = new SparseMerkleSumTree(hasher); + trees.set(key, [asset.id, tree]); + } + + await tree.addLeaf(tokenId.toBitString().toBigInt(), asset.id.bytes, asset.value); + } + } + + // Parse this from user object + const paymentData = await parsePaymentData(token.genesis.data); + const assets = paymentData.assets; + + if (trees.size !== assets.size()) { + throw new TokenAssetCountMismatchError(); + } + + const aggregationTree = new SparseMerkleTree(hasher); + const assetTreeRoots = new Map(); + for (const [assetId, tree] of trees.values()) { + const tokenAsset = assets.get(assetId); + if (tokenAsset == null) { + throw new TokenAssetMissingError(assetId); + } + + const root = await tree.calculateRoot(); + if (root.value !== tokenAsset.value) { + throw new TokenAssetValueMismatchError(assetId, tokenAsset.value, root.value); + } + + assetTreeRoots.set(HexConverter.encode(assetId.bytes), root); + await aggregationTree.addLeaf(assetId.toBitString().toBigInt(), root.hash.imprint); + } + + const aggregationRoot = await aggregationTree.calculateRoot(); + const burnPredicate = BurnPredicate.create(aggregationRoot.hash.imprint); + const burnTransaction = await TransferTransaction.create( + token, + ownerPredicate, + await PayToScriptHash.create(burnPredicate), + crypto.getRandomValues(new Uint8Array(32)), + CborSerializer.encodeNull(), + ); + + const proofs: [TokenId, SplitReasonProof[]][] = []; + for (const [tokenId, assets] of splitTokens) { + proofs.push([ + tokenId, + assets + .toArray() + .map((asset) => + SplitReasonProof.create( + asset.id, + aggregationRoot.getPath(asset.id.toBitString().toBigInt()), + assetTreeRoots.get(HexConverter.encode(asset.id.bytes))!.getPath(tokenId.toBitString().toBigInt()), + ), + ), + ]); + } + + return { + burn: { + ownerPredicate: burnPredicate, + transaction: burnTransaction, + }, + proofs: ProofMap.create(proofs), + }; + } + + public static async verify( + token: Token, + parsePaymentData: (bytes: Uint8Array) => Promise, + trustBase: RootTrustBase, + predicateVerifier: PredicateVerifier, + ): Promise> { + const data = await parsePaymentData(token.genesis.data); + + if (data.assets == null) { + return new VerificationResult( + 'TokenSplitReasonVerificationRule', + VerificationStatus.FAIL, + 'Assets data is missing.', + [], + ); + } + + const verificationResult = await data.reason.token.verify(trustBase, predicateVerifier); + if (verificationResult.status !== VerificationStatus.OK) { + return new VerificationResult( + 'TokenSplitReasonVerificationRule', + VerificationStatus.FAIL, + 'Burn token verification failed.', + [verificationResult], + ); + } + + if (data.assets.size() !== data.reason.proofs.length) { + return new VerificationResult( + 'TokenSplitReasonVerificationRule', + VerificationStatus.FAIL, + 'Total amount of coins differ in token and proofs.', + [], + ); + } + + const burntTokenLastTransaction = data.reason.token.transactions.at(-1); + for (const proof of data.reason.proofs) { + const aggregationPathResult = await proof.aggregationPath.verify(proof.assetId.toBitString().toBigInt()); + if (!aggregationPathResult.isSuccessful) { + return new VerificationResult( + 'TokenSplitReasonVerificationRule', + VerificationStatus.FAIL, + `Aggregation path verification failed for coin: ${proof.assetId.toString()}`, + [], + ); + } + + const assetTreePathResult = await proof.coinTreePath.verify(token.id.toBitString().toBigInt()); + if (!assetTreePathResult.isSuccessful) { + return new VerificationResult( + 'TokenSplitReasonVerificationRule', + VerificationStatus.FAIL, + `Asset tree path verification failed for token: ${token.id.toString()}`, + [], + ); + } + + if (!areUint8ArraysEqual(proof.coinTreePath.root.imprint, proof.aggregationPath.steps.at(0)?.data)) { + return new VerificationResult( + 'TokenSplitReasonVerificationRule', + VerificationStatus.FAIL, + 'Asset tree root does not match aggregation path leaf.', + [], + ); + } + + const amount = data.assets.get(proof.assetId)?.value; + if (amount === null) { + return new VerificationResult( + 'TokenSplitReasonVerificationRule', + VerificationStatus.FAIL, + `Asset id ${proof.assetId.toString()} not found in asset data.`, + [], + ); + } + + if (proof.coinTreePath.steps.at(0)?.value !== amount) { + return new VerificationResult( + 'TokenSplitReasonVerificationRule', + VerificationStatus.FAIL, + `Asset amount for asset id ${proof.assetId.toString()} does not match asset tree leaf.`, + [], + ); + } + + if ( + !burntTokenLastTransaction?.recipient.equals( + await PayToScriptHash.create(BurnPredicate.create(proof.aggregationPath.root.imprint)), + ) + ) { + return new VerificationResult( + 'TokenSplitReasonVerificationRule', + VerificationStatus.FAIL, + `Aggregation path root does not match burn predicate.`, + [], + ); + } + } + + return new VerificationResult('TokenSplitReasonVerificationRule', VerificationStatus.OK); + } +} diff --git a/src/payment/error/TokenAssetCountMismatchError.ts b/src/payment/error/TokenAssetCountMismatchError.ts new file mode 100644 index 00000000..c6f9e37a --- /dev/null +++ b/src/payment/error/TokenAssetCountMismatchError.ts @@ -0,0 +1,5 @@ +export class TokenAssetCountMismatchError extends Error { + public constructor() { + super('Token and split tokens asset counts differ.'); + } +} \ No newline at end of file diff --git a/src/payment/error/TokenAssetMissingError.ts b/src/payment/error/TokenAssetMissingError.ts new file mode 100644 index 00000000..b632966d --- /dev/null +++ b/src/payment/error/TokenAssetMissingError.ts @@ -0,0 +1,7 @@ +import { AssetId } from '../asset/AssetId.js'; + +export class TokenAssetMissingError extends Error { + public constructor(assetId: AssetId) { + super(`Token did not contain asset ${assetId.toString()}.`); + } +} \ No newline at end of file diff --git a/src/payment/error/TokenAssetValueMismatchError.ts b/src/payment/error/TokenAssetValueMismatchError.ts new file mode 100644 index 00000000..6a0eecb1 --- /dev/null +++ b/src/payment/error/TokenAssetValueMismatchError.ts @@ -0,0 +1,7 @@ +import { AssetId } from '../asset/AssetId.js'; + +export class TokenAssetValueMismatchError extends Error { + public constructor(assetId: AssetId, value: bigint, splitValue: bigint) { + super(`Token contained ${value} ${assetId.toString()} assets, but tree has ${splitValue}`); + } +} \ No newline at end of file diff --git a/src/payment/predicate/builtin/BuiltInPredicateVerifierFactory.ts b/src/payment/predicate/builtin/BuiltInPredicateVerifierFactory.ts deleted file mode 100644 index 8911f305..00000000 --- a/src/payment/predicate/builtin/BuiltInPredicateVerifierFactory.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BurnPredicate } from './BurnPredicate.js'; -import { BuiltInPredicateVerifierFactory } from '../../../predicate/builtin/BuiltInPredicateVerifierFactory.js'; -import { PayToPublicKeyPredicate } from '../../../predicate/builtin/PayToPublicKeyPredicate.js'; -import { PayToPublicKeyPredicateVerifier } from '../../../predicate/builtin/verification/PayToPublicKeyPredicateVerifier.js'; -import { IPredicateVerifier } from '../../../predicate/verification/IPredicateVerifier.js'; - -export class PaymentPredicateVerifierFactory extends BuiltInPredicateVerifierFactory { - public constructor(factories: Map) { - super(factories); - } - - public static create(): PaymentPredicateVerifierFactory { - return new BuiltInPredicateVerifierFactory( - new Map([ - [PayToPublicKeyPredicate.TYPE, new PayToPublicKeyPredicateVerifier()], - [BurnPredicate.TYPE, new PayToPublicKeyPredicateVerifier()], - ]), - ); - } -} diff --git a/src/payment/predicate/builtin/BurnPredicate.ts b/src/payment/predicate/builtin/BurnPredicate.ts index 00d59856..b6016801 100644 --- a/src/payment/predicate/builtin/BurnPredicate.ts +++ b/src/payment/predicate/builtin/BurnPredicate.ts @@ -2,8 +2,8 @@ import { IPredicate } from '../../../predicate/IPredicate.js'; import { PredicateEngine } from '../../../predicate/PredicateEngine.js'; import { CborDeserializer } from '../../../serialization/cbor/CborDeserializer.js'; import { CborSerializer } from '../../../serialization/cbor/CborSerializer.js'; -import { dedent } from '../../../util/StringUtils.js'; import { HexConverter } from '../../../serialization/HexConverter.js'; +import { dedent } from '../../../util/StringUtils.js'; export class BurnPredicate implements IPredicate { public static readonly TYPE: bigint = 0x02n; diff --git a/src/payment/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts b/src/payment/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts deleted file mode 100644 index 9802f8d4..00000000 --- a/src/payment/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { CertificationData } from '../../../api/CertificationData.js'; -import { DataHasher } from '../../../crypto/hash/DataHasher.js'; -import { HashAlgorithm } from '../../../crypto/hash/HashAlgorithm.js'; -import { Signature } from '../../../crypto/secp256k1/Signature.js'; -import { SigningService } from '../../../crypto/secp256k1/SigningService.js'; -import { CborSerializer } from '../../../serialization/cbor/CborSerializer.js'; -import { VerificationResult } from '../../../verification/VerificationResult.js'; -import { VerificationStatus } from '../../../verification/VerificationStatus.js'; -import { IPredicate } from '../../IPredicate.js'; -import { IPredicateVerifier } from '../../verification/IPredicateVerifier.js'; -import { PayToPublicKeyPredicate } from '../PayToPublicKeyPredicate.js'; - -export class PayToPublicKeyPredicateVerifier implements IPredicateVerifier { - public async verify( - encodedPredicate: IPredicate, - certificationData: CertificationData, - ): Promise> { - const predicate = PayToPublicKeyPredicate.decode(encodedPredicate.toCBOR()); - - if (certificationData === null) { - return new VerificationResult( - 'PayToPublicKeyPredicateVerifier', - VerificationStatus.FAIL, - 'Certification data is missing.', - ); - } - - const result = await SigningService.verifyWithPublicKey( - await new DataHasher(HashAlgorithm.SHA256) - .update( - CborSerializer.encodeArray( - certificationData.sourceStateHash.toCBOR(), - certificationData.transactionHash.toCBOR(), - ), - ) - .digest(), - Signature.decode(certificationData.unlockScript).bytes, - predicate.publicKey, - ); - - if (!result) { - return new VerificationResult( - 'PayToPublicKeyPredicateVerifier', - VerificationStatus.FAIL, - 'Signature verification failed.', - ); - } - - return new VerificationResult('PayToPublicKeyPredicateVerifier', VerificationStatus.OK); - } -} diff --git a/src/transaction/Token.ts b/src/transaction/Token.ts index 5533267f..b241d2f4 100644 --- a/src/transaction/Token.ts +++ b/src/transaction/Token.ts @@ -1,5 +1,7 @@ import { CertifiedMintTransaction } from './CertifiedMintTransaction.js'; import { CertifiedTransferTransaction } from './CertifiedTransferTransaction.js'; +import { TokenId } from './TokenId.js'; +import { TokenType } from './TokenType.js'; import { RootTrustBase } from '../api/bft/RootTrustBase.js'; import { PredicateVerifier } from '../predicate/verification/PredicateVerifier.js'; import { CborDeserializer } from '../serialization/cbor/CborDeserializer.js'; @@ -17,10 +19,18 @@ export class Token { private readonly _transactions: CertifiedTransferTransaction[] = [], ) {} + public get id(): TokenId { + return this.genesis.tokenId; + } + public get transactions(): CertifiedTransferTransaction[] { return this._transactions.slice(); } + public get type(): TokenType { + return this.genesis.tokenType; + } + public static async fromCBOR(bytes: Uint8Array): Promise { const data = CborDeserializer.decodeArray(bytes); const transactions = CborDeserializer.decodeArray(data[1]); diff --git a/src/transaction/TokenId.ts b/src/transaction/TokenId.ts index 13dcd401..5be57f03 100644 --- a/src/transaction/TokenId.ts +++ b/src/transaction/TokenId.ts @@ -1,13 +1,9 @@ -import { DataHasher } from '../crypto/hash/DataHasher.js'; -import { HashAlgorithm } from '../crypto/hash/HashAlgorithm.js'; import { CborDeserializer } from '../serialization/cbor/CborDeserializer.js'; import { CborSerializer } from '../serialization/cbor/CborSerializer.js'; import { HexConverter } from '../serialization/HexConverter.js'; import { BitString } from '../util/BitString.js'; import { areUint8ArraysEqual } from '../util/TypedArrayUtils.js'; -const textEncoder = new TextEncoder(); - /** * Globally unique identifier of a token. */ @@ -31,17 +27,6 @@ export class TokenId { return new TokenId(HexConverter.decode(input)); } - /** - * Create token id from nametag. - * - * @param name nametag - * @return token id - */ - public static async fromNameTag(name: string): Promise { - const hash = await new DataHasher(HashAlgorithm.SHA256).update(textEncoder.encode(name)).digest(); - return new TokenId(hash.imprint); - } - public equals(o: unknown): boolean { if (this === o) { return true; diff --git a/tests/functional/payment/SplitBuilderTest.ts b/tests/functional/payment/SplitBuilderTest.ts new file mode 100644 index 00000000..ed77b6e6 --- /dev/null +++ b/tests/functional/payment/SplitBuilderTest.ts @@ -0,0 +1,158 @@ +import { TestPaymentData } from './TestPaymentData.js'; +import { CertificationData } from '../../../src/api/CertificationData.js'; +import { CertificationStatus } from '../../../src/api/CertificationResponse.js'; +import { SigningService } from '../../../src/crypto/secp256k1/SigningService.js'; +import { Asset } from '../../../src/payment/asset/Asset.js'; +import { AssetId } from '../../../src/payment/asset/AssetId.js'; +import { PaymentAssetCollection } from '../../../src/payment/asset/PaymentAssetCollection.js'; +import { TokenAssetCountMismatchError } from '../../../src/payment/error/TokenAssetCountMismatchError.js'; +import { TokenAssetMissingError } from '../../../src/payment/error/TokenAssetMissingError.js'; +import { TokenAssetValueMismatchError } from '../../../src/payment/error/TokenAssetValueMismatchError.js'; +import { SplitReason } from '../../../src/payment/SplitReason.js'; +import { TokenSplit } from '../../../src/payment/TokenSplit.js'; +import { BuiltInPredicateVerifierFactory } from '../../../src/predicate/builtin/BuiltInPredicateVerifierFactory.js'; +import { PayToPublicKeyPredicate } from '../../../src/predicate/builtin/PayToPublicKeyPredicate.js'; +import { PredicateEngine } from '../../../src/predicate/PredicateEngine.js'; +import { PredicateVerifier } from '../../../src/predicate/verification/PredicateVerifier.js'; +import { StateTransitionClient } from '../../../src/StateTransitionClient.js'; +import { MintTransaction } from '../../../src/transaction/MintTransaction.js'; +import { PayToScriptHash } from '../../../src/transaction/PayToScriptHash.js'; +import { Token } from '../../../src/transaction/Token.js'; +import { TokenId } from '../../../src/transaction/TokenId.js'; +import { TokenType } from '../../../src/transaction/TokenType.js'; +import { waitInclusionProof } from '../../../src/util/InclusionProofUtils.js'; +import { VerificationStatus } from '../../../src/verification/VerificationStatus.js'; +import { TestAggregatorClient } from '../TestAggregatorClient.js'; +import { TestSplitPaymentData } from './TestSplitPaymentData.js'; + +describe('SplitBuilder Functional Test', () => { + it('should mint a token and split it using SplitBuilder', async () => { + const aggregatorClient = TestAggregatorClient.create(); + const trustBase = aggregatorClient.rootTrustBase; + const client = new StateTransitionClient(aggregatorClient); + const predicateVerifier = new PredicateVerifier( + new Map([[PredicateEngine.BUILT_IN, BuiltInPredicateVerifierFactory.create()]]), + ); + + const signingService = new SigningService(SigningService.generatePrivateKey()); + const predicate = PayToPublicKeyPredicate.create(signingService); + + const assets = [ + new Asset(new AssetId(crypto.getRandomValues(new Uint8Array(10))), 500n), + new Asset(new AssetId(crypto.getRandomValues(new Uint8Array(10))), 500n), + ]; + + const paymentData = new TestPaymentData(PaymentAssetCollection.create(...assets)); + const mintTransaction = await MintTransaction.create( + await PayToScriptHash.create(predicate), + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + new TokenType(crypto.getRandomValues(new Uint8Array(32))), + await paymentData.toCBOR(), + ); + let certificationData = await CertificationData.fromMintTransaction(mintTransaction); + + let response = await client.submitCertificationRequest(certificationData); + expect(response.status).toEqual(CertificationStatus.SUCCESS); + + let token = await Token.mint( + trustBase, + predicateVerifier, + await mintTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), + ), + ); + + await expect( + TokenSplit.split(token, predicate, TestPaymentData.fromCBOR, [ + [new TokenId(crypto.getRandomValues(new Uint8Array(32))), PaymentAssetCollection.create(assets[0])], + ]), + ).rejects.toThrow(TokenAssetCountMismatchError); + + await expect( + TokenSplit.split(token, predicate, TestPaymentData.fromCBOR, [ + [ + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + PaymentAssetCollection.create( + assets[0], + new Asset(new AssetId(crypto.getRandomValues(new Uint8Array(10))), 400n), + ), + ], + ]), + ).rejects.toThrow(TokenAssetMissingError); + + await expect( + TokenSplit.split(token, predicate, TestPaymentData.fromCBOR, [ + [ + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + PaymentAssetCollection.create(assets[0], new Asset(assets[1].id, 1500n)), + ], + ]), + ).rejects.toThrow(TokenAssetValueMismatchError); + + const splitTokens: [TokenId, PaymentAssetCollection][] = [ + [new TokenId(crypto.getRandomValues(new Uint8Array(32))), PaymentAssetCollection.create(assets[0])], + [new TokenId(crypto.getRandomValues(new Uint8Array(32))), PaymentAssetCollection.create(assets[1])], + ]; + const result = await TokenSplit.split(token, predicate, TestPaymentData.fromCBOR, splitTokens); + + certificationData = await CertificationData.fromTransferTransaction( + result.burn.transaction, + await PayToPublicKeyPredicate.generateUnlockScript(result.burn.transaction, signingService), + ); + + response = await client.submitCertificationRequest(certificationData); + expect(response.status).toEqual(CertificationStatus.SUCCESS); + + token = await token.transfer( + trustBase, + predicateVerifier, + await result.burn.transaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, result.burn.transaction), + ), + ); + + for (const [tokenId, assets] of splitTokens) { + const entry = result.proofs.get(tokenId); + if (entry == null) { + throw new Error('Missing split reason proof for token.'); + } + + const paymentData = new TestSplitPaymentData(assets, SplitReason.create(token, entry.proofs)); + + const mintTransaction = await MintTransaction.create( + await PayToScriptHash.create(predicate), + tokenId, + new TokenType(crypto.getRandomValues(new Uint8Array(32))), + await paymentData.toCBOR(), + ); + + const certificationData = await CertificationData.fromMintTransaction(mintTransaction); + + const response = await client.submitCertificationRequest(certificationData); + expect(response.status).toEqual(CertificationStatus.SUCCESS); + + const splitToken = await Token.mint( + trustBase, + predicateVerifier, + await mintTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), + ), + ); + + await expect( + TokenSplit.verify( + await Token.fromCBOR(splitToken.toCBOR()), + TestSplitPaymentData.fromCBOR, + trustBase, + predicateVerifier, + ).then((result) => result.status), + ).resolves.toEqual(VerificationStatus.OK); + } + }); +}); diff --git a/tests/functional/payment/TestPaymentData.ts b/tests/functional/payment/TestPaymentData.ts new file mode 100644 index 00000000..ac768ce9 --- /dev/null +++ b/tests/functional/payment/TestPaymentData.ts @@ -0,0 +1,15 @@ +import { PaymentAssetCollection } from '../../../src/payment/asset/PaymentAssetCollection.js'; +import { IPaymentData } from '../../../src/payment/IPaymentData.js'; +import { CborSerializer } from '../../../src/serialization/cbor/CborSerializer.js'; + +export class TestPaymentData implements IPaymentData { + public constructor(public readonly assets: PaymentAssetCollection) {} + + public static fromCBOR(bytes: Uint8Array): Promise { + return Promise.resolve(new TestPaymentData(PaymentAssetCollection.fromCBOR(bytes))); + } + + public toCBOR(): Promise { + return Promise.resolve(CborSerializer.encodeArray(...this.assets.toArray().map((asset) => asset.toCBOR()))); + } +} diff --git a/tests/functional/payment/TestSplitPaymentData.ts b/tests/functional/payment/TestSplitPaymentData.ts new file mode 100644 index 00000000..78fb57ed --- /dev/null +++ b/tests/functional/payment/TestSplitPaymentData.ts @@ -0,0 +1,24 @@ +import { PaymentAssetCollection } from '../../../src/payment/asset/PaymentAssetCollection.js'; +import { ISplitPaymentData } from '../../../src/payment/ISplitPaymentData.js'; +import { SplitReason } from '../../../src/payment/SplitReason.js'; +import { CborDeserializer } from '../../../src/serialization/cbor/CborDeserializer.js'; +import { CborSerializer } from '../../../src/serialization/cbor/CborSerializer.js'; + +export class TestSplitPaymentData implements ISplitPaymentData { + public constructor( + public readonly assets: PaymentAssetCollection, + public readonly reason: SplitReason, + ) {} + + public static async fromCBOR(bytes: Uint8Array): Promise { + const data = CborDeserializer.decodeArray(bytes); + + return Promise.resolve( + new TestSplitPaymentData(PaymentAssetCollection.fromCBOR(data[0]), await SplitReason.fromCBOR(data[1])), + ); + } + + public toCBOR(): Promise { + return Promise.resolve(CborSerializer.encodeArray(this.assets.toCBOR(), this.reason.toCBOR())); + } +} From aa2704727abc00798e34450aae083e1059351aeb Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Wed, 28 Jan 2026 10:47:30 +0200 Subject: [PATCH 03/15] #91 Fix lint errors, burn predicate getter --- src/payment/asset/Asset.ts | 3 +-- src/payment/error/TokenAssetCountMismatchError.ts | 2 +- src/payment/error/TokenAssetMissingError.ts | 2 +- src/payment/error/TokenAssetValueMismatchError.ts | 2 +- src/payment/predicate/builtin/BurnPredicate.ts | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/payment/asset/Asset.ts b/src/payment/asset/Asset.ts index 59bca898..07911adb 100644 --- a/src/payment/asset/Asset.ts +++ b/src/payment/asset/Asset.ts @@ -7,8 +7,7 @@ export class Asset { public constructor( public readonly id: AssetId, private readonly _value: bigint, - ) { - } + ) {} public get value(): bigint { return this._value; diff --git a/src/payment/error/TokenAssetCountMismatchError.ts b/src/payment/error/TokenAssetCountMismatchError.ts index c6f9e37a..8bfd6b62 100644 --- a/src/payment/error/TokenAssetCountMismatchError.ts +++ b/src/payment/error/TokenAssetCountMismatchError.ts @@ -2,4 +2,4 @@ export class TokenAssetCountMismatchError extends Error { public constructor() { super('Token and split tokens asset counts differ.'); } -} \ No newline at end of file +} diff --git a/src/payment/error/TokenAssetMissingError.ts b/src/payment/error/TokenAssetMissingError.ts index b632966d..8f7cbae0 100644 --- a/src/payment/error/TokenAssetMissingError.ts +++ b/src/payment/error/TokenAssetMissingError.ts @@ -4,4 +4,4 @@ export class TokenAssetMissingError extends Error { public constructor(assetId: AssetId) { super(`Token did not contain asset ${assetId.toString()}.`); } -} \ No newline at end of file +} diff --git a/src/payment/error/TokenAssetValueMismatchError.ts b/src/payment/error/TokenAssetValueMismatchError.ts index 6a0eecb1..4979f0ce 100644 --- a/src/payment/error/TokenAssetValueMismatchError.ts +++ b/src/payment/error/TokenAssetValueMismatchError.ts @@ -4,4 +4,4 @@ export class TokenAssetValueMismatchError extends Error { public constructor(assetId: AssetId, value: bigint, splitValue: bigint) { super(`Token contained ${value} ${assetId.toString()} assets, but tree has ${splitValue}`); } -} \ No newline at end of file +} diff --git a/src/payment/predicate/builtin/BurnPredicate.ts b/src/payment/predicate/builtin/BurnPredicate.ts index b6016801..4c148e91 100644 --- a/src/payment/predicate/builtin/BurnPredicate.ts +++ b/src/payment/predicate/builtin/BurnPredicate.ts @@ -16,7 +16,7 @@ export class BurnPredicate implements IPredicate { return PredicateEngine.BUILT_IN; } - public get publicKey(): Uint8Array { + public get reason(): Uint8Array { return new Uint8Array(this._reason); } From 83cb9da21dd6b2490587d9f678e8bd3121301a4f Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Wed, 28 Jan 2026 10:49:35 +0200 Subject: [PATCH 04/15] Fix variable names to use asset instead of coins --- src/payment/SplitReasonProof.ts | 12 ++++++++---- src/payment/TokenSplit.ts | 10 +++++----- src/payment/asset/AssetId.ts | 6 +++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/payment/SplitReasonProof.ts b/src/payment/SplitReasonProof.ts index 088bad46..d03c28c0 100644 --- a/src/payment/SplitReasonProof.ts +++ b/src/payment/SplitReasonProof.ts @@ -8,15 +8,15 @@ export class SplitReasonProof { private constructor( public readonly assetId: AssetId, public readonly aggregationPath: SparseMerkleTreePath, - public readonly coinTreePath: SparseMerkleSumTreePath, + public readonly assetTreePath: SparseMerkleSumTreePath, ) {} public static create( assetId: AssetId, aggregationPath: SparseMerkleTreePath, - coinTreePath: SparseMerkleSumTreePath, + assetTreePath: SparseMerkleSumTreePath, ): SplitReasonProof { - return new SplitReasonProof(assetId, aggregationPath, coinTreePath); + return new SplitReasonProof(assetId, aggregationPath, assetTreePath); } /** @@ -36,6 +36,10 @@ export class SplitReasonProof { } public toCBOR(): Uint8Array { - return CborSerializer.encodeArray(this.assetId.toCBOR(), this.aggregationPath.toCBOR(), this.coinTreePath.toCBOR()); + return CborSerializer.encodeArray( + this.assetId.toCBOR(), + this.aggregationPath.toCBOR(), + this.assetTreePath.toCBOR(), + ); } } diff --git a/src/payment/TokenSplit.ts b/src/payment/TokenSplit.ts index a5939c97..261f19a2 100644 --- a/src/payment/TokenSplit.ts +++ b/src/payment/TokenSplit.ts @@ -198,7 +198,7 @@ export class TokenSplit { return new VerificationResult( 'TokenSplitReasonVerificationRule', VerificationStatus.FAIL, - 'Total amount of coins differ in token and proofs.', + 'Total amount of assets differ in token and proofs.', [], ); } @@ -210,12 +210,12 @@ export class TokenSplit { return new VerificationResult( 'TokenSplitReasonVerificationRule', VerificationStatus.FAIL, - `Aggregation path verification failed for coin: ${proof.assetId.toString()}`, + `Aggregation path verification failed for asset: ${proof.assetId.toString()}`, [], ); } - const assetTreePathResult = await proof.coinTreePath.verify(token.id.toBitString().toBigInt()); + const assetTreePathResult = await proof.assetTreePath.verify(token.id.toBitString().toBigInt()); if (!assetTreePathResult.isSuccessful) { return new VerificationResult( 'TokenSplitReasonVerificationRule', @@ -225,7 +225,7 @@ export class TokenSplit { ); } - if (!areUint8ArraysEqual(proof.coinTreePath.root.imprint, proof.aggregationPath.steps.at(0)?.data)) { + if (!areUint8ArraysEqual(proof.assetTreePath.root.imprint, proof.aggregationPath.steps.at(0)?.data)) { return new VerificationResult( 'TokenSplitReasonVerificationRule', VerificationStatus.FAIL, @@ -244,7 +244,7 @@ export class TokenSplit { ); } - if (proof.coinTreePath.steps.at(0)?.value !== amount) { + if (proof.assetTreePath.steps.at(0)?.value !== amount) { return new VerificationResult( 'TokenSplitReasonVerificationRule', VerificationStatus.FAIL, diff --git a/src/payment/asset/AssetId.ts b/src/payment/asset/AssetId.ts index 8820d74d..0cd85d95 100644 --- a/src/payment/asset/AssetId.ts +++ b/src/payment/asset/AssetId.ts @@ -3,7 +3,7 @@ import { CborSerializer } from '../../serialization/cbor/CborSerializer.js'; import { HexConverter } from '../../serialization/HexConverter.js'; import { BitString } from '../../util/BitString.js'; -/** Identifier for a fungible coin type. */ +/** Identifier for a asset. */ export class AssetId { /** * @param _bytes Raw byte representation @@ -17,7 +17,7 @@ export class AssetId { } /** - * Creates a CoinId from a byte array encoded in CBOR. + * Creates a AssetId from a byte array encoded in CBOR. * @param bytes */ public static fromCBOR(bytes: Uint8Array): AssetId { @@ -25,7 +25,7 @@ export class AssetId { } /** - * Converts the CoinId to a bitstring representation. + * Converts the AssetId to a bitstring representation. */ public toBitString(): BitString { return new BitString(this._bytes); From ffb42c27d71ed07271cced343a9e23ac896bf3b9 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Fri, 30 Jan 2026 08:21:21 +0200 Subject: [PATCH 05/15] #91 Added secondary user to test flow. Fix token verification --- src/payment/asset/PaymentAssetCollection.ts | 6 +-- src/transaction/Token.ts | 2 +- tests/functional/TransitionFlowTest.ts | 47 ++++++++++++++++--- tests/unit/transaction/PayToScriptHashTest.ts | 13 +++++ 4 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 tests/unit/transaction/PayToScriptHashTest.ts diff --git a/src/payment/asset/PaymentAssetCollection.ts b/src/payment/asset/PaymentAssetCollection.ts index f738b41a..96629fec 100644 --- a/src/payment/asset/PaymentAssetCollection.ts +++ b/src/payment/asset/PaymentAssetCollection.ts @@ -10,11 +10,11 @@ export class PaymentAssetCollection { public static create(...data: Asset[]): PaymentAssetCollection { const assets = new Map(); for (const asset of data) { - const name = HexConverter.encode(asset.id.bytes); - if (assets.has(name)) { + const key = HexConverter.encode(asset.id.bytes); + if (assets.has(key)) { throw new Error('Invalid payment asset collection. Duplicate assets found.'); } - assets.set(name, asset); + assets.set(key, asset); } return new PaymentAssetCollection(assets); diff --git a/src/transaction/Token.ts b/src/transaction/Token.ts index b241d2f4..fe648a1c 100644 --- a/src/transaction/Token.ts +++ b/src/transaction/Token.ts @@ -106,7 +106,7 @@ export class Token { const transferResults: VerificationResult[] = []; for (let i = 0; i < this._transactions.length; i++) { const transaction = this._transactions[i]; - const token = new Token(this.genesis, this._transactions.slice(0, i - 1)); + const token = new Token(this.genesis, this._transactions.slice(0, i)); const result = await CertifiedTransferTransactionVerificationRule.verify( trustBase, predicateVerifier, diff --git a/tests/functional/TransitionFlowTest.ts b/tests/functional/TransitionFlowTest.ts index d7a38691..6e6fb4ea 100644 --- a/tests/functional/TransitionFlowTest.ts +++ b/tests/functional/TransitionFlowTest.ts @@ -50,11 +50,15 @@ describe('Transition', () => { ), ); - // TODO: Sending to another person besides myself + const receiverSigningService = new SigningService(SigningService.generatePrivateKey()); + // Second user will generate his predicate + const receiverPredicate = PayToPublicKeyPredicate.create(receiverSigningService); + // Create pay to script hash for sender + const receiverScriptHash = await PayToScriptHash.create(receiverPredicate); const transferTransaction = await TransferTransaction.create( token, predicate, - await PayToScriptHash.create(predicate), + await PayToScriptHash.fromString(receiverScriptHash.toString()), crypto.getRandomValues(new Uint8Array(32)), CborSerializer.encodeArray(), ); @@ -85,7 +89,6 @@ describe('Transition', () => { ), ).rejects.toThrow(); - // Finish initial transfer token = await token.transfer( trustBase, predicateVerifier, @@ -96,11 +99,41 @@ describe('Transition', () => { ), ); - const importedToken = await Token.fromCBOR(token.toCBOR()); - await expect(importedToken.verify(trustBase, predicateVerifier).then((result) => result.status)).resolves.toEqual( - VerificationStatus.OK, + await expect( + Token.fromCBOR(token.toCBOR()).then((importedToken) => + importedToken.verify(trustBase, predicateVerifier).then((result) => result.status), + ), + ).resolves.toEqual(VerificationStatus.OK); + + // Return token to initial minter + const returnTransferTransaction = await TransferTransaction.create( + token, + receiverPredicate, + await PayToScriptHash.create(predicate), + crypto.getRandomValues(new Uint8Array(32)), + CborSerializer.encodeArray(), + ); + + certificationData = await CertificationData.fromTransferTransaction( + returnTransferTransaction, + await PayToPublicKeyPredicate.generateUnlockScript(returnTransferTransaction, receiverSigningService), + ); + + response = await client.submitCertificationRequest(certificationData); + expect(response.status).toEqual(CertificationStatus.SUCCESS); + + token = await token.transfer( + trustBase, + predicateVerifier, + await returnTransferTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, returnTransferTransaction), + ), ); - // console.log(importedToken.toString()); + await expect(token.verify(trustBase, predicateVerifier).then((result) => result.status)).resolves.toEqual( + VerificationStatus.OK, + ); }, 30000); }); diff --git a/tests/unit/transaction/PayToScriptHashTest.ts b/tests/unit/transaction/PayToScriptHashTest.ts new file mode 100644 index 00000000..f253fba6 --- /dev/null +++ b/tests/unit/transaction/PayToScriptHashTest.ts @@ -0,0 +1,13 @@ +import { SigningService } from '../../../src/crypto/secp256k1/SigningService.js'; +import { PayToPublicKeyPredicate } from '../../../src/predicate/builtin/PayToPublicKeyPredicate.js'; +import { PayToScriptHash } from '../../../src/transaction/PayToScriptHash.js'; + +describe('PayToScriptHash', () => { + it('should correctly result initial hash with fromString', async () => { + const predicate = PayToPublicKeyPredicate.create(new SigningService(SigningService.generatePrivateKey())); + const scriptHash = await PayToScriptHash.create(predicate); + await expect(PayToScriptHash.fromString(scriptHash.toString()).then((hash) => hash.toString())).resolves.toEqual( + scriptHash.toString(), + ); + }); +}); From 81c30613c391c46d077f4c84094d5df0b16db6e8 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Fri, 30 Jan 2026 15:38:52 +0200 Subject: [PATCH 06/15] Add E2E tests. Add more checks to inclusion proof verification --- src/transaction/MintTransaction.ts | 8 +- src/transaction/TransferTransaction.ts | 8 +- ...ertifiedMintTransactionVerificationRule.ts | 8 +- ...fiedTransferTransactionVerificationRule.ts | 3 +- .../rule/InclusionProofVerificationRule.ts | 34 ++-- src/util/InclusionProofUtils.ts | 2 +- tests/e2e/E2ETransitionFlowTest.ts | 13 ++ tests/e2e/trust-base.json | 20 +++ .../FunctionalTransitionFlowTest.ts | 11 ++ tests/functional/TestAggregatorClient.ts | 8 +- tests/functional/TransitionFlowTest.ts | 139 ---------------- tests/unit/api/InclusionProofTest.ts | 66 ++++---- tests/utils/TransitionFlow.ts | 152 ++++++++++++++++++ 13 files changed, 267 insertions(+), 205 deletions(-) create mode 100644 tests/e2e/E2ETransitionFlowTest.ts create mode 100644 tests/e2e/trust-base.json create mode 100644 tests/functional/FunctionalTransitionFlowTest.ts delete mode 100644 tests/functional/TransitionFlowTest.ts create mode 100644 tests/utils/TransitionFlow.ts diff --git a/src/transaction/MintTransaction.ts b/src/transaction/MintTransaction.ts index c5b21da6..c7f2a386 100644 --- a/src/transaction/MintTransaction.ts +++ b/src/transaction/MintTransaction.ts @@ -16,7 +16,6 @@ import { InclusionProofVerificationStatus, } from './verification/rule/InclusionProofVerificationRule.js'; import { RootTrustBase } from '../api/bft/RootTrustBase.js'; -import { StateId } from '../api/StateId.js'; import { DataHash } from '../crypto/hash/DataHash.js'; import { MintSigningService } from '../crypto/MintSigningService.js'; import { IPredicate } from '../predicate/IPredicate.js'; @@ -103,12 +102,7 @@ export class MintTransaction implements ITransaction { predicateVerifier: PredicateVerifier, inclusionProof: InclusionProof, ): Promise { - const result = await InclusionProofVerificationRule.verify( - trustBase, - predicateVerifier, - inclusionProof, - await StateId.fromTransaction(this), - ); + const result = await InclusionProofVerificationRule.verify(trustBase, predicateVerifier, inclusionProof, this); if (result.status !== InclusionProofVerificationStatus.OK) { throw new Error(`Inclusion proof verification failed: ${result.status.toString()}`); } diff --git a/src/transaction/TransferTransaction.ts b/src/transaction/TransferTransaction.ts index 74863ce8..89e087f5 100644 --- a/src/transaction/TransferTransaction.ts +++ b/src/transaction/TransferTransaction.ts @@ -4,7 +4,6 @@ import { PayToScriptHash } from './PayToScriptHash.js'; import { Token } from './Token.js'; import { RootTrustBase } from '../api/bft/RootTrustBase.js'; import { InclusionProof } from '../api/InclusionProof.js'; -import { StateId } from '../api/StateId.js'; import { InclusionProofVerificationRule, InclusionProofVerificationStatus, @@ -101,12 +100,7 @@ export class TransferTransaction implements ITransaction { predicateVerifier: PredicateVerifier, inclusionProof: InclusionProof, ): Promise { - const result = await InclusionProofVerificationRule.verify( - trustBase, - predicateVerifier, - inclusionProof, - await StateId.fromTransaction(this), - ); + const result = await InclusionProofVerificationRule.verify(trustBase, predicateVerifier, inclusionProof, this); if (result.status !== InclusionProofVerificationStatus.OK) { throw new Error(`Inclusion proof verification failed: ${result.status.toString()}`); } diff --git a/src/transaction/verification/rule/CertifiedMintTransactionVerificationRule.ts b/src/transaction/verification/rule/CertifiedMintTransactionVerificationRule.ts index 582a22bb..ca335391 100644 --- a/src/transaction/verification/rule/CertifiedMintTransactionVerificationRule.ts +++ b/src/transaction/verification/rule/CertifiedMintTransactionVerificationRule.ts @@ -1,6 +1,5 @@ import { InclusionProofVerificationRule, InclusionProofVerificationStatus } from './InclusionProofVerificationRule.js'; import { RootTrustBase } from '../../../api/bft/RootTrustBase.js'; -import { StateId } from '../../../api/StateId.js'; import { MintSigningService } from '../../../crypto/MintSigningService.js'; import { PayToPublicKeyPredicate } from '../../../predicate/builtin/PayToPublicKeyPredicate.js'; import { PredicateVerifier } from '../../../predicate/verification/PredicateVerifier.js'; @@ -37,12 +36,7 @@ export class CertifiedMintTransactionVerificationRule { ); } - result = await InclusionProofVerificationRule.verify( - trustBase, - predicateVerifier, - genesis.inclusionProof, - await StateId.fromTransaction(genesis), - ); + result = await InclusionProofVerificationRule.verify(trustBase, predicateVerifier, genesis.inclusionProof, genesis); results.push(result); if (result.status !== InclusionProofVerificationStatus.OK) { return new VerificationResult( diff --git a/src/transaction/verification/rule/CertifiedTransferTransactionVerificationRule.ts b/src/transaction/verification/rule/CertifiedTransferTransactionVerificationRule.ts index 044060e2..716707ef 100644 --- a/src/transaction/verification/rule/CertifiedTransferTransactionVerificationRule.ts +++ b/src/transaction/verification/rule/CertifiedTransferTransactionVerificationRule.ts @@ -1,6 +1,5 @@ import { InclusionProofVerificationRule, InclusionProofVerificationStatus } from './InclusionProofVerificationRule.js'; import { RootTrustBase } from '../../../api/bft/RootTrustBase.js'; -import { StateId } from '../../../api/StateId.js'; import { PredicateVerifier } from '../../../predicate/verification/PredicateVerifier.js'; import { VerificationResult } from '../../../verification/VerificationResult.js'; import { VerificationStatus } from '../../../verification/VerificationStatus.js'; @@ -24,7 +23,7 @@ export class CertifiedTransferTransactionVerificationRule { trustBase, predicateVerifier, transaction.inclusionProof, - await StateId.fromTransaction(transaction), + transaction, ); results.push(result); diff --git a/src/transaction/verification/rule/InclusionProofVerificationRule.ts b/src/transaction/verification/rule/InclusionProofVerificationRule.ts index 9dbc6ad6..d1a89b2c 100644 --- a/src/transaction/verification/rule/InclusionProofVerificationRule.ts +++ b/src/transaction/verification/rule/InclusionProofVerificationRule.ts @@ -6,12 +6,16 @@ import { DataHash } from '../../../crypto/hash/DataHash.js'; import { PredicateVerifier } from '../../../predicate/verification/PredicateVerifier.js'; import { VerificationResult } from '../../../verification/VerificationResult.js'; import { VerificationStatus } from '../../../verification/VerificationStatus.js'; +import { ITransaction } from '../../ITransaction.js'; /** * Status codes for verifying an InclusionProof. */ export enum InclusionProofVerificationStatus { INVALID_TRUSTBASE = 'INVALID_TRUSTBASE', + LEAF_VALUE_MISMATCH = 'LEAF_VALUE_MISMATCH', + MISSING_CERTIFICATION_DATA = 'MISSING_CERTIFICATION_DATA', + TRANSACTION_HASH_MISMATCH = 'TRANSACTION_HASH_MISMATCH', NOT_AUTHENTICATED = 'NOT_AUTHENTICATED', PATH_NOT_INCLUDED = 'PATH_NOT_INCLUDED', PATH_INVALID = 'PATH_INVALID', @@ -26,7 +30,7 @@ export class InclusionProofVerificationRule { trustBase: RootTrustBase, predicateVerifierFactory: PredicateVerifier, inclusionProof: InclusionProof, - stateId: StateId, + transaction: ITransaction, ): Promise> { const unicityCertificateVerificationResult = await UnicityCertificateVerification.verify(trustBase, inclusionProof); @@ -39,22 +43,37 @@ export class InclusionProofVerificationRule { ); } + const stateId = await StateId.fromTransaction(transaction); const result = await inclusionProof.merkleTreePath.verify(stateId.toBitString().toBigInt()); if (!result.isPathValid) { return new VerificationResult('InclusionProofVerificationRule', InclusionProofVerificationStatus.PATH_INVALID); } + if (!result.isPathIncluded) { + return new VerificationResult( + 'InclusionProofVerificationRule', + InclusionProofVerificationStatus.PATH_NOT_INCLUDED, + ); + } + const certificationData = inclusionProof.certificationData; if (!certificationData) { return new VerificationResult( 'InclusionProofVerificationRule', - InclusionProofVerificationStatus.PATH_NOT_INCLUDED, + InclusionProofVerificationStatus.MISSING_CERTIFICATION_DATA, + ); + } + + if (!certificationData.transactionHash.equals(await transaction.calculateTransactionHash())) { + return new VerificationResult( + 'InclusionProofVerificationRule', + InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH, ); } const predicateVerificationResult = await predicateVerifierFactory.verify( certificationData.lockScript, - inclusionProof.certificationData, + certificationData, ); if (predicateVerificationResult.status !== VerificationStatus.OK) { return new VerificationResult( @@ -70,14 +89,7 @@ export class InclusionProofVerificationRule { if (!pathValue || !leafValue.equals(DataHash.fromImprint(pathValue))) { return new VerificationResult( 'InclusionProofVerificationRule', - InclusionProofVerificationStatus.PATH_NOT_INCLUDED, - ); - } - - if (!result.isPathIncluded) { - return new VerificationResult( - 'InclusionProofVerificationRule', - InclusionProofVerificationStatus.PATH_NOT_INCLUDED, + InclusionProofVerificationStatus.LEAF_VALUE_MISMATCH, ); } diff --git a/src/util/InclusionProofUtils.ts b/src/util/InclusionProofUtils.ts index bd1c4747..a19c671f 100644 --- a/src/util/InclusionProofUtils.ts +++ b/src/util/InclusionProofUtils.ts @@ -47,7 +47,7 @@ export async function waitInclusionProof( trustBase, predicateVerifier, inclusionProof, - stateId, + transaction, ); switch (verificationStatus.status) { diff --git a/tests/e2e/E2ETransitionFlowTest.ts b/tests/e2e/E2ETransitionFlowTest.ts new file mode 100644 index 00000000..7a9af804 --- /dev/null +++ b/tests/e2e/E2ETransitionFlowTest.ts @@ -0,0 +1,13 @@ +import trustBaseJson from './trust-base.json' with { type: 'json' }; +import { AggregatorClient } from '../../src/api/AggregatorClient.js'; +import { RootTrustBase } from '../../src/api/bft/RootTrustBase.js'; +import { StateTransitionClient } from '../../src/StateTransitionClient.js'; +import { transitionFlowTest } from '../utils/TransitionFlow.js'; + +describe('E2E TransitionFlow', () => { + const aggregatorClient = new AggregatorClient('http://localhost:3000'); + const client = new StateTransitionClient(aggregatorClient); + const trustBase = RootTrustBase.fromJSON(trustBaseJson); + + transitionFlowTest(client, trustBase); +}); diff --git a/tests/e2e/trust-base.json b/tests/e2e/trust-base.json new file mode 100644 index 00000000..7ffc9271 --- /dev/null +++ b/tests/e2e/trust-base.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "networkId": 3, + "epoch": 1, + "epochStartRound": 1, + "rootNodes": [ + { + "nodeId": "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU", + "sigKey": "0x039afb2acb65f5fbc272d8907f763d0a5d189aadc9b97afdcc5897ea4dd112e68b", + "stake": 1 + } + ], + "quorumThreshold": 1, + "stateHash": "", + "changeRecordHash": "", + "previousEntryHash": "", + "signatures": { + "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU": "0xf157c9fdd8a378e3ca70d354ccc4475ab2cd8de360127bc46b0aeab4b453a80f07fd9136a5843b60a8babaff23e20acc8879861f7651440a5e2829f7541b31f100" + } +} \ No newline at end of file diff --git a/tests/functional/FunctionalTransitionFlowTest.ts b/tests/functional/FunctionalTransitionFlowTest.ts new file mode 100644 index 00000000..a148f1de --- /dev/null +++ b/tests/functional/FunctionalTransitionFlowTest.ts @@ -0,0 +1,11 @@ +import { TestAggregatorClient } from './TestAggregatorClient.js'; +import { StateTransitionClient } from '../../src/StateTransitionClient.js'; +import { transitionFlowTest } from '../utils/TransitionFlow.js'; + +describe('Functional TransitionFlow', () => { + const aggregatorClient = TestAggregatorClient.create(); + const client = new StateTransitionClient(aggregatorClient); + const trustBase = aggregatorClient.rootTrustBase; + + transitionFlowTest(client, trustBase); +}); diff --git a/tests/functional/TestAggregatorClient.ts b/tests/functional/TestAggregatorClient.ts index 1d9377c2..474e1291 100644 --- a/tests/functional/TestAggregatorClient.ts +++ b/tests/functional/TestAggregatorClient.ts @@ -70,9 +70,11 @@ export class TestAggregatorClient implements IAggregatorClient { } const path = stateId.toBitString().toBigInt(); - const leafValue = await certificationData.calculateLeafValue(); - await this.smt.addLeaf(path, leafValue.imprint); - this.requests.set(path, certificationData); + if (!this.requests.has(path)) { + const leafValue = await certificationData.calculateLeafValue(); + await this.smt.addLeaf(path, leafValue.imprint); + this.requests.set(path, certificationData); + } return CertificationResponse.create(CertificationStatus.SUCCESS); } diff --git a/tests/functional/TransitionFlowTest.ts b/tests/functional/TransitionFlowTest.ts deleted file mode 100644 index 6e6fb4ea..00000000 --- a/tests/functional/TransitionFlowTest.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { TestAggregatorClient } from './TestAggregatorClient.js'; -import { CertificationData } from '../../src/api/CertificationData.js'; -import { CertificationStatus } from '../../src/api/CertificationResponse.js'; -import { SigningService } from '../../src/crypto/secp256k1/SigningService.js'; -import { BuiltInPredicateVerifierFactory } from '../../src/predicate/builtin/BuiltInPredicateVerifierFactory.js'; -import { PayToPublicKeyPredicate } from '../../src/predicate/builtin/PayToPublicKeyPredicate.js'; -import { PredicateEngine } from '../../src/predicate/PredicateEngine.js'; -import { PredicateVerifier } from '../../src/predicate/verification/PredicateVerifier.js'; -import { CborSerializer } from '../../src/serialization/cbor/CborSerializer.js'; -import { StateTransitionClient } from '../../src/StateTransitionClient.js'; -import { MintTransaction } from '../../src/transaction/MintTransaction.js'; -import { PayToScriptHash } from '../../src/transaction/PayToScriptHash.js'; -import { Token } from '../../src/transaction/Token.js'; -import { TokenId } from '../../src/transaction/TokenId.js'; -import { TokenType } from '../../src/transaction/TokenType.js'; -import { TransferTransaction } from '../../src/transaction/TransferTransaction.js'; -import { waitInclusionProof } from '../../src/util/InclusionProofUtils.js'; -import { VerificationStatus } from '../../src/verification/VerificationStatus.js'; - -describe('Transition', () => { - it('default successful flow', async () => { - const aggregatorClient = TestAggregatorClient.create(); - const trustBase = aggregatorClient.rootTrustBase; - const client = new StateTransitionClient(aggregatorClient); - const predicateVerifier = new PredicateVerifier( - new Map([[PredicateEngine.BUILT_IN, BuiltInPredicateVerifierFactory.create()]]), - ); - - const signingService = new SigningService(SigningService.generatePrivateKey()); - const predicate = PayToPublicKeyPredicate.create(signingService); - - const mintTransaction = await MintTransaction.create( - await PayToScriptHash.create(predicate), - new TokenId(crypto.getRandomValues(new Uint8Array(32))), - new TokenType(crypto.getRandomValues(new Uint8Array(32))), - CborSerializer.encodeArray(), - ); - let certificationData = await CertificationData.fromMintTransaction(mintTransaction); - - let response = await client.submitCertificationRequest(certificationData); - expect(response.status).toEqual(CertificationStatus.SUCCESS); - - let token = await Token.mint( - trustBase, - predicateVerifier, - await mintTransaction.toCertifiedTransaction( - trustBase, - predicateVerifier, - await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), - ), - ); - - const receiverSigningService = new SigningService(SigningService.generatePrivateKey()); - // Second user will generate his predicate - const receiverPredicate = PayToPublicKeyPredicate.create(receiverSigningService); - // Create pay to script hash for sender - const receiverScriptHash = await PayToScriptHash.create(receiverPredicate); - const transferTransaction = await TransferTransaction.create( - token, - predicate, - await PayToScriptHash.fromString(receiverScriptHash.toString()), - crypto.getRandomValues(new Uint8Array(32)), - CborSerializer.encodeArray(), - ); - - certificationData = await CertificationData.fromTransferTransaction( - transferTransaction, - await PayToPublicKeyPredicate.generateUnlockScript(transferTransaction, signingService), - ); - - response = await client.submitCertificationRequest(certificationData); - expect(response.status).toEqual(CertificationStatus.SUCCESS); - - // Test double spend attempt - const doubleSpendTransferTransaction = await TransferTransaction.create( - token, - predicate, - await PayToScriptHash.create(predicate), - crypto.getRandomValues(new Uint8Array(32)), - CborSerializer.encodeArray(), - ); - - await expect(async () => - client.submitCertificationRequest( - await CertificationData.fromTransferTransaction( - doubleSpendTransferTransaction, - await PayToPublicKeyPredicate.generateUnlockScript(doubleSpendTransferTransaction, signingService), - ), - ), - ).rejects.toThrow(); - - token = await token.transfer( - trustBase, - predicateVerifier, - await transferTransaction.toCertifiedTransaction( - trustBase, - predicateVerifier, - await waitInclusionProof(trustBase, predicateVerifier, client, transferTransaction), - ), - ); - - await expect( - Token.fromCBOR(token.toCBOR()).then((importedToken) => - importedToken.verify(trustBase, predicateVerifier).then((result) => result.status), - ), - ).resolves.toEqual(VerificationStatus.OK); - - // Return token to initial minter - const returnTransferTransaction = await TransferTransaction.create( - token, - receiverPredicate, - await PayToScriptHash.create(predicate), - crypto.getRandomValues(new Uint8Array(32)), - CborSerializer.encodeArray(), - ); - - certificationData = await CertificationData.fromTransferTransaction( - returnTransferTransaction, - await PayToPublicKeyPredicate.generateUnlockScript(returnTransferTransaction, receiverSigningService), - ); - - response = await client.submitCertificationRequest(certificationData); - expect(response.status).toEqual(CertificationStatus.SUCCESS); - - token = await token.transfer( - trustBase, - predicateVerifier, - await returnTransferTransaction.toCertifiedTransaction( - trustBase, - predicateVerifier, - await waitInclusionProof(trustBase, predicateVerifier, client, returnTransferTransaction), - ), - ); - - await expect(token.verify(trustBase, predicateVerifier).then((result) => result.status)).resolves.toEqual( - VerificationStatus.OK, - ); - }, 30000); -}); diff --git a/tests/unit/api/InclusionProofTest.ts b/tests/unit/api/InclusionProofTest.ts index 3dd2f9b8..b01ceee1 100644 --- a/tests/unit/api/InclusionProofTest.ts +++ b/tests/unit/api/InclusionProofTest.ts @@ -9,6 +9,7 @@ import { HashAlgorithm } from '../../../src/crypto/hash/HashAlgorithm.js'; import { NodeDataHasher } from '../../../src/crypto/hash/NodeDataHasher.js'; import { SigningService } from '../../../src/crypto/secp256k1/SigningService.js'; import { BuiltInPredicateVerifierFactory } from '../../../src/predicate/builtin/BuiltInPredicateVerifierFactory.js'; +import { PayToPublicKeyPredicate } from '../../../src/predicate/builtin/PayToPublicKeyPredicate.js'; import { PayToPublicKeyPredicateVerifier } from '../../../src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.js'; import { PredicateEngine } from '../../../src/predicate/PredicateEngine.js'; import { PredicateVerifier } from '../../../src/predicate/verification/PredicateVerifier.js'; @@ -16,6 +17,10 @@ import { CborSerializer } from '../../../src/serialization/cbor/CborSerializer.j import { HexConverter } from '../../../src/serialization/HexConverter.js'; import { SparseMerkleTree } from '../../../src/smt/plain/SparseMerkleTree.js'; import { SparseMerkleTreePath } from '../../../src/smt/plain/SparseMerkleTreePath.js'; +import { MintTransaction } from '../../../src/transaction/MintTransaction.js'; +import { PayToScriptHash } from '../../../src/transaction/PayToScriptHash.js'; +import { TokenId } from '../../../src/transaction/TokenId.js'; +import { TokenType } from '../../../src/transaction/TokenType.js'; import { InclusionProofVerificationRule, InclusionProofVerificationStatus, @@ -37,24 +42,23 @@ describe('InclusionProof', () => { ]), ); + let transaction: MintTransaction; let certificationData: CertificationData; let merkleTreePath: SparseMerkleTreePath; let unicityCertificate: UnicityCertificate; let trustBase: RootTrustBase; beforeAll(async () => { - certificationData = CertificationData.fromJSON({ - ownerPredicate: '8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - sourceStateHash: '00000000000000000000000000000000000000000000000000000000000000000000', - transactionHash: '00000000000000000000000000000000000000000000000000000000000000000000', - witness: - '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', - }); - - const smt = new SparseMerkleTree(new DataHasherFactory(HashAlgorithm.SHA256, NodeDataHasher)); - const stateId = await StateId.fromCertificationData(certificationData).then((stateId) => - stateId.toBitString().toBigInt(), + transaction = await MintTransaction.create( + await PayToScriptHash.create(PayToPublicKeyPredicate.create(signingService)), + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + new TokenType(crypto.getRandomValues(new Uint8Array(32))), + new Uint8Array(), ); + const smt = new SparseMerkleTree(new DataHasherFactory(HashAlgorithm.SHA256, NodeDataHasher)); + const stateId = await StateId.fromTransaction(transaction).then((stateId) => stateId.toBitString().toBigInt()); + certificationData = await CertificationData.fromMintTransaction(transaction); + await smt.addLeaf(stateId, await certificationData.calculateLeafValue().then((value) => value.imprint)); const root = await smt.calculateRoot(); @@ -66,7 +70,11 @@ describe('InclusionProof', () => { }); it('should encode and decode json', () => { - const inclusionProof = new InclusionProof(merkleTreePath, certificationData, unicityCertificate); + const inclusionProof = new InclusionProof( + merkleTreePath, + CertificationData.fromCBOR(certificationData.toCBOR()), + unicityCertificate, + ); expect(inclusionProof.toJSON()).toEqual({ certificationData: certificationData.toJSON(), merkleTreePath: merkleTreePath.toJSON(), @@ -84,7 +92,11 @@ describe('InclusionProof', () => { }); it('should encode and decode cbor', () => { - const inclusionProof = new InclusionProof(merkleTreePath, certificationData, unicityCertificate); + const inclusionProof = new InclusionProof( + merkleTreePath, + CertificationData.fromCBOR(certificationData.toCBOR()), + unicityCertificate, + ); expect(inclusionProof.toCBOR()).toStrictEqual( CborSerializer.encodeArray(certificationData.toCBOR(), merkleTreePath.toCBOR(), unicityCertificate.toCBOR()), @@ -99,11 +111,10 @@ describe('InclusionProof', () => { }); it('verifies', async () => { - const stateId = await StateId.fromCertificationData(certificationData); const inclusionProof = new InclusionProof(merkleTreePath, certificationData, unicityCertificate); await expect( - InclusionProofVerificationRule.verify(trustBase, predicateVerifierFactory, inclusionProof, stateId).then( + InclusionProofVerificationRule.verify(trustBase, predicateVerifierFactory, inclusionProof, transaction).then( (result) => result.status, ), ).resolves.toEqual(InclusionProofVerificationStatus.OK); @@ -112,7 +123,12 @@ describe('InclusionProof', () => { trustBase, predicateVerifierFactory, inclusionProof, - StateId.fromJSON('00000000000000000000000000000000000000000000000000000000000000000000'), + await MintTransaction.create( + await PayToScriptHash.create(transaction.lockScript), + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + transaction.tokenType, + transaction.data, + ), ).then((result) => result.status), ).resolves.toEqual(InclusionProofVerificationStatus.PATH_NOT_INCLUDED); @@ -134,37 +150,31 @@ describe('InclusionProof', () => { trustBase, predicateVerifierFactory, invalidTransactionHashInclusionProof, - stateId, + transaction, ).then((result) => result.status), - ).resolves.toEqual(InclusionProofVerificationStatus.NOT_AUTHENTICATED); + ).resolves.toEqual(InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH); }); it('verification fails with invalid transaction hash', async () => { - const stateId = await StateId.fromCertificationData(certificationData); - const inclusionProof = new InclusionProof( merkleTreePath, CertificationData.fromJSON({ ownerPredicate: HexConverter.encode(certificationData.lockScript.toCBOR()), sourceStateHash: certificationData.sourceStateHash.toJSON(), - transactionHash: DataHash.fromImprint( - HexConverter.decode('00000000000000000000000000000000000000000000000000000000000000000001'), - ).toJSON(), - witness: HexConverter.encode(certificationData.unlockScript), + transactionHash: certificationData.transactionHash.toJSON(), + witness: HexConverter.encode(new Uint8Array(65)), }), unicityCertificate, ); await expect( - InclusionProofVerificationRule.verify(trustBase, predicateVerifierFactory, inclusionProof, stateId).then( + InclusionProofVerificationRule.verify(trustBase, predicateVerifierFactory, inclusionProof, transaction).then( (result) => result.status, ), ).resolves.toEqual(InclusionProofVerificationStatus.NOT_AUTHENTICATED); }); it('verification fails with invalid trustbase', async () => { - const stateId = await StateId.fromCertificationData(certificationData); - const inclusionProof = new InclusionProof(merkleTreePath, certificationData, unicityCertificate); const predicateVerifier = new PredicateVerifier( new Map([ @@ -180,7 +190,7 @@ describe('InclusionProof', () => { createRootTrustBase(HexConverter.decode('0000000000000000000000000000000000000000000000000000000000000001')), predicateVerifier, inclusionProof, - stateId, + transaction, ).then((result) => result.status), ).resolves.toEqual(InclusionProofVerificationStatus.INVALID_TRUSTBASE); }); diff --git a/tests/utils/TransitionFlow.ts b/tests/utils/TransitionFlow.ts new file mode 100644 index 00000000..23c8a1d8 --- /dev/null +++ b/tests/utils/TransitionFlow.ts @@ -0,0 +1,152 @@ +import { RootTrustBase } from '../../src/api/bft/RootTrustBase.js'; +import { CertificationData } from '../../src/api/CertificationData.js'; +import { CertificationStatus } from '../../src/api/CertificationResponse.js'; +import { SigningService } from '../../src/crypto/secp256k1/SigningService.js'; +import { BuiltInPredicateVerifierFactory } from '../../src/predicate/builtin/BuiltInPredicateVerifierFactory.js'; +import { PayToPublicKeyPredicate } from '../../src/predicate/builtin/PayToPublicKeyPredicate.js'; +import { PredicateEngine } from '../../src/predicate/PredicateEngine.js'; +import { PredicateVerifier } from '../../src/predicate/verification/PredicateVerifier.js'; +import { CborSerializer } from '../../src/serialization/cbor/CborSerializer.js'; +import { StateTransitionClient } from '../../src/StateTransitionClient.js'; +import { MintTransaction } from '../../src/transaction/MintTransaction.js'; +import { PayToScriptHash } from '../../src/transaction/PayToScriptHash.js'; +import { Token } from '../../src/transaction/Token.js'; +import { TokenId } from '../../src/transaction/TokenId.js'; +import { TokenType } from '../../src/transaction/TokenType.js'; +import { TransferTransaction } from '../../src/transaction/TransferTransaction.js'; +import { waitInclusionProof } from '../../src/util/InclusionProofUtils.js'; +import { VerificationStatus } from '../../src/verification/VerificationStatus.js'; + +export const transitionFlowTest = (client: StateTransitionClient, trustBase: RootTrustBase): void => { + describe('Transition', () => { + it('default successful flow', async () => { + const predicateVerifier = new PredicateVerifier( + new Map([[PredicateEngine.BUILT_IN, BuiltInPredicateVerifierFactory.create()]]), + ); + + const signingService = new SigningService(SigningService.generatePrivateKey()); + const predicate = PayToPublicKeyPredicate.create(signingService); + + const mintTransaction = await MintTransaction.create( + await PayToScriptHash.create(predicate), + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + new TokenType(crypto.getRandomValues(new Uint8Array(32))), + CborSerializer.encodeArray(), + ); + let certificationData = await CertificationData.fromMintTransaction(mintTransaction); + + let response = await client.submitCertificationRequest(certificationData); + expect(response.status).toEqual(CertificationStatus.SUCCESS); + + let token = await Token.mint( + trustBase, + predicateVerifier, + await mintTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), + ), + ); + + const receiverSigningService = new SigningService(SigningService.generatePrivateKey()); + // Second user will generate his predicate + const receiverPredicate = PayToPublicKeyPredicate.create(receiverSigningService); + // Create pay to script hash for sender + const receiverScriptHash = await PayToScriptHash.create(receiverPredicate); + const transferTransaction = await TransferTransaction.create( + token, + predicate, + await PayToScriptHash.fromString(receiverScriptHash.toString()), + crypto.getRandomValues(new Uint8Array(32)), + CborSerializer.encodeArray(), + ); + + certificationData = await CertificationData.fromTransferTransaction( + transferTransaction, + await PayToPublicKeyPredicate.generateUnlockScript(transferTransaction, signingService), + ); + + await expect( + client + .submitCertificationRequest( + await CertificationData.fromTransferTransaction( + transferTransaction, + await PayToPublicKeyPredicate.generateUnlockScript(transferTransaction, signingService), + ), + ) + .then((response) => response.status), + ).resolves.toEqual(CertificationStatus.SUCCESS); + + // Test double spend attempt + const doubleSpendTransferTransaction = await TransferTransaction.create( + token, + predicate, + await PayToScriptHash.create(predicate), + crypto.getRandomValues(new Uint8Array(32)), + CborSerializer.encodeArray(), + ); + + await expect( + client + .submitCertificationRequest( + await CertificationData.fromTransferTransaction( + doubleSpendTransferTransaction, + await PayToPublicKeyPredicate.generateUnlockScript(doubleSpendTransferTransaction, signingService), + ), + ) + .then((response) => response.status), + ).resolves.toEqual(CertificationStatus.SUCCESS); + + await expect( + waitInclusionProof(trustBase, predicateVerifier, client, doubleSpendTransferTransaction), + ).rejects.toThrow('Invalid inclusion proof status: TRANSACTION_HASH_MISMATCH'); + + token = await token.transfer( + trustBase, + predicateVerifier, + await transferTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, transferTransaction), + ), + ); + + await expect( + Token.fromCBOR(token.toCBOR()).then((importedToken) => + importedToken.verify(trustBase, predicateVerifier).then((result) => result.status), + ), + ).resolves.toEqual(VerificationStatus.OK); + + // Return token to initial minter + const returnTransferTransaction = await TransferTransaction.create( + token, + receiverPredicate, + await PayToScriptHash.create(predicate), + crypto.getRandomValues(new Uint8Array(32)), + CborSerializer.encodeArray(), + ); + + certificationData = await CertificationData.fromTransferTransaction( + returnTransferTransaction, + await PayToPublicKeyPredicate.generateUnlockScript(returnTransferTransaction, receiverSigningService), + ); + + response = await client.submitCertificationRequest(certificationData); + expect(response.status).toEqual(CertificationStatus.SUCCESS); + + token = await token.transfer( + trustBase, + predicateVerifier, + await returnTransferTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, returnTransferTransaction), + ), + ); + + await expect(token.verify(trustBase, predicateVerifier).then((result) => result.status)).resolves.toEqual( + VerificationStatus.OK, + ); + }, 30000); + }); +}; From aaae694f27a4aa55dea85353c22d84e32e6df340 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Tue, 3 Feb 2026 16:15:43 +0200 Subject: [PATCH 07/15] WIP: #96 Made certification data to use plain hash. Removed some json methods --- package-lock.json | 905 +++++++++--------- package.json | 16 +- src/api/AggregatorClient.ts | 2 +- src/api/CertificationData.ts | 85 +- src/api/CertificationRequest.ts | 27 +- src/api/InclusionProof.ts | 56 +- src/api/InclusionProofResponse.ts | 39 +- src/api/StateId.ts | 49 +- src/api/bft/UnicityCertificate.ts | 4 - src/crypto/hash/DataHash.ts | 29 +- src/crypto/hash/DataHasher.ts | 14 +- src/crypto/hash/HashAlgorithm.ts | 41 +- src/crypto/hash/NodeDataHasher.ts | 12 +- src/crypto/hash/SubtleCryptoDataHasher.ts | 14 +- .../builtin/PayToPublicKeyPredicate.ts | 4 +- .../PayToPublicKeyPredicateVerifier.ts | 4 +- src/smt/plain/SparseMerkleTreePath.ts | 42 +- src/smt/plain/SparseMerkleTreePathStep.ts | 32 - src/smt/sum/SparseMerkleSumTreePath.ts | 14 +- src/smt/sum/SparseMerkleSumTreePathStep.ts | 8 - src/transaction/MintTransaction.ts | 11 +- src/transaction/PayToScriptHash.ts | 58 +- src/transaction/TokenId.ts | 9 - src/transaction/TokenType.ts | 9 - src/transaction/TransferTransaction.ts | 15 +- src/util/BitString.ts | 7 +- tests/unit/api/CertificationDataTest.ts | 38 +- tests/unit/api/CertificationRequestTest.ts | 90 +- tests/unit/api/CertificationResponseTest.ts | 58 +- tests/unit/api/InclusionProofTest.ts | 57 +- tests/unit/api/StateIdTest.ts | 19 +- .../smt/plain/SparseMerkleTreePathTest.ts | 97 +- tests/unit/smt/plain/SparseMerkleTreeTest.ts | 5 +- .../smt/sum/SparseMerkleSumTreePathTest.ts | 5 - tests/unit/transaction/PayToScriptHashTest.ts | 13 - tests/utils/TransitionFlow.ts | 2 +- 36 files changed, 799 insertions(+), 1091 deletions(-) delete mode 100644 tests/unit/transaction/PayToScriptHashTest.ts diff --git a/package-lock.json b/package-lock.json index dd7af49e..f6a71eba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,19 +14,19 @@ "uuid": "13.0.0" }, "devDependencies": { - "@babel/preset-env": "7.28.5", + "@babel/preset-env": "7.29.0", "@babel/preset-typescript": "7.28.5", - "@eslint/js": "9.39.1", + "@eslint/js": "9.39.2", "@types/jest": "30.0.0", "babel-jest": "30.2.0", - "eslint": "9.39.1", + "eslint": "9.39.2", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "2.32.0", - "eslint-plugin-prettier": "5.5.4", - "globals": "16.5.0", + "eslint-plugin-prettier": "5.5.5", + "globals": "17.3.0", "jest": "30.2.0", "typescript": "5.9.3", - "typescript-eslint": "8.49.0" + "typescript-eslint": "8.54.0" } }, "node_modules/@ampproject/remapping": { @@ -44,13 +44,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -59,9 +59,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -101,14 +101,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", + "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -131,13 +131,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -148,18 +148,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", - "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.5", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "engines": { @@ -170,14 +170,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "engines": { @@ -188,17 +188,17 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" + "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -229,29 +229,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -274,9 +274,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -302,15 +302,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -364,15 +364,15 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", - "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -393,13 +393,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -476,14 +476,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -561,13 +561,13 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -577,13 +577,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -794,15 +794,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -812,14 +812,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { @@ -846,13 +846,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", - "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -862,14 +862,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -879,14 +879,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -896,18 +896,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -917,14 +917,14 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -951,14 +951,14 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -984,14 +984,14 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1017,14 +1017,14 @@ } }, "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", - "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1034,13 +1034,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", - "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1101,13 +1101,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1133,13 +1133,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", - "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1182,14 +1182,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1199,16 +1199,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1235,14 +1235,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1268,13 +1268,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1284,13 +1284,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1300,17 +1300,17 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1337,13 +1337,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1353,13 +1353,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", - "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -1386,14 +1386,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1403,15 +1403,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1437,13 +1437,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1453,14 +1453,14 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1502,13 +1502,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -1603,14 +1603,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1637,14 +1637,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1654,81 +1654,81 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", - "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", + "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.5", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.4", - "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.28.5", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.28.5", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.28.5", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.4", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "engines": { @@ -1774,33 +1774,33 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -1808,9 +1808,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -1863,9 +1863,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1895,9 +1895,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -2027,9 +2027,9 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", "engines": { @@ -2692,9 +2692,9 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", "engines": { @@ -2888,20 +2888,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.49.0.tgz", - "integrity": "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.49.0", - "@typescript-eslint/type-utils": "8.49.0", - "@typescript-eslint/utils": "8.49.0", - "@typescript-eslint/visitor-keys": "8.49.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/type-utils": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2911,7 +2911,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.49.0", + "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -2927,18 +2927,18 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.49.0.tgz", - "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.49.0", - "@typescript-eslint/types": "8.49.0", - "@typescript-eslint/typescript-estree": "8.49.0", - "@typescript-eslint/visitor-keys": "8.49.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2953,15 +2953,15 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.49.0.tgz", - "integrity": "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.49.0", - "@typescript-eslint/types": "^8.49.0", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2975,14 +2975,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.49.0.tgz", - "integrity": "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.49.0", - "@typescript-eslint/visitor-keys": "8.49.0" + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2993,9 +2993,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.49.0.tgz", - "integrity": "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", "dev": true, "license": "MIT", "engines": { @@ -3010,17 +3010,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.49.0.tgz", - "integrity": "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.49.0", - "@typescript-eslint/typescript-estree": "8.49.0", - "@typescript-eslint/utils": "8.49.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3035,9 +3035,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.49.0.tgz", - "integrity": "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", "dev": true, "license": "MIT", "engines": { @@ -3049,21 +3049,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.49.0.tgz", - "integrity": "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.49.0", - "@typescript-eslint/tsconfig-utils": "8.49.0", - "@typescript-eslint/types": "8.49.0", - "@typescript-eslint/visitor-keys": "8.49.0", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3116,16 +3116,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.49.0.tgz", - "integrity": "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.49.0", - "@typescript-eslint/types": "8.49.0", - "@typescript-eslint/typescript-estree": "8.49.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3140,13 +3140,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.49.0.tgz", - "integrity": "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3727,14 +3727,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { @@ -3742,27 +3742,27 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", + "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" + "@babel/helper-define-polyfill-provider": "^0.6.6", + "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" + "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -3820,9 +3820,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.14", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.14.tgz", - "integrity": "sha512-GM9c0cWWR8Ga7//Ves/9KRgTS8nLausCkP3CGiFLrnwA2CDUluXgaQqvrULoR2Ujrd/mz/lkX87F5BHFsNr5sQ==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3854,9 +3854,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -3875,11 +3875,11 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -3976,9 +3976,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001749", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz", - "integrity": "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==", + "version": "1.0.30001767", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001767.tgz", + "integrity": "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==", "dev": true, "funding": [ { @@ -4114,13 +4114,13 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.45.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", - "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.3" + "browserslist": "^4.28.1" }, "funding": { "type": "opencollective", @@ -4328,9 +4328,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.233", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.233.tgz", - "integrity": "sha512-iUdTQSf7EFXsDdQsp8MwJz5SVk4APEFqXU/S47OtQ0YLqacSwPXdZ5vRlMX3neb07Cy2vgioNuRnWUXFwuslkg==", + "version": "1.5.283", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", "dev": true, "license": "ISC" }, @@ -4537,9 +4537,9 @@ } }, "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", "peer": true, @@ -4550,7 +4550,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", + "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -4709,14 +4709,14 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", - "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", "dev": true, "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -5341,9 +5341,9 @@ } }, "node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", + "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", "dev": true, "license": "MIT", "engines": { @@ -7050,9 +7050,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.23", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", - "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -7474,9 +7474,9 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, "license": "MIT", "dependencies": { @@ -7579,9 +7579,9 @@ "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "dev": true, "license": "MIT", "dependencies": { @@ -7613,18 +7613,18 @@ } }, "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "dev": true, "license": "MIT", "dependencies": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", + "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", + "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { "node": ">=4" @@ -7638,31 +7638,18 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~3.0.2" + "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7674,13 +7661,13 @@ } }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -8203,13 +8190,13 @@ } }, "node_modules/synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.4" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -8325,9 +8312,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -8511,16 +8498,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.49.0.tgz", - "integrity": "sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", + "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.49.0", - "@typescript-eslint/parser": "8.49.0", - "@typescript-eslint/typescript-estree": "8.49.0", - "@typescript-eslint/utils": "8.49.0" + "@typescript-eslint/eslint-plugin": "8.54.0", + "@typescript-eslint/parser": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8585,9 +8572,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "dev": true, "license": "MIT", "engines": { @@ -8595,9 +8582,9 @@ } }, "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "dev": true, "license": "MIT", "engines": { @@ -8640,9 +8627,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 2ecf724a..58eb9e67 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,10 @@ "main": "./lib/index.js", "types": "./lib/index.d.ts", "type": "module", + "exports": { + "./lib/*.js": "./lib/*.js", + "./lib/*": "./lib/*.js" + }, "scripts": { "prebuild": "rm -rf lib", "build": "tsc", @@ -37,18 +41,18 @@ "uuid": "13.0.0" }, "devDependencies": { - "@babel/preset-env": "7.28.5", + "@babel/preset-env": "7.29.0", "@babel/preset-typescript": "7.28.5", - "@eslint/js": "9.39.1", + "@eslint/js": "9.39.2", "@types/jest": "30.0.0", "babel-jest": "30.2.0", - "eslint": "9.39.1", + "eslint": "9.39.2", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "2.32.0", - "eslint-plugin-prettier": "5.5.4", - "globals": "16.5.0", + "eslint-plugin-prettier": "5.5.5", + "globals": "17.3.0", "jest": "30.2.0", "typescript": "5.9.3", - "typescript-eslint": "8.49.0" + "typescript-eslint": "8.54.0" } } diff --git a/src/api/AggregatorClient.ts b/src/api/AggregatorClient.ts index 80a4d4a4..75fdb661 100644 --- a/src/api/AggregatorClient.ts +++ b/src/api/AggregatorClient.ts @@ -45,7 +45,7 @@ export class AggregatorClient implements IAggregatorClient { * @inheritDoc */ public async getInclusionProof(stateId: StateId): Promise { - const data = { stateId: stateId.toJSON() }; + const data = { stateId: HexConverter.encode(stateId.bytes) }; return InclusionProofResponse.fromCBOR( HexConverter.decode((await this.transport.request('get_inclusion_proof.v2', data)) as string), ); diff --git a/src/api/CertificationData.ts b/src/api/CertificationData.ts index 7cf45715..fef2f31e 100644 --- a/src/api/CertificationData.ts +++ b/src/api/CertificationData.ts @@ -2,7 +2,6 @@ import { DataHash } from '../crypto/hash/DataHash.js'; import { DataHasher } from '../crypto/hash/DataHasher.js'; import { HashAlgorithm } from '../crypto/hash/HashAlgorithm.js'; import { MintSigningService } from '../crypto/MintSigningService.js'; -import { InvalidJsonStructureError } from '../InvalidJsonStructureError.js'; import { EncodedPredicate } from '../predicate/EncodedPredicate.js'; import { IPredicate } from '../predicate/IPredicate.js'; import { CborDeserializer } from '../serialization/cbor/CborDeserializer.js'; @@ -12,20 +11,6 @@ import { MintTransaction } from '../transaction/MintTransaction.js'; import { TransferTransaction } from '../transaction/TransferTransaction.js'; import { dedent } from '../util/StringUtils.js'; -/** - * JSON representation of a certification data. - */ -export interface ICertificationDataJson { - /** The lock predicate as a hex string. */ - readonly ownerPredicate: string; - /** The source state hash imprint as a hex string. */ - readonly sourceStateHash: string; - /** The transaction hash imprint as a hex string. */ - readonly transactionHash: string; - /** The witness as a hex string. */ - readonly witness: string; -} - export class CertificationData { /** * Create a certification data object. @@ -60,39 +45,24 @@ export class CertificationData { const data = CborDeserializer.decodeArray(bytes); return new CertificationData( EncodedPredicate.fromCBOR(data[0]), - DataHash.fromCBOR(data[1]), - DataHash.fromCBOR(data[2]), + new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(data[1])), + new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(data[2])), CborDeserializer.decodeByteString(data[3]), ); } - /** - * Create CertificationData from JSON object. - * @param {unknown} data Raw certification data - * - * @returns {CertificationData} certification data - * @throws {InvalidJsonStructureError} if the data does not match the expected shape - */ - public static fromJSON(data: unknown): CertificationData { - if (!CertificationData.isJSON(data)) { - throw new InvalidJsonStructureError(); - } - - return new CertificationData( - EncodedPredicate.fromCBOR(HexConverter.decode(data.ownerPredicate)), - DataHash.fromJSON(data.sourceStateHash), - DataHash.fromJSON(data.transactionHash), - HexConverter.decode(data.witness), - ); - } - public static async fromMintTransaction(transaction: MintTransaction): Promise { const signingService = await MintSigningService.create(transaction.tokenId); const transactionHash = await transaction.calculateTransactionHash(); const signatureDataHash = await new DataHasher(HashAlgorithm.SHA256) - .update(CborSerializer.encodeArray(transaction.sourceStateHash.toCBOR(), transactionHash.toCBOR())) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(transaction.sourceStateHash.imprint), + CborSerializer.encodeByteString(transactionHash.imprint), + ), + ) .digest(); const unlockScript = await signingService.sign(signatureDataHash); @@ -114,28 +84,6 @@ export class CertificationData { return CertificationData.create(transaction.lockScript, transaction.sourceStateHash, transactionHash, unlockScript); } - /** - * Check if the given data is a valid JSON certification data object. - * - * @param {unknown} data Raw certification data - * - * @returns {boolean} True if the data is a valid JSON certification data object - */ - public static isJSON(data: unknown): data is ICertificationDataJson { - return ( - typeof data === 'object' && - data !== null && - 'ownerPredicate' in data && - typeof data.ownerPredicate === 'string' && - 'sourceStateHash' in data && - typeof data.sourceStateHash === 'string' && - 'transactionHash' in data && - typeof data.transactionHash === 'string' && - 'witness' in data && - typeof data.witness === 'string' - ); - } - private static create( lockScript: IPredicate, sourceStateHash: DataHash, @@ -162,25 +110,12 @@ export class CertificationData { public toCBOR(): Uint8Array { return CborSerializer.encodeArray( this.lockScript.toCBOR(), - this.sourceStateHash.toCBOR(), - this.transactionHash.toCBOR(), + CborSerializer.encodeByteString(this.sourceStateHash.data), + CborSerializer.encodeByteString(this.transactionHash.data), CborSerializer.encodeByteString(this._unlockScript), ); } - /** - * Convert the certification data to a JSON object. - * @returns JSON object - */ - public toJSON(): ICertificationDataJson { - return { - ownerPredicate: HexConverter.encode(this.lockScript.toCBOR()), - sourceStateHash: this.sourceStateHash.toJSON(), - transactionHash: this.transactionHash.toJSON(), - witness: HexConverter.encode(this._unlockScript), - }; - } - /** * Returns a string representation of the CertificationData. * @returns The string representation. diff --git a/src/api/CertificationRequest.ts b/src/api/CertificationRequest.ts index 97500cb4..31b6823a 100644 --- a/src/api/CertificationRequest.ts +++ b/src/api/CertificationRequest.ts @@ -1,19 +1,7 @@ -import { CertificationData, ICertificationDataJson } from './CertificationData.js'; +import { CertificationData } from './CertificationData.js'; import { StateId } from './StateId.js'; import { CborSerializer } from '../serialization/cbor/CborSerializer.js'; -/** - * JSON representation of a certification request. - */ -export interface ICertificationRequestJson { - /** The certification data json. */ - readonly certificationData: ICertificationDataJson; - /** Optional flag to request a receipt. */ - readonly receipt?: boolean; - /** The state ID as a string. */ - readonly stateId: string; -} - /** * Certification request object sent by the client to the aggregator. */ @@ -54,17 +42,4 @@ export class CertificationRequest { CborSerializer.encodeUnsignedInteger(0), ); } - - /** - * Convert the request to a JSON object. - * - * @returns JSON object - */ - public toJSON(): ICertificationRequestJson { - return { - certificationData: this.certificationData.toJSON(), - receipt: this.receipt, - stateId: this.stateId.toJSON(), - }; - } } diff --git a/src/api/InclusionProof.ts b/src/api/InclusionProof.ts index 4f95753b..81bdc533 100644 --- a/src/api/InclusionProof.ts +++ b/src/api/InclusionProof.ts @@ -1,23 +1,10 @@ import { UnicityCertificate } from './bft/UnicityCertificate.js'; -import { CertificationData, ICertificationDataJson } from './CertificationData.js'; -import { InvalidJsonStructureError } from '../InvalidJsonStructureError.js'; +import { CertificationData } from './CertificationData.js'; import { CborDeserializer } from '../serialization/cbor/CborDeserializer.js'; import { CborSerializer } from '../serialization/cbor/CborSerializer.js'; -import { ISparseMerkleTreePathJson, SparseMerkleTreePath } from '../smt/plain/SparseMerkleTreePath.js'; +import { SparseMerkleTreePath } from '../smt/plain/SparseMerkleTreePath.js'; import { dedent } from '../util/StringUtils.js'; -/** - * Interface representing the JSON structure of an InclusionProof. - */ -export interface IInclusionProofJson { - /** The certification data as JSON or null. */ - readonly certificationData: ICertificationDataJson | null; - /** The sparse merkle tree path as JSON. */ - readonly merkleTreePath: ISparseMerkleTreePathJson; - /** The unicity certificate as a hex string. */ - readonly unicityCertificate: string; -} - /** * Represents a proof of inclusion or non inclusion in a sparse merkle tree. */ @@ -49,33 +36,6 @@ export class InclusionProof { ); } - /** - * Creates an InclusionProof from a JSON object. - * @param data The JSON data. - * @returns An InclusionProof instance. - * @throws Error if parsing fails. - */ - public static fromJSON(data: unknown): InclusionProof { - if (!InclusionProof.isJSON(data)) { - throw new InvalidJsonStructureError(); - } - - return new InclusionProof( - SparseMerkleTreePath.fromJSON(data.merkleTreePath), - data.certificationData ? CertificationData.fromJSON(data.certificationData) : null, - UnicityCertificate.fromJSON(data.unicityCertificate), - ); - } - - /** - * Type guard to check if data is IInclusionProofJson. - * @param data The data to check. - * @returns True if data is IInclusionProofJson, false otherwise. - */ - public static isJSON(data: unknown): data is IInclusionProofJson { - return typeof data === 'object' && data !== null && 'merkleTreePath' in data && 'unicityCertificate' in data; - } - /** * Encodes the InclusionProof to CBOR format. * @returns The CBOR-encoded bytes. @@ -88,18 +48,6 @@ export class InclusionProof { ); } - /** - * Converts the InclusionProof to a JSON object. - * @returns The InclusionProof as IInclusionProofJson. - */ - public toJSON(): IInclusionProofJson { - return { - certificationData: this.certificationData?.toJSON() ?? null, - merkleTreePath: this.merkleTreePath.toJSON(), - unicityCertificate: this.unicityCertificate.toJSON(), - }; - } - /** * Returns a string representation of the InclusionProof. * @returns The string representation. diff --git a/src/api/InclusionProofResponse.ts b/src/api/InclusionProofResponse.ts index 646f1f2e..4a07e406 100644 --- a/src/api/InclusionProofResponse.ts +++ b/src/api/InclusionProofResponse.ts @@ -1,13 +1,7 @@ -import { InvalidJsonStructureError } from '../InvalidJsonStructureError.js'; -import { IInclusionProofJson, InclusionProof } from './InclusionProof.js'; +import { InclusionProof } from './InclusionProof.js'; import { CborDeserializer } from '../serialization/cbor/CborDeserializer.js'; import { CborSerializer } from '../serialization/cbor/CborSerializer.js'; -interface IInclusionProofResponseJson { - readonly blockNumber: string; - readonly inclusionProof: IInclusionProofJson; -} - /** * Inclusion proof response. */ @@ -39,41 +33,10 @@ export class InclusionProofResponse { ); } - /** - * Create response from JSON string. - * - * @param input JSON string - * @return inclusion proof response - */ - public static fromJSON(input: unknown): InclusionProofResponse { - if (!InclusionProofResponse.isJSON(input)) { - throw new InvalidJsonStructureError(); - } - - return new InclusionProofResponse(BigInt(input.blockNumber), InclusionProof.fromJSON(input.inclusionProof)); - } - - public static isJSON(input: unknown): input is IInclusionProofResponseJson { - return ( - typeof input === 'object' && - input !== null && - 'inclusionProof' in input && - 'blockNumber' in input && - typeof input.blockNumber === 'string' - ); - } - public toCBOR(): Uint8Array { return CborSerializer.encodeArray( CborSerializer.encodeUnsignedInteger(this.blockNumber), this.inclusionProof.toCBOR(), ); } - - public toJSON(): IInclusionProofResponseJson { - return { - blockNumber: this.blockNumber.toString(), - inclusionProof: this.inclusionProof.toJSON(), - }; - } } diff --git a/src/api/StateId.ts b/src/api/StateId.ts index face9f69..68fab918 100644 --- a/src/api/StateId.ts +++ b/src/api/StateId.ts @@ -3,27 +3,29 @@ import { DataHash } from '../crypto/hash/DataHash.js'; import { DataHasher } from '../crypto/hash/DataHasher.js'; import { HashAlgorithm } from '../crypto/hash/HashAlgorithm.js'; import { IPredicate } from '../predicate/IPredicate.js'; +import { CborDeserializer } from '../serialization/cbor/CborDeserializer.js'; import { CborSerializer } from '../serialization/cbor/CborSerializer.js'; +import { HexConverter } from '../serialization/HexConverter.js'; import { ITransaction } from '../transaction/ITransaction.js'; import { BitString } from '../util/BitString.js'; +import { areUint8ArraysEqual } from '../util/TypedArrayUtils.js'; /** * Represents a unique state identifier derived from a public key and state hash. */ export class StateId { - /** - * Constructs a StateId instance. - * @param hash The DataHash representing the state ID. - */ - private constructor(private readonly hash: DataHash) {} + private readonly _bytes: Uint8Array; - /** - * Decodes a StateId from CBOR bytes. - * @param data The CBOR-encoded bytes. - * @returns A StateId instance. - */ - public static fromCBOR(data: Uint8Array): StateId { - return new StateId(DataHash.fromCBOR(data)); + private constructor(hash: DataHash) { + this._bytes = new Uint8Array(hash.data); + } + + public get bytes(): Uint8Array { + return new Uint8Array(this._bytes); + } + + public static fromCBOR(bytes: Uint8Array): StateId { + return new StateId(new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(bytes))); } /** @@ -34,15 +36,6 @@ export class StateId { return StateId.create(certificationData.lockScript, certificationData.sourceStateHash); } - /** - * Creates a StateId from a JSON string. - * @param data The JSON string. - * @returns A StateId instance. - */ - public static fromJSON(data: string): StateId { - return new StateId(DataHash.fromJSON(data)); - } - public static fromTransaction(transaction: ITransaction): Promise { return StateId.create(transaction.lockScript, transaction.sourceStateHash); } @@ -61,20 +54,20 @@ export class StateId { return new StateId(hash); } + public equals(id: StateId): boolean { + return areUint8ArraysEqual(this._bytes, id._bytes); + } + /** * Converts the StateId to a BitString. * @return The BitString representation of the StateId. */ public toBitString(): BitString { - return BitString.fromStateId(this.hash); + return BitString.fromStateId(this); } public toCBOR(): Uint8Array { - return this.hash.toCBOR(); - } - - public toJSON(): string { - return this.hash.toJSON(); + return CborSerializer.encodeByteString(this._bytes); } /** @@ -82,6 +75,6 @@ export class StateId { * @returns The string representation. */ public toString(): string { - return `StateId[${this.hash.toString()}]`; + return `StateId[${HexConverter.encode(this._bytes)}]`; } } diff --git a/src/api/bft/UnicityCertificate.ts b/src/api/bft/UnicityCertificate.ts index ae3d4ab7..25844ea8 100644 --- a/src/api/bft/UnicityCertificate.ts +++ b/src/api/bft/UnicityCertificate.ts @@ -124,10 +124,6 @@ export class UnicityCertificate { ); } - public toJSON(): string { - return HexConverter.encode(this.toCBOR()); - } - /** * Returns a string representation of the UnicityCertificate. * @returns The string representation. diff --git a/src/crypto/hash/DataHash.ts b/src/crypto/hash/DataHash.ts index 3eb63d8e..d70ee283 100644 --- a/src/crypto/hash/DataHash.ts +++ b/src/crypto/hash/DataHash.ts @@ -3,6 +3,7 @@ import { HashError } from './HashError.js'; import { CborDeserializer } from '../../serialization/cbor/CborDeserializer.js'; import { CborSerializer } from '../../serialization/cbor/CborSerializer.js'; import { HexConverter } from '../../serialization/HexConverter.js'; +import { areUint8ArraysEqual } from '../../util/TypedArrayUtils.js'; export class DataHash { private readonly _imprint: Uint8Array; @@ -11,9 +12,13 @@ export class DataHash { public readonly algorithm: HashAlgorithm, private readonly _data: Uint8Array, ) { + if (_data.length !== algorithm.length) { + throw new HashError('Invalid data length for the specified hash algorithm.'); + } + this._data = new Uint8Array(_data); this._imprint = new Uint8Array(_data.length + 2); - this._imprint.set([(algorithm & 0xff00) >> 8, algorithm & 0xff]); + this._imprint.set([(algorithm.id & 0xff00) >> 8, algorithm.id & 0xff]); this._imprint.set(new Uint8Array(_data), 2); } @@ -30,36 +35,20 @@ export class DataHash { return new Uint8Array(this._imprint); } - public static fromCBOR(bytes: Uint8Array): DataHash { - return DataHash.fromImprint(CborDeserializer.decodeByteString(bytes)); - } - public static fromImprint(imprint: Uint8Array): DataHash { if (imprint.length < 3) { throw new HashError('Imprint must have 2 bytes of algorithm and at least 1 byte of data.'); } const algorithm = (imprint[0] << 8) | imprint[1]; - return new DataHash(algorithm, imprint.subarray(2)); - } - - public static fromJSON(data: string): DataHash { - return DataHash.fromImprint(HexConverter.decode(data)); + return new DataHash(HashAlgorithm.fromId(algorithm), imprint.subarray(2)); } public equals(hash: DataHash): boolean { - return HexConverter.encode(this._imprint) === HexConverter.encode(hash._imprint); - } - - public toCBOR(): Uint8Array { - return CborSerializer.encodeByteString(this._imprint); - } - - public toJSON(): string { - return HexConverter.encode(this._imprint); + return areUint8ArraysEqual(this._imprint, hash._imprint); } public toString(): string { - return `[${HashAlgorithm[this.algorithm]}]${HexConverter.encode(this._data)}`; + return `[${this.algorithm.toString()}]${HexConverter.encode(this._data)}`; } } diff --git a/src/crypto/hash/DataHasher.ts b/src/crypto/hash/DataHasher.ts index ed8e2d78..95819e07 100644 --- a/src/crypto/hash/DataHasher.ts +++ b/src/crypto/hash/DataHasher.ts @@ -15,11 +15,11 @@ interface IMessageDigest { } export const Algorithm = { - [HashAlgorithm.RIPEMD160]: ripemd160, - [HashAlgorithm.SHA224]: sha224, - [HashAlgorithm.SHA256]: sha256, - [HashAlgorithm.SHA384]: sha384, - [HashAlgorithm.SHA512]: sha512, + [HashAlgorithm.RIPEMD160.id]: ripemd160, + [HashAlgorithm.SHA224.id]: sha224, + [HashAlgorithm.SHA256.id]: sha256, + [HashAlgorithm.SHA384.id]: sha384, + [HashAlgorithm.SHA512.id]: sha512, }; /** @@ -33,11 +33,11 @@ export class DataHasher implements IDataHasher { * @param {HashAlgorithm} algorithm */ public constructor(public readonly algorithm: HashAlgorithm) { - if (!Algorithm[algorithm]) { + if (!Algorithm[algorithm.id]) { throw new UnsupportedHashAlgorithmError(algorithm); } - this._messageDigest = Algorithm[algorithm].create(); + this._messageDigest = Algorithm[algorithm.id].create(); } /** diff --git a/src/crypto/hash/HashAlgorithm.ts b/src/crypto/hash/HashAlgorithm.ts index 99bb990f..5532a533 100644 --- a/src/crypto/hash/HashAlgorithm.ts +++ b/src/crypto/hash/HashAlgorithm.ts @@ -1,7 +1,36 @@ -export enum HashAlgorithm { - SHA256 = 0, - SHA224 = 1, - SHA384 = 2, - SHA512 = 3, - RIPEMD160 = 4, +import { HashError } from './HashError.js'; + +export class HashAlgorithm { + public static readonly RIPEMD160 = new HashAlgorithm(4, 'RIPEMD-160', 20); + public static readonly SHA224 = new HashAlgorithm(1, 'SHA-224', 28); + public static readonly SHA256 = new HashAlgorithm(0, 'SHA-256', 32); + public static readonly SHA384 = new HashAlgorithm(2, 'SHA-384', 48); + public static readonly SHA512 = new HashAlgorithm(3, 'SHA-512', 64); + + private constructor( + public readonly id: number, + public readonly name: string, + public readonly length: number, + ) {} + + public static fromId(id: number): HashAlgorithm { + switch (id) { + case HashAlgorithm.SHA256.id: + return HashAlgorithm.SHA256; + case HashAlgorithm.SHA224.id: + return HashAlgorithm.SHA224; + case HashAlgorithm.SHA384.id: + return HashAlgorithm.SHA384; + case HashAlgorithm.SHA512.id: + return HashAlgorithm.SHA512; + case HashAlgorithm.RIPEMD160.id: + return HashAlgorithm.RIPEMD160; + default: + throw new HashError(`Unsupported hash algorithm ID: ${id}`); + } + } + + public toString(): string { + return this.name; + } } diff --git a/src/crypto/hash/NodeDataHasher.ts b/src/crypto/hash/NodeDataHasher.ts index 83c633b9..e0c9a3b9 100644 --- a/src/crypto/hash/NodeDataHasher.ts +++ b/src/crypto/hash/NodeDataHasher.ts @@ -5,11 +5,11 @@ import { HashAlgorithm } from './HashAlgorithm.js'; import { IDataHasher } from './IDataHasher.js'; export const Algorithm = { - [HashAlgorithm.RIPEMD160]: 'RIPEMD160', - [HashAlgorithm.SHA224]: 'SHA224', - [HashAlgorithm.SHA256]: 'SHA256', - [HashAlgorithm.SHA384]: 'SHA384', - [HashAlgorithm.SHA512]: 'SHA512', + [HashAlgorithm.RIPEMD160.id]: 'RIPEMD160', + [HashAlgorithm.SHA224.id]: 'SHA224', + [HashAlgorithm.SHA256.id]: 'SHA256', + [HashAlgorithm.SHA384.id]: 'SHA384', + [HashAlgorithm.SHA512.id]: 'SHA512', }; export class NodeDataHasher implements IDataHasher { @@ -20,7 +20,7 @@ export class NodeDataHasher implements IDataHasher { * @param {string} algorithm */ public constructor(public readonly algorithm: HashAlgorithm) { - this._hasher = createHash(Algorithm[this.algorithm]); + this._hasher = createHash(Algorithm[this.algorithm.id]); } /** diff --git a/src/crypto/hash/SubtleCryptoDataHasher.ts b/src/crypto/hash/SubtleCryptoDataHasher.ts index cf124e6c..00a2c876 100644 --- a/src/crypto/hash/SubtleCryptoDataHasher.ts +++ b/src/crypto/hash/SubtleCryptoDataHasher.ts @@ -4,11 +4,11 @@ import { IDataHasher } from './IDataHasher.js'; import { UnsupportedHashAlgorithmError } from './UnsupportedHashAlgorithmError.js'; export const Algorithm = { - [HashAlgorithm.RIPEMD160]: null, - [HashAlgorithm.SHA224]: null, - [HashAlgorithm.SHA256]: 'SHA-256', - [HashAlgorithm.SHA384]: 'SHA-384', - [HashAlgorithm.SHA512]: 'SHA-512', + [HashAlgorithm.RIPEMD160.id]: null, + [HashAlgorithm.SHA224.id]: null, + [HashAlgorithm.SHA256.id]: 'SHA-256', + [HashAlgorithm.SHA384.id]: 'SHA-384', + [HashAlgorithm.SHA512.id]: 'SHA-512', }; /** @@ -22,7 +22,7 @@ export class SubtleCryptoDataHasher implements IDataHasher { * @param {string} algorithm */ public constructor(public readonly algorithm: HashAlgorithm) { - if (!Algorithm[algorithm]) { + if (!Algorithm[algorithm.id]) { throw new UnsupportedHashAlgorithmError(algorithm); } @@ -36,7 +36,7 @@ export class SubtleCryptoDataHasher implements IDataHasher { public async digest(): Promise { return new DataHash( this.algorithm, - new Uint8Array(await window.crypto.subtle.digest({ name: Algorithm[this.algorithm] as string }, this._data)), + new Uint8Array(await window.crypto.subtle.digest({ name: Algorithm[this.algorithm.id] as string }, this._data)), ); } diff --git a/src/predicate/builtin/PayToPublicKeyPredicate.ts b/src/predicate/builtin/PayToPublicKeyPredicate.ts index 337a098f..b25fc9fd 100644 --- a/src/predicate/builtin/PayToPublicKeyPredicate.ts +++ b/src/predicate/builtin/PayToPublicKeyPredicate.ts @@ -54,8 +54,8 @@ export class PayToPublicKeyPredicate implements IPredicate { const hash = await new DataHasher(HashAlgorithm.SHA256) .update( CborSerializer.encodeArray( - transaction.sourceStateHash.toCBOR(), - await transaction.calculateTransactionHash().then((hash) => hash.toCBOR()), + CborSerializer.encodeByteString(transaction.sourceStateHash.imprint), + await transaction.calculateTransactionHash().then((hash) => CborSerializer.encodeByteString(hash.imprint)), ), ) .digest(); diff --git a/src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts b/src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts index 9802f8d4..1f592902 100644 --- a/src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts +++ b/src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts @@ -29,8 +29,8 @@ export class PayToPublicKeyPredicateVerifier implements IPredicateVerifier { await new DataHasher(HashAlgorithm.SHA256) .update( CborSerializer.encodeArray( - certificationData.sourceStateHash.toCBOR(), - certificationData.transactionHash.toCBOR(), + CborSerializer.encodeByteString(certificationData.sourceStateHash.imprint), + CborSerializer.encodeByteString(certificationData.transactionHash.imprint), ), ) .digest(), diff --git a/src/smt/plain/SparseMerkleTreePath.ts b/src/smt/plain/SparseMerkleTreePath.ts index 7d6d4b6f..a3c27d8d 100644 --- a/src/smt/plain/SparseMerkleTreePath.ts +++ b/src/smt/plain/SparseMerkleTreePath.ts @@ -1,20 +1,14 @@ import { bitLen } from '@noble/curves/utils.js'; import { PathVerificationResult } from '../PathVerificationResult.js'; -import { ISparseMerkleTreePathStepJson, SparseMerkleTreePathStep } from './SparseMerkleTreePathStep.js'; +import { SparseMerkleTreePathStep } from './SparseMerkleTreePathStep.js'; import { DataHash } from '../../crypto/hash/DataHash.js'; import { DataHasher } from '../../crypto/hash/DataHasher.js'; -import { InvalidJsonStructureError } from '../../InvalidJsonStructureError.js'; import { BigintConverter } from '../../serialization/BigintConverter.js'; import { CborDeserializer } from '../../serialization/cbor/CborDeserializer.js'; import { CborSerializer } from '../../serialization/cbor/CborSerializer.js'; import { dedent } from '../../util/StringUtils.js'; -export interface ISparseMerkleTreePathJson { - readonly root: string; - readonly steps: ReadonlyArray; -} - export class SparseMerkleTreePath { public constructor( public readonly root: DataHash, @@ -26,48 +20,18 @@ export class SparseMerkleTreePath { const steps = CborDeserializer.decodeArray(data[1]); return new SparseMerkleTreePath( - DataHash.fromCBOR(data[0]), + DataHash.fromImprint(CborDeserializer.decodeByteString(data[0])), steps.map((step) => SparseMerkleTreePathStep.fromCBOR(step)), ); } - public static fromJSON(data: unknown): SparseMerkleTreePath { - if (!SparseMerkleTreePath.isJSON(data)) { - throw new InvalidJsonStructureError(); - } - - return new SparseMerkleTreePath( - DataHash.fromJSON(data.root), - data.steps.map((step: unknown) => SparseMerkleTreePathStep.fromJSON(step)), - ); - } - - public static isJSON(data: unknown): data is ISparseMerkleTreePathJson { - return ( - typeof data === 'object' && - data !== null && - 'root' in data && - typeof data.root === 'string' && - 'steps' in data && - Array.isArray(data.steps) && - data.steps.length > 0 - ); - } - public toCBOR(): Uint8Array { return CborSerializer.encodeArray( - this.root.toCBOR(), + CborSerializer.encodeByteString(this.root.imprint), CborSerializer.encodeArray(...this.steps.map((step: SparseMerkleTreePathStep) => step.toCBOR())), ); } - public toJSON(): ISparseMerkleTreePathJson { - return { - root: this.root.toJSON(), - steps: this.steps.map((step) => step.toJSON()), - }; - } - public toString(): string { return dedent` Merkle Tree Path diff --git a/src/smt/plain/SparseMerkleTreePathStep.ts b/src/smt/plain/SparseMerkleTreePathStep.ts index 17241648..bb47418d 100644 --- a/src/smt/plain/SparseMerkleTreePathStep.ts +++ b/src/smt/plain/SparseMerkleTreePathStep.ts @@ -1,15 +1,9 @@ -import { InvalidJsonStructureError } from '../../InvalidJsonStructureError.js'; import { BigintConverter } from '../../serialization/BigintConverter.js'; import { CborDeserializer } from '../../serialization/cbor/CborDeserializer.js'; import { CborSerializer } from '../../serialization/cbor/CborSerializer.js'; import { HexConverter } from '../../serialization/HexConverter.js'; import { dedent } from '../../util/StringUtils.js'; -export interface ISparseMerkleTreePathStepJson { - readonly data: string | null; - readonly path: string; -} - export class SparseMerkleTreePathStep { public constructor( public readonly path: bigint, @@ -33,25 +27,6 @@ export class SparseMerkleTreePathStep { ); } - public static fromJSON(data: unknown): SparseMerkleTreePathStep { - if (!SparseMerkleTreePathStep.isJSON(data)) { - throw new InvalidJsonStructureError(); - } - - return new SparseMerkleTreePathStep(BigInt(data.path), data.data ? HexConverter.decode(data.data) : null); - } - - public static isJSON(data: unknown): data is ISparseMerkleTreePathStepJson { - return ( - typeof data === 'object' && - data !== null && - 'path' in data && - typeof data.path === 'string' && - 'data' in data && - (data.data === null || typeof data.data === 'string') - ); - } - public toCBOR(): Uint8Array { return CborSerializer.encodeArray( CborSerializer.encodeByteString(BigintConverter.encode(this.path)), @@ -59,13 +34,6 @@ export class SparseMerkleTreePathStep { ); } - public toJSON(): ISparseMerkleTreePathStepJson { - return { - data: this._data ? HexConverter.encode(this._data) : null, - path: this.path.toString(), - }; - } - public toString(): string { return dedent` Merkle Tree Path Step diff --git a/src/smt/sum/SparseMerkleSumTreePath.ts b/src/smt/sum/SparseMerkleSumTreePath.ts index 04eb8635..68fb1d4a 100644 --- a/src/smt/sum/SparseMerkleSumTreePath.ts +++ b/src/smt/sum/SparseMerkleSumTreePath.ts @@ -7,6 +7,7 @@ import { InvalidJsonStructureError } from '../../InvalidJsonStructureError.js'; import { BigintConverter } from '../../serialization/BigintConverter.js'; import { CborDeserializer } from '../../serialization/cbor/CborDeserializer.js'; import { CborSerializer } from '../../serialization/cbor/CborSerializer.js'; +import { HexConverter } from '../../serialization/HexConverter.js'; import { dedent } from '../../util/StringUtils.js'; import { PathVerificationResult } from '../PathVerificationResult.js'; @@ -26,7 +27,7 @@ export class SparseMerkleSumTreePath { const steps = CborDeserializer.decodeArray(data[1]); return new SparseMerkleSumTreePath( - DataHash.fromCBOR(data[0]), + DataHash.fromImprint(CborDeserializer.decodeByteString(data[0])), steps.map((step) => SparseMerkleSumTreePathStep.fromCBOR(step)), ); } @@ -37,7 +38,7 @@ export class SparseMerkleSumTreePath { } return new SparseMerkleSumTreePath( - DataHash.fromJSON(data.root), + DataHash.fromImprint(HexConverter.decode(data.root)), data.steps.map((step: unknown) => SparseMerkleSumTreePathStep.fromJSON(step)), ); } @@ -56,18 +57,11 @@ export class SparseMerkleSumTreePath { public toCBOR(): Uint8Array { return CborSerializer.encodeArray( - this.root.toCBOR(), + CborSerializer.encodeByteString(this.root.imprint), CborSerializer.encodeArray(...this.steps.map((step) => step.toCBOR())), ); } - public toJSON(): ISparseMerkleSumTreePathJson { - return { - root: this.root.toJSON(), - steps: this.steps.map((step) => step.toJSON()), - }; - } - public toString(): string { return dedent` Merkle Tree Path diff --git a/src/smt/sum/SparseMerkleSumTreePathStep.ts b/src/smt/sum/SparseMerkleSumTreePathStep.ts index 41cf9486..a519fb06 100644 --- a/src/smt/sum/SparseMerkleSumTreePathStep.ts +++ b/src/smt/sum/SparseMerkleSumTreePathStep.ts @@ -73,14 +73,6 @@ export class SparseMerkleSumTreePathStep { ); } - public toJSON(): ISparseMerkleSumTreePathStepJson { - return { - data: this._data ? HexConverter.encode(this._data) : null, - path: this.path.toString(), - value: this.value.toString(), - }; - } - public toString(): string { return dedent` Merkle Tree Path Step diff --git a/src/transaction/MintTransaction.ts b/src/transaction/MintTransaction.ts index c7f2a386..05cc5fd5 100644 --- a/src/transaction/MintTransaction.ts +++ b/src/transaction/MintTransaction.ts @@ -59,12 +59,12 @@ export class MintTransaction implements ITransaction { ); } - public static async fromCBOR(bytes: Uint8Array): Promise { + public static fromCBOR(bytes: Uint8Array): Promise { const data = CborDeserializer.decodeArray(bytes); const aux = CborDeserializer.decodeArray(data[2]); return MintTransaction.create( - await PayToScriptHash.fromCBOR(data[0]), + PayToScriptHash.fromCBOR(data[0]), TokenId.fromCBOR(data[1]), TokenType.fromCBOR(aux[0]), CborDeserializer.decodeByteString(aux[1]), @@ -73,7 +73,12 @@ export class MintTransaction implements ITransaction { public calculateStateHash(): Promise { return new DataHasher(HashAlgorithm.SHA256) - .update(CborSerializer.encodeArray(this.sourceStateHash.toCBOR(), CborSerializer.encodeByteString(this.x))) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(this.sourceStateHash.imprint), + CborSerializer.encodeByteString(this.x), + ), + ) .digest(); } diff --git a/src/transaction/PayToScriptHash.ts b/src/transaction/PayToScriptHash.ts index db12eb8b..dc1e3b7c 100644 --- a/src/transaction/PayToScriptHash.ts +++ b/src/transaction/PayToScriptHash.ts @@ -8,62 +8,40 @@ import { HexConverter } from '../serialization/HexConverter.js'; import { areUint8ArraysEqual } from '../util/TypedArrayUtils.js'; export class PayToScriptHash { - private constructor( - private readonly hash: DataHash, - private readonly _checksum: Uint8Array, - ) { - this._checksum = new Uint8Array(_checksum); + private constructor(private readonly _bytes: Uint8Array) { + this._bytes = new Uint8Array(_bytes); } - public static async create(predicate: IPredicate): Promise { - const hash = await new DataHasher(HashAlgorithm.SHA256).update(predicate.toCBOR()).digest(); - return PayToScriptHash.createFromHash(hash); + public get bytes(): Uint8Array { + return new Uint8Array(this._bytes); } - public static fromCBOR(bytes: Uint8Array): Promise { - return PayToScriptHash.fromString(CborDeserializer.decodeTextString(bytes)); + public static async create(predicate: IPredicate): Promise { + const hash = await new DataHasher(HashAlgorithm.SHA256).update(predicate.toCBOR()).digest(); + return new PayToScriptHash(hash.data); } - public static async fromString(data: string): Promise { - if (!data.startsWith('alpha1')) { - throw new Error('Invalid PayToScriptHash string format.'); - } - - const hash = HexConverter.decode(data.slice(6, -8)); - const checksum = HexConverter.decode(data.slice(-8)); - - const calculatedChecksum = await new DataHasher(HashAlgorithm.SHA256) - .update(hash) - .digest() - .then((d) => d.imprint.slice(-4)); - - if (!areUint8ArraysEqual(checksum, calculatedChecksum)) { - throw new Error('Invalid PayToScriptHash checksum.'); - } - - return new PayToScriptHash(DataHash.fromImprint(hash), checksum); + public static fromBytes(bytes: Uint8Array): PayToScriptHash { + return new PayToScriptHash(bytes); } - private static async createFromHash(hash: DataHash): Promise { - return new PayToScriptHash( - hash, - // TODO: Replace with CRC32 checksum - await new DataHasher(HashAlgorithm.SHA256) - .update(hash.imprint) - .digest() - .then((d) => d.imprint.slice(-4)), - ); + public static fromCBOR(bytes: Uint8Array): PayToScriptHash { + return PayToScriptHash.fromBytes(CborDeserializer.decodeByteString(bytes)); } public equals(hash: PayToScriptHash): boolean { - return this.toString() === hash.toString(); + return areUint8ArraysEqual(this._bytes, hash._bytes); } public toCBOR(): Uint8Array { - return CborSerializer.encodeTextString(this.toString()); + return CborSerializer.encodeByteString(this._bytes); } + /** + * Returns a string representation of the PayToScriptHash. + * @returns The string representation. + */ public toString(): string { - return `alpha1${HexConverter.encode(this.hash.imprint)}${HexConverter.encode(this._checksum)}`; + return `PayToScriptHash[${HexConverter.encode(this._bytes)}]`; } } diff --git a/src/transaction/TokenId.ts b/src/transaction/TokenId.ts index 5be57f03..68b936db 100644 --- a/src/transaction/TokenId.ts +++ b/src/transaction/TokenId.ts @@ -23,10 +23,6 @@ export class TokenId { return new TokenId(CborDeserializer.decodeByteString(bytes)); } - public static fromJSON(input: string): TokenId { - return new TokenId(HexConverter.decode(input)); - } - public equals(o: unknown): boolean { if (this === o) { return true; @@ -51,11 +47,6 @@ export class TokenId { return CborSerializer.encodeByteString(this._bytes); } - /** Encode as a hex string for JSON. */ - public toJSON(): string { - return HexConverter.encode(this._bytes); - } - /** Convert instance to readable string */ public toString(): string { return `TokenId[${HexConverter.encode(this._bytes)}]`; diff --git a/src/transaction/TokenType.ts b/src/transaction/TokenType.ts index 1c3f252a..c306459f 100644 --- a/src/transaction/TokenType.ts +++ b/src/transaction/TokenType.ts @@ -19,20 +19,11 @@ export class TokenType { return new TokenType(CborDeserializer.decodeByteString(bytes)); } - public static fromJSON(input: string): TokenType { - return new TokenType(HexConverter.decode(input)); - } - /** CBOR serialization. */ public toCBOR(): Uint8Array { return CborSerializer.encodeByteString(this._bytes); } - /** Hex representation for JSON serialization. */ - public toJSON(): string { - return HexConverter.encode(this._bytes); - } - /** Convert instance to readable string */ public toString(): string { return `TokenType[${HexConverter.encode(this._bytes)}]`; diff --git a/src/transaction/TransferTransaction.ts b/src/transaction/TransferTransaction.ts index 89e087f5..21ab07a0 100644 --- a/src/transaction/TransferTransaction.ts +++ b/src/transaction/TransferTransaction.ts @@ -55,13 +55,13 @@ export class TransferTransaction implements ITransaction { return new TransferTransaction(sourceStateHash, owner, recipient, x, data); } - public static async fromCBOR(bytes: Uint8Array): Promise { + public static fromCBOR(bytes: Uint8Array): TransferTransaction { const data = CborDeserializer.decodeArray(bytes); return new TransferTransaction( - DataHash.fromCBOR(data[0]), + new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(data[0])), EncodedPredicate.fromCBOR(CborDeserializer.decodeByteString(data[1])), - await PayToScriptHash.fromCBOR(data[2]), + PayToScriptHash.fromCBOR(data[2]), CborDeserializer.decodeByteString(data[3]), CborDeserializer.decodeByteString(data[4]), ); @@ -69,7 +69,12 @@ export class TransferTransaction implements ITransaction { public calculateStateHash(): Promise { return new DataHasher(HashAlgorithm.SHA256) - .update(CborSerializer.encodeArray(this.sourceStateHash.toCBOR(), CborSerializer.encodeByteString(this._x))) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(this.sourceStateHash.imprint), + CborSerializer.encodeByteString(this._x), + ), + ) .digest(); } @@ -87,7 +92,7 @@ export class TransferTransaction implements ITransaction { public toCBOR(): Uint8Array { return CborSerializer.encodeArray( - this.sourceStateHash.toCBOR(), + CborSerializer.encodeByteString(this.sourceStateHash.data), CborSerializer.encodeByteString(this.lockScript.toCBOR()), this.recipient.toCBOR(), CborSerializer.encodeByteString(this._x), diff --git a/src/util/BitString.ts b/src/util/BitString.ts index 4d11607d..2376015d 100644 --- a/src/util/BitString.ts +++ b/src/util/BitString.ts @@ -1,6 +1,7 @@ import { DataHash } from '../crypto/hash/DataHash.js'; import { BigintConverter } from '../serialization/BigintConverter.js'; import { HexConverter } from '../serialization/HexConverter.js'; +import { StateId } from '../api/StateId.js'; export class BitString { /** @@ -18,11 +19,11 @@ export class BitString { /** * Creates a BitString from a DataHash imprint. - * @param data DataHash + * @param {StateId} stateId * @return {BitString} A BitString instance */ - public static fromStateId(data: DataHash): BitString { - return new BitString(data.imprint); + public static fromStateId(stateId: StateId): BitString { + return new BitString(stateId.bytes); } /** diff --git a/tests/unit/api/CertificationDataTest.ts b/tests/unit/api/CertificationDataTest.ts index 837c5696..ac9d985e 100644 --- a/tests/unit/api/CertificationDataTest.ts +++ b/tests/unit/api/CertificationDataTest.ts @@ -1,26 +1,28 @@ import { CertificationData } from '../../../src/api/CertificationData.js'; import { HexConverter } from '../../../src/serialization/HexConverter.js'; +import { MintTransaction } from '../../../src/transaction/MintTransaction.js'; +import { PayToScriptHash } from '../../../src/transaction/PayToScriptHash.js'; +import { TokenId } from '../../../src/transaction/TokenId.js'; +import { TokenType } from '../../../src/transaction/TokenType.js'; describe('CertificationData', () => { - it('should encode and decode to exactly same object', () => { - const certificationData = CertificationData.fromJSON({ - ownerPredicate: '8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - sourceStateHash: '00000000000000000000000000000000000000000000000000000000000000000000', - transactionHash: '00000000000000000000000000000000000000000000000000000000000000000000', - witness: - '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', - }); + it('should encode and decode to exactly same object', async () => { + const certificationData = await CertificationData.fromMintTransaction( + await MintTransaction.create( + PayToScriptHash.fromBytes(new Uint8Array(32)), + new TokenId(new Uint8Array(32)), + new TokenType(new Uint8Array(32)), + new Uint8Array(0), + ), + ); expect(HexConverter.encode(certificationData.toCBOR())).toStrictEqual( - '848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179858220000000000000000000000000000000000000000000000000000000000000000000058220000000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', + '8483014101582103b00b30dcd21feaa837132ccd4b7b9595f704c9714ac66eed085f52bc396f9050582080201eff2f0c27ea9c8433eb999b4fd0fa5bfb4fe47fa2690859f0c83651604e5820d3428d83066c996da3b3ec8a68851dfe1e0df6065bac29fce57a95b35360cecc584173f16f156b8d0a854274fb765d1158ce7dc591de4b7c503f3fb06d8f92e9d96164ea64082ac1616009842c154bdcdd974c18c831899632d023e43a840cc9ba9000', ); - expect(CertificationData.fromCBOR(certificationData.toCBOR())).toStrictEqual(certificationData); - expect(certificationData.toJSON()).toEqual({ - ownerPredicate: '8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - sourceStateHash: '00000000000000000000000000000000000000000000000000000000000000000000', - transactionHash: '00000000000000000000000000000000000000000000000000000000000000000000', - witness: - '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', - }); - expect(CertificationData.fromJSON(certificationData.toJSON())).toStrictEqual(certificationData); + const result = CertificationData.fromCBOR(certificationData.toCBOR()); + + expect(result.lockScript.toCBOR()).toStrictEqual(certificationData.lockScript.toCBOR()); + expect(result.sourceStateHash.imprint).toStrictEqual(certificationData.sourceStateHash.imprint); + expect(result.transactionHash.imprint).toStrictEqual(certificationData.transactionHash.imprint); + expect(HexConverter.encode(result.unlockScript)).toStrictEqual(HexConverter.encode(certificationData.unlockScript)); }); }); diff --git a/tests/unit/api/CertificationRequestTest.ts b/tests/unit/api/CertificationRequestTest.ts index ea0df781..e0fab722 100644 --- a/tests/unit/api/CertificationRequestTest.ts +++ b/tests/unit/api/CertificationRequestTest.ts @@ -1,83 +1,47 @@ import { CertificationData } from '../../../src/api/CertificationData.js'; import { CertificationRequest } from '../../../src/api/CertificationRequest.js'; +import { CborSerializer } from '../../../src/serialization/cbor/CborSerializer.js'; import { HexConverter } from '../../../src/serialization/HexConverter.js'; describe('CertificationRequest', () => { - it('should encode and decode JSON to exactly same object', async () => { - let request = await CertificationRequest.create( - CertificationData.fromJSON({ - ownerPredicate: '8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - sourceStateHash: '00000000000000000000000000000000000000000000000000000000000000000000', - transactionHash: '00000000000000000000000000000000000000000000000000000000000000000000', - witness: - '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', - }), - ); - - expect(request.toJSON()).toEqual({ - certificationData: { - ownerPredicate: '8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - sourceStateHash: '00000000000000000000000000000000000000000000000000000000000000000000', - transactionHash: '00000000000000000000000000000000000000000000000000000000000000000000', - witness: - '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', - }, - receipt: false, - stateId: '0000e63bd8ab7ed68af6d65a2f4c7658122e85831b30c5517a5db76ce4a72df74680', - }); - - request = await CertificationRequest.create( - CertificationData.fromJSON({ - ownerPredicate: '8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - sourceStateHash: '00000000000000000000000000000000000000000000000000000000000000000000', - transactionHash: '00000000000000000000000000000000000000000000000000000000000000000000', - witness: - '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', - }), - true, - ); - - expect(request.toJSON()).toEqual({ - certificationData: { - ownerPredicate: '8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - sourceStateHash: '00000000000000000000000000000000000000000000000000000000000000000000', - transactionHash: '00000000000000000000000000000000000000000000000000000000000000000000', - witness: - '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', - }, - receipt: true, - stateId: '0000e63bd8ab7ed68af6d65a2f4c7658122e85831b30c5517a5db76ce4a72df74680', - }); - }); - it('should encode and decode CBOR to exactly same object', async () => { let request = await CertificationRequest.create( - CertificationData.fromJSON({ - ownerPredicate: '8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - sourceStateHash: '00000000000000000000000000000000000000000000000000000000000000000000', - transactionHash: '00000000000000000000000000000000000000000000000000000000000000000000', - witness: - '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', - }), + CertificationData.fromCBOR( + CborSerializer.encodeArray( + HexConverter.decode('8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), + CborSerializer.encodeByteString(new Uint8Array(32)), + CborSerializer.encodeByteString(new Uint8Array(32)), + CborSerializer.encodeByteString( + HexConverter.decode( + '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', + ), + ), + ), + ), ); expect(HexConverter.encode(request.toCBOR())).toEqual( - '8458220000e63bd8ab7ed68af6d65a2f4c7658122e85831b30c5517a5db76ce4a72df74680848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179858220000000000000000000000000000000000000000000000000000000000000000000058220000000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01f400', + '845820e63bd8ab7ed68af6d65a2f4c7658122e85831b30c5517a5db76ce4a72df74680848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798582000000000000000000000000000000000000000000000000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01f400', ); request = await CertificationRequest.create( - CertificationData.fromJSON({ - ownerPredicate: '8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - sourceStateHash: '00000000000000000000000000000000000000000000000000000000000000000000', - transactionHash: '00000000000000000000000000000000000000000000000000000000000000000000', - witness: - '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', - }), + CertificationData.fromCBOR( + CborSerializer.encodeArray( + HexConverter.decode('8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), + CborSerializer.encodeByteString(new Uint8Array(32)), + CborSerializer.encodeByteString(new Uint8Array(32)), + CborSerializer.encodeByteString( + HexConverter.decode( + '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', + ), + ), + ), + ), true, ); expect(HexConverter.encode(request.toCBOR())).toEqual( - '8458220000e63bd8ab7ed68af6d65a2f4c7658122e85831b30c5517a5db76ce4a72df74680848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179858220000000000000000000000000000000000000000000000000000000000000000000058220000000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01f500', + '845820e63bd8ab7ed68af6d65a2f4c7658122e85831b30c5517a5db76ce4a72df74680848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798582000000000000000000000000000000000000000000000000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01f500', ); }); }); diff --git a/tests/unit/api/CertificationResponseTest.ts b/tests/unit/api/CertificationResponseTest.ts index d2444c89..63d71f2f 100644 --- a/tests/unit/api/CertificationResponseTest.ts +++ b/tests/unit/api/CertificationResponseTest.ts @@ -2,6 +2,7 @@ import { CertificationData } from '../../../src/api/CertificationData.js'; import { CertificationResponse, CertificationStatus } from '../../../src/api/CertificationResponse.js'; import { SigningService } from '../../../src/crypto/secp256k1/SigningService.js'; import { InvalidJsonStructureError } from '../../../src/InvalidJsonStructureError.js'; +import { CborSerializer } from '../../../src/serialization/cbor/CborSerializer.js'; import { HexConverter } from '../../../src/serialization/HexConverter.js'; describe('CertificationResponse', () => { @@ -27,13 +28,18 @@ describe('CertificationResponse', () => { response = await CertificationResponse.createWithReceipt( signingService, - CertificationData.fromJSON({ - ownerPredicate: '8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - sourceStateHash: '00000000000000000000000000000000000000000000000000000000000000000000', - transactionHash: '00000000000000000000000000000000000000000000000000000000000000000000', - witness: - '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', - }), + CertificationData.fromCBOR( + CborSerializer.encodeArray( + HexConverter.decode('8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), + CborSerializer.encodeByteString(new Uint8Array(32)), + CborSerializer.encodeByteString(new Uint8Array(32)), + CborSerializer.encodeByteString( + HexConverter.decode( + '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', + ), + ), + ), + ), CertificationStatus.SUCCESS, ); @@ -72,13 +78,18 @@ describe('CertificationResponse', () => { new Uint8Array(HexConverter.decode('0000000000000000000000000000000000000000000000000000000000000001')), ); - const certificationData = CertificationData.fromJSON({ - ownerPredicate: '8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - sourceStateHash: '00000000000000000000000000000000000000000000000000000000000000000000', - transactionHash: '00000000000000000000000000000000000000000000000000000000000000000000', - witness: - '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', - }); + const certificationData = CertificationData.fromCBOR( + CborSerializer.encodeArray( + HexConverter.decode('8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), + CborSerializer.encodeByteString(new Uint8Array(32)), + CborSerializer.encodeByteString(new Uint8Array(32)), + CborSerializer.encodeByteString( + HexConverter.decode( + '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01', + ), + ), + ), + ); let response = await CertificationResponse.createWithReceipt( signingService, certificationData, @@ -92,13 +103,18 @@ describe('CertificationResponse', () => { await expect(response.verifyReceipt(certificationData)).resolves.toBe(true); // Test with wrong signature should fail verification - const invalidCertificationData = CertificationData.fromJSON({ - ownerPredicate: '8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - sourceStateHash: '00000000000000000000000000000000000000000000000000000000000000000000', - transactionHash: '00000000000000000000000000000000000000000000000000000000000000000000', - witness: - '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a00', - }); + const invalidCertificationData = CertificationData.fromCBOR( + CborSerializer.encodeArray( + HexConverter.decode('8301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'), + CborSerializer.encodeByteString(new Uint8Array(32)), + CborSerializer.encodeByteString(new Uint8Array(32)), + CborSerializer.encodeByteString( + HexConverter.decode( + '8c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a00', + ), + ), + ), + ); await expect(response.verifyReceipt(invalidCertificationData)).resolves.toBe(false); diff --git a/tests/unit/api/InclusionProofTest.ts b/tests/unit/api/InclusionProofTest.ts index b01ceee1..d94bfc15 100644 --- a/tests/unit/api/InclusionProofTest.ts +++ b/tests/unit/api/InclusionProofTest.ts @@ -69,28 +69,6 @@ describe('InclusionProof', () => { trustBase = createRootTrustBase(signingService.publicKey); }); - it('should encode and decode json', () => { - const inclusionProof = new InclusionProof( - merkleTreePath, - CertificationData.fromCBOR(certificationData.toCBOR()), - unicityCertificate, - ); - expect(inclusionProof.toJSON()).toEqual({ - certificationData: certificationData.toJSON(), - merkleTreePath: merkleTreePath.toJSON(), - unicityCertificate: unicityCertificate.toJSON(), - }); - - expect(InclusionProof.fromJSON(inclusionProof.toJSON())).toStrictEqual(inclusionProof); - expect( - InclusionProof.fromJSON({ - certificationData: null, - merkleTreePath: merkleTreePath.toJSON(), - unicityCertificate: unicityCertificate.toJSON(), - }), - ).toStrictEqual(new InclusionProof(merkleTreePath, null, unicityCertificate)); - }); - it('should encode and decode cbor', () => { const inclusionProof = new InclusionProof( merkleTreePath, @@ -134,17 +112,20 @@ describe('InclusionProof', () => { const invalidTransactionHashInclusionProof = new InclusionProof( merkleTreePath, - CertificationData.fromJSON({ - ownerPredicate: HexConverter.encode(certificationData.lockScript.toCBOR()), - sourceStateHash: certificationData.sourceStateHash.toJSON(), - transactionHash: DataHash.fromImprint( - HexConverter.decode('00000000000000000000000000000000000000000000000000000000000000000001'), - ).toJSON(), - witness: HexConverter.encode(certificationData.unlockScript), - }), + CertificationData.fromCBOR( + CborSerializer.encodeArray( + certificationData.lockScript.toCBOR(), + CborSerializer.encodeByteString(certificationData.sourceStateHash.data), + CborSerializer.encodeByteString( + DataHash.fromImprint( + HexConverter.decode('00000000000000000000000000000000000000000000000000000000000000000001'), + ).data, + ), + CborSerializer.encodeByteString(certificationData.unlockScript), + ), + ), unicityCertificate, ); - await expect( InclusionProofVerificationRule.verify( trustBase, @@ -158,12 +139,14 @@ describe('InclusionProof', () => { it('verification fails with invalid transaction hash', async () => { const inclusionProof = new InclusionProof( merkleTreePath, - CertificationData.fromJSON({ - ownerPredicate: HexConverter.encode(certificationData.lockScript.toCBOR()), - sourceStateHash: certificationData.sourceStateHash.toJSON(), - transactionHash: certificationData.transactionHash.toJSON(), - witness: HexConverter.encode(new Uint8Array(65)), - }), + CertificationData.fromCBOR( + CborSerializer.encodeArray( + certificationData.lockScript.toCBOR(), + CborSerializer.encodeByteString(certificationData.sourceStateHash.data), + CborSerializer.encodeByteString(certificationData.transactionHash.data), + CborSerializer.encodeByteString(new Uint8Array(65)), + ), + ), unicityCertificate, ); diff --git a/tests/unit/api/StateIdTest.ts b/tests/unit/api/StateIdTest.ts index 7647a51c..5e1d2318 100644 --- a/tests/unit/api/StateIdTest.ts +++ b/tests/unit/api/StateIdTest.ts @@ -1,15 +1,24 @@ import { StateId } from '../../../src/api/StateId.js'; import { HexConverter } from '../../../src/serialization/HexConverter.js'; +import { MintTransaction } from '../../../src/transaction/MintTransaction.js'; +import { PayToScriptHash } from '../../../src/transaction/PayToScriptHash.js'; +import { TokenId } from '../../../src/transaction/TokenId.js'; +import { TokenType } from '../../../src/transaction/TokenType.js'; describe('StateId', () => { - it('should encode and decode to exactly same object', () => { - const stateId = StateId.fromJSON('0000c2db43b488c83f1000ef6e6fb9d12fba5e1423cefd1909d5eb2018d3855a9323'); + it('should encode and decode to exactly same object', async () => { + const stateId = await StateId.fromTransaction( + await MintTransaction.create( + PayToScriptHash.fromBytes(new Uint8Array(32)), + new TokenId(new Uint8Array(32)), + new TokenType(new Uint8Array(32)), + new Uint8Array(0), + ), + ); expect(HexConverter.encode(stateId.toCBOR())).toStrictEqual( - '58220000c2db43b488c83f1000ef6e6fb9d12fba5e1423cefd1909d5eb2018d3855a9323', + '58202d3f8a3769426d10ccafa08f332955faf34ba22873ae096d0ee2b19303b2d171', ); expect(StateId.fromCBOR(stateId.toCBOR())).toStrictEqual(stateId); - expect(stateId.toJSON()).toStrictEqual('0000c2db43b488c83f1000ef6e6fb9d12fba5e1423cefd1909d5eb2018d3855a9323'); - expect(StateId.fromJSON(stateId.toJSON())).toStrictEqual(stateId); }); }); diff --git a/tests/unit/smt/plain/SparseMerkleTreePathTest.ts b/tests/unit/smt/plain/SparseMerkleTreePathTest.ts index de1b6f01..c51a37bc 100644 --- a/tests/unit/smt/plain/SparseMerkleTreePathTest.ts +++ b/tests/unit/smt/plain/SparseMerkleTreePathTest.ts @@ -1,4 +1,6 @@ import { DataHash } from '../../../../src/crypto/hash/DataHash.js'; +import { BigintConverter } from '../../../../src/serialization/BigintConverter.js'; +import { CborSerializer } from '../../../../src/serialization/cbor/CborSerializer.js'; import { HexConverter } from '../../../../src/serialization/HexConverter.js'; import { SparseMerkleTreePath } from '../../../../src/smt/plain/SparseMerkleTreePath.js'; import { SparseMerkleTreePathStep } from '../../../../src/smt/plain/SparseMerkleTreePathStep.js'; @@ -13,47 +15,86 @@ describe('SparseMerkleTreePath', () => { '8258220000000000000000000000000000000000000000000000000000000000000000000081824043010203', ); expect(SparseMerkleTreePath.fromCBOR(path.toCBOR())).toStrictEqual(path); - expect(path.toJSON()).toEqual({ - root: '00000000000000000000000000000000000000000000000000000000000000000000', - steps: [{ data: '010203', path: '0' }], - }); - expect(SparseMerkleTreePath.fromJSON(path.toJSON())).toStrictEqual(path); }); it('should verify inclusion path', async () => { - const path = SparseMerkleTreePath.fromJSON({ - root: '0000e9748bbd0c45fc357ffe7c221c7db1ef02f589680d8b0a370b48a669435bde13', - steps: [ - { data: '76616c756535', path: '69' }, - { data: '8471f8ea3c9a0e50627df4c72d9bd5affbdc12050ee7f4250974ed64949f3b0f', path: '4' }, - { data: '66507538ce0fae31018cfc7b01841b5308e7e44306445710acee947ec4a4b2cd', path: '1' }, - ], - }); + const path = SparseMerkleTreePath.fromCBOR( + CborSerializer.encodeArray( + CborSerializer.encodeByteString( + HexConverter.decode('0000e9748bbd0c45fc357ffe7c221c7db1ef02f589680d8b0a370b48a669435bde13'), + ), + CborSerializer.encodeArray( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigintConverter.encode(69n)), + CborSerializer.encodeByteString(HexConverter.decode('76616c756535')), + ), + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigintConverter.encode(4n)), + CborSerializer.encodeByteString( + HexConverter.decode('8471f8ea3c9a0e50627df4c72d9bd5affbdc12050ee7f4250974ed64949f3b0f'), + ), + ), + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigintConverter.encode(1n)), + CborSerializer.encodeByteString( + HexConverter.decode('66507538ce0fae31018cfc7b01841b5308e7e44306445710acee947ec4a4b2cd'), + ), + ), + ), + ), + ); expect(await path.verify(0b100010100n)).toEqual({ isPathIncluded: true, isPathValid: true, isSuccessful: true }); }); it('should verify non inclusion path', async () => { - const path = SparseMerkleTreePath.fromJSON({ - root: '0000e9748bbd0c45fc357ffe7c221c7db1ef02f589680d8b0a370b48a669435bde13', - steps: [ - { data: '76616c756535', path: '69' }, - { data: '8471f8ea3c9a0e50627df4c72d9bd5affbdc12050ee7f4250974ed64949f3b0f', path: '4' }, - { data: '66507538ce0fae31018cfc7b01841b5308e7e44306445710acee947ec4a4b2cd', path: '1' }, - ], - }); + const path = SparseMerkleTreePath.fromCBOR( + CborSerializer.encodeArray( + CborSerializer.encodeByteString( + HexConverter.decode('0000e9748bbd0c45fc357ffe7c221c7db1ef02f589680d8b0a370b48a669435bde13'), + ), + CborSerializer.encodeArray( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigintConverter.encode(69n)), + CborSerializer.encodeByteString(HexConverter.decode('76616c756535')), + ), + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigintConverter.encode(4n)), + CborSerializer.encodeByteString( + HexConverter.decode('8471f8ea3c9a0e50627df4c72d9bd5affbdc12050ee7f4250974ed64949f3b0f'), + ), + ), + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigintConverter.encode(1n)), + CborSerializer.encodeByteString( + HexConverter.decode('66507538ce0fae31018cfc7b01841b5308e7e44306445710acee947ec4a4b2cd'), + ), + ), + ), + ), + ); expect(await path.verify(0b1000000n)).toEqual({ isPathIncluded: false, isPathValid: true, isSuccessful: false }); }); it('should verify empty tree path', async () => { - const path = SparseMerkleTreePath.fromJSON({ - root: '00001e54402898172f2948615fb17627733abbd120a85381c624ad060d28321be672', - steps: [ - { data: null, path: '1' }, - { data: null, path: '1' }, - ], - }); + const path = SparseMerkleTreePath.fromCBOR( + CborSerializer.encodeArray( + CborSerializer.encodeByteString( + HexConverter.decode('00001e54402898172f2948615fb17627733abbd120a85381c624ad060d28321be672'), + ), + CborSerializer.encodeArray( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigintConverter.encode(1n)), + CborSerializer.encodeNull(), + ), + CborSerializer.encodeArray( + CborSerializer.encodeByteString(BigintConverter.encode(1n)), + CborSerializer.encodeNull(), + ), + ), + ), + ); expect(await path.verify(101n)).toEqual({ isPathIncluded: false, isPathValid: true, isSuccessful: false }); }); diff --git a/tests/unit/smt/plain/SparseMerkleTreeTest.ts b/tests/unit/smt/plain/SparseMerkleTreeTest.ts index 878dd23b..caffe795 100644 --- a/tests/unit/smt/plain/SparseMerkleTreeTest.ts +++ b/tests/unit/smt/plain/SparseMerkleTreeTest.ts @@ -2,6 +2,7 @@ import { DataHash } from '../../../../src/crypto/hash/DataHash.js'; import { DataHasherFactory } from '../../../../src/crypto/hash/DataHasherFactory.js'; import { HashAlgorithm } from '../../../../src/crypto/hash/HashAlgorithm.js'; import { NodeDataHasher } from '../../../../src/crypto/hash/NodeDataHasher.js'; +import { HexConverter } from '../../../../src/serialization/HexConverter.js'; import { FinalizedLeafBranch } from '../../../../src/smt/plain/FinalizedLeafBranch.js'; import { FinalizedNodeBranch } from '../../../../src/smt/plain/FinalizedNodeBranch.js'; import { PendingLeafBranch } from '../../../../src/smt/plain/PendingLeafBranch.js'; @@ -45,7 +46,9 @@ describe('Sparse Merkle Tree tests', function () { const root = await smt.calculateRoot(); - expect(root.hash.toJSON()).toStrictEqual('0000d2fcbfec1b01fc404a03776b7b351786bf91bf94321a006c23376ccb1807faf8'); + expect(root.hash.imprint).toStrictEqual( + HexConverter.decode('0000d2fcbfec1b01fc404a03776b7b351786bf91bf94321a006c23376ccb1807faf8'), + ); }); it('get path', async () => { diff --git a/tests/unit/smt/sum/SparseMerkleSumTreePathTest.ts b/tests/unit/smt/sum/SparseMerkleSumTreePathTest.ts index 433be8ab..e7e6d03f 100644 --- a/tests/unit/smt/sum/SparseMerkleSumTreePathTest.ts +++ b/tests/unit/smt/sum/SparseMerkleSumTreePathTest.ts @@ -13,11 +13,6 @@ describe('SparseMerkleTreePath', () => { '8258220000000000000000000000000000000000000000000000000000000000000000000081834043010203410a', ); expect(SparseMerkleSumTreePath.fromCBOR(path.toCBOR())).toStrictEqual(path); - expect(path.toJSON()).toEqual({ - root: '00000000000000000000000000000000000000000000000000000000000000000000', - steps: [{ data: '010203', path: '0', value: '10' }], - }); - expect(SparseMerkleSumTreePath.fromJSON(path.toJSON())).toStrictEqual(path); }); it('should verify inclusion path', async () => { diff --git a/tests/unit/transaction/PayToScriptHashTest.ts b/tests/unit/transaction/PayToScriptHashTest.ts deleted file mode 100644 index f253fba6..00000000 --- a/tests/unit/transaction/PayToScriptHashTest.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SigningService } from '../../../src/crypto/secp256k1/SigningService.js'; -import { PayToPublicKeyPredicate } from '../../../src/predicate/builtin/PayToPublicKeyPredicate.js'; -import { PayToScriptHash } from '../../../src/transaction/PayToScriptHash.js'; - -describe('PayToScriptHash', () => { - it('should correctly result initial hash with fromString', async () => { - const predicate = PayToPublicKeyPredicate.create(new SigningService(SigningService.generatePrivateKey())); - const scriptHash = await PayToScriptHash.create(predicate); - await expect(PayToScriptHash.fromString(scriptHash.toString()).then((hash) => hash.toString())).resolves.toEqual( - scriptHash.toString(), - ); - }); -}); diff --git a/tests/utils/TransitionFlow.ts b/tests/utils/TransitionFlow.ts index 23c8a1d8..e55aaa37 100644 --- a/tests/utils/TransitionFlow.ts +++ b/tests/utils/TransitionFlow.ts @@ -56,7 +56,7 @@ export const transitionFlowTest = (client: StateTransitionClient, trustBase: Roo const transferTransaction = await TransferTransaction.create( token, predicate, - await PayToScriptHash.fromString(receiverScriptHash.toString()), + PayToScriptHash.fromBytes(receiverScriptHash.bytes), crypto.getRandomValues(new Uint8Array(32)), CborSerializer.encodeArray(), ); From d2eb230f3088bb113d11f5b7a614036f9cde064b Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Tue, 3 Feb 2026 16:20:28 +0200 Subject: [PATCH 08/15] Remove unused imports --- src/crypto/hash/DataHash.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/crypto/hash/DataHash.ts b/src/crypto/hash/DataHash.ts index d70ee283..7bcd75bb 100644 --- a/src/crypto/hash/DataHash.ts +++ b/src/crypto/hash/DataHash.ts @@ -1,7 +1,5 @@ import { HashAlgorithm } from './HashAlgorithm.js'; import { HashError } from './HashError.js'; -import { CborDeserializer } from '../../serialization/cbor/CborDeserializer.js'; -import { CborSerializer } from '../../serialization/cbor/CborSerializer.js'; import { HexConverter } from '../../serialization/HexConverter.js'; import { areUint8ArraysEqual } from '../../util/TypedArrayUtils.js'; From 8eccb2286f0c56125aafdad8afb2420747c0e66b Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Tue, 3 Feb 2026 16:22:27 +0200 Subject: [PATCH 09/15] Remove unused imports, fix unsupported hash algorithm error --- src/crypto/hash/UnsupportedHashAlgorithmError.ts | 2 +- src/transaction/CertifiedTransferTransaction.ts | 7 ++----- src/transaction/PayToScriptHash.ts | 1 - src/transaction/Token.ts | 2 +- src/util/BitString.ts | 3 +-- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/crypto/hash/UnsupportedHashAlgorithmError.ts b/src/crypto/hash/UnsupportedHashAlgorithmError.ts index c89b17e1..d60a2d71 100644 --- a/src/crypto/hash/UnsupportedHashAlgorithmError.ts +++ b/src/crypto/hash/UnsupportedHashAlgorithmError.ts @@ -2,7 +2,7 @@ import { HashAlgorithm } from './HashAlgorithm.js'; export class UnsupportedHashAlgorithmError extends Error { public constructor(algorithm: HashAlgorithm) { - super(`Unsupported hash algorithm: ${algorithm}`); + super(`Unsupported hash algorithm: ${algorithm.name}`); this.name = 'UnsupportedHashAlgorithm'; } diff --git a/src/transaction/CertifiedTransferTransaction.ts b/src/transaction/CertifiedTransferTransaction.ts index e5ba2ffd..29b0b9e1 100644 --- a/src/transaction/CertifiedTransferTransaction.ts +++ b/src/transaction/CertifiedTransferTransaction.ts @@ -34,12 +34,9 @@ export class CertifiedTransferTransaction implements ITransaction { return this.transaction.x; } - public static async fromCBOR(bytes: Uint8Array): Promise { + public static fromCBOR(bytes: Uint8Array): CertifiedTransferTransaction { const data = CborDeserializer.decodeArray(bytes); - return new CertifiedTransferTransaction( - await TransferTransaction.fromCBOR(data[0]), - InclusionProof.fromCBOR(data[1]), - ); + return new CertifiedTransferTransaction(TransferTransaction.fromCBOR(data[0]), InclusionProof.fromCBOR(data[1])); } public calculateStateHash(): Promise { diff --git a/src/transaction/PayToScriptHash.ts b/src/transaction/PayToScriptHash.ts index dc1e3b7c..034d488f 100644 --- a/src/transaction/PayToScriptHash.ts +++ b/src/transaction/PayToScriptHash.ts @@ -1,4 +1,3 @@ -import { DataHash } from '../crypto/hash/DataHash.js'; import { DataHasher } from '../crypto/hash/DataHasher.js'; import { HashAlgorithm } from '../crypto/hash/HashAlgorithm.js'; import { IPredicate } from '../predicate/IPredicate.js'; diff --git a/src/transaction/Token.ts b/src/transaction/Token.ts index fe648a1c..96a16cb5 100644 --- a/src/transaction/Token.ts +++ b/src/transaction/Token.ts @@ -37,7 +37,7 @@ export class Token { return new Token( await CertifiedMintTransaction.fromCBOR(data[0]), - await Promise.all(transactions.map((transaction) => CertifiedTransferTransaction.fromCBOR(transaction))), + transactions.map((transaction) => CertifiedTransferTransaction.fromCBOR(transaction)), ); } diff --git a/src/util/BitString.ts b/src/util/BitString.ts index 2376015d..a8632a2e 100644 --- a/src/util/BitString.ts +++ b/src/util/BitString.ts @@ -1,7 +1,6 @@ -import { DataHash } from '../crypto/hash/DataHash.js'; +import { StateId } from '../api/StateId.js'; import { BigintConverter } from '../serialization/BigintConverter.js'; import { HexConverter } from '../serialization/HexConverter.js'; -import { StateId } from '../api/StateId.js'; export class BitString { /** From 88ff652f6a8f5cf7f74c5ba646da5b7385a0d4cf Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Thu, 5 Feb 2026 17:58:26 +0200 Subject: [PATCH 10/15] #96 Update readme, create examples, improve usability --- README.md | 15 +- examples/mint/config.json | 3 + examples/mint/index.ts | 50 ++++++ examples/split/CustomPaymentData.ts | 24 +++ examples/split/CustomSplitPaymentData.ts | 24 +++ examples/split/config.json | 3 + examples/split/index.ts | 151 ++++++++++++++++++ examples/transfer/config.json | 5 + examples/transfer/index.ts | 67 ++++++++ examples/trust-base.json | 20 +++ package.json | 4 +- src/payment/SplitReason.ts | 1 + src/payment/TokenSplit.ts | 4 +- .../BuiltInPredicateVerifierFactory.ts | 22 ++- .../PayToPublicKeyPredicateVerifier.ts | 2 + .../verification/IPredicateVerifier.ts | 2 + .../verification/IPredicateVerifierFactory.ts | 3 + .../verification/PredicateVerifier.ts | 19 ++- tests/functional/TestAggregatorClient.ts | 28 ++-- tests/functional/payment/SplitBuilderTest.ts | 6 +- tests/unit/api/InclusionProofTest.ts | 21 +-- tests/utils/TransitionFlow.ts | 6 +- tsconfig.eslint.json | 3 +- 23 files changed, 415 insertions(+), 68 deletions(-) create mode 100644 examples/mint/config.json create mode 100644 examples/mint/index.ts create mode 100644 examples/split/CustomPaymentData.ts create mode 100644 examples/split/CustomSplitPaymentData.ts create mode 100644 examples/split/config.json create mode 100644 examples/split/index.ts create mode 100644 examples/transfer/config.json create mode 100644 examples/transfer/index.ts create mode 100644 examples/trust-base.json diff --git a/README.md b/README.md index ae9b5943..39999891 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ # State Transition SDK -An SDK for managing assets on the Unicity Protocol, supporting off-chain state with on-chain security guarantees. - ## Overview The State Transition SDK is a TypeScript library that provides an off-chain token transaction framework. Tokens are managed, stored, and transferred off-chain with only cryptographic commitments published on-chain, ensuring privacy while preventing double-spending through single-spend proofs. - +This is a low-level SDK, that supports transferring tokens, making payments, and splitting tokens. In this system, tokens are self-contained entities containing complete transaction history and cryptographic proofs attesting to their current state (ownership, value, etc.). State transitions are verified through consultation with blockchain infrastructure (Unicity) to produce proof of single spend. ### Key Features @@ -129,16 +127,11 @@ npm run lint:fix ## Examples -### Minting Tokens - -Note that the examples here are using some utility functions and classes that are defined below in a separate section. - -```typescript -``` +### Minting Tokens +`examples/mint/index.ts` ### Token Transfer - -### Checking Token Status +`examples/transfer/index.ts` ## Unicity Signature Standard diff --git a/examples/mint/config.json b/examples/mint/config.json new file mode 100644 index 00000000..7c8fe61d --- /dev/null +++ b/examples/mint/config.json @@ -0,0 +1,3 @@ +{ + "ownerPrivateKey": "0202020202020202020202020202020202020202020202020202020202020202" +} \ No newline at end of file diff --git a/examples/mint/index.ts b/examples/mint/index.ts new file mode 100644 index 00000000..56e90aee --- /dev/null +++ b/examples/mint/index.ts @@ -0,0 +1,50 @@ +import { AggregatorClient } from '../../src/api/AggregatorClient.js'; +import { RootTrustBase } from '../../src/api/bft/RootTrustBase.js'; +import { CertificationData } from '../../src/api/CertificationData.js'; +import { SigningService } from '../../src/crypto/secp256k1/SigningService.js'; +import { PayToPublicKeyPredicate } from '../../src/predicate/builtin/PayToPublicKeyPredicate.js'; +import { PredicateVerifier } from '../../src/predicate/verification/PredicateVerifier.js'; +import { CborSerializer } from '../../src/serialization/cbor/CborSerializer.js'; +import { HexConverter } from '../../src/serialization/HexConverter.js'; +import { StateTransitionClient } from '../../src/StateTransitionClient.js'; +import { MintTransaction } from '../../src/transaction/MintTransaction.js'; +import { PayToScriptHash } from '../../src/transaction/PayToScriptHash.js'; +import { Token } from '../../src/transaction/Token.js'; +import { TokenId } from '../../src/transaction/TokenId.js'; +import { TokenType } from '../../src/transaction/TokenType.js'; +import { waitInclusionProof } from '../../src/util/InclusionProofUtils.js'; +import trustBaseJson from '../trust-base.json' with { type: 'json' }; +import config from './config.json' with { type: 'json' }; + +const aggregatorClient = new AggregatorClient('http://localhost:3000'); +const trustBase = RootTrustBase.fromJSON(trustBaseJson); + +const client = new StateTransitionClient(aggregatorClient); + +const predicateVerifier = PredicateVerifier.create(); + +const ownerPrivateKey = HexConverter.decode(config.ownerPrivateKey); +const ownerSigningService = new SigningService(ownerPrivateKey); +const ownerPredicate = PayToPublicKeyPredicate.create(ownerSigningService); + +const mintTransaction = await MintTransaction.create( + await PayToScriptHash.create(ownerPredicate), + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + new TokenType(crypto.getRandomValues(new Uint8Array(32))), + CborSerializer.encodeTextString('My custom data'), +); +const certificationData = await CertificationData.fromMintTransaction(mintTransaction); + +await client.submitCertificationRequest(certificationData); + +const token = await Token.mint( + trustBase, + predicateVerifier, + await mintTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), + ), +); + +console.log(HexConverter.encode(token.toCBOR())); diff --git a/examples/split/CustomPaymentData.ts b/examples/split/CustomPaymentData.ts new file mode 100644 index 00000000..796527ec --- /dev/null +++ b/examples/split/CustomPaymentData.ts @@ -0,0 +1,24 @@ +import { PaymentAssetCollection } from '../../src/payment/asset/PaymentAssetCollection.js'; +import { IPaymentData } from '../../src/payment/IPaymentData.js'; +import { CborDeserializer } from '../../src/serialization/cbor/CborDeserializer.js'; +import { CborSerializer } from '../../src/serialization/cbor/CborSerializer.js'; + +export class CustomPaymentData implements IPaymentData { + public constructor( + public readonly assets: PaymentAssetCollection, + public readonly otherData: string, + ) {} + + public static fromCBOR(bytes: Uint8Array): Promise { + const data = CborDeserializer.decodeArray(bytes); + return Promise.resolve( + new CustomPaymentData(PaymentAssetCollection.fromCBOR(data[0]), CborDeserializer.decodeTextString(data[1])), + ); + } + + public toCBOR(): Promise { + return Promise.resolve( + CborSerializer.encodeArray(this.assets.toCBOR(), CborSerializer.encodeTextString(this.otherData)), + ); + } +} diff --git a/examples/split/CustomSplitPaymentData.ts b/examples/split/CustomSplitPaymentData.ts new file mode 100644 index 00000000..8314b798 --- /dev/null +++ b/examples/split/CustomSplitPaymentData.ts @@ -0,0 +1,24 @@ +import { PaymentAssetCollection } from '../../src/payment/asset/PaymentAssetCollection.js'; +import { ISplitPaymentData } from '../../src/payment/ISplitPaymentData.js'; +import { SplitReason } from '../../src/payment/SplitReason.js'; +import { CborDeserializer } from '../../src/serialization/cbor/CborDeserializer.js'; +import { CborSerializer } from '../../src/serialization/cbor/CborSerializer.js'; + +export class CustomSplitPaymentData implements ISplitPaymentData { + public constructor( + public readonly assets: PaymentAssetCollection, + public readonly reason: SplitReason, + ) {} + + public static async fromCBOR(bytes: Uint8Array): Promise { + const data = CborDeserializer.decodeArray(bytes); + + return Promise.resolve( + new CustomSplitPaymentData(PaymentAssetCollection.fromCBOR(data[0]), await SplitReason.fromCBOR(data[1])), + ); + } + + public toCBOR(): Promise { + return Promise.resolve(CborSerializer.encodeArray(this.assets.toCBOR(), this.reason.toCBOR())); + } +} diff --git a/examples/split/config.json b/examples/split/config.json new file mode 100644 index 00000000..7c8fe61d --- /dev/null +++ b/examples/split/config.json @@ -0,0 +1,3 @@ +{ + "ownerPrivateKey": "0202020202020202020202020202020202020202020202020202020202020202" +} \ No newline at end of file diff --git a/examples/split/index.ts b/examples/split/index.ts new file mode 100644 index 00000000..fc0b5cf3 --- /dev/null +++ b/examples/split/index.ts @@ -0,0 +1,151 @@ +import config from './config.json' with { type: 'json' }; +import { CustomPaymentData } from './CustomPaymentData.js'; +import { CustomSplitPaymentData } from './CustomSplitPaymentData.js'; +import { AggregatorClient } from '../../src/api/AggregatorClient.js'; +import { RootTrustBase } from '../../src/api/bft/RootTrustBase.js'; +import { CertificationData } from '../../src/api/CertificationData.js'; +import { CertificationStatus } from '../../src/api/CertificationResponse.js'; +import { SigningService } from '../../src/crypto/secp256k1/SigningService.js'; +import { Asset } from '../../src/payment/asset/Asset.js'; +import { AssetId } from '../../src/payment/asset/AssetId.js'; +import { PaymentAssetCollection } from '../../src/payment/asset/PaymentAssetCollection.js'; +import { SplitReason } from '../../src/payment/SplitReason.js'; +import { TokenSplit } from '../../src/payment/TokenSplit.js'; +import { PayToPublicKeyPredicate } from '../../src/predicate/builtin/PayToPublicKeyPredicate.js'; +import { PredicateVerifier } from '../../src/predicate/verification/PredicateVerifier.js'; +import { HexConverter } from '../../src/serialization/HexConverter.js'; +import { StateTransitionClient } from '../../src/StateTransitionClient.js'; +import { MintTransaction } from '../../src/transaction/MintTransaction.js'; +import { PayToScriptHash } from '../../src/transaction/PayToScriptHash.js'; +import { Token } from '../../src/transaction/Token.js'; +import { TokenId } from '../../src/transaction/TokenId.js'; +import { TokenType } from '../../src/transaction/TokenType.js'; +import { waitInclusionProof } from '../../src/util/InclusionProofUtils.js'; +import { VerificationStatus } from '../../src/verification/VerificationStatus.js'; +import trustBaseJson from '../trust-base.json' with { type: 'json' }; + +const aggregatorClient = new AggregatorClient('http://localhost:3000'); +const trustBase = RootTrustBase.fromJSON(trustBaseJson); + +const client = new StateTransitionClient(aggregatorClient); + +const predicateVerifier = PredicateVerifier.create(); + +const ownerPrivateKey = HexConverter.decode(config.ownerPrivateKey); +const ownerSigningService = new SigningService(ownerPrivateKey); +const ownerPredicate = PayToPublicKeyPredicate.create(ownerSigningService); + +const textEncoder = new TextEncoder(); + +const assets = [ + new Asset(new AssetId(textEncoder.encode('EUR')), 300n), + new Asset(new AssetId(textEncoder.encode('USD')), 500n), +]; + +const paymentData = new CustomPaymentData(PaymentAssetCollection.create(...assets), 'my other data'); +const mintTransaction = await MintTransaction.create( + await PayToScriptHash.create(ownerPredicate), + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + new TokenType(crypto.getRandomValues(new Uint8Array(32))), + await paymentData.toCBOR(), +); + +let response = await client.submitCertificationRequest(await CertificationData.fromMintTransaction(mintTransaction)); +if (response.status !== CertificationStatus.SUCCESS) { + throw new Error(`Token mint certification failed: ${response.status}`); +} + +const token = await Token.mint( + trustBase, + predicateVerifier, + await mintTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), + ), +); + +const splitTokens: [TokenId, PaymentAssetCollection][] = [ + [ + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + PaymentAssetCollection.create(new Asset(new AssetId(textEncoder.encode('EUR')), 150n)), + ], + [ + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + PaymentAssetCollection.create(new Asset(new AssetId(textEncoder.encode('EUR')), 150n)), + ], + [ + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + PaymentAssetCollection.create(new Asset(new AssetId(textEncoder.encode('USD')), 500n)), + ], +]; + +const result = await TokenSplit.split(token, ownerPredicate, CustomPaymentData.fromCBOR, splitTokens); + +response = await client.submitCertificationRequest( + await CertificationData.fromTransferTransaction( + result.burn.transaction, + await PayToPublicKeyPredicate.generateUnlockScript(result.burn.transaction, ownerSigningService), + ), +); + +if (response.status !== CertificationStatus.SUCCESS) { + throw new Error(`Token certification failed: ${response.status}`); +} + +const burntToken = await token.transfer( + trustBase, + predicateVerifier, + await result.burn.transaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, result.burn.transaction), + ), +); + +let i = 1; +for (const [tokenId, assets] of splitTokens) { + const entry = result.proofs.get(tokenId); + if (entry == null) { + throw new Error('Missing split reason proof for token.'); + } + + const splitPaymentData = new CustomSplitPaymentData(assets, SplitReason.create(burntToken, entry.proofs)); + + const mintTransaction = await MintTransaction.create( + await PayToScriptHash.create(ownerPredicate), + tokenId, + new TokenType(crypto.getRandomValues(new Uint8Array(32))), + await splitPaymentData.toCBOR(), + ); + + const certificationData = await CertificationData.fromMintTransaction(mintTransaction); + + const response = await client.submitCertificationRequest(certificationData); + if (response.status !== CertificationStatus.SUCCESS) { + throw new Error(`Token certification failed: ${response.status}`); + } + + const splitToken = await Token.mint( + trustBase, + predicateVerifier, + await mintTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), + ), + ); + + const splitResult = await TokenSplit.verify( + await Token.fromCBOR(splitToken.toCBOR()), + CustomSplitPaymentData.fromCBOR, + trustBase, + predicateVerifier, + ); + + if (splitResult.status !== VerificationStatus.OK) { + throw new Error(`Split token verification failed: ${splitResult.status}`); + } + + console.log(`Token[${i++}]: `, HexConverter.encode(splitToken.toCBOR()), '\n'); +} diff --git a/examples/transfer/config.json b/examples/transfer/config.json new file mode 100644 index 00000000..ee28df69 --- /dev/null +++ b/examples/transfer/config.json @@ -0,0 +1,5 @@ +{ + "token": "8282835820dfa3d66dcb38707fef03142d032570b04470183853d06bf0db33089f31aee0e15820f5c3a8c9645befb1bd3226bb7be71e56f0a29a3e281007fd1cf06ee5800669308258203ae6e9c6107c913f41c31cdd046a3237a40fa2a63050e9f109d07a5b66235a494f6e4d7920637573746f6d20646174618384830141015821035c3ac84a9eaf413a946bba95986360deb9b317f141c3c7764c2b74a59207fd8b582083645aaa3b018eef21c6552a73e4b78620de199e6073d9d17f76067a6a102b175820f05532263005d9bc489141283a61a5204c3b28e2edda8397ddcf1b5d4248b05458415fc9c1c5adca1fcc8bb6349de2cd5bc7a2dddf520881973698e8fa926e2d0f58389ab846a61683160ea83e6a2b62ecf98fc47efcf0f14749776311284d549d98008258220000cca3d3e015b88799777158fb1555537e3cd63f91a1567153f3f52fbd3f947fb382825821018614763a8ab3559206704d5ac7bedd9783d92bfe8b0400909481b2cc4eca4d48582200001956ad7178c8a716d50fb8db456185607d48ff065549b8d4e8d392ced8bdca51824101f6d903ef8700d903f08a000000f658220000cca3d3e015b88799777158fb1555537e3cd63f91a1567153f3f52fbd3f947fb34000f600f6f658200000000000000000000000000000000000000000000000000000000000000000824080d903f683000080d903e9880000000000f65820050bd030caf5914948d7ab70c4fe57f02594040d4ca55e33773d50c0791f3450a1644e4f44455841da184fffc6a6e4b0f1ae7ea98ea1e8845ff91d071850c9036e7af3762f942a4d6155e763f986b493ce896323af88a777e894000bf8cacfaa30aedb096be767280180", + "ownerPrivateKey": "0202020202020202020202020202020202020202020202020202020202020202", + "payToScriptHash": "73c4dd24bea201b6b5cc06143de913456717bc8fa0c696bd249528d10308bcfa" +} \ No newline at end of file diff --git a/examples/transfer/index.ts b/examples/transfer/index.ts new file mode 100644 index 00000000..dac9453b --- /dev/null +++ b/examples/transfer/index.ts @@ -0,0 +1,67 @@ +import trustBaseJson from '../trust-base.json' with { type: 'json' }; +import config from './config.json' with { type: 'json' }; +import { AggregatorClient } from '../../src/api/AggregatorClient.js'; +import { RootTrustBase } from '../../src/api/bft/RootTrustBase.js'; +import { CertificationData } from '../../src/api/CertificationData.js'; +import { CertificationStatus } from '../../src/api/CertificationResponse.js'; +import { SigningService } from '../../src/crypto/secp256k1/SigningService.js'; +import { PayToPublicKeyPredicate } from '../../src/predicate/builtin/PayToPublicKeyPredicate.js'; +import { PredicateVerifier } from '../../src/predicate/verification/PredicateVerifier.js'; +import { CborSerializer } from '../../src/serialization/cbor/CborSerializer.js'; +import { HexConverter } from '../../src/serialization/HexConverter.js'; +import { StateTransitionClient } from '../../src/StateTransitionClient.js'; +import { PayToScriptHash } from '../../src/transaction/PayToScriptHash.js'; +import { Token } from '../../src/transaction/Token.js'; +import { TransferTransaction } from '../../src/transaction/TransferTransaction.js'; +import { waitInclusionProof } from '../../src/util/InclusionProofUtils.js'; +import { VerificationStatus } from '../../src/verification/VerificationStatus.js'; + +const aggregatorClient = new AggregatorClient('http://localhost:3000'); +const trustBase = RootTrustBase.fromJSON(trustBaseJson); +const client = new StateTransitionClient(aggregatorClient); + +const predicateVerifier = PredicateVerifier.create(); + +const ownerPrivateKey = HexConverter.decode(config.ownerPrivateKey); +const ownerSigningService = new SigningService(ownerPrivateKey); +const ownerPredicate = PayToPublicKeyPredicate.create(ownerSigningService); + +const tokenCBOR = HexConverter.decode(config.token); + +const token = await Token.fromCBOR(tokenCBOR); +const result = await token.verify(trustBase, predicateVerifier); +if (result.status !== VerificationStatus.OK) { + throw new Error(`Token verification failed: ${result.status}`); +} + +const payToScriptHash = PayToScriptHash.fromBytes(HexConverter.decode(config.payToScriptHash)); +const transferTransaction = await TransferTransaction.create( + token, + ownerPredicate, + payToScriptHash, + crypto.getRandomValues(new Uint8Array(32)), + CborSerializer.encodeTextString('My custom transfer data'), +); + +const certificationData = await CertificationData.fromTransferTransaction( + transferTransaction, + await PayToPublicKeyPredicate.generateUnlockScript(transferTransaction, ownerSigningService), +); + +const response = await client.submitCertificationRequest(certificationData); + +if (response.status !== CertificationStatus.SUCCESS) { + throw new Error(`Token certification failed: ${response.status}`); +} + +const transferToken = await token.transfer( + trustBase, + predicateVerifier, + await transferTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, transferTransaction), + ), +); + +console.log(HexConverter.encode(transferToken.toCBOR())); diff --git a/examples/trust-base.json b/examples/trust-base.json new file mode 100644 index 00000000..7ffc9271 --- /dev/null +++ b/examples/trust-base.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "networkId": 3, + "epoch": 1, + "epochStartRound": 1, + "rootNodes": [ + { + "nodeId": "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU", + "sigKey": "0x039afb2acb65f5fbc272d8907f763d0a5d189aadc9b97afdcc5897ea4dd112e68b", + "stake": 1 + } + ], + "quorumThreshold": 1, + "stateHash": "", + "changeRecordHash": "", + "previousEntryHash": "", + "signatures": { + "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU": "0xf157c9fdd8a378e3ca70d354ccc4475ab2cd8de360127bc46b0aeab4b453a80f07fd9136a5843b60a8babaff23e20acc8879861f7651440a5e2829f7541b31f100" + } +} \ No newline at end of file diff --git a/package.json b/package.json index 58eb9e67..369163a7 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "scripts": { "prebuild": "rm -rf lib", "build": "tsc", - "build:check": "tsc --noEmit", - "lint": "eslint \"src/**/*\" \"tests/**/*\"", + "build:check": "tsc --project tsconfig.eslint.json --noEmit", + "lint": "eslint \"src/**/*\" \"tests/**/*\" \"examples/**/*\"", "lint:fix": "eslint \"src/**/*\" \"tests/**/*\" --fix", "test": "jest --testPathPatterns=tests --testPathIgnorePatterns=tests/e2e", "test:unit": "jest --testPathPatterns=tests/unit --testPathPatterns=tests/functional", diff --git a/src/payment/SplitReason.ts b/src/payment/SplitReason.ts index af0e7929..5649993b 100644 --- a/src/payment/SplitReason.ts +++ b/src/payment/SplitReason.ts @@ -15,6 +15,7 @@ export class SplitReason { return this._proofs.slice(); } + // TODO: should we verify if reason integrity is valid here? public static create(token: Token, proofs: SplitReasonProof[]): SplitReason { if (proofs.length === 0) { throw new Error('proofs cannot be empty.'); diff --git a/src/payment/TokenSplit.ts b/src/payment/TokenSplit.ts index 261f19a2..a7e52ef6 100644 --- a/src/payment/TokenSplit.ts +++ b/src/payment/TokenSplit.ts @@ -172,7 +172,9 @@ export class TokenSplit { parsePaymentData: (bytes: Uint8Array) => Promise, trustBase: RootTrustBase, predicateVerifier: PredicateVerifier, - ): Promise> { + ): Promise> { + // TODO: Check initial token also or that should be done by client beforehand? + const data = await parsePaymentData(token.genesis.data); if (data.assets == null) { diff --git a/src/predicate/builtin/BuiltInPredicateVerifierFactory.ts b/src/predicate/builtin/BuiltInPredicateVerifierFactory.ts index 0b3f674d..1984e5e5 100644 --- a/src/predicate/builtin/BuiltInPredicateVerifierFactory.ts +++ b/src/predicate/builtin/BuiltInPredicateVerifierFactory.ts @@ -1,20 +1,32 @@ -import { PayToPublicKeyPredicate } from './PayToPublicKeyPredicate.js'; import { CertificationData } from '../../api/CertificationData.js'; import { CborDeserializer } from '../../serialization/cbor/CborDeserializer.js'; import { VerificationResult } from '../../verification/VerificationResult.js'; import { VerificationStatus } from '../../verification/VerificationStatus.js'; import { IPredicate } from '../IPredicate.js'; +import { PredicateEngine } from '../PredicateEngine.js'; import { IPredicateVerifier } from '../verification/IPredicateVerifier.js'; import { IPredicateVerifierFactory } from '../verification/IPredicateVerifierFactory.js'; import { PayToPublicKeyPredicateVerifier } from './verification/PayToPublicKeyPredicateVerifier.js'; export class BuiltInPredicateVerifierFactory implements IPredicateVerifierFactory { - public constructor(private readonly factories: Map) {} + public readonly engine: PredicateEngine = PredicateEngine.BUILT_IN; + + private readonly factories: Map; + public constructor(factories: IPredicateVerifier[]) { + const result = new Map(); + for (const factory of factories) { + if (result.has(factory.type)) { + throw new Error('Found duplicate predicate verifier.'); + } + + result.set(factory.type, factory); + } + + this.factories = result; + } public static create(): BuiltInPredicateVerifierFactory { - return new BuiltInPredicateVerifierFactory( - new Map([[PayToPublicKeyPredicate.TYPE, new PayToPublicKeyPredicateVerifier()]]), - ); + return new BuiltInPredicateVerifierFactory([new PayToPublicKeyPredicateVerifier()]); } public verify( diff --git a/src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts b/src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts index 1f592902..54e97a1c 100644 --- a/src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts +++ b/src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts @@ -11,6 +11,8 @@ import { IPredicateVerifier } from '../../verification/IPredicateVerifier.js'; import { PayToPublicKeyPredicate } from '../PayToPublicKeyPredicate.js'; export class PayToPublicKeyPredicateVerifier implements IPredicateVerifier { + public readonly type = PayToPublicKeyPredicate.TYPE; + public async verify( encodedPredicate: IPredicate, certificationData: CertificationData, diff --git a/src/predicate/verification/IPredicateVerifier.ts b/src/predicate/verification/IPredicateVerifier.ts index 8d10e7a0..aaa751bd 100644 --- a/src/predicate/verification/IPredicateVerifier.ts +++ b/src/predicate/verification/IPredicateVerifier.ts @@ -4,6 +4,8 @@ import { VerificationStatus } from '../../verification/VerificationStatus.js'; import { IPredicate } from '../IPredicate.js'; export interface IPredicateVerifier { + readonly type: bigint; + verify( encodedPredicate: IPredicate, certificationData: CertificationData, diff --git a/src/predicate/verification/IPredicateVerifierFactory.ts b/src/predicate/verification/IPredicateVerifierFactory.ts index 1a8cd239..9fb36ecc 100644 --- a/src/predicate/verification/IPredicateVerifierFactory.ts +++ b/src/predicate/verification/IPredicateVerifierFactory.ts @@ -2,7 +2,10 @@ import { CertificationData } from '../../api/CertificationData.js'; import { VerificationResult } from '../../verification/VerificationResult.js'; import { VerificationStatus } from '../../verification/VerificationStatus.js'; import { IPredicate } from '../IPredicate.js'; +import { PredicateEngine } from '../PredicateEngine.js'; export interface IPredicateVerifierFactory { + readonly engine: PredicateEngine; + verify(predicate: IPredicate, certificationData: CertificationData): Promise>; } diff --git a/src/predicate/verification/PredicateVerifier.ts b/src/predicate/verification/PredicateVerifier.ts index 551b9e79..743f9740 100644 --- a/src/predicate/verification/PredicateVerifier.ts +++ b/src/predicate/verification/PredicateVerifier.ts @@ -2,11 +2,28 @@ import { IPredicateVerifierFactory } from './IPredicateVerifierFactory.js'; import { CertificationData } from '../../api/CertificationData.js'; import { VerificationResult } from '../../verification/VerificationResult.js'; import { VerificationStatus } from '../../verification/VerificationStatus.js'; +import { BuiltInPredicateVerifierFactory } from '../builtin/BuiltInPredicateVerifierFactory.js'; import { IPredicate } from '../IPredicate.js'; import { PredicateEngine } from '../PredicateEngine.js'; export class PredicateVerifier { - public constructor(private readonly factories: Map) {} + private readonly factories: Map; + public constructor(factories: IPredicateVerifierFactory[]) { + const result = new Map(); + for (const factory of factories) { + if (result.has(factory.engine)) { + throw new Error('Found duplicate predicate verifier factory.'); + } + + result.set(factory.engine, factory); + } + + this.factories = result; + } + + public static create(): PredicateVerifier { + return new PredicateVerifier([BuiltInPredicateVerifierFactory.create()]); + } public verify( predicate: IPredicate, diff --git a/tests/functional/TestAggregatorClient.ts b/tests/functional/TestAggregatorClient.ts index 474e1291..c7ec0220 100644 --- a/tests/functional/TestAggregatorClient.ts +++ b/tests/functional/TestAggregatorClient.ts @@ -9,11 +9,7 @@ import { DataHasher } from '../../src/crypto/hash/DataHasher.js'; import { DataHasherFactory } from '../../src/crypto/hash/DataHasherFactory.js'; import { HashAlgorithm } from '../../src/crypto/hash/HashAlgorithm.js'; import { SigningService } from '../../src/crypto/secp256k1/SigningService.js'; -import { BuiltInPredicateVerifierFactory } from '../../src/predicate/builtin/BuiltInPredicateVerifierFactory.js'; -import { PayToPublicKeyPredicate } from '../../src/predicate/builtin/PayToPublicKeyPredicate.js'; -import { PayToPublicKeyPredicateVerifier } from '../../src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.js'; import { EncodedPredicate } from '../../src/predicate/EncodedPredicate.js'; -import { PredicateEngine } from '../../src/predicate/PredicateEngine.js'; import { PredicateVerifier } from '../../src/predicate/verification/PredicateVerifier.js'; import { SparseMerkleTree } from '../../src/smt/plain/SparseMerkleTree.js'; import { createRootTrustBase } from '../utils/RootTrustBaseFixture.js'; @@ -21,25 +17,21 @@ import { createUnicityCertificate } from '../utils/UnicityCertificateFixture.js' export class TestAggregatorClient implements IAggregatorClient { public readonly rootTrustBase: RootTrustBase; - private readonly predicateVerifier = new PredicateVerifier( - new Map([ - [ - PredicateEngine.BUILT_IN, - new BuiltInPredicateVerifierFactory( - new Map([[PayToPublicKeyPredicate.TYPE, new PayToPublicKeyPredicateVerifier()]]), - ), - ], - ]), - ); + private readonly predicateVerifier = PredicateVerifier.create(); private readonly requests: Map = new Map(); - private readonly signingService = new SigningService(SigningService.generatePrivateKey()); - private constructor(private readonly smt: SparseMerkleTree) { + private constructor( + private readonly smt: SparseMerkleTree, + private readonly signingService: SigningService, + ) { this.rootTrustBase = createRootTrustBase(this.signingService.publicKey); } - public static create(): TestAggregatorClient { - return new TestAggregatorClient(new SparseMerkleTree(new DataHasherFactory(HashAlgorithm.SHA256, DataHasher))); + public static create(privateKey: Uint8Array = SigningService.generatePrivateKey()): TestAggregatorClient { + return new TestAggregatorClient( + new SparseMerkleTree(new DataHasherFactory(HashAlgorithm.SHA256, DataHasher)), + new SigningService(privateKey), + ); } public async getInclusionProof(stateId: StateId): Promise { diff --git a/tests/functional/payment/SplitBuilderTest.ts b/tests/functional/payment/SplitBuilderTest.ts index ed77b6e6..9f9466af 100644 --- a/tests/functional/payment/SplitBuilderTest.ts +++ b/tests/functional/payment/SplitBuilderTest.ts @@ -10,9 +10,7 @@ import { TokenAssetMissingError } from '../../../src/payment/error/TokenAssetMis import { TokenAssetValueMismatchError } from '../../../src/payment/error/TokenAssetValueMismatchError.js'; import { SplitReason } from '../../../src/payment/SplitReason.js'; import { TokenSplit } from '../../../src/payment/TokenSplit.js'; -import { BuiltInPredicateVerifierFactory } from '../../../src/predicate/builtin/BuiltInPredicateVerifierFactory.js'; import { PayToPublicKeyPredicate } from '../../../src/predicate/builtin/PayToPublicKeyPredicate.js'; -import { PredicateEngine } from '../../../src/predicate/PredicateEngine.js'; import { PredicateVerifier } from '../../../src/predicate/verification/PredicateVerifier.js'; import { StateTransitionClient } from '../../../src/StateTransitionClient.js'; import { MintTransaction } from '../../../src/transaction/MintTransaction.js'; @@ -30,9 +28,7 @@ describe('SplitBuilder Functional Test', () => { const aggregatorClient = TestAggregatorClient.create(); const trustBase = aggregatorClient.rootTrustBase; const client = new StateTransitionClient(aggregatorClient); - const predicateVerifier = new PredicateVerifier( - new Map([[PredicateEngine.BUILT_IN, BuiltInPredicateVerifierFactory.create()]]), - ); + const predicateVerifier = PredicateVerifier.create(); const signingService = new SigningService(SigningService.generatePrivateKey()); const predicate = PayToPublicKeyPredicate.create(signingService); diff --git a/tests/unit/api/InclusionProofTest.ts b/tests/unit/api/InclusionProofTest.ts index d94bfc15..9c122d80 100644 --- a/tests/unit/api/InclusionProofTest.ts +++ b/tests/unit/api/InclusionProofTest.ts @@ -8,10 +8,7 @@ import { DataHasherFactory } from '../../../src/crypto/hash/DataHasherFactory.js import { HashAlgorithm } from '../../../src/crypto/hash/HashAlgorithm.js'; import { NodeDataHasher } from '../../../src/crypto/hash/NodeDataHasher.js'; import { SigningService } from '../../../src/crypto/secp256k1/SigningService.js'; -import { BuiltInPredicateVerifierFactory } from '../../../src/predicate/builtin/BuiltInPredicateVerifierFactory.js'; import { PayToPublicKeyPredicate } from '../../../src/predicate/builtin/PayToPublicKeyPredicate.js'; -import { PayToPublicKeyPredicateVerifier } from '../../../src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.js'; -import { PredicateEngine } from '../../../src/predicate/PredicateEngine.js'; import { PredicateVerifier } from '../../../src/predicate/verification/PredicateVerifier.js'; import { CborSerializer } from '../../../src/serialization/cbor/CborSerializer.js'; import { HexConverter } from '../../../src/serialization/HexConverter.js'; @@ -33,14 +30,7 @@ describe('InclusionProof', () => { new Uint8Array(HexConverter.decode('0000000000000000000000000000000000000000000000000000000000000001')), ); - const predicateVerifierFactory = new PredicateVerifier( - new Map([ - [ - PredicateEngine.BUILT_IN, - new BuiltInPredicateVerifierFactory(new Map([[1n, new PayToPublicKeyPredicateVerifier()]])), - ], - ]), - ); + const predicateVerifierFactory = PredicateVerifier.create(); let transaction: MintTransaction; let certificationData: CertificationData; @@ -159,14 +149,7 @@ describe('InclusionProof', () => { it('verification fails with invalid trustbase', async () => { const inclusionProof = new InclusionProof(merkleTreePath, certificationData, unicityCertificate); - const predicateVerifier = new PredicateVerifier( - new Map([ - [ - PredicateEngine.BUILT_IN, - new BuiltInPredicateVerifierFactory(new Map([[1n, new PayToPublicKeyPredicateVerifier()]])), - ], - ]), - ); + const predicateVerifier = PredicateVerifier.create(); await expect( InclusionProofVerificationRule.verify( diff --git a/tests/utils/TransitionFlow.ts b/tests/utils/TransitionFlow.ts index e55aaa37..deedda68 100644 --- a/tests/utils/TransitionFlow.ts +++ b/tests/utils/TransitionFlow.ts @@ -2,9 +2,7 @@ import { RootTrustBase } from '../../src/api/bft/RootTrustBase.js'; import { CertificationData } from '../../src/api/CertificationData.js'; import { CertificationStatus } from '../../src/api/CertificationResponse.js'; import { SigningService } from '../../src/crypto/secp256k1/SigningService.js'; -import { BuiltInPredicateVerifierFactory } from '../../src/predicate/builtin/BuiltInPredicateVerifierFactory.js'; import { PayToPublicKeyPredicate } from '../../src/predicate/builtin/PayToPublicKeyPredicate.js'; -import { PredicateEngine } from '../../src/predicate/PredicateEngine.js'; import { PredicateVerifier } from '../../src/predicate/verification/PredicateVerifier.js'; import { CborSerializer } from '../../src/serialization/cbor/CborSerializer.js'; import { StateTransitionClient } from '../../src/StateTransitionClient.js'; @@ -20,9 +18,7 @@ import { VerificationStatus } from '../../src/verification/VerificationStatus.js export const transitionFlowTest = (client: StateTransitionClient, trustBase: RootTrustBase): void => { describe('Transition', () => { it('default successful flow', async () => { - const predicateVerifier = new PredicateVerifier( - new Map([[PredicateEngine.BUILT_IN, BuiltInPredicateVerifierFactory.create()]]), - ); + const predicateVerifier = PredicateVerifier.create(); const signingService = new SigningService(SigningService.generatePrivateKey()); const predicate = PayToPublicKeyPredicate.create(signingService); diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 5a6360d2..2efe79ce 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -9,6 +9,7 @@ ], "include": [ "src/**/*.ts", - "tests/**/*.ts" + "tests/**/*.ts", + "examples/**/*.ts", ], } \ No newline at end of file From bb9337be375135927be62b9b8bcd4c2c8b575874 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Sat, 7 Feb 2026 13:12:56 +0200 Subject: [PATCH 11/15] #96 Fix inconsistencies between aggregator and SDK --- README.md | 29 +++++++++++++++++++ src/api/AggregatorClient.ts | 2 +- src/api/CertificationData.ts | 17 ++--------- src/api/StateId.ts | 19 ++++++------ .../builtin/PayToPublicKeyPredicate.ts | 4 +-- .../PayToPublicKeyPredicateVerifier.ts | 4 +-- src/util/BitString.ts | 2 +- tests/functional/TestAggregatorClient.ts | 13 +++++++++ 8 files changed, 60 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 39999891..4eb7e06b 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,35 @@ I --> J[End] ### Token Structure +``` +Token { + genesis: CertifiedMintTransaction { + transaction: MintTransaction { + sourceStateHash: MintTransactionState, + lockScript: IPredicate, + recipient: PayToScriptHash, + tokenId: TokenId, + tokenType: TokenType, + data: Uint8Array + }, + inclusionProof: InclusionProof + }, + transactions: [ + CertifiedTransferTransaction { + transaction: TransferTransaction { + sourceStateHash: DataHash, + lockScript: IPredicate, + recipient: PayToScriptHash, + x: Uint8Array, + data: Uint8Array + }, + inclusionProof: InclusionProof + }, + ... + ] +} +``` + ### Privacy Model - **Commitment-based**: Only cryptographic commitments published on-chain - **Self-contained**: Tokens include complete transaction history diff --git a/src/api/AggregatorClient.ts b/src/api/AggregatorClient.ts index 75fdb661..35a856c0 100644 --- a/src/api/AggregatorClient.ts +++ b/src/api/AggregatorClient.ts @@ -45,7 +45,7 @@ export class AggregatorClient implements IAggregatorClient { * @inheritDoc */ public async getInclusionProof(stateId: StateId): Promise { - const data = { stateId: HexConverter.encode(stateId.bytes) }; + const data = { stateId: HexConverter.encode(stateId.data) }; return InclusionProofResponse.fromCBOR( HexConverter.decode((await this.transport.request('get_inclusion_proof.v2', data)) as string), ); diff --git a/src/api/CertificationData.ts b/src/api/CertificationData.ts index fef2f31e..b5bce13a 100644 --- a/src/api/CertificationData.ts +++ b/src/api/CertificationData.ts @@ -2,6 +2,7 @@ import { DataHash } from '../crypto/hash/DataHash.js'; import { DataHasher } from '../crypto/hash/DataHasher.js'; import { HashAlgorithm } from '../crypto/hash/HashAlgorithm.js'; import { MintSigningService } from '../crypto/MintSigningService.js'; +import { PayToPublicKeyPredicate } from '../predicate/builtin/PayToPublicKeyPredicate.js'; import { EncodedPredicate } from '../predicate/EncodedPredicate.js'; import { IPredicate } from '../predicate/IPredicate.js'; import { CborDeserializer } from '../serialization/cbor/CborDeserializer.js'; @@ -54,23 +55,11 @@ export class CertificationData { public static async fromMintTransaction(transaction: MintTransaction): Promise { const signingService = await MintSigningService.create(transaction.tokenId); - const transactionHash = await transaction.calculateTransactionHash(); - - const signatureDataHash = await new DataHasher(HashAlgorithm.SHA256) - .update( - CborSerializer.encodeArray( - CborSerializer.encodeByteString(transaction.sourceStateHash.imprint), - CborSerializer.encodeByteString(transactionHash.imprint), - ), - ) - .digest(); - const unlockScript = await signingService.sign(signatureDataHash); - return CertificationData.create( transaction.lockScript, transaction.sourceStateHash, - transactionHash, - unlockScript.encode(), + await transaction.calculateTransactionHash(), + await PayToPublicKeyPredicate.generateUnlockScript(transaction, signingService), ); } diff --git a/src/api/StateId.ts b/src/api/StateId.ts index 68fab918..86f84cb9 100644 --- a/src/api/StateId.ts +++ b/src/api/StateId.ts @@ -8,20 +8,19 @@ import { CborSerializer } from '../serialization/cbor/CborSerializer.js'; import { HexConverter } from '../serialization/HexConverter.js'; import { ITransaction } from '../transaction/ITransaction.js'; import { BitString } from '../util/BitString.js'; -import { areUint8ArraysEqual } from '../util/TypedArrayUtils.js'; /** * Represents a unique state identifier derived from a public key and state hash. */ export class StateId { - private readonly _bytes: Uint8Array; + private constructor(private readonly hash: DataHash) {} - private constructor(hash: DataHash) { - this._bytes = new Uint8Array(hash.data); + public get data(): Uint8Array { + return this.hash.data; } - public get bytes(): Uint8Array { - return new Uint8Array(this._bytes); + public get imprint(): Uint8Array { + return this.hash.imprint; } public static fromCBOR(bytes: Uint8Array): StateId { @@ -48,14 +47,14 @@ export class StateId { */ private static async create(predicate: IPredicate, stateHash: DataHash): Promise { const hash = await new DataHasher(HashAlgorithm.SHA256) - .update(CborSerializer.encodeArray(predicate.toCBOR(), CborSerializer.encodeByteString(stateHash.imprint))) + .update(CborSerializer.encodeArray(predicate.toCBOR(), CborSerializer.encodeByteString(stateHash.data))) .digest(); return new StateId(hash); } public equals(id: StateId): boolean { - return areUint8ArraysEqual(this._bytes, id._bytes); + return this.hash.equals(id.hash); } /** @@ -67,7 +66,7 @@ export class StateId { } public toCBOR(): Uint8Array { - return CborSerializer.encodeByteString(this._bytes); + return CborSerializer.encodeByteString(this.data); } /** @@ -75,6 +74,6 @@ export class StateId { * @returns The string representation. */ public toString(): string { - return `StateId[${HexConverter.encode(this._bytes)}]`; + return `StateId[${HexConverter.encode(this.data)}]`; } } diff --git a/src/predicate/builtin/PayToPublicKeyPredicate.ts b/src/predicate/builtin/PayToPublicKeyPredicate.ts index b25fc9fd..e0bd4d85 100644 --- a/src/predicate/builtin/PayToPublicKeyPredicate.ts +++ b/src/predicate/builtin/PayToPublicKeyPredicate.ts @@ -54,8 +54,8 @@ export class PayToPublicKeyPredicate implements IPredicate { const hash = await new DataHasher(HashAlgorithm.SHA256) .update( CborSerializer.encodeArray( - CborSerializer.encodeByteString(transaction.sourceStateHash.imprint), - await transaction.calculateTransactionHash().then((hash) => CborSerializer.encodeByteString(hash.imprint)), + CborSerializer.encodeByteString(transaction.sourceStateHash.data), + await transaction.calculateTransactionHash().then((hash) => CborSerializer.encodeByteString(hash.data)), ), ) .digest(); diff --git a/src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts b/src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts index 54e97a1c..28899c82 100644 --- a/src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts +++ b/src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.ts @@ -31,8 +31,8 @@ export class PayToPublicKeyPredicateVerifier implements IPredicateVerifier { await new DataHasher(HashAlgorithm.SHA256) .update( CborSerializer.encodeArray( - CborSerializer.encodeByteString(certificationData.sourceStateHash.imprint), - CborSerializer.encodeByteString(certificationData.transactionHash.imprint), + CborSerializer.encodeByteString(certificationData.sourceStateHash.data), + CborSerializer.encodeByteString(certificationData.transactionHash.data), ), ) .digest(), diff --git a/src/util/BitString.ts b/src/util/BitString.ts index a8632a2e..4aa673b8 100644 --- a/src/util/BitString.ts +++ b/src/util/BitString.ts @@ -22,7 +22,7 @@ export class BitString { * @return {BitString} A BitString instance */ public static fromStateId(stateId: StateId): BitString { - return new BitString(stateId.bytes); + return new BitString(stateId.imprint); } /** diff --git a/tests/functional/TestAggregatorClient.ts b/tests/functional/TestAggregatorClient.ts index c7ec0220..06842bc2 100644 --- a/tests/functional/TestAggregatorClient.ts +++ b/tests/functional/TestAggregatorClient.ts @@ -15,6 +15,9 @@ import { SparseMerkleTree } from '../../src/smt/plain/SparseMerkleTree.js'; import { createRootTrustBase } from '../utils/RootTrustBaseFixture.js'; import { createUnicityCertificate } from '../utils/UnicityCertificateFixture.js'; +/** + * Test aggregator client implementation that stores all submitted certification requests in memory. + */ export class TestAggregatorClient implements IAggregatorClient { public readonly rootTrustBase: RootTrustBase; private readonly predicateVerifier = PredicateVerifier.create(); @@ -27,6 +30,10 @@ export class TestAggregatorClient implements IAggregatorClient { this.rootTrustBase = createRootTrustBase(this.signingService.publicKey); } + /** + * Creates a new TestAggregatorClient instance with optional private key. + * If no private key is provided, a new one is generated. + */ public static create(privateKey: Uint8Array = SigningService.generatePrivateKey()): TestAggregatorClient { return new TestAggregatorClient( new SparseMerkleTree(new DataHasherFactory(HashAlgorithm.SHA256, DataHasher)), @@ -34,6 +41,9 @@ export class TestAggregatorClient implements IAggregatorClient { ); } + /** + * @inheritDoc + */ public async getInclusionProof(stateId: StateId): Promise { const certificationData = this.requests.get(stateId.toBitString().toBigInt()); const root = await this.smt.calculateRoot(); @@ -49,6 +59,9 @@ export class TestAggregatorClient implements IAggregatorClient { ); } + /** + * @inheritDoc + */ public async submitCertificationRequest(certificationData: CertificationData): Promise { const stateId = await StateId.fromCertificationData(certificationData); From 157621359bebf74600b8f293ee4dd39bafd95c38 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Sat, 7 Feb 2026 13:34:51 +0200 Subject: [PATCH 12/15] #96 Fix failing tests related to state id changes --- tests/unit/api/CertificationDataTest.ts | 2 +- tests/unit/api/CertificationRequestTest.ts | 4 ++-- tests/unit/api/StateIdTest.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/api/CertificationDataTest.ts b/tests/unit/api/CertificationDataTest.ts index ac9d985e..e8bddd33 100644 --- a/tests/unit/api/CertificationDataTest.ts +++ b/tests/unit/api/CertificationDataTest.ts @@ -16,7 +16,7 @@ describe('CertificationData', () => { ), ); expect(HexConverter.encode(certificationData.toCBOR())).toStrictEqual( - '8483014101582103b00b30dcd21feaa837132ccd4b7b9595f704c9714ac66eed085f52bc396f9050582080201eff2f0c27ea9c8433eb999b4fd0fa5bfb4fe47fa2690859f0c83651604e5820d3428d83066c996da3b3ec8a68851dfe1e0df6065bac29fce57a95b35360cecc584173f16f156b8d0a854274fb765d1158ce7dc591de4b7c503f3fb06d8f92e9d96164ea64082ac1616009842c154bdcdd974c18c831899632d023e43a840cc9ba9000', + '8483014101582103b00b30dcd21feaa837132ccd4b7b9595f704c9714ac66eed085f52bc396f9050582080201eff2f0c27ea9c8433eb999b4fd0fa5bfb4fe47fa2690859f0c83651604e5820d3428d83066c996da3b3ec8a68851dfe1e0df6065bac29fce57a95b35360cecc584159613057b2c04cbf774d53325912cd9840db04c1dea4dd0ba1debf4a0e37a11a696f9c31ceec1898ea7bb83bc6d959d31f24b5810653064639883470f930deb100', ); const result = CertificationData.fromCBOR(certificationData.toCBOR()); diff --git a/tests/unit/api/CertificationRequestTest.ts b/tests/unit/api/CertificationRequestTest.ts index e0fab722..c1f8c33d 100644 --- a/tests/unit/api/CertificationRequestTest.ts +++ b/tests/unit/api/CertificationRequestTest.ts @@ -21,7 +21,7 @@ describe('CertificationRequest', () => { ); expect(HexConverter.encode(request.toCBOR())).toEqual( - '845820e63bd8ab7ed68af6d65a2f4c7658122e85831b30c5517a5db76ce4a72df74680848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798582000000000000000000000000000000000000000000000000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01f400', + '8458207191bb9f044715f712ca5e77e91b585cf892eb5755ae4d77231ad429c53cf661848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798582000000000000000000000000000000000000000000000000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01f400', ); request = await CertificationRequest.create( @@ -41,7 +41,7 @@ describe('CertificationRequest', () => { ); expect(HexConverter.encode(request.toCBOR())).toEqual( - '845820e63bd8ab7ed68af6d65a2f4c7658122e85831b30c5517a5db76ce4a72df74680848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798582000000000000000000000000000000000000000000000000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01f500', + '8458207191bb9f044715f712ca5e77e91b585cf892eb5755ae4d77231ad429c53cf661848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798582000000000000000000000000000000000000000000000000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01f500', ); }); }); diff --git a/tests/unit/api/StateIdTest.ts b/tests/unit/api/StateIdTest.ts index 5e1d2318..dff67ba4 100644 --- a/tests/unit/api/StateIdTest.ts +++ b/tests/unit/api/StateIdTest.ts @@ -17,7 +17,7 @@ describe('StateId', () => { ); expect(HexConverter.encode(stateId.toCBOR())).toStrictEqual( - '58202d3f8a3769426d10ccafa08f332955faf34ba22873ae096d0ee2b19303b2d171', + '58205b4e69562ce02e38923a4c48727fabb8f0b2f4489e5d02c014a618ad83382891', ); expect(StateId.fromCBOR(stateId.toCBOR())).toStrictEqual(stateId); }); From b9498ff359e2f8b0a8e83f8002aa2f974fa485c1 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Mon, 9 Feb 2026 15:56:17 +0200 Subject: [PATCH 13/15] #96 Fix comments and enforce SHA-256 force PayToScriptHash --- package.json | 2 +- src/transaction/PayToScriptHash.ts | 4 +++- src/util/BitString.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 369163a7..b5442a5b 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "build": "tsc", "build:check": "tsc --project tsconfig.eslint.json --noEmit", "lint": "eslint \"src/**/*\" \"tests/**/*\" \"examples/**/*\"", - "lint:fix": "eslint \"src/**/*\" \"tests/**/*\" --fix", + "lint:fix": "eslint \"src/**/*\" \"tests/**/*\" \"examples/**/*\" --fix", "test": "jest --testPathPatterns=tests --testPathIgnorePatterns=tests/e2e", "test:unit": "jest --testPathPatterns=tests/unit --testPathPatterns=tests/functional", "test:integration": "jest --testPathPatterns=tests/integration", diff --git a/src/transaction/PayToScriptHash.ts b/src/transaction/PayToScriptHash.ts index 034d488f..780231d2 100644 --- a/src/transaction/PayToScriptHash.ts +++ b/src/transaction/PayToScriptHash.ts @@ -1,3 +1,4 @@ +import { DataHash } from '../crypto/hash/DataHash.js'; import { DataHasher } from '../crypto/hash/DataHasher.js'; import { HashAlgorithm } from '../crypto/hash/HashAlgorithm.js'; import { IPredicate } from '../predicate/IPredicate.js'; @@ -21,7 +22,8 @@ export class PayToScriptHash { } public static fromBytes(bytes: Uint8Array): PayToScriptHash { - return new PayToScriptHash(bytes); + const hash = new DataHash(HashAlgorithm.SHA256, bytes); + return new PayToScriptHash(hash.data); } public static fromCBOR(bytes: Uint8Array): PayToScriptHash { diff --git a/src/util/BitString.ts b/src/util/BitString.ts index 4aa673b8..d6d3bfba 100644 --- a/src/util/BitString.ts +++ b/src/util/BitString.ts @@ -17,7 +17,7 @@ export class BitString { } /** - * Creates a BitString from a DataHash imprint. + * Creates a BitString from a StateId. * @param {StateId} stateId * @return {BitString} A BitString instance */ From e475fd2a54f69ce0ff3469b4aee2c93b1c48ec8b Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Tue, 10 Feb 2026 15:18:30 +0200 Subject: [PATCH 14/15] #96 Convert examples into tests to make them run --- README.md | 7 +- examples/mint/index.ts | 50 ------ examples/split/index.ts | 151 ----------------- examples/transfer/config.json | 5 - examples/transfer/index.ts | 67 -------- package.json | 12 +- tests/examples/mint/ExampleTest.ts | 52 ++++++ {examples => tests/examples}/mint/config.json | 1 + .../examples}/split/CustomPaymentData.ts | 8 +- .../examples}/split/CustomSplitPaymentData.ts | 10 +- tests/examples/split/ExampleTest.ts | 153 ++++++++++++++++++ .../examples}/split/config.json | 1 + tests/examples/transfer/ExampleTest.ts | 101 ++++++++++++ tests/examples/transfer/config.json | 5 + {examples => tests/examples}/trust-base.json | 0 tsconfig.eslint.json | 1 - 16 files changed, 332 insertions(+), 292 deletions(-) delete mode 100644 examples/mint/index.ts delete mode 100644 examples/split/index.ts delete mode 100644 examples/transfer/config.json delete mode 100644 examples/transfer/index.ts create mode 100644 tests/examples/mint/ExampleTest.ts rename {examples => tests/examples}/mint/config.json (67%) rename {examples => tests/examples}/split/CustomPaymentData.ts (65%) rename {examples => tests/examples}/split/CustomSplitPaymentData.ts (60%) create mode 100644 tests/examples/split/ExampleTest.ts rename {examples => tests/examples}/split/config.json (67%) create mode 100644 tests/examples/transfer/ExampleTest.ts create mode 100644 tests/examples/transfer/config.json rename {examples => tests/examples}/trust-base.json (100%) diff --git a/README.md b/README.md index 4eb7e06b..5b592007 100644 --- a/README.md +++ b/README.md @@ -157,10 +157,13 @@ npm run lint:fix ## Examples ### Minting Tokens -`examples/mint/index.ts` +`tests/examples/mint/ExampleTest.ts` ### Token Transfer -`examples/transfer/index.ts` +`tests/examples/transfer/ExampleTest.ts` + +### Token Splitting +`tests/examples/split/ExampleTest.ts` ## Unicity Signature Standard diff --git a/examples/mint/index.ts b/examples/mint/index.ts deleted file mode 100644 index 56e90aee..00000000 --- a/examples/mint/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { AggregatorClient } from '../../src/api/AggregatorClient.js'; -import { RootTrustBase } from '../../src/api/bft/RootTrustBase.js'; -import { CertificationData } from '../../src/api/CertificationData.js'; -import { SigningService } from '../../src/crypto/secp256k1/SigningService.js'; -import { PayToPublicKeyPredicate } from '../../src/predicate/builtin/PayToPublicKeyPredicate.js'; -import { PredicateVerifier } from '../../src/predicate/verification/PredicateVerifier.js'; -import { CborSerializer } from '../../src/serialization/cbor/CborSerializer.js'; -import { HexConverter } from '../../src/serialization/HexConverter.js'; -import { StateTransitionClient } from '../../src/StateTransitionClient.js'; -import { MintTransaction } from '../../src/transaction/MintTransaction.js'; -import { PayToScriptHash } from '../../src/transaction/PayToScriptHash.js'; -import { Token } from '../../src/transaction/Token.js'; -import { TokenId } from '../../src/transaction/TokenId.js'; -import { TokenType } from '../../src/transaction/TokenType.js'; -import { waitInclusionProof } from '../../src/util/InclusionProofUtils.js'; -import trustBaseJson from '../trust-base.json' with { type: 'json' }; -import config from './config.json' with { type: 'json' }; - -const aggregatorClient = new AggregatorClient('http://localhost:3000'); -const trustBase = RootTrustBase.fromJSON(trustBaseJson); - -const client = new StateTransitionClient(aggregatorClient); - -const predicateVerifier = PredicateVerifier.create(); - -const ownerPrivateKey = HexConverter.decode(config.ownerPrivateKey); -const ownerSigningService = new SigningService(ownerPrivateKey); -const ownerPredicate = PayToPublicKeyPredicate.create(ownerSigningService); - -const mintTransaction = await MintTransaction.create( - await PayToScriptHash.create(ownerPredicate), - new TokenId(crypto.getRandomValues(new Uint8Array(32))), - new TokenType(crypto.getRandomValues(new Uint8Array(32))), - CborSerializer.encodeTextString('My custom data'), -); -const certificationData = await CertificationData.fromMintTransaction(mintTransaction); - -await client.submitCertificationRequest(certificationData); - -const token = await Token.mint( - trustBase, - predicateVerifier, - await mintTransaction.toCertifiedTransaction( - trustBase, - predicateVerifier, - await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), - ), -); - -console.log(HexConverter.encode(token.toCBOR())); diff --git a/examples/split/index.ts b/examples/split/index.ts deleted file mode 100644 index fc0b5cf3..00000000 --- a/examples/split/index.ts +++ /dev/null @@ -1,151 +0,0 @@ -import config from './config.json' with { type: 'json' }; -import { CustomPaymentData } from './CustomPaymentData.js'; -import { CustomSplitPaymentData } from './CustomSplitPaymentData.js'; -import { AggregatorClient } from '../../src/api/AggregatorClient.js'; -import { RootTrustBase } from '../../src/api/bft/RootTrustBase.js'; -import { CertificationData } from '../../src/api/CertificationData.js'; -import { CertificationStatus } from '../../src/api/CertificationResponse.js'; -import { SigningService } from '../../src/crypto/secp256k1/SigningService.js'; -import { Asset } from '../../src/payment/asset/Asset.js'; -import { AssetId } from '../../src/payment/asset/AssetId.js'; -import { PaymentAssetCollection } from '../../src/payment/asset/PaymentAssetCollection.js'; -import { SplitReason } from '../../src/payment/SplitReason.js'; -import { TokenSplit } from '../../src/payment/TokenSplit.js'; -import { PayToPublicKeyPredicate } from '../../src/predicate/builtin/PayToPublicKeyPredicate.js'; -import { PredicateVerifier } from '../../src/predicate/verification/PredicateVerifier.js'; -import { HexConverter } from '../../src/serialization/HexConverter.js'; -import { StateTransitionClient } from '../../src/StateTransitionClient.js'; -import { MintTransaction } from '../../src/transaction/MintTransaction.js'; -import { PayToScriptHash } from '../../src/transaction/PayToScriptHash.js'; -import { Token } from '../../src/transaction/Token.js'; -import { TokenId } from '../../src/transaction/TokenId.js'; -import { TokenType } from '../../src/transaction/TokenType.js'; -import { waitInclusionProof } from '../../src/util/InclusionProofUtils.js'; -import { VerificationStatus } from '../../src/verification/VerificationStatus.js'; -import trustBaseJson from '../trust-base.json' with { type: 'json' }; - -const aggregatorClient = new AggregatorClient('http://localhost:3000'); -const trustBase = RootTrustBase.fromJSON(trustBaseJson); - -const client = new StateTransitionClient(aggregatorClient); - -const predicateVerifier = PredicateVerifier.create(); - -const ownerPrivateKey = HexConverter.decode(config.ownerPrivateKey); -const ownerSigningService = new SigningService(ownerPrivateKey); -const ownerPredicate = PayToPublicKeyPredicate.create(ownerSigningService); - -const textEncoder = new TextEncoder(); - -const assets = [ - new Asset(new AssetId(textEncoder.encode('EUR')), 300n), - new Asset(new AssetId(textEncoder.encode('USD')), 500n), -]; - -const paymentData = new CustomPaymentData(PaymentAssetCollection.create(...assets), 'my other data'); -const mintTransaction = await MintTransaction.create( - await PayToScriptHash.create(ownerPredicate), - new TokenId(crypto.getRandomValues(new Uint8Array(32))), - new TokenType(crypto.getRandomValues(new Uint8Array(32))), - await paymentData.toCBOR(), -); - -let response = await client.submitCertificationRequest(await CertificationData.fromMintTransaction(mintTransaction)); -if (response.status !== CertificationStatus.SUCCESS) { - throw new Error(`Token mint certification failed: ${response.status}`); -} - -const token = await Token.mint( - trustBase, - predicateVerifier, - await mintTransaction.toCertifiedTransaction( - trustBase, - predicateVerifier, - await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), - ), -); - -const splitTokens: [TokenId, PaymentAssetCollection][] = [ - [ - new TokenId(crypto.getRandomValues(new Uint8Array(32))), - PaymentAssetCollection.create(new Asset(new AssetId(textEncoder.encode('EUR')), 150n)), - ], - [ - new TokenId(crypto.getRandomValues(new Uint8Array(32))), - PaymentAssetCollection.create(new Asset(new AssetId(textEncoder.encode('EUR')), 150n)), - ], - [ - new TokenId(crypto.getRandomValues(new Uint8Array(32))), - PaymentAssetCollection.create(new Asset(new AssetId(textEncoder.encode('USD')), 500n)), - ], -]; - -const result = await TokenSplit.split(token, ownerPredicate, CustomPaymentData.fromCBOR, splitTokens); - -response = await client.submitCertificationRequest( - await CertificationData.fromTransferTransaction( - result.burn.transaction, - await PayToPublicKeyPredicate.generateUnlockScript(result.burn.transaction, ownerSigningService), - ), -); - -if (response.status !== CertificationStatus.SUCCESS) { - throw new Error(`Token certification failed: ${response.status}`); -} - -const burntToken = await token.transfer( - trustBase, - predicateVerifier, - await result.burn.transaction.toCertifiedTransaction( - trustBase, - predicateVerifier, - await waitInclusionProof(trustBase, predicateVerifier, client, result.burn.transaction), - ), -); - -let i = 1; -for (const [tokenId, assets] of splitTokens) { - const entry = result.proofs.get(tokenId); - if (entry == null) { - throw new Error('Missing split reason proof for token.'); - } - - const splitPaymentData = new CustomSplitPaymentData(assets, SplitReason.create(burntToken, entry.proofs)); - - const mintTransaction = await MintTransaction.create( - await PayToScriptHash.create(ownerPredicate), - tokenId, - new TokenType(crypto.getRandomValues(new Uint8Array(32))), - await splitPaymentData.toCBOR(), - ); - - const certificationData = await CertificationData.fromMintTransaction(mintTransaction); - - const response = await client.submitCertificationRequest(certificationData); - if (response.status !== CertificationStatus.SUCCESS) { - throw new Error(`Token certification failed: ${response.status}`); - } - - const splitToken = await Token.mint( - trustBase, - predicateVerifier, - await mintTransaction.toCertifiedTransaction( - trustBase, - predicateVerifier, - await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), - ), - ); - - const splitResult = await TokenSplit.verify( - await Token.fromCBOR(splitToken.toCBOR()), - CustomSplitPaymentData.fromCBOR, - trustBase, - predicateVerifier, - ); - - if (splitResult.status !== VerificationStatus.OK) { - throw new Error(`Split token verification failed: ${splitResult.status}`); - } - - console.log(`Token[${i++}]: `, HexConverter.encode(splitToken.toCBOR()), '\n'); -} diff --git a/examples/transfer/config.json b/examples/transfer/config.json deleted file mode 100644 index ee28df69..00000000 --- a/examples/transfer/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "token": "8282835820dfa3d66dcb38707fef03142d032570b04470183853d06bf0db33089f31aee0e15820f5c3a8c9645befb1bd3226bb7be71e56f0a29a3e281007fd1cf06ee5800669308258203ae6e9c6107c913f41c31cdd046a3237a40fa2a63050e9f109d07a5b66235a494f6e4d7920637573746f6d20646174618384830141015821035c3ac84a9eaf413a946bba95986360deb9b317f141c3c7764c2b74a59207fd8b582083645aaa3b018eef21c6552a73e4b78620de199e6073d9d17f76067a6a102b175820f05532263005d9bc489141283a61a5204c3b28e2edda8397ddcf1b5d4248b05458415fc9c1c5adca1fcc8bb6349de2cd5bc7a2dddf520881973698e8fa926e2d0f58389ab846a61683160ea83e6a2b62ecf98fc47efcf0f14749776311284d549d98008258220000cca3d3e015b88799777158fb1555537e3cd63f91a1567153f3f52fbd3f947fb382825821018614763a8ab3559206704d5ac7bedd9783d92bfe8b0400909481b2cc4eca4d48582200001956ad7178c8a716d50fb8db456185607d48ff065549b8d4e8d392ced8bdca51824101f6d903ef8700d903f08a000000f658220000cca3d3e015b88799777158fb1555537e3cd63f91a1567153f3f52fbd3f947fb34000f600f6f658200000000000000000000000000000000000000000000000000000000000000000824080d903f683000080d903e9880000000000f65820050bd030caf5914948d7ab70c4fe57f02594040d4ca55e33773d50c0791f3450a1644e4f44455841da184fffc6a6e4b0f1ae7ea98ea1e8845ff91d071850c9036e7af3762f942a4d6155e763f986b493ce896323af88a777e894000bf8cacfaa30aedb096be767280180", - "ownerPrivateKey": "0202020202020202020202020202020202020202020202020202020202020202", - "payToScriptHash": "73c4dd24bea201b6b5cc06143de913456717bc8fa0c696bd249528d10308bcfa" -} \ No newline at end of file diff --git a/examples/transfer/index.ts b/examples/transfer/index.ts deleted file mode 100644 index dac9453b..00000000 --- a/examples/transfer/index.ts +++ /dev/null @@ -1,67 +0,0 @@ -import trustBaseJson from '../trust-base.json' with { type: 'json' }; -import config from './config.json' with { type: 'json' }; -import { AggregatorClient } from '../../src/api/AggregatorClient.js'; -import { RootTrustBase } from '../../src/api/bft/RootTrustBase.js'; -import { CertificationData } from '../../src/api/CertificationData.js'; -import { CertificationStatus } from '../../src/api/CertificationResponse.js'; -import { SigningService } from '../../src/crypto/secp256k1/SigningService.js'; -import { PayToPublicKeyPredicate } from '../../src/predicate/builtin/PayToPublicKeyPredicate.js'; -import { PredicateVerifier } from '../../src/predicate/verification/PredicateVerifier.js'; -import { CborSerializer } from '../../src/serialization/cbor/CborSerializer.js'; -import { HexConverter } from '../../src/serialization/HexConverter.js'; -import { StateTransitionClient } from '../../src/StateTransitionClient.js'; -import { PayToScriptHash } from '../../src/transaction/PayToScriptHash.js'; -import { Token } from '../../src/transaction/Token.js'; -import { TransferTransaction } from '../../src/transaction/TransferTransaction.js'; -import { waitInclusionProof } from '../../src/util/InclusionProofUtils.js'; -import { VerificationStatus } from '../../src/verification/VerificationStatus.js'; - -const aggregatorClient = new AggregatorClient('http://localhost:3000'); -const trustBase = RootTrustBase.fromJSON(trustBaseJson); -const client = new StateTransitionClient(aggregatorClient); - -const predicateVerifier = PredicateVerifier.create(); - -const ownerPrivateKey = HexConverter.decode(config.ownerPrivateKey); -const ownerSigningService = new SigningService(ownerPrivateKey); -const ownerPredicate = PayToPublicKeyPredicate.create(ownerSigningService); - -const tokenCBOR = HexConverter.decode(config.token); - -const token = await Token.fromCBOR(tokenCBOR); -const result = await token.verify(trustBase, predicateVerifier); -if (result.status !== VerificationStatus.OK) { - throw new Error(`Token verification failed: ${result.status}`); -} - -const payToScriptHash = PayToScriptHash.fromBytes(HexConverter.decode(config.payToScriptHash)); -const transferTransaction = await TransferTransaction.create( - token, - ownerPredicate, - payToScriptHash, - crypto.getRandomValues(new Uint8Array(32)), - CborSerializer.encodeTextString('My custom transfer data'), -); - -const certificationData = await CertificationData.fromTransferTransaction( - transferTransaction, - await PayToPublicKeyPredicate.generateUnlockScript(transferTransaction, ownerSigningService), -); - -const response = await client.submitCertificationRequest(certificationData); - -if (response.status !== CertificationStatus.SUCCESS) { - throw new Error(`Token certification failed: ${response.status}`); -} - -const transferToken = await token.transfer( - trustBase, - predicateVerifier, - await transferTransaction.toCertifiedTransaction( - trustBase, - predicateVerifier, - await waitInclusionProof(trustBase, predicateVerifier, client, transferTransaction), - ), -); - -console.log(HexConverter.encode(transferToken.toCBOR())); diff --git a/package.json b/package.json index b5442a5b..02dc4247 100644 --- a/package.json +++ b/package.json @@ -10,18 +10,15 @@ "./lib/*": "./lib/*.js" }, "scripts": { - "prebuild": "rm -rf lib", "build": "tsc", "build:check": "tsc --project tsconfig.eslint.json --noEmit", "lint": "eslint \"src/**/*\" \"tests/**/*\" \"examples/**/*\"", "lint:fix": "eslint \"src/**/*\" \"tests/**/*\" \"examples/**/*\" --fix", - "test": "jest --testPathPatterns=tests --testPathIgnorePatterns=tests/e2e", + "test": "npm run-script test:unit", "test:unit": "jest --testPathPatterns=tests/unit --testPathPatterns=tests/functional", - "test:integration": "jest --testPathPatterns=tests/integration", "test:e2e": "jest --testPathPatterns=tests/e2e", - "test:ci": "DEBUG=testcontainers:containers jest --testPathPatterns=tests --testPathIgnorePatterns=tests/e2e --ci --reporters=default", - "test:single": "jest", - "prepublishOnly": "npm run build" + "test:examples": "jest --testPathPatterns=tests/examples", + "test:ci": "jest --testPathPatterns=tests --testPathIgnorePatterns=tests/e2e --testPathIgnorePatterns=tests/examples --ci --reporters=default" }, "repository": { "type": "git", @@ -53,6 +50,7 @@ "globals": "17.3.0", "jest": "30.2.0", "typescript": "5.9.3", - "typescript-eslint": "8.54.0" + "typescript-eslint": "8.54.0", + "ts-node": "10.9.2" } } diff --git a/tests/examples/mint/ExampleTest.ts b/tests/examples/mint/ExampleTest.ts new file mode 100644 index 00000000..c9d407a2 --- /dev/null +++ b/tests/examples/mint/ExampleTest.ts @@ -0,0 +1,52 @@ +import config from './config.json' with { type: 'json' }; +import { AggregatorClient } from '../../../src/api/AggregatorClient.js'; +import { RootTrustBase } from '../../../src/api/bft/RootTrustBase.js'; +import { CertificationData } from '../../../src/api/CertificationData.js'; +import { SigningService } from '../../../src/crypto/secp256k1/SigningService.js'; +import { PayToPublicKeyPredicate } from '../../../src/predicate/builtin/PayToPublicKeyPredicate.js'; +import { PredicateVerifier } from '../../../src/predicate/verification/PredicateVerifier.js'; +import { CborSerializer } from '../../../src/serialization/cbor/CborSerializer.js'; +import { HexConverter } from '../../../src/serialization/HexConverter.js'; +import { StateTransitionClient } from '../../../src/StateTransitionClient.js'; +import { MintTransaction } from '../../../src/transaction/MintTransaction.js'; +import { PayToScriptHash } from '../../../src/transaction/PayToScriptHash.js'; +import { Token } from '../../../src/transaction/Token.js'; +import { TokenId } from '../../../src/transaction/TokenId.js'; +import { TokenType } from '../../../src/transaction/TokenType.js'; +import { waitInclusionProof } from '../../../src/util/InclusionProofUtils.js'; +import trustBaseJson from '../trust-base.json' with { type: 'json' }; + +it('Token minting', async () => { + const aggregatorClient = new AggregatorClient(config.aggregatorUrl); + const trustBase = RootTrustBase.fromJSON(trustBaseJson); + + const client = new StateTransitionClient(aggregatorClient); + + const predicateVerifier = PredicateVerifier.create(); + + const ownerPrivateKey = HexConverter.decode(config.ownerPrivateKey); + const ownerSigningService = new SigningService(ownerPrivateKey); + const ownerPredicate = PayToPublicKeyPredicate.create(ownerSigningService); + + const mintTransaction = await MintTransaction.create( + await PayToScriptHash.create(ownerPredicate), + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + new TokenType(crypto.getRandomValues(new Uint8Array(32))), + CborSerializer.encodeTextString('My custom data'), + ); + const certificationData = await CertificationData.fromMintTransaction(mintTransaction); + + await client.submitCertificationRequest(certificationData); + + const token = await Token.mint( + trustBase, + predicateVerifier, + await mintTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), + ), + ); + + console.log(HexConverter.encode(token.toCBOR())); +}, 30000); diff --git a/examples/mint/config.json b/tests/examples/mint/config.json similarity index 67% rename from examples/mint/config.json rename to tests/examples/mint/config.json index 7c8fe61d..c2a7c2da 100644 --- a/examples/mint/config.json +++ b/tests/examples/mint/config.json @@ -1,3 +1,4 @@ { + "aggregatorUrl": "http://localhost:3000", "ownerPrivateKey": "0202020202020202020202020202020202020202020202020202020202020202" } \ No newline at end of file diff --git a/examples/split/CustomPaymentData.ts b/tests/examples/split/CustomPaymentData.ts similarity index 65% rename from examples/split/CustomPaymentData.ts rename to tests/examples/split/CustomPaymentData.ts index 796527ec..b1344b7b 100644 --- a/examples/split/CustomPaymentData.ts +++ b/tests/examples/split/CustomPaymentData.ts @@ -1,7 +1,7 @@ -import { PaymentAssetCollection } from '../../src/payment/asset/PaymentAssetCollection.js'; -import { IPaymentData } from '../../src/payment/IPaymentData.js'; -import { CborDeserializer } from '../../src/serialization/cbor/CborDeserializer.js'; -import { CborSerializer } from '../../src/serialization/cbor/CborSerializer.js'; +import { PaymentAssetCollection } from '../../../src/payment/asset/PaymentAssetCollection.js'; +import { IPaymentData } from '../../../src/payment/IPaymentData.js'; +import { CborDeserializer } from '../../../src/serialization/cbor/CborDeserializer.js'; +import { CborSerializer } from '../../../src/serialization/cbor/CborSerializer.js'; export class CustomPaymentData implements IPaymentData { public constructor( diff --git a/examples/split/CustomSplitPaymentData.ts b/tests/examples/split/CustomSplitPaymentData.ts similarity index 60% rename from examples/split/CustomSplitPaymentData.ts rename to tests/examples/split/CustomSplitPaymentData.ts index 8314b798..9bca8532 100644 --- a/examples/split/CustomSplitPaymentData.ts +++ b/tests/examples/split/CustomSplitPaymentData.ts @@ -1,8 +1,8 @@ -import { PaymentAssetCollection } from '../../src/payment/asset/PaymentAssetCollection.js'; -import { ISplitPaymentData } from '../../src/payment/ISplitPaymentData.js'; -import { SplitReason } from '../../src/payment/SplitReason.js'; -import { CborDeserializer } from '../../src/serialization/cbor/CborDeserializer.js'; -import { CborSerializer } from '../../src/serialization/cbor/CborSerializer.js'; +import { PaymentAssetCollection } from '../../../src/payment/asset/PaymentAssetCollection.js'; +import { ISplitPaymentData } from '../../../src/payment/ISplitPaymentData.js'; +import { SplitReason } from '../../../src/payment/SplitReason.js'; +import { CborDeserializer } from '../../../src/serialization/cbor/CborDeserializer.js'; +import { CborSerializer } from '../../../src/serialization/cbor/CborSerializer.js'; export class CustomSplitPaymentData implements ISplitPaymentData { public constructor( diff --git a/tests/examples/split/ExampleTest.ts b/tests/examples/split/ExampleTest.ts new file mode 100644 index 00000000..fd40bbcf --- /dev/null +++ b/tests/examples/split/ExampleTest.ts @@ -0,0 +1,153 @@ +import config from './config.json' with { type: 'json' }; +import { CustomPaymentData } from './CustomPaymentData.js'; +import { CustomSplitPaymentData } from './CustomSplitPaymentData.js'; +import { AggregatorClient } from '../../../src/api/AggregatorClient.js'; +import { RootTrustBase } from '../../../src/api/bft/RootTrustBase.js'; +import { CertificationData } from '../../../src/api/CertificationData.js'; +import { CertificationStatus } from '../../../src/api/CertificationResponse.js'; +import { SigningService } from '../../../src/crypto/secp256k1/SigningService.js'; +import { Asset } from '../../../src/payment/asset/Asset.js'; +import { AssetId } from '../../../src/payment/asset/AssetId.js'; +import { PaymentAssetCollection } from '../../../src/payment/asset/PaymentAssetCollection.js'; +import { SplitReason } from '../../../src/payment/SplitReason.js'; +import { TokenSplit } from '../../../src/payment/TokenSplit.js'; +import { PayToPublicKeyPredicate } from '../../../src/predicate/builtin/PayToPublicKeyPredicate.js'; +import { PredicateVerifier } from '../../../src/predicate/verification/PredicateVerifier.js'; +import { HexConverter } from '../../../src/serialization/HexConverter.js'; +import { StateTransitionClient } from '../../../src/StateTransitionClient.js'; +import { MintTransaction } from '../../../src/transaction/MintTransaction.js'; +import { PayToScriptHash } from '../../../src/transaction/PayToScriptHash.js'; +import { Token } from '../../../src/transaction/Token.js'; +import { TokenId } from '../../../src/transaction/TokenId.js'; +import { TokenType } from '../../../src/transaction/TokenType.js'; +import { waitInclusionProof } from '../../../src/util/InclusionProofUtils.js'; +import { VerificationStatus } from '../../../src/verification/VerificationStatus.js'; +import trustBaseJson from '../trust-base.json' with { type: 'json' }; + +it('Token splitting', async () => { + const aggregatorClient = new AggregatorClient(config.aggregatorUrl); + const trustBase = RootTrustBase.fromJSON(trustBaseJson); + + const client = new StateTransitionClient(aggregatorClient); + + const predicateVerifier = PredicateVerifier.create(); + + const ownerPrivateKey = HexConverter.decode(config.ownerPrivateKey); + const ownerSigningService = new SigningService(ownerPrivateKey); + const ownerPredicate = PayToPublicKeyPredicate.create(ownerSigningService); + + const textEncoder = new TextEncoder(); + + const assets = [ + new Asset(new AssetId(textEncoder.encode('EUR')), 300n), + new Asset(new AssetId(textEncoder.encode('USD')), 500n), + ]; + + const paymentData = new CustomPaymentData(PaymentAssetCollection.create(...assets), 'my other data'); + const mintTransaction = await MintTransaction.create( + await PayToScriptHash.create(ownerPredicate), + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + new TokenType(crypto.getRandomValues(new Uint8Array(32))), + await paymentData.toCBOR(), + ); + + let response = await client.submitCertificationRequest(await CertificationData.fromMintTransaction(mintTransaction)); + if (response.status !== CertificationStatus.SUCCESS) { + throw new Error(`Token mint certification failed: ${response.status}`); + } + + const token = await Token.mint( + trustBase, + predicateVerifier, + await mintTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), + ), + ); + + const splitTokens: [TokenId, PaymentAssetCollection][] = [ + [ + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + PaymentAssetCollection.create(new Asset(new AssetId(textEncoder.encode('EUR')), 150n)), + ], + [ + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + PaymentAssetCollection.create(new Asset(new AssetId(textEncoder.encode('EUR')), 150n)), + ], + [ + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + PaymentAssetCollection.create(new Asset(new AssetId(textEncoder.encode('USD')), 500n)), + ], + ]; + + const result = await TokenSplit.split(token, ownerPredicate, CustomPaymentData.fromCBOR, splitTokens); + + response = await client.submitCertificationRequest( + await CertificationData.fromTransferTransaction( + result.burn.transaction, + await PayToPublicKeyPredicate.generateUnlockScript(result.burn.transaction, ownerSigningService), + ), + ); + + if (response.status !== CertificationStatus.SUCCESS) { + throw new Error(`Token certification failed: ${response.status}`); + } + + const burntToken = await token.transfer( + trustBase, + predicateVerifier, + await result.burn.transaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, result.burn.transaction), + ), + ); + + let i = 1; + for (const [tokenId, assets] of splitTokens) { + const entry = result.proofs.get(tokenId); + if (entry == null) { + throw new Error('Missing split reason proof for token.'); + } + + const splitPaymentData = new CustomSplitPaymentData(assets, SplitReason.create(burntToken, entry.proofs)); + + const mintTransaction = await MintTransaction.create( + await PayToScriptHash.create(ownerPredicate), + tokenId, + new TokenType(crypto.getRandomValues(new Uint8Array(32))), + await splitPaymentData.toCBOR(), + ); + + const certificationData = await CertificationData.fromMintTransaction(mintTransaction); + + const response = await client.submitCertificationRequest(certificationData); + if (response.status !== CertificationStatus.SUCCESS) { + throw new Error(`Token certification failed: ${response.status}`); + } + + const splitToken = await Token.mint( + trustBase, + predicateVerifier, + await mintTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), + ), + ); + + const splitResult = await TokenSplit.verify( + await Token.fromCBOR(splitToken.toCBOR()), + CustomSplitPaymentData.fromCBOR, + trustBase, + predicateVerifier, + ); + + if (splitResult.status !== VerificationStatus.OK) { + throw new Error(`Split token verification failed: ${splitResult.status}`); + } + + console.log(`Token[${i++}]: `, HexConverter.encode(splitToken.toCBOR()), '\n'); + } +}, 30000); diff --git a/examples/split/config.json b/tests/examples/split/config.json similarity index 67% rename from examples/split/config.json rename to tests/examples/split/config.json index 7c8fe61d..c2a7c2da 100644 --- a/examples/split/config.json +++ b/tests/examples/split/config.json @@ -1,3 +1,4 @@ { + "aggregatorUrl": "http://localhost:3000", "ownerPrivateKey": "0202020202020202020202020202020202020202020202020202020202020202" } \ No newline at end of file diff --git a/tests/examples/transfer/ExampleTest.ts b/tests/examples/transfer/ExampleTest.ts new file mode 100644 index 00000000..367be28d --- /dev/null +++ b/tests/examples/transfer/ExampleTest.ts @@ -0,0 +1,101 @@ +import config from './config.json' with { type: 'json' }; +import { AggregatorClient } from '../../../src/api/AggregatorClient.js'; +import { RootTrustBase } from '../../../src/api/bft/RootTrustBase.js'; +import { CertificationData } from '../../../src/api/CertificationData.js'; +import { CertificationStatus } from '../../../src/api/CertificationResponse.js'; +import { SigningService } from '../../../src/crypto/secp256k1/SigningService.js'; +import { PayToPublicKeyPredicate } from '../../../src/predicate/builtin/PayToPublicKeyPredicate.js'; +import { PredicateVerifier } from '../../../src/predicate/verification/PredicateVerifier.js'; +import { CborSerializer } from '../../../src/serialization/cbor/CborSerializer.js'; +import { HexConverter } from '../../../src/serialization/HexConverter.js'; +import { StateTransitionClient } from '../../../src/StateTransitionClient.js'; +import { MintTransaction } from '../../../src/transaction/MintTransaction.js'; +import { PayToScriptHash } from '../../../src/transaction/PayToScriptHash.js'; +import { Token } from '../../../src/transaction/Token.js'; +import { TokenId } from '../../../src/transaction/TokenId.js'; +import { TokenType } from '../../../src/transaction/TokenType.js'; +import { TransferTransaction } from '../../../src/transaction/TransferTransaction.js'; +import { waitInclusionProof } from '../../../src/util/InclusionProofUtils.js'; +import { VerificationStatus } from '../../../src/verification/VerificationStatus.js'; +import trustBaseJson from '../trust-base.json' with { type: 'json' }; + +async function receiveToken(client: StateTransitionClient, trustBase: RootTrustBase): Promise { + const predicateVerifier = PredicateVerifier.create(); + + const ownerPrivateKey = HexConverter.decode(config.ownerPrivateKey); + const ownerSigningService = new SigningService(ownerPrivateKey); + const ownerPredicate = PayToPublicKeyPredicate.create(ownerSigningService); + + const mintTransaction = await MintTransaction.create( + await PayToScriptHash.create(ownerPredicate), + new TokenId(crypto.getRandomValues(new Uint8Array(32))), + new TokenType(crypto.getRandomValues(new Uint8Array(32))), + CborSerializer.encodeTextString('My custom data'), + ); + const certificationData = await CertificationData.fromMintTransaction(mintTransaction); + + await client.submitCertificationRequest(certificationData); + + const token = await Token.mint( + trustBase, + predicateVerifier, + await mintTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, mintTransaction), + ), + ); + + return HexConverter.encode(token.toCBOR()); +} + +it('Token transfer', async () => { + const aggregatorClient = new AggregatorClient(config.aggregatorUrl); + const trustBase = RootTrustBase.fromJSON(trustBaseJson); + const client = new StateTransitionClient(aggregatorClient); + const predicateVerifier = PredicateVerifier.create(); + + const ownerPrivateKey = HexConverter.decode(config.ownerPrivateKey); + const ownerSigningService = new SigningService(ownerPrivateKey); + const ownerPredicate = PayToPublicKeyPredicate.create(ownerSigningService); + + const tokenCBOR = HexConverter.decode(await receiveToken(client, trustBase)); + + const token = await Token.fromCBOR(tokenCBOR); + const result = await token.verify(trustBase, predicateVerifier); + if (result.status !== VerificationStatus.OK) { + throw new Error(`Token verification failed: ${result.status}`); + } + + const payToScriptHash = PayToScriptHash.fromBytes(HexConverter.decode(config.payToScriptHash)); + const transferTransaction = await TransferTransaction.create( + token, + ownerPredicate, + payToScriptHash, + crypto.getRandomValues(new Uint8Array(32)), + CborSerializer.encodeTextString('My custom transfer data'), + ); + + const certificationData = await CertificationData.fromTransferTransaction( + transferTransaction, + await PayToPublicKeyPredicate.generateUnlockScript(transferTransaction, ownerSigningService), + ); + + const response = await client.submitCertificationRequest(certificationData); + + if (response.status !== CertificationStatus.SUCCESS) { + throw new Error(`Token certification failed: ${response.status}`); + } + + const transferToken = await token.transfer( + trustBase, + predicateVerifier, + await transferTransaction.toCertifiedTransaction( + trustBase, + predicateVerifier, + await waitInclusionProof(trustBase, predicateVerifier, client, transferTransaction), + ), + ); + + console.log(HexConverter.encode(transferToken.toCBOR())); +}, 30000); diff --git a/tests/examples/transfer/config.json b/tests/examples/transfer/config.json new file mode 100644 index 00000000..cfa91419 --- /dev/null +++ b/tests/examples/transfer/config.json @@ -0,0 +1,5 @@ +{ + "aggregatorUrl": "http://localhost:3000", + "ownerPrivateKey": "0202020202020202020202020202020202020202020202020202020202020202", + "payToScriptHash": "73c4dd24bea201b6b5cc06143de913456717bc8fa0c696bd249528d10308bcfa" +} \ No newline at end of file diff --git a/examples/trust-base.json b/tests/examples/trust-base.json similarity index 100% rename from examples/trust-base.json rename to tests/examples/trust-base.json diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 2efe79ce..c0c7ec6f 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -10,6 +10,5 @@ "include": [ "src/**/*.ts", "tests/**/*.ts", - "examples/**/*.ts", ], } \ No newline at end of file From 654a2c5896212a6db298667998d4cbdef858a315 Mon Sep 17 00:00:00 2001 From: Martti Marran Date: Tue, 10 Feb 2026 15:21:17 +0200 Subject: [PATCH 15/15] #96 Fix lint config --- package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 02dc4247..2853e365 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "scripts": { "build": "tsc", "build:check": "tsc --project tsconfig.eslint.json --noEmit", - "lint": "eslint \"src/**/*\" \"tests/**/*\" \"examples/**/*\"", - "lint:fix": "eslint \"src/**/*\" \"tests/**/*\" \"examples/**/*\" --fix", + "lint": "eslint \"src/**/*\" \"tests/**/*\"", + "lint:fix": "eslint \"src/**/*\" \"tests/**/*\" --fix", "test": "npm run-script test:unit", "test:unit": "jest --testPathPatterns=tests/unit --testPathPatterns=tests/functional", "test:e2e": "jest --testPathPatterns=tests/e2e", @@ -50,7 +50,6 @@ "globals": "17.3.0", "jest": "30.2.0", "typescript": "5.9.3", - "typescript-eslint": "8.54.0", - "ts-node": "10.9.2" + "typescript-eslint": "8.54.0" } }