diff --git a/README.md b/README.md index ae9b5943..5b592007 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 @@ -63,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 @@ -129,16 +156,14 @@ 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 +`tests/examples/mint/ExampleTest.ts` ### Token Transfer +`tests/examples/transfer/ExampleTest.ts` -### Checking Token Status +### Token Splitting +`tests/examples/split/ExampleTest.ts` ## Unicity Signature Standard 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..2853e365 100644 --- a/package.json +++ b/package.json @@ -5,19 +5,20 @@ "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", - "build:check": "tsc --noEmit", + "build:check": "tsc --project tsconfig.eslint.json --noEmit", "lint": "eslint \"src/**/*\" \"tests/**/*\"", "lint:fix": "eslint \"src/**/*\" \"tests/**/*\" --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", @@ -37,18 +38,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..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: stateId.toJSON() }; + 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 c5f26589..b5bce13a 100644 --- a/src/api/CertificationData.ts +++ b/src/api/CertificationData.ts @@ -2,7 +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 { InvalidJsonStructureError } from '../InvalidJsonStructureError.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'; @@ -12,20 +12,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,44 +46,21 @@ 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 sourceStateHash = await transaction.calculateStateHash(); - const transactionHash = await transaction.calculateTransactionHash(); - - const signatureDataHash = await new DataHasher(HashAlgorithm.SHA256) - .update(CborSerializer.encodeArray(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, + await transaction.calculateTransactionHash(), + await PayToPublicKeyPredicate.generateUnlockScript(transaction, signingService), + ); } public static async fromTransferTransaction( @@ -105,33 +68,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); - } - - /** - * 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' - ); + return CertificationData.create(transaction.lockScript, transaction.sourceStateHash, transactionHash, unlockScript); } private static create( @@ -160,25 +99,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 7df36193..86f84cb9 100644 --- a/src/api/StateId.ts +++ b/src/api/StateId.ts @@ -3,7 +3,9 @@ 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'; @@ -11,19 +13,18 @@ import { BitString } from '../util/BitString.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) {} - /** - * 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)); + public get data(): Uint8Array { + return this.hash.data; + } + + public get imprint(): Uint8Array { + return this.hash.imprint; + } + + public static fromCBOR(bytes: Uint8Array): StateId { + return new StateId(new DataHash(HashAlgorithm.SHA256, CborDeserializer.decodeByteString(bytes))); } /** @@ -34,17 +35,8 @@ 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 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); } /** @@ -55,26 +47,26 @@ 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 this.hash.equals(id.hash); + } + /** * 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.data); } /** @@ -82,6 +74,6 @@ export class StateId { * @returns The string representation. */ public toString(): string { - return `StateId[${this.hash.toString()}]`; + return `StateId[${HexConverter.encode(this.data)}]`; } } 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..7bcd75bb 100644 --- a/src/crypto/hash/DataHash.ts +++ b/src/crypto/hash/DataHash.ts @@ -1,8 +1,7 @@ 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'; export class DataHash { private readonly _imprint: Uint8Array; @@ -11,9 +10,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 +33,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/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/payment/IPaymentData.ts b/src/payment/IPaymentData.ts new file mode 100644 index 00000000..c347e506 --- /dev/null +++ b/src/payment/IPaymentData.ts @@ -0,0 +1,7 @@ +import { PaymentAssetCollection } from './asset/PaymentAssetCollection.js'; + +export interface IPaymentData { + readonly assets: PaymentAssetCollection; + + toCBOR(): Promise; +} diff --git a/src/payment/ISplitPaymentData.ts b/src/payment/ISplitPaymentData.ts new file mode 100644 index 00000000..62c9f985 --- /dev/null +++ b/src/payment/ISplitPaymentData.ts @@ -0,0 +1,6 @@ +import { IPaymentData } from './IPaymentData.js'; +import { SplitReason } from './SplitReason.js'; + +export interface ISplitPaymentData extends IPaymentData { + readonly reason: SplitReason; +} diff --git a/src/payment/SplitReason.ts b/src/payment/SplitReason.ts new file mode 100644 index 00000000..5649993b --- /dev/null +++ b/src/payment/SplitReason.ts @@ -0,0 +1,42 @@ +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(); + } + + // 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.'); + } + + 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..d03c28c0 --- /dev/null +++ b/src/payment/SplitReasonProof.ts @@ -0,0 +1,45 @@ +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 assetTreePath: SparseMerkleSumTreePath, + ) {} + + public static create( + assetId: AssetId, + aggregationPath: SparseMerkleTreePath, + assetTreePath: SparseMerkleSumTreePath, + ): SplitReasonProof { + return new SplitReasonProof(assetId, aggregationPath, assetTreePath); + } + + /** + * 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.assetTreePath.toCBOR(), + ); + } +} diff --git a/src/payment/TokenSplit.ts b/src/payment/TokenSplit.ts new file mode 100644 index 00000000..a7e52ef6 --- /dev/null +++ b/src/payment/TokenSplit.ts @@ -0,0 +1,274 @@ +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> { + // TODO: Check initial token also or that should be done by client beforehand? + + 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 assets 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 asset: ${proof.assetId.toString()}`, + [], + ); + } + + const assetTreePathResult = await proof.assetTreePath.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.assetTreePath.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.assetTreePath.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/asset/Asset.ts b/src/payment/asset/Asset.ts new file mode 100644 index 00000000..07911adb --- /dev/null +++ b/src/payment/asset/Asset.ts @@ -0,0 +1,28 @@ +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..0cd85d95 --- /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 asset. */ +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 AssetId from a byte array encoded in CBOR. + * @param bytes + */ + public static fromCBOR(bytes: Uint8Array): AssetId { + return new AssetId(CborDeserializer.decodeByteString(bytes)); + } + + /** + * Converts the AssetId 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..96629fec --- /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 key = HexConverter.encode(asset.id.bytes); + if (assets.has(key)) { + throw new Error('Invalid payment asset collection. Duplicate assets found.'); + } + assets.set(key, 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/error/TokenAssetCountMismatchError.ts b/src/payment/error/TokenAssetCountMismatchError.ts new file mode 100644 index 00000000..8bfd6b62 --- /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.'); + } +} diff --git a/src/payment/error/TokenAssetMissingError.ts b/src/payment/error/TokenAssetMissingError.ts new file mode 100644 index 00000000..8f7cbae0 --- /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()}.`); + } +} diff --git a/src/payment/error/TokenAssetValueMismatchError.ts b/src/payment/error/TokenAssetValueMismatchError.ts new file mode 100644 index 00000000..4979f0ce --- /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}`); + } +} diff --git a/src/payment/predicate/builtin/BurnPredicate.ts b/src/payment/predicate/builtin/BurnPredicate.ts new file mode 100644 index 00000000..4c148e91 --- /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 { HexConverter } from '../../../serialization/HexConverter.js'; +import { dedent } from '../../../util/StringUtils.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 reason(): 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/predicate/builtin/BuiltInPredicateVerifierFactory.ts b/src/predicate/builtin/BuiltInPredicateVerifierFactory.ts index 293d6361..1984e5e5 100644 --- a/src/predicate/builtin/BuiltInPredicateVerifierFactory.ts +++ b/src/predicate/builtin/BuiltInPredicateVerifierFactory.ts @@ -3,11 +3,31 @@ 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 PayToPublicKeyPredicateVerifier()]); + } public verify( predicate: IPredicate, diff --git a/src/predicate/builtin/PayToPublicKeyPredicate.ts b/src/predicate/builtin/PayToPublicKeyPredicate.ts index f717a2bf..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( - await transaction.calculateStateHash().then((hash) => hash.toCBOR()), - await transaction.calculateTransactionHash().then((hash) => hash.toCBOR()), + 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 9802f8d4..28899c82 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, @@ -29,8 +31,8 @@ export class PayToPublicKeyPredicateVerifier implements IPredicateVerifier { await new DataHasher(HashAlgorithm.SHA256) .update( CborSerializer.encodeArray( - certificationData.sourceStateHash.toCBOR(), - certificationData.transactionHash.toCBOR(), + CborSerializer.encodeByteString(certificationData.sourceStateHash.data), + CborSerializer.encodeByteString(certificationData.transactionHash.data), ), ) .digest(), 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/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/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/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/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..05cc5fd5 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'; @@ -25,6 +24,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,15 +49,22 @@ 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 { + 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]), @@ -65,7 +72,14 @@ export class MintTransaction implements ITransaction { } public calculateStateHash(): Promise { - return MintTransactionState.create(this.tokenId); + return new DataHasher(HashAlgorithm.SHA256) + .update( + CborSerializer.encodeArray( + CborSerializer.encodeByteString(this.sourceStateHash.imprint), + CborSerializer.encodeByteString(this.x), + ), + ) + .digest(); } public calculateTransactionHash(): Promise { @@ -93,12 +107,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/PayToScriptHash.ts b/src/transaction/PayToScriptHash.ts index db12eb8b..780231d2 100644 --- a/src/transaction/PayToScriptHash.ts +++ b/src/transaction/PayToScriptHash.ts @@ -8,62 +8,41 @@ 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 { + const hash = new DataHash(HashAlgorithm.SHA256, bytes); + return new PayToScriptHash(hash.data); } - 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/Token.ts b/src/transaction/Token.ts index 5533267f..96a16cb5 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,17 +19,25 @@ 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]); return new Token( await CertifiedMintTransaction.fromCBOR(data[0]), - await Promise.all(transactions.map((transaction) => CertifiedTransferTransaction.fromCBOR(transaction))), + transactions.map((transaction) => CertifiedTransferTransaction.fromCBOR(transaction)), ); } @@ -96,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/src/transaction/TokenId.ts b/src/transaction/TokenId.ts index 13dcd401..68b936db 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. */ @@ -27,21 +23,6 @@ export class TokenId { return new TokenId(CborDeserializer.decodeByteString(bytes)); } - public static fromJSON(input: string): 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; @@ -66,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 87784b00..21ab07a0 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, @@ -48,17 +47,21 @@ 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); } - 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]), ); @@ -66,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(); } @@ -84,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), @@ -97,12 +105,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/BitString.ts b/src/util/BitString.ts index 4d11607d..d6d3bfba 100644 --- a/src/util/BitString.ts +++ b/src/util/BitString.ts @@ -1,4 +1,4 @@ -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'; @@ -17,12 +17,12 @@ export class BitString { } /** - * Creates a BitString from a DataHash imprint. - * @param data DataHash + * Creates a BitString from a StateId. + * @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.imprint); } /** diff --git a/src/util/InclusionProofUtils.ts b/src/util/InclusionProofUtils.ts index a247cb02..a19c671f 100644 --- a/src/util/InclusionProofUtils.ts +++ b/src/util/InclusionProofUtils.ts @@ -47,8 +47,9 @@ export async function waitInclusionProof( trustBase, predicateVerifier, inclusionProof, - stateId, + transaction, ); + switch (verificationStatus.status) { case InclusionProofVerificationStatus.OK: return inclusionProof; 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/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/tests/examples/mint/config.json b/tests/examples/mint/config.json new file mode 100644 index 00000000..c2a7c2da --- /dev/null +++ b/tests/examples/mint/config.json @@ -0,0 +1,4 @@ +{ + "aggregatorUrl": "http://localhost:3000", + "ownerPrivateKey": "0202020202020202020202020202020202020202020202020202020202020202" +} \ No newline at end of file diff --git a/tests/examples/split/CustomPaymentData.ts b/tests/examples/split/CustomPaymentData.ts new file mode 100644 index 00000000..b1344b7b --- /dev/null +++ b/tests/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/tests/examples/split/CustomSplitPaymentData.ts b/tests/examples/split/CustomSplitPaymentData.ts new file mode 100644 index 00000000..9bca8532 --- /dev/null +++ b/tests/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/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/tests/examples/split/config.json b/tests/examples/split/config.json new file mode 100644 index 00000000..c2a7c2da --- /dev/null +++ b/tests/examples/split/config.json @@ -0,0 +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/tests/examples/trust-base.json b/tests/examples/trust-base.json new file mode 100644 index 00000000..7ffc9271 --- /dev/null +++ b/tests/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/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 f53d310d..06842bc2 100644 --- a/tests/functional/TestAggregatorClient.ts +++ b/tests/functional/TestAggregatorClient.ts @@ -9,36 +9,41 @@ 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 { 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'; 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 = new PredicateVerifier( - new Map([ - [ - PredicateEngine.BUILT_IN, - new BuiltInPredicateVerifierFactory(new Map([[1n, 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))); + /** + * 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)), + new SigningService(privateKey), + ); } + /** + * @inheritDoc + */ public async getInclusionProof(stateId: StateId): Promise { const certificationData = this.requests.get(stateId.toBitString().toBigInt()); const root = await this.smt.calculateRoot(); @@ -54,6 +59,9 @@ export class TestAggregatorClient implements IAggregatorClient { ); } + /** + * @inheritDoc + */ public async submitCertificationRequest(certificationData: CertificationData): Promise { const stateId = await StateId.fromCertificationData(certificationData); @@ -67,9 +75,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 0b118b6b..00000000 --- a/tests/functional/TransitionFlowTest.ts +++ /dev/null @@ -1,92 +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 { 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 { 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, - new BuiltInPredicateVerifierFactory(new Map([[1n, new PayToPublicKeyPredicateVerifier()]])), - ], - ]), - ); - - 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 transferTransaction = await TransferTransaction.create( - token, - predicate, - await PayToScriptHash.create(predicate), - 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); - - token = await token.transfer( - trustBase, - predicateVerifier, - await transferTransaction.toCertifiedTransaction( - trustBase, - predicateVerifier, - await waitInclusionProof(trustBase, predicateVerifier, client, transferTransaction), - ), - ); - - const importedToken = await Token.fromCBOR(token.toCBOR()); - await expect(importedToken.verify(trustBase, predicateVerifier).then((result) => result.status)).resolves.toEqual( - VerificationStatus.OK, - ); - - console.log(importedToken.toString()); - }, 30000); -}); diff --git a/tests/functional/payment/SplitBuilderTest.ts b/tests/functional/payment/SplitBuilderTest.ts new file mode 100644 index 00000000..9f9466af --- /dev/null +++ b/tests/functional/payment/SplitBuilderTest.ts @@ -0,0 +1,154 @@ +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 { PayToPublicKeyPredicate } from '../../../src/predicate/builtin/PayToPublicKeyPredicate.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 = PredicateVerifier.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())); + } +} diff --git a/tests/unit/api/CertificationDataTest.ts b/tests/unit/api/CertificationDataTest.ts index 837c5696..e8bddd33 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', + '8483014101582103b00b30dcd21feaa837132ccd4b7b9595f704c9714ac66eed085f52bc396f9050582080201eff2f0c27ea9c8433eb999b4fd0fa5bfb4fe47fa2690859f0c83651604e5820d3428d83066c996da3b3ec8a68851dfe1e0df6065bac29fce57a95b35360cecc584159613057b2c04cbf774d53325912cd9840db04c1dea4dd0ba1debf4a0e37a11a696f9c31ceec1898ea7bb83bc6d959d31f24b5810653064639883470f930deb100', ); - 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..c1f8c33d 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', + '8458207191bb9f044715f712ca5e77e91b585cf892eb5755ae4d77231ad429c53cf661848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798582000000000000000000000000000000000000000000000000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01f400', ); 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', + '8458207191bb9f044715f712ca5e77e91b585cf892eb5755ae4d77231ad429c53cf661848301410158210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798582000000000000000000000000000000000000000000000000000000000000000005820000000000000000000000000000000000000000000000000000000000000000058418c3f91708445bf0ddec220f0821461bcf84860a8769275f9930e798d1f645d157bb6a2998c61941108b0993c5aed6a7b92ccf31d11b50fe80d9ff93da392336a01f500', ); }); }); 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 3dd2f9b8..9c122d80 100644 --- a/tests/unit/api/InclusionProofTest.ts +++ b/tests/unit/api/InclusionProofTest.ts @@ -8,14 +8,16 @@ 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 { PayToPublicKeyPredicateVerifier } from '../../../src/predicate/builtin/verification/PayToPublicKeyPredicateVerifier.js'; -import { PredicateEngine } from '../../../src/predicate/PredicateEngine.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 { 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, @@ -28,33 +30,25 @@ 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; 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(); @@ -65,26 +59,12 @@ describe('InclusionProof', () => { trustBase = createRootTrustBase(signingService.publicKey); }); - it('should encode and decode json', () => { - const inclusionProof = new InclusionProof(merkleTreePath, certificationData, 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, 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 +79,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,75 +91,72 @@ 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); 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, 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), - }), + CertificationData.fromCBOR( + CborSerializer.encodeArray( + certificationData.lockScript.toCBOR(), + CborSerializer.encodeByteString(certificationData.sourceStateHash.data), + CborSerializer.encodeByteString(certificationData.transactionHash.data), + CborSerializer.encodeByteString(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([ - [ - PredicateEngine.BUILT_IN, - new BuiltInPredicateVerifierFactory(new Map([[1n, new PayToPublicKeyPredicateVerifier()]])), - ], - ]), - ); + const predicateVerifier = PredicateVerifier.create(); await expect( InclusionProofVerificationRule.verify( createRootTrustBase(HexConverter.decode('0000000000000000000000000000000000000000000000000000000000000001')), predicateVerifier, inclusionProof, - stateId, + transaction, ).then((result) => result.status), ).resolves.toEqual(InclusionProofVerificationStatus.INVALID_TRUSTBASE); }); diff --git a/tests/unit/api/StateIdTest.ts b/tests/unit/api/StateIdTest.ts index 7647a51c..dff67ba4 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', + '58205b4e69562ce02e38923a4c48727fabb8f0b2f4489e5d02c014a618ad83382891', ); 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/utils/TransitionFlow.ts b/tests/utils/TransitionFlow.ts new file mode 100644 index 00000000..deedda68 --- /dev/null +++ b/tests/utils/TransitionFlow.ts @@ -0,0 +1,148 @@ +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 { 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 = PredicateVerifier.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, + PayToScriptHash.fromBytes(receiverScriptHash.bytes), + 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); + }); +}; diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 5a6360d2..c0c7ec6f 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -9,6 +9,6 @@ ], "include": [ "src/**/*.ts", - "tests/**/*.ts" + "tests/**/*.ts", ], } \ No newline at end of file