From 49e4fec68825f565421c5550a4af3ad48869596b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 14:32:06 +0000 Subject: [PATCH 1/9] :arrow_up: (repo) [NO-ISSUE]: Bump the eslint group with 3 updates Bumps the eslint group with 3 updates: [eslint](https://github.com/eslint/eslint), [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint). Updates `eslint` from 9.10.0 to 9.11.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v9.10.0...v9.11.0) Updates `@eslint/js` from 9.10.0 to 9.11.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/commits/v9.11.0/packages/js) Updates `typescript-eslint` from 8.5.0 to 8.6.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.6.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor dependency-group: eslint - dependency-name: "@eslint/js" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: eslint - dependency-name: typescript-eslint dependency-type: direct:development update-type: version-update:semver-minor dependency-group: eslint ... Signed-off-by: dependabot[bot] --- package.json | 2 +- packages/config/eslint/package.json | 4 +- pnpm-lock.yaml | 174 ++++++++++++++-------------- 3 files changed, 90 insertions(+), 90 deletions(-) diff --git a/package.json b/package.json index 86161045c..e1d2dc0be 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@types/node": "^22.5.5", "concurrently": "^8.2.2", "danger": "^12.3.3", - "eslint": "9.10.0", + "eslint": "9.11.0", "gitmoji-cli": "^9.4.0", "hygen": "^6.2.11", "jest": "^29.7.0", diff --git a/packages/config/eslint/package.json b/packages/config/eslint/package.json index 80e0721d4..347203fef 100644 --- a/packages/config/eslint/package.json +++ b/packages/config/eslint/package.json @@ -10,10 +10,10 @@ ], "type": "module", "devDependencies": { - "@eslint/js": "9.10.0", + "@eslint/js": "9.11.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-simple-import-sort": "12.1.1", "globals": "15.9.0", - "typescript-eslint": "8.5.0" + "typescript-eslint": "8.6.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22bfb84dd..8889e6c0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,8 +27,8 @@ importers: specifier: ^12.3.3 version: 12.3.3 eslint: - specifier: 9.10.0 - version: 9.10.0 + specifier: 9.11.0 + version: 9.11.0 gitmoji-cli: specifier: ^9.4.0 version: 9.4.0 @@ -127,20 +127,20 @@ importers: packages/config/eslint: devDependencies: '@eslint/js': - specifier: 9.10.0 - version: 9.10.0 + specifier: 9.11.0 + version: 9.11.0 eslint-plugin-prettier: specifier: ^5.2.1 - version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.10.0))(eslint@9.10.0)(prettier@3.3.2) + version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.11.0))(eslint@9.11.0)(prettier@3.3.2) eslint-plugin-simple-import-sort: specifier: 12.1.1 - version: 12.1.1(eslint@9.10.0) + version: 12.1.1(eslint@9.11.0) globals: specifier: 15.9.0 version: 15.9.0 typescript-eslint: - specifier: 8.5.0 - version: 8.5.0(eslint@9.10.0)(typescript@5.6.2) + specifier: 8.6.0 + version: 8.6.0(eslint@9.11.0)(typescript@5.6.2) packages/config/jest: devDependencies: @@ -1362,8 +1362,8 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.11.0': - resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} + '@eslint-community/regexpp@4.11.1': + resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/config-array@0.18.0': @@ -1374,16 +1374,16 @@ packages: resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.10.0': - resolution: {integrity: sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==} + '@eslint/js@9.11.0': + resolution: {integrity: sha512-LPkkenkDqyzTFauZLLAPhIb48fj6drrfMvRGSL9tS3AcZBSVTllemLSNyCvHNNL2t797S/6DJNSIwRwXgMO/eQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.4': resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.1.0': - resolution: {integrity: sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==} + '@eslint/plugin-kit@0.2.0': + resolution: {integrity: sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ethersproject/abi@5.7.0': @@ -2529,8 +2529,8 @@ packages: '@types/yargs@17.0.32': resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} - '@typescript-eslint/eslint-plugin@8.5.0': - resolution: {integrity: sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==} + '@typescript-eslint/eslint-plugin@8.6.0': + resolution: {integrity: sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -2540,8 +2540,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.5.0': - resolution: {integrity: sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==} + '@typescript-eslint/parser@8.6.0': + resolution: {integrity: sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2550,12 +2550,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@8.5.0': - resolution: {integrity: sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==} + '@typescript-eslint/scope-manager@8.6.0': + resolution: {integrity: sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.5.0': - resolution: {integrity: sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==} + '@typescript-eslint/type-utils@8.6.0': + resolution: {integrity: sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -2563,12 +2563,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@8.5.0': - resolution: {integrity: sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==} + '@typescript-eslint/types@8.6.0': + resolution: {integrity: sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.5.0': - resolution: {integrity: sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==} + '@typescript-eslint/typescript-estree@8.6.0': + resolution: {integrity: sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -2576,14 +2576,14 @@ packages: typescript: optional: true - '@typescript-eslint/utils@8.5.0': - resolution: {integrity: sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==} + '@typescript-eslint/utils@8.6.0': + resolution: {integrity: sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - '@typescript-eslint/visitor-keys@8.5.0': - resolution: {integrity: sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==} + '@typescript-eslint/visitor-keys@8.6.0': + resolution: {integrity: sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@webassemblyjs/ast@1.12.1': @@ -3623,8 +3623,8 @@ packages: resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.10.0: - resolution: {integrity: sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==} + eslint@9.11.0: + resolution: {integrity: sha512-yVS6XODx+tMFMDFcG4+Hlh+qG7RM6cCJXtQhCKLSsr3XkLvWggHjCqjfh0XsPPnt1c56oaT6PMgW9XWQQjdHXA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -6233,8 +6233,8 @@ packages: typeforce@1.18.0: resolution: {integrity: sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==} - typescript-eslint@8.5.0: - resolution: {integrity: sha512-uD+XxEoSIvqtm4KE97etm32Tn5MfaZWgWfMMREStLxR6JzvHkc2Tkj7zhTEK5XmtpTmKHNnG8Sot6qDfhHtR1Q==} + typescript-eslint@8.6.0: + resolution: {integrity: sha512-eEhhlxCEpCd4helh3AO1hk0UP2MvbRi9CtIAJTVPQjuSXOOO2jsEacNi4UdcJzZJbeuVg1gMhtZ8UYb+NFYPrA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -7943,12 +7943,12 @@ snapshots: '@emotion/weak-memoize@0.3.1': {} - '@eslint-community/eslint-utils@4.4.0(eslint@9.10.0)': + '@eslint-community/eslint-utils@4.4.0(eslint@9.11.0)': dependencies: - eslint: 9.10.0 + eslint: 9.11.0 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.0': {} + '@eslint-community/regexpp@4.11.1': {} '@eslint/config-array@0.18.0': dependencies: @@ -7972,11 +7972,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.10.0': {} + '@eslint/js@9.11.0': {} '@eslint/object-schema@2.1.4': {} - '@eslint/plugin-kit@0.1.0': + '@eslint/plugin-kit@0.2.0': dependencies: levn: 0.4.1 @@ -9793,15 +9793,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.5.0(@typescript-eslint/parser@8.5.0(eslint@9.10.0)(typescript@5.6.2))(eslint@9.10.0)(typescript@5.6.2)': + '@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2)': dependencies: - '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 8.5.0(eslint@9.10.0)(typescript@5.6.2) - '@typescript-eslint/scope-manager': 8.5.0 - '@typescript-eslint/type-utils': 8.5.0(eslint@9.10.0)(typescript@5.6.2) - '@typescript-eslint/utils': 8.5.0(eslint@9.10.0)(typescript@5.6.2) - '@typescript-eslint/visitor-keys': 8.5.0 - eslint: 9.10.0 + '@eslint-community/regexpp': 4.11.1 + '@typescript-eslint/parser': 8.6.0(eslint@9.11.0)(typescript@5.6.2) + '@typescript-eslint/scope-manager': 8.6.0 + '@typescript-eslint/type-utils': 8.6.0(eslint@9.11.0)(typescript@5.6.2) + '@typescript-eslint/utils': 8.6.0(eslint@9.11.0)(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.6.0 + eslint: 9.11.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -9811,28 +9811,28 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.5.0(eslint@9.10.0)(typescript@5.6.2)': + '@typescript-eslint/parser@8.6.0(eslint@9.11.0)(typescript@5.6.2)': dependencies: - '@typescript-eslint/scope-manager': 8.5.0 - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) - '@typescript-eslint/visitor-keys': 8.5.0 + '@typescript-eslint/scope-manager': 8.6.0 + '@typescript-eslint/types': 8.6.0 + '@typescript-eslint/typescript-estree': 8.6.0(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.6.0 debug: 4.3.7(supports-color@5.5.0) - eslint: 9.10.0 + eslint: 9.11.0 optionalDependencies: typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.5.0': + '@typescript-eslint/scope-manager@8.6.0': dependencies: - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/visitor-keys': 8.5.0 + '@typescript-eslint/types': 8.6.0 + '@typescript-eslint/visitor-keys': 8.6.0 - '@typescript-eslint/type-utils@8.5.0(eslint@9.10.0)(typescript@5.6.2)': + '@typescript-eslint/type-utils@8.6.0(eslint@9.11.0)(typescript@5.6.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) - '@typescript-eslint/utils': 8.5.0(eslint@9.10.0)(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 8.6.0(typescript@5.6.2) + '@typescript-eslint/utils': 8.6.0(eslint@9.11.0)(typescript@5.6.2) debug: 4.3.7(supports-color@5.5.0) ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: @@ -9841,12 +9841,12 @@ snapshots: - eslint - supports-color - '@typescript-eslint/types@8.5.0': {} + '@typescript-eslint/types@8.6.0': {} - '@typescript-eslint/typescript-estree@8.5.0(typescript@5.6.2)': + '@typescript-eslint/typescript-estree@8.6.0(typescript@5.6.2)': dependencies: - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/visitor-keys': 8.5.0 + '@typescript-eslint/types': 8.6.0 + '@typescript-eslint/visitor-keys': 8.6.0 debug: 4.3.7(supports-color@5.5.0) fast-glob: 3.3.2 is-glob: 4.0.3 @@ -9858,20 +9858,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.5.0(eslint@9.10.0)(typescript@5.6.2)': + '@typescript-eslint/utils@8.6.0(eslint@9.11.0)(typescript@5.6.2)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.10.0) - '@typescript-eslint/scope-manager': 8.5.0 - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) - eslint: 9.10.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0) + '@typescript-eslint/scope-manager': 8.6.0 + '@typescript-eslint/types': 8.6.0 + '@typescript-eslint/typescript-estree': 8.6.0(typescript@5.6.2) + eslint: 9.11.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@8.5.0': + '@typescript-eslint/visitor-keys@8.6.0': dependencies: - '@typescript-eslint/types': 8.5.0 + '@typescript-eslint/types': 8.6.0 eslint-visitor-keys: 3.4.3 '@webassemblyjs/ast@1.12.1': @@ -10979,24 +10979,24 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-prettier@9.1.0(eslint@9.10.0): + eslint-config-prettier@9.1.0(eslint@9.11.0): dependencies: - eslint: 9.10.0 + eslint: 9.11.0 optional: true - eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.10.0))(eslint@9.10.0)(prettier@3.3.2): + eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.11.0))(eslint@9.11.0)(prettier@3.3.2): dependencies: - eslint: 9.10.0 + eslint: 9.11.0 prettier: 3.3.2 prettier-linter-helpers: 1.0.0 synckit: 0.9.1 optionalDependencies: '@types/eslint': 9.6.1 - eslint-config-prettier: 9.1.0(eslint@9.10.0) + eslint-config-prettier: 9.1.0(eslint@9.11.0) - eslint-plugin-simple-import-sort@12.1.1(eslint@9.10.0): + eslint-plugin-simple-import-sort@12.1.1(eslint@9.11.0): dependencies: - eslint: 9.10.0 + eslint: 9.11.0 eslint-scope@5.1.1: dependencies: @@ -11012,14 +11012,14 @@ snapshots: eslint-visitor-keys@4.0.0: {} - eslint@9.10.0: + eslint@9.11.0: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.10.0) - '@eslint-community/regexpp': 4.11.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0) + '@eslint-community/regexpp': 4.11.1 '@eslint/config-array': 0.18.0 '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.10.0 - '@eslint/plugin-kit': 0.1.0 + '@eslint/js': 9.11.0 + '@eslint/plugin-kit': 0.2.0 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.3.0 '@nodelib/fs.walk': 1.2.8 @@ -14006,11 +14006,11 @@ snapshots: typeforce@1.18.0: {} - typescript-eslint@8.5.0(eslint@9.10.0)(typescript@5.6.2): + typescript-eslint@8.6.0(eslint@9.11.0)(typescript@5.6.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.5.0(@typescript-eslint/parser@8.5.0(eslint@9.10.0)(typescript@5.6.2))(eslint@9.10.0)(typescript@5.6.2) - '@typescript-eslint/parser': 8.5.0(eslint@9.10.0)(typescript@5.6.2) - '@typescript-eslint/utils': 8.5.0(eslint@9.10.0)(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2) + '@typescript-eslint/parser': 8.6.0(eslint@9.11.0)(typescript@5.6.2) + '@typescript-eslint/utils': 8.6.0(eslint@9.11.0)(typescript@5.6.2) optionalDependencies: typescript: 5.6.2 transitivePeerDependencies: From 3fd4ddb14e7949a62495d590bafaf63a3c77be68 Mon Sep 17 00:00:00 2001 From: Pierre Aoun Date: Thu, 5 Sep 2024 16:37:58 +0200 Subject: [PATCH 2/9] :recycle: (core): Extract ByteArrayParser from the APDU parser --- .../src/api/apdu/utils/ApduParser.test.ts | 437 +----------------- .../core/src/api/apdu/utils/ApduParser.ts | 140 +----- .../api/apdu/utils/ByteArrayParser.test.ts | 414 +++++++++++++++++ .../src/api/apdu/utils/ByteArrayParser.ts | 192 ++++++++ packages/core/src/api/index.ts | 1 + 5 files changed, 652 insertions(+), 532 deletions(-) create mode 100644 packages/core/src/api/apdu/utils/ByteArrayParser.test.ts create mode 100644 packages/core/src/api/apdu/utils/ByteArrayParser.ts diff --git a/packages/core/src/api/apdu/utils/ApduParser.test.ts b/packages/core/src/api/apdu/utils/ApduParser.test.ts index 551ea6e12..8ede0fad6 100644 --- a/packages/core/src/api/apdu/utils/ApduParser.test.ts +++ b/packages/core/src/api/apdu/utils/ApduParser.test.ts @@ -5,8 +5,6 @@ import { ApduParser } from "./ApduParser"; const STATUS_WORD_SUCCESS = new Uint8Array([0x90, 0x00]); const RESPONSE_ONE_BYTE = new Uint8Array([0x01]); const RESPONSE_LV_ZERO = new Uint8Array([0x00]); -const RESPONSE_TWO_BYTES = new Uint8Array([0x01, 0x01]); -const RESPONSE_TLV_ZERO = new Uint8Array([0xab, 0x00]); const RESPONSE_ALL_BYTES = new Uint8Array([ 0x01, 0x02, @@ -14,45 +12,6 @@ const RESPONSE_ALL_BYTES = new Uint8Array([ ...Array(253).fill(0xaa), ]); -/* -Type : 33 00 00 04 -> nanoX -Version SE (LV): 2.2.3 -Flag: E600000000 - PIN OK - Factory init Ok - Onboarding done -Version MCU(LV): 2.30 -Version BootLoader(LV): 1.16 -HW rev: 0 -Language(LV): Fra & Eng -Recover state (LV): 1 -*/ -const DEVICE_TYPE = "33000004"; -const DEVICE_FLAGS = "0xe6000000"; -const NUMERIC_FLAGS = 0xe6000000; -const VERSION_FW_SE = "2.2.3"; -const VERSION_FW_MCU = "2.30"; -const VERSION_FW_BL = "1.16"; -const HARDWARE_REV = 0; -const LANGUAGE_PACK = 1; -const RECOVER_STATE = 0; -const RESPONSE_GET_VERSION = new Uint8Array([ - 0x33, 0x00, 0x00, 0x04, 0x05, 0x32, 0x2e, 0x32, 0x2e, 0x33, 0x04, 0xe6, 0x00, - 0x00, 0x00, 0x04, 0x32, 0x2e, 0x33, 0x30, 0x04, 0x31, 0x2e, 0x31, 0x36, 0x01, - 0x00, 0x01, 0x01, 0x01, 0x00, -]); - -/* -Format version: 1 -Name: BOLOS -Version: 2.2.3 -*/ -const DASHBOARD_HEX = new Uint8Array([0x42, 0x4f, 0x4c, 0x4f, 0x53]); -const DASHBOARD_NAME = "BOLOS"; -const RESPONSE_GET_APP_VERSION = new Uint8Array([ - 0x01, 0x05, 0x42, 0x4f, 0x4c, 0x4f, 0x53, 0x05, 0x32, 0x2e, 0x32, 0x2e, 0x33, -]); - let parser: ApduParser; let response: ApduResponse = new ApduResponse({ statusCode: STATUS_WORD_SUCCESS, @@ -60,384 +19,34 @@ let response: ApduResponse = new ApduResponse({ }); describe("ApduParser", () => { - describe("clean", () => { - it("should create an instance", () => { - parser = new ApduParser(response); - expect(parser).toBeDefined(); - expect(parser).toBeInstanceOf(ApduParser); - }); - - it("Extract a single byte", () => { - parser = new ApduParser(response); - expect(parser.extract8BitUInt()).toBe(0x01); - expect(parser.getCurrentIndex()).toBe(1); - expect(parser.getUnparsedRemainingLength()).toBe(0); - }); - - it("Extract one byte", () => { - response = new ApduResponse({ - statusCode: STATUS_WORD_SUCCESS, - data: RESPONSE_ALL_BYTES, - }); - parser = new ApduParser(response); - let index = 0; - let length = RESPONSE_ALL_BYTES.length; - - expect(length).toBe(256); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - index++; - length--; - - expect(parser.extract8BitUInt()).toBe(0x01); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - index++; - length--; - - expect(parser.extract8BitUInt()).toBe(0x02); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - index++; - length--; - - expect(parser.extract8BitUInt()).toBe(0x03); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - index++; - length--; - - while (length != 0) { - expect(parser.extract8BitUInt()).toBe(0xaa); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - index++; - length--; - } - }); - - it("Extract 16-bit & 32-bit number", () => { - response = new ApduResponse({ - statusCode: STATUS_WORD_SUCCESS, - data: RESPONSE_ALL_BYTES, - }); - parser = new ApduParser(response); - let index = 0; - let length = RESPONSE_ALL_BYTES.length; - - expect(length).toBe(256); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - expect(parser.extract16BitUInt()).toBe(0x0102); - index += 2; - length -= 2; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - expect(parser.extract16BitUInt()).toBe(0x03aa); - index += 2; - length -= 2; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - parser.resetIndex(); - index = 0; - length = RESPONSE_ALL_BYTES.length; - - expect(parser.extract32BitUInt()).toBe(0x010203aa); - index += 4; - length -= 4; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - expect(parser.extract32BitUInt()).toBe(0xaaaaaaaa); - index += 4; - length -= 4; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - }); - - it("Parse a GetAppVersion response", () => { - response = new ApduResponse({ - statusCode: STATUS_WORD_SUCCESS, - data: RESPONSE_GET_APP_VERSION, - }); - parser = new ApduParser(response); - let index = 0; - let length = RESPONSE_GET_APP_VERSION.length; - - // Parse the response considering the first field to be the format field - expect(length).toBe(13); - - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - const value = parser.extract8BitUInt(); - index++; - length--; - expect(value).toBe(1); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - let array = parser.extractFieldLVEncoded(); - expect(array).toStrictEqual(DASHBOARD_HEX); - expect(parser.encodeToString(array)).toBe(DASHBOARD_NAME); - index += 6; - length -= 6; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - array = parser.extractFieldLVEncoded(); - expect(parser.encodeToString(array)).toBe(VERSION_FW_SE); - index += 6; - length -= 6; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - // Reparse the response considering the first field to be the TLV formatted - parser.resetIndex(); - index = 0; - length = RESPONSE_GET_APP_VERSION.length; - - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - const field = parser.extractFieldTLVEncoded(); - expect(field?.tag).toBe(0x01); - expect(field?.value).toStrictEqual(DASHBOARD_HEX); - expect(parser.encodeToString(field?.value)).toBe(DASHBOARD_NAME); - index += 7; - length -= 7; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - array = parser.extractFieldLVEncoded(); - expect(parser.encodeToString(array)).toBe(VERSION_FW_SE); - index += 6; - length -= 6; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - }); - - it("Parse a GetVersion response", () => { - response = new ApduResponse({ - statusCode: STATUS_WORD_SUCCESS, - data: RESPONSE_GET_VERSION, - }); - parser = new ApduParser(response); - let index = 0; - let length = RESPONSE_GET_VERSION.length; - - expect(length).toBe(31); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - expect(parser.testMinimalLength(25)).toBe(true); - - let array = parser.extractFieldByLength(4); - expect(parser.encodeToHexaString(array)).toBe(DEVICE_TYPE); - index += 4; - length -= 4; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - array = parser.extractFieldLVEncoded(); - expect(parser.encodeToString(array)).toBe(VERSION_FW_SE); - index += 6; - length -= 6; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - array = parser.extractFieldLVEncoded(); - const flags = parser.encodeToHexaString(array, true); - expect(flags).toBe(DEVICE_FLAGS); - expect(parseInt(flags, 16)).toBe(NUMERIC_FLAGS); - index += 5; - length -= 5; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - array = parser.extractFieldLVEncoded(); - expect(parser.encodeToString(array)).toBe(VERSION_FW_MCU); - index += 5; - length -= 5; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - array = parser.extractFieldLVEncoded(); - expect(parser.encodeToString(array)).toBe(VERSION_FW_BL); - index += 5; - length -= 5; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - array = parser.extractFieldLVEncoded(); - expect(array?.at(0)).toBe(HARDWARE_REV); - index += 2; - length -= 2; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - array = parser.extractFieldLVEncoded(); - expect(array?.at(0)).toBe(LANGUAGE_PACK); - index += 2; - length -= 2; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - array = parser.extractFieldLVEncoded(); - expect(array?.at(0)).toBe(RECOVER_STATE); - index += 2; - length -= 2; - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - }); + it("should create an instance", () => { + parser = new ApduParser(response); + expect(parser).toBeDefined(); + expect(parser).toBeInstanceOf(ApduParser); }); - describe("errors", () => { - it("no response", () => { - response = new ApduResponse({ - statusCode: STATUS_WORD_SUCCESS, - data: new Uint8Array(), - }); - parser = new ApduParser(response); - const index = 0; - const length = 0; - - expect(parser.testMinimalLength(1)).toBe(false); - - expect(parser.extract8BitUInt()).toBeUndefined(); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - expect(parser.extract16BitUInt()).toBeUndefined(); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - expect(parser.extract32BitUInt()).toBeUndefined(); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - let array = parser.extractFieldByLength(2); - expect(array).toBeUndefined(); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - array = parser.extractFieldLVEncoded(); - expect(array).toBeUndefined(); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - const field = parser.extractFieldTLVEncoded(); - expect(field).toBeUndefined(); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - }); - - it("length error", () => { - response = new ApduResponse({ - statusCode: STATUS_WORD_SUCCESS, - data: RESPONSE_ONE_BYTE, - }); - parser = new ApduParser(response); - const index = 0; - const length = RESPONSE_ONE_BYTE.length; - - expect(length).toBe(1); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - expect(parser.extract16BitUInt()).toBeUndefined(); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - expect(parser.extract32BitUInt()).toBeUndefined(); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - let array = parser.extractFieldByLength(2); - expect(array).toBeUndefined(); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - array = parser.extractFieldLVEncoded(); - expect(array).toBeUndefined(); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - let field = parser.extractFieldTLVEncoded(); - expect(field).toBeUndefined(); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - response = new ApduResponse({ - statusCode: STATUS_WORD_SUCCESS, - data: RESPONSE_TWO_BYTES, - }); - parser = new ApduParser(response); + it("Extract a single byte", () => { + parser = new ApduParser(response); + expect(parser.extract8BitUInt()).toBe(0x01); + }); - field = parser.extractFieldTLVEncoded(); - expect(field).toBeUndefined(); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe( - RESPONSE_TWO_BYTES.length, - ); + it("Extract 16-bit & 32-bit number", () => { + response = new ApduResponse({ + statusCode: STATUS_WORD_SUCCESS, + data: RESPONSE_ALL_BYTES, }); + parser = new ApduParser(response); + expect(parser.extract16BitUInt()).toBe(0x0102); + expect(parser.extract16BitUInt()).toBe(0x03aa); + }); - it("Test zero length", () => { - response = new ApduResponse({ - statusCode: STATUS_WORD_SUCCESS, - data: RESPONSE_LV_ZERO, - }); - parser = new ApduParser(response); - const zero = new Uint8Array(); - - const index = 0; - let length = RESPONSE_LV_ZERO.length; - - expect(length).toBe(1); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - const value = parser.extract8BitUInt(); - expect(value).toBe(0); - expect(parser.getCurrentIndex()).toBe(1); - expect(parser.getUnparsedRemainingLength()).toBe(0); - - parser.resetIndex(); - - let array = parser.extractFieldByLength(0); - expect(array).toStrictEqual(zero); - expect(parser.encodeToString(array)).toBe(""); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - array = parser.extractFieldLVEncoded(); - expect(parser.getCurrentIndex()).toBe(1); - expect(parser.getUnparsedRemainingLength()).toBe(0); - expect(array).toStrictEqual(zero); - expect(parser.encodeToString(array)).toBe(""); - - response = new ApduResponse({ - statusCode: STATUS_WORD_SUCCESS, - data: RESPONSE_TLV_ZERO, - }); - parser = new ApduParser(response); - length = RESPONSE_TLV_ZERO.length; - - expect(length).toBe(2); - expect(parser.getCurrentIndex()).toBe(index); - expect(parser.getUnparsedRemainingLength()).toBe(length); - - const field = parser.extractFieldTLVEncoded(); - expect(field?.tag).toBe(0xab); - expect(field?.value).toStrictEqual(zero); - expect(parser.encodeToString(field?.value)).toBe(""); - expect(parser.getCurrentIndex()).toBe(2); - expect(parser.getUnparsedRemainingLength()).toBe(0); - - expect(parser.encodeToHexaString()).toBe(""); - expect(parser.encodeToString()).toBe(""); + it("Test zero length", () => { + const response: ApduResponse = new ApduResponse({ + statusCode: STATUS_WORD_SUCCESS, + data: RESPONSE_LV_ZERO, }); + parser = new ApduParser(response); + const value = parser.extract8BitUInt(); + expect(value).toBe(0); }); }); diff --git a/packages/core/src/api/apdu/utils/ApduParser.ts b/packages/core/src/api/apdu/utils/ApduParser.ts index 80449262f..43253482c 100644 --- a/packages/core/src/api/apdu/utils/ApduParser.ts +++ b/packages/core/src/api/apdu/utils/ApduParser.ts @@ -1,6 +1,8 @@ import { ApduResponse } from "@api/device-session/ApduResponse"; import { HexaString } from "@api/utils/HexaString"; +import { ByteArrayParser } from "./ByteArrayParser"; + export type TaggedField = { readonly tag: number; readonly value: Uint8Array; @@ -19,108 +21,59 @@ export type TaggedField = { * ``` */ export class ApduParser { - private _index: number; - private readonly _response: Uint8Array; + private parser: ByteArrayParser; constructor(response: ApduResponse) { - this._index = 0; - this._response = response.data; + this.parser = new ByteArrayParser(response.data); } - // ========== - // Public API - // ========== - /** * Test if the length is greater than the response length * @param length: number * @returns {boolean} - Returns false if the length is greater than the response length */ - testMinimalLength(length: number): boolean { - return length <= this._response.length; - } + testMinimalLength = (length: number): boolean => + this.parser.testMinimalLength(length); /** * Extract a single byte from the response * @returns {number | undefined} - Returns the byte extracted from the response */ - extract8BitUInt(): number | undefined { - if (this._outOfRange(1)) return; - return this._response[this._index++]; - } + extract8BitUInt = (): number | undefined => this.parser.extract8BitUInt(); /** * Extract a 16-bit unsigned integer (Big Endian coding) from the response * @returns {number | undefined} - Returns the 16-bit unsigned integer extracted from the response */ - extract16BitUInt(): number | undefined { - if (this._outOfRange(2)) return; - let msb = this.extract8BitUInt(); - if (msb === undefined) return; - const lsb = this.extract8BitUInt(); - if (lsb === undefined) return; - msb *= 0x100; - return msb + lsb; - } + extract16BitUInt = (): number | undefined => this.parser.extract16BitUInt(); /** * Extract a 32-bit unsigned integer (Big Endian coding) from the response * @returns {number | undefined} - Returns the 32-bit unsigned integer extracted from the response */ - extract32BitUInt(): number | undefined { - if (this._outOfRange(4)) return; - let msw = this.extract16BitUInt(); - if (msw === undefined) return; - const lsw = this.extract16BitUInt(); - if (lsw === undefined) return; - msw *= 0x10000; - return msw + lsw; - } + extract32BitUInt = (): number | undefined => this.parser.extract32BitUInt(); /** * Extract a field of a specified length from the response * @param length: number - The length of the field to extract * @returns {Uint8Array | undefined} - Returns the field extracted from the response */ - extractFieldByLength(length: number): Uint8Array | undefined { - if (this._outOfRange(length)) return; - if (length == 0) return new Uint8Array(); - const field = this._response.slice(this._index, this._index + length); - this._index += length; - return field; - } + extractFieldByLength = (length: number): Uint8Array | undefined => + this.parser.extractFieldByLength(length); /** * Extract a field from the response that is length-value encoded * @returns {Uint8Array | undefined} - Returns the field extracted from the response */ - extractFieldLVEncoded(): Uint8Array | undefined { - // extract Length field - const length = this.extract8BitUInt() ?? -1; - if (length === -1) return; - if (length === 0) return new Uint8Array(); - const field = this.extractFieldByLength(length); - // if the field is inconsistent then roll back to the initial point - if (!field) this._index--; - return field; - } + extractFieldLVEncoded = (): Uint8Array | undefined => + this.parser.extractFieldLVEncoded(); /** * Extract a field from the response that is tag-length-value encoded * @returns {TaggedField | undefined} - Returns the field extracted from the response */ - extractFieldTLVEncoded(): TaggedField | undefined { - if (this._outOfRange(2)) return; - - const tag = this.extract8BitUInt(); - const value = this.extractFieldLVEncoded(); - - if (!tag || !value) { - this._index--; - return; - } - return { tag, value }; - } + extractFieldTLVEncoded = (): TaggedField | undefined => + this.parser.extractFieldTLVEncoded(); /** * Encode a value to a hexadecimal string @@ -134,17 +87,9 @@ export class ApduParser { value?: Uint8Array, prefix: boolean = false, ): HexaString | string { - let result = ""; - let index = 0; - - if (!value) return result; - - while (index <= value.length) { - const item = value[index]?.toString(16); - if (item) result += item.length < 2 ? "0" + item : item; - index++; - } - return prefix ? `0x${result}` : result; + return prefix + ? this.parser.encodeToHexaString(value, true) + : this.parser.encodeToHexaString(value, false); } /** @@ -152,54 +97,13 @@ export class ApduParser { * @param value {Uint8Array} - The value to encode * @returns {string} - The encoded value as an ASCII string */ - encodeToString(value?: Uint8Array): string { - let result = ""; - let index = 0; - - if (!value) return result; - - while (index <= value.length) { - const item = value[index]; - if (item) result += String.fromCharCode(item); - index++; - } - - return result; - } - - /** - * Get the current index of the parser - * @returns {number} - The current index of the parser - */ - getCurrentIndex(): number { - return this._index; - } - - /** - * Reset the index of the parser to 0 - */ - resetIndex() { - this._index = 0; - } + encodeToString = (value?: Uint8Array): string => + this.parser.encodeToString(value); /** * Get the remaining length of the response * @returns {number} - The remaining length of the response */ - getUnparsedRemainingLength(): number { - return this._response.length - this._index; - } - - // =========== - // Private API - // =========== - - /** - * Check whether the expected length is out of range - * @param length: number - * @returns {boolean} - Returns true if the expected length is out of range - */ - private _outOfRange(length: number): boolean { - return this._index + length > this._response.length; - } + getUnparsedRemainingLength = (): number => + this.parser.getUnparsedRemainingLength(); } diff --git a/packages/core/src/api/apdu/utils/ByteArrayParser.test.ts b/packages/core/src/api/apdu/utils/ByteArrayParser.test.ts new file mode 100644 index 000000000..7fe4815d2 --- /dev/null +++ b/packages/core/src/api/apdu/utils/ByteArrayParser.test.ts @@ -0,0 +1,414 @@ +import { ByteArrayParser } from "./ByteArrayParser"; + +const RESPONSE_ONE_BYTE = new Uint8Array([0x01]); +const RESPONSE_LV_ZERO = new Uint8Array([0x00]); +const RESPONSE_TWO_BYTES = new Uint8Array([0x01, 0x01]); +const RESPONSE_TLV_ZERO = new Uint8Array([0xab, 0x00]); +const RESPONSE_ALL_BYTES = new Uint8Array([ + 0x01, + 0x02, + 0x03, + ...Array(253).fill(0xaa), +]); + +/* +Type : 33 00 00 04 -> nanoX +Version SE (LV): 2.2.3 +Flag: E600000000 + PIN OK + Factory init Ok + Onboarding done +Version MCU(LV): 2.30 +Version BootLoader(LV): 1.16 +HW rev: 0 +Language(LV): Fra & Eng +Recover state (LV): 1 +*/ +const DEVICE_TYPE = "33000004"; +const DEVICE_FLAGS = "0xe6000000"; +const NUMERIC_FLAGS = 0xe6000000; +const VERSION_FW_SE = "2.2.3"; +const VERSION_FW_MCU = "2.30"; +const VERSION_FW_BL = "1.16"; +const HARDWARE_REV = 0; +const LANGUAGE_PACK = 1; +const RECOVER_STATE = 0; +const RESPONSE_GET_VERSION = new Uint8Array([ + 0x33, 0x00, 0x00, 0x04, 0x05, 0x32, 0x2e, 0x32, 0x2e, 0x33, 0x04, 0xe6, 0x00, + 0x00, 0x00, 0x04, 0x32, 0x2e, 0x33, 0x30, 0x04, 0x31, 0x2e, 0x31, 0x36, 0x01, + 0x00, 0x01, 0x01, 0x01, 0x00, +]); + +/* +Format version: 1 +Name: BOLOS +Version: 2.2.3 +*/ +const DASHBOARD_HEX = new Uint8Array([0x42, 0x4f, 0x4c, 0x4f, 0x53]); +const DASHBOARD_NAME = "BOLOS"; +const RESPONSE_GET_APP_VERSION = new Uint8Array([ + 0x01, 0x05, 0x42, 0x4f, 0x4c, 0x4f, 0x53, 0x05, 0x32, 0x2e, 0x32, 0x2e, 0x33, +]); + +let parser: ByteArrayParser; +let response = RESPONSE_ONE_BYTE; + +describe("ByteArrayParser", () => { + describe("clean", () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it("should create an instance", () => { + parser = new ByteArrayParser(response); + expect(parser).toBeDefined(); + expect(parser).toBeInstanceOf(ByteArrayParser); + }); + + it("Extract a single byte", () => { + parser = new ByteArrayParser(response); + expect(parser.extract8BitUInt()).toBe(0x01); + expect(parser.getCurrentIndex()).toBe(1); + expect(parser.getUnparsedRemainingLength()).toBe(0); + }); + + it("Extract one byte", () => { + response = RESPONSE_ALL_BYTES; + parser = new ByteArrayParser(response); + let index = 0; + let length = RESPONSE_ALL_BYTES.length; + + expect(length).toBe(256); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + index++; + length--; + + expect(parser.extract8BitUInt()).toBe(0x01); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + index++; + length--; + + expect(parser.extract8BitUInt()).toBe(0x02); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + index++; + length--; + + expect(parser.extract8BitUInt()).toBe(0x03); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + index++; + length--; + + while (length != 0) { + expect(parser.extract8BitUInt()).toBe(0xaa); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + index++; + length--; + } + }); + + it("Extract 16-bit & 32-bit number", () => { + response = RESPONSE_ALL_BYTES; + parser = new ByteArrayParser(response); + let index = 0; + let length = RESPONSE_ALL_BYTES.length; + + expect(length).toBe(256); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + expect(parser.extract16BitUInt()).toBe(0x0102); + index += 2; + length -= 2; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + expect(parser.extract16BitUInt()).toBe(0x03aa); + index += 2; + length -= 2; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + parser.resetIndex(); + index = 0; + length = RESPONSE_ALL_BYTES.length; + + expect(parser.extract32BitUInt()).toBe(0x010203aa); + index += 4; + length -= 4; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + expect(parser.extract32BitUInt()).toBe(0xaaaaaaaa); + index += 4; + length -= 4; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + }); + + it("Parse a GetAppVersion response", () => { + response = RESPONSE_GET_APP_VERSION; + parser = new ByteArrayParser(response); + let index = 0; + let length = RESPONSE_GET_APP_VERSION.length; + + // Parse the response considering the first field to be the format field + expect(length).toBe(13); + + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + const value = parser.extract8BitUInt(); + index++; + length--; + expect(value).toBe(1); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + let array = parser.extractFieldLVEncoded(); + expect(array).toStrictEqual(DASHBOARD_HEX); + expect(parser.encodeToString(array)).toBe(DASHBOARD_NAME); + index += 6; + length -= 6; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + array = parser.extractFieldLVEncoded(); + expect(parser.encodeToString(array)).toBe(VERSION_FW_SE); + index += 6; + length -= 6; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + // Reparse the response considering the first field to be the TLV formatted + parser.resetIndex(); + index = 0; + length = RESPONSE_GET_APP_VERSION.length; + + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + const field = parser.extractFieldTLVEncoded(); + expect(field?.tag).toBe(0x01); + expect(field?.value).toStrictEqual(DASHBOARD_HEX); + expect(parser.encodeToString(field?.value)).toBe(DASHBOARD_NAME); + index += 7; + length -= 7; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + array = parser.extractFieldLVEncoded(); + expect(parser.encodeToString(array)).toBe(VERSION_FW_SE); + index += 6; + length -= 6; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + }); + + it("Parse a GetVersion response", () => { + response = RESPONSE_GET_VERSION; + parser = new ByteArrayParser(response); + let index = 0; + let length = RESPONSE_GET_VERSION.length; + + expect(length).toBe(31); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + expect(parser.testMinimalLength(25)).toBe(true); + + let array = parser.extractFieldByLength(4); + expect(parser.encodeToHexaString(array)).toBe(DEVICE_TYPE); + index += 4; + length -= 4; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + array = parser.extractFieldLVEncoded(); + expect(parser.encodeToString(array)).toBe(VERSION_FW_SE); + index += 6; + length -= 6; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + array = parser.extractFieldLVEncoded(); + const flags = parser.encodeToHexaString(array, true); + expect(flags).toBe(DEVICE_FLAGS); + expect(parseInt(flags, 16)).toBe(NUMERIC_FLAGS); + index += 5; + length -= 5; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + array = parser.extractFieldLVEncoded(); + expect(parser.encodeToString(array)).toBe(VERSION_FW_MCU); + index += 5; + length -= 5; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + array = parser.extractFieldLVEncoded(); + expect(parser.encodeToString(array)).toBe(VERSION_FW_BL); + index += 5; + length -= 5; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + array = parser.extractFieldLVEncoded(); + expect(array?.at(0)).toBe(HARDWARE_REV); + index += 2; + length -= 2; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + array = parser.extractFieldLVEncoded(); + expect(array?.at(0)).toBe(LANGUAGE_PACK); + index += 2; + length -= 2; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + array = parser.extractFieldLVEncoded(); + expect(array?.at(0)).toBe(RECOVER_STATE); + index += 2; + length -= 2; + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + }); + }); + + describe("errors", () => { + it("no response", () => { + response = new Uint8Array(); + parser = new ByteArrayParser(response); + const index = 0; + const length = 0; + + expect(parser.testMinimalLength(1)).toBe(false); + + expect(parser.extract8BitUInt()).toBeUndefined(); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + expect(parser.extract16BitUInt()).toBeUndefined(); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + expect(parser.extract32BitUInt()).toBeUndefined(); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + let array = parser.extractFieldByLength(2); + expect(array).toBeUndefined(); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + array = parser.extractFieldLVEncoded(); + expect(array).toBeUndefined(); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + const field = parser.extractFieldTLVEncoded(); + expect(field).toBeUndefined(); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + }); + + it("length error", () => { + response = RESPONSE_ONE_BYTE; + parser = new ByteArrayParser(response); + const index = 0; + const length = RESPONSE_ONE_BYTE.length; + + expect(length).toBe(1); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + expect(parser.extract16BitUInt()).toBeUndefined(); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + expect(parser.extract32BitUInt()).toBeUndefined(); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + let array = parser.extractFieldByLength(2); + expect(array).toBeUndefined(); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + array = parser.extractFieldLVEncoded(); + expect(array).toBeUndefined(); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + let field = parser.extractFieldTLVEncoded(); + expect(field).toBeUndefined(); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + response = RESPONSE_TWO_BYTES; + parser = new ByteArrayParser(response); + + field = parser.extractFieldTLVEncoded(); + expect(field).toBeUndefined(); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe( + RESPONSE_TWO_BYTES.length, + ); + }); + + it("Test zero length", () => { + response = RESPONSE_LV_ZERO; + parser = new ByteArrayParser(response); + const zero = new Uint8Array(); + + const index = 0; + let length = RESPONSE_LV_ZERO.length; + + expect(length).toBe(1); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + const value = parser.extract8BitUInt(); + expect(value).toBe(0); + expect(parser.getCurrentIndex()).toBe(1); + expect(parser.getUnparsedRemainingLength()).toBe(0); + + parser.resetIndex(); + + let array = parser.extractFieldByLength(0); + expect(array).toStrictEqual(zero); + expect(parser.encodeToString(array)).toBe(""); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + array = parser.extractFieldLVEncoded(); + expect(parser.getCurrentIndex()).toBe(1); + expect(parser.getUnparsedRemainingLength()).toBe(0); + expect(array).toStrictEqual(zero); + expect(parser.encodeToString(array)).toBe(""); + + response = RESPONSE_TLV_ZERO; + parser = new ByteArrayParser(response); + length = RESPONSE_TLV_ZERO.length; + + expect(length).toBe(2); + expect(parser.getCurrentIndex()).toBe(index); + expect(parser.getUnparsedRemainingLength()).toBe(length); + + const field = parser.extractFieldTLVEncoded(); + expect(field?.tag).toBe(0xab); + expect(field?.value).toStrictEqual(zero); + expect(parser.encodeToString(field?.value)).toBe(""); + expect(parser.getCurrentIndex()).toBe(2); + expect(parser.getUnparsedRemainingLength()).toBe(0); + + expect(parser.encodeToHexaString()).toBe(""); + expect(parser.encodeToString()).toBe(""); + }); + }); +}); diff --git a/packages/core/src/api/apdu/utils/ByteArrayParser.ts b/packages/core/src/api/apdu/utils/ByteArrayParser.ts new file mode 100644 index 000000000..b131f98bc --- /dev/null +++ b/packages/core/src/api/apdu/utils/ByteArrayParser.ts @@ -0,0 +1,192 @@ +import { bufferToHexaString, HexaString } from "@api/utils/HexaString"; + +export type TaggedField = { + readonly tag: number; + readonly value: Uint8Array; +}; + +/** + * ByteArrayParser is a utility class to help parse a byte array. + * + * It provides methods to extract fields of different types from the buffer. + * + * @example + * ``` + * const parser = new ByteArrayParser(buffer); + * const targetId = parser.encodeToHexaString(parser.extractFieldByLength(4)); + * const seVersion = parser.encodeToString(parser.extractFieldLVEncoded()); + * ``` + */ +export class ByteArrayParser { + private index: number = 0; + + constructor(private readonly buffer: Uint8Array) {} + + // ========== + // Public API + // ========== + + /** + * Test if the length is greater than the response length + * @param length: number + * @returns {boolean} - Returns false if the length is greater than the response length + */ + testMinimalLength(length: number): boolean { + return length <= this.buffer.length - this.index; + } + + /** + * Extract a single byte from the response + * @returns {number | undefined} - Returns the byte extracted from the response + */ + extract8BitUInt(): number | undefined { + if (this.outOfRange(1)) return; + return this.buffer[this.index++]; + } + + /** + * Extract a 16-bit unsigned integer (Big Endian coding) from the response + * @returns {number | undefined} - Returns the 16-bit unsigned integer extracted from the response + */ + extract16BitUInt(): number | undefined { + if (this.outOfRange(2)) return; + let msb = this.extract8BitUInt(); + if (msb === undefined) return; + const lsb = this.extract8BitUInt(); + if (lsb === undefined) return; + msb *= 0x100; + return msb + lsb; + } + + /** + * Extract a 32-bit unsigned integer (Big Endian coding) from the response + * @returns {number | undefined} - Returns the 32-bit unsigned integer extracted from the response + */ + extract32BitUInt(): number | undefined { + if (this.outOfRange(4)) return; + let msw = this.extract16BitUInt(); + if (msw === undefined) return; + const lsw = this.extract16BitUInt(); + if (lsw === undefined) return; + msw *= 0x10000; + return msw + lsw; + } + + /** + * Extract a field of a specified length from the response + * @param length: number - The length of the field to extract + * @returns {Uint8Array | undefined} - Returns the field extracted from the response + */ + extractFieldByLength(length: number): Uint8Array | undefined { + if (this.outOfRange(length)) return; + if (length === 0) return new Uint8Array(); + const field = this.buffer.slice(this.index, this.index + length); + this.index += length; + return field; + } + + /** + * Extract a field from the response that is length-value encoded + * @returns {Uint8Array | undefined} - Returns the field extracted from the response + */ + extractFieldLVEncoded(): Uint8Array | undefined { + // extract Length field + const length = this.extract8BitUInt(); + if (length === undefined) return; + else if (length === 0) return new Uint8Array(); + const field = this.extractFieldByLength(length); + // if the field is inconsistent then roll back to the initial point + if (field === undefined) this.index--; + return field; + } + + /** + * Extract a field from the response that is tag-length-value encoded + * @returns {TaggedField | undefined} - Returns the field extracted from the response + */ + extractFieldTLVEncoded(): TaggedField | undefined { + if (this.outOfRange(2)) return; + + const tag = this.extract8BitUInt(); + const value = this.extractFieldLVEncoded(); + + if (tag === undefined || value === undefined) { + this.index--; + return; + } + return { tag, value }; + } + + /** + * Encode a value to a hexadecimal string + * @param value {Uint8Array} - The value to encode + * @param prefix {boolean} - Whether to add a prefix to the encoded value + * @returns {string} - The encoded value as a hexadecimal string + */ + encodeToHexaString(value?: Uint8Array, prefix?: false): string; + encodeToHexaString(value?: Uint8Array, prefix?: true): HexaString; + encodeToHexaString( + value?: Uint8Array, + prefix: boolean = false, + ): HexaString | string { + if (value === undefined || value.length === 0) return ""; + const result = bufferToHexaString(value); + return prefix ? result : result.slice(2); + } + + /** + * Encode a value to an ASCII string + * @param value {Uint8Array} - The value to encode + * @returns {string} - The encoded value as an ASCII string + */ + encodeToString(value?: Uint8Array): string { + let result = ""; + let index = 0; + + if (!value) return result; + + while (index <= value.length) { + const item = value[index]; + if (item) result += String.fromCharCode(item); + index++; + } + + return result; + } + + /** + * Get the current index of the parser + * @returns {number} - The current index of the parser + */ + getCurrentIndex(): number { + return this.index; + } + + /** + * Reset the index of the parser to 0 + */ + resetIndex() { + this.index = 0; + } + + /** + * Get the remaining length of the response + * @returns {number} - The remaining length of the response + */ + getUnparsedRemainingLength(): number { + return this.buffer.length - this.index; + } + + // =========== + // Private API + // =========== + + /** + * Check whether the expected length is out of range + * @param length: number + * @returns {boolean} - Returns true if the expected length is out of range + */ + private outOfRange(length: number): boolean { + return this.index + length > this.buffer.length; + } +} diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts index 318e3a44e..833e1710b 100644 --- a/packages/core/src/api/index.ts +++ b/packages/core/src/api/index.ts @@ -4,6 +4,7 @@ export { Apdu } from "./apdu/model/Apdu"; export { APDU_MAX_PAYLOAD, ApduBuilder } from "./apdu/utils/ApduBuilder"; export { ApduParser } from "./apdu/utils/ApduParser"; export { ByteArrayBuilder } from "./apdu/utils/ByteArrayBuilder"; +export { ByteArrayParser } from "./apdu/utils/ByteArrayParser"; export { CommandResultFactory, CommandResultStatus, From bbc9ee6bc656203b8a32a1a5414037f13b048354 Mon Sep 17 00:00:00 2001 From: Pierre Aoun Date: Fri, 6 Sep 2024 18:15:53 +0200 Subject: [PATCH 3/9] :sparkles: (core): Add missing numbers encoding to builder and parser --- .../src/api/apdu/utils/AppBuilderError.ts | 2 +- .../api/apdu/utils/ByteArrayBuilder.test.ts | 207 +++++++++-------- .../src/api/apdu/utils/ByteArrayBuilder.ts | 218 +++++++++++++----- .../api/apdu/utils/ByteArrayParser.test.ts | 76 ++++++ .../src/api/apdu/utils/ByteArrayParser.ts | 107 +++++++-- 5 files changed, 446 insertions(+), 164 deletions(-) diff --git a/packages/core/src/api/apdu/utils/AppBuilderError.ts b/packages/core/src/api/apdu/utils/AppBuilderError.ts index 8aec7f108..748e18f93 100644 --- a/packages/core/src/api/apdu/utils/AppBuilderError.ts +++ b/packages/core/src/api/apdu/utils/AppBuilderError.ts @@ -10,7 +10,7 @@ export class ValueOverflowError implements SdkAppBuilderError { readonly _tag = "ValueOverflow"; readonly originalError?: Error; readonly message: string; - constructor(value: string, max: number = APDU_MAX_PAYLOAD) { + constructor(value: string, max: number | bigint = APDU_MAX_PAYLOAD) { this.message = `Value overflow for ${value}, max is ${max}`; } } diff --git a/packages/core/src/api/apdu/utils/ByteArrayBuilder.test.ts b/packages/core/src/api/apdu/utils/ByteArrayBuilder.test.ts index 39a29abce..41f692116 100644 --- a/packages/core/src/api/apdu/utils/ByteArrayBuilder.test.ts +++ b/packages/core/src/api/apdu/utils/ByteArrayBuilder.test.ts @@ -1,19 +1,13 @@ +import { hexaStringToBuffer } from "@api/utils/HexaString"; + import { APDU_MAX_PAYLOAD } from "./ApduBuilder"; -import { - DataOverflowError, - HexaStringEncodeError, - ValueOverflowError, -} from "./AppBuilderError"; +import { DataOverflowError, HexaStringEncodeError } from "./AppBuilderError"; import { ByteArrayBuilder } from "./ByteArrayBuilder"; const COMMAND_NO_BODY = new Uint8Array([]); const COMMAND_BODY_SINGLE = new Uint8Array([0x01]); -const COMMAND_BODY_TWO = new Uint8Array([0x33, 0x02]); - -const COMMAND_BODY_EIGHT = new Uint8Array([0x01, 0x23, 0x45, 0x67]); - const COMMAND_BODY_HEXA1 = new Uint8Array([0x80, 0x81, 0x82, 0x83, 0x84]); const COMMAND_BODY_HEXA2 = new Uint8Array([0x85, 0x86, 0x87, 0x88]); @@ -39,6 +33,39 @@ const COMMAND_BODY_NEARLY = new Uint8Array([...Array(254).fill(0xaa)]); let builder: ByteArrayBuilder; describe("ByteArrayBuilder", () => { + const builderAddNumber = ( + num: number | bigint, + bigEndian: boolean, + sizeInBits: number, + signed: boolean, + ) => { + if (signed) { + switch (sizeInBits) { + case 2: + builder.add16BitIntToData(num, bigEndian); + break; + case 4: + builder.add32BitIntToData(num, bigEndian); + break; + case 8: + builder.add64BitIntToData(num, bigEndian); + break; + } + } else { + switch (sizeInBits) { + case 2: + builder.add16BitUIntToData(num, bigEndian); + break; + case 4: + builder.add32BitUIntToData(num, bigEndian); + break; + case 8: + builder.add64BitUIntToData(num, bigEndian); + break; + } + } + }; + describe("clean", () => { beforeEach(() => { builder = new ByteArrayBuilder(APDU_MAX_PAYLOAD); @@ -66,18 +93,66 @@ describe("ByteArrayBuilder", () => { expect(builder.getErrors()).toEqual([]); }); - it("should serialize with an 2 byte body", () => { - builder = new ByteArrayBuilder(2); - builder.add16BitUIntToData(0x3302); - expect(builder.build()).toEqual(COMMAND_BODY_TWO); - expect(builder.getErrors()).toEqual([]); - }); - - it("should serialize with an 4 byte body from an hexastring", () => { + it.each([ + [2, false, true, 0x3302, "3302"], + [2, false, false, 0x3302n, "0233"], + [2, true, true, 4200n, "1068"], + [2, true, true, -4200n, "ef98"], + [2, true, false, 4200, "6810"], + [2, true, false, -4200, "98ef"], + [4, false, true, 0x01234567n, "01234567"], + [4, false, false, 0x01234567n, "67452301"], + [4, true, true, 123456789, "075bcd15"], + [4, true, true, -123456789, "f8a432eb"], + [4, true, false, 123456789, "15cd5b07"], + [4, true, false, -123456789, "eb32a4f8"], + [8, false, true, 14147778004927559n, "0032435442584447"], + [8, false, false, 14147778004927559n, "4744584254433200"], + [8, true, true, 14147778004927559n, "0032435442584447"], + [8, true, true, -14147778004927559n, "ffcdbcabbda7bbb9"], + [8, true, false, 14147778004927559n, "4744584254433200"], + [8, true, false, -14147778004927559n, "b9bba7bdabbccdff"], + ])( + "serialize the following number: size %i, signed %s, bigEndian %s, value %i, expected %s", + (sizeInBits, signed, bigEndian, input, output) => { + builder = new ByteArrayBuilder(sizeInBits); + builderAddNumber(input, bigEndian, sizeInBits, signed); + expect(builder.build()).toEqual(hexaStringToBuffer(output)); + expect(builder.getErrors()).toEqual([]); + + // Retry with a buffer too small + builder = new ByteArrayBuilder(sizeInBits - 1); + builderAddNumber(input, bigEndian, sizeInBits, signed); + expect(builder.getErrors().length).toEqual(1); + expect(builder.build()).toEqual(Uint8Array.from([])); + }, + ); + + it.each([ + [2, false, true, 0xffffn, "ffff"], + [2, true, true, 0x7fffn, "7fff"], + [2, true, true, -0x8000n, "8000"], + [4, false, true, 0xffffffffn, "ffffffff"], + [4, true, true, 0x7fffffffn, "7fffffff"], + [4, true, true, -0x80000000n, "80000000"], + [8, false, true, 0xffffffffffffffffn, "ffffffffffffffff"], + [8, true, true, 0x7fffffffffffffffn, "7fffffffffffffff"], + [8, true, true, -0x8000000000000000n, "8000000000000000"], + ])( + "serialize the number to the limit: size %i, signed %s, bigEndian %s, value %i, expected %s", + (sizeInBits, signed, bigEndian, input, output) => { + builder = new ByteArrayBuilder(sizeInBits); + builderAddNumber(input, bigEndian, sizeInBits, signed); + expect(builder.build()).toEqual(hexaStringToBuffer(output)); + expect(builder.getErrors()).toEqual([]); + }, + ); + + it("Serialize from float to bigint", () => { builder = new ByteArrayBuilder(4); - builder.add32BitUIntToData(0x01234567); - expect(builder.build()).toEqual(COMMAND_BODY_EIGHT); - expect(builder.getErrors()).toEqual([]); + builder.add32BitIntToData(123456789.3, false); + expect(builder.getErrors().length).toEqual(1); + expect(builder.build()).toEqual(Uint8Array.from([])); }); it("should serialize with an 5 byte body from an hexastring", () => { @@ -175,32 +250,25 @@ describe("ByteArrayBuilder", () => { builder = new ByteArrayBuilder(APDU_MAX_PAYLOAD); }); - it("error due value greater than 8-bit integer", () => { - builder.add8BitUIntToData(0x100); - expect(builder.build()).toEqual(COMMAND_NO_BODY); - expect(builder.getAvailablePayloadLength()).toBe(APDU_MAX_PAYLOAD); - expect(builder.getErrors()).toEqual([ - new ValueOverflowError((0x100).toString(), 255), - ]); - }); - - it("error due value greater than 16-bit integer", () => { - builder.add16BitUIntToData(0x10000); - expect(builder.build()).toEqual(COMMAND_NO_BODY); - expect(builder.getAvailablePayloadLength()).toBe(APDU_MAX_PAYLOAD); - expect(builder.getErrors()).toEqual([ - new ValueOverflowError((0x10000).toString(), 65535), - ]); - }); - - it("error due value greater than 32-bit integer", () => { - builder.add32BitUIntToData(0x100000000); - expect(builder.build()).toEqual(COMMAND_NO_BODY); - expect(builder.getAvailablePayloadLength()).toBe(APDU_MAX_PAYLOAD); - expect(builder.getErrors()).toEqual([ - new ValueOverflowError((0x100000000).toString(), 4294967295), - ]); - }); + it.each([ + [2, false, true, 0x10000n], + [2, true, true, 0x8000n], + [2, true, true, -0x8001n], + [4, false, true, 0x100000000n], + [4, true, true, 0x80000000n], + [4, true, true, -0x80000001n], + [8, false, true, 0x10000000000000000n], + [8, true, true, 0x8000000000000000n], + [8, true, true, -0x8000000000000001n], + ])( + "serialize the number overflowed: size %i, signed %s, bigEndian %s, value %i", + (sizeInBits, signed, bigEndian, input) => { + builder = new ByteArrayBuilder(sizeInBits); + builderAddNumber(input, bigEndian, sizeInBits, signed); + expect(builder.getErrors().length).toEqual(1); + expect(builder.build()).toEqual(Uint8Array.from([])); + }, + ); it("error due to a string not well coded", () => { builder @@ -227,53 +295,6 @@ describe("ByteArrayBuilder", () => { ]); }); - it("error due to subsequent overflow with one byte", () => { - const myarray = new Uint8Array(APDU_MAX_PAYLOAD).fill( - 0xaa, - 0, - APDU_MAX_PAYLOAD, - ); - builder.addBufferToData(myarray); - expect(builder.build()).toEqual(COMMAND_BODY_MAX); - expect(builder.getAvailablePayloadLength()).toBe(0); - - builder.add8BitUIntToData(0); - expect(builder.build()).toEqual(COMMAND_BODY_MAX); - expect(builder.getErrors()).toEqual([new DataOverflowError("0")]); - }); - - it("error due to subsequent overflow with 2 bytes", () => { - const myarray = new Uint8Array(APDU_MAX_PAYLOAD).fill( - 0xaa, - 0, - APDU_MAX_PAYLOAD, - ); - builder.addBufferToData(myarray); - expect(builder.build()).toEqual(COMMAND_BODY_MAX); - expect(builder.getAvailablePayloadLength()).toBe(0); - - builder.add16BitUIntToData(0); - expect(builder.build()).toEqual(COMMAND_BODY_MAX); - expect(builder.getAvailablePayloadLength()).toBe(0); - expect(builder.getErrors()).toEqual([new DataOverflowError("0")]); - }); - - it("error due to subsequent overflow with 4 bytes", () => { - const myarray = new Uint8Array(APDU_MAX_PAYLOAD).fill( - 0xaa, - 0, - APDU_MAX_PAYLOAD, - ); - builder.addBufferToData(myarray); - expect(builder.build()).toEqual(COMMAND_BODY_MAX); - expect(builder.getAvailablePayloadLength()).toBe(0); - - builder.add32BitUIntToData(0); - expect(builder.build()).toEqual(COMMAND_BODY_MAX); - expect(builder.getAvailablePayloadLength()).toBe(0); - expect(builder.getErrors()).toEqual([new DataOverflowError("0")]); - }); - it("error due to subsequent overflow with 1-byte array", () => { const myarray = new Uint8Array(APDU_MAX_PAYLOAD).fill( 0xaa, diff --git a/packages/core/src/api/apdu/utils/ByteArrayBuilder.ts b/packages/core/src/api/apdu/utils/ByteArrayBuilder.ts index 4ec865756..b403687df 100644 --- a/packages/core/src/api/apdu/utils/ByteArrayBuilder.ts +++ b/packages/core/src/api/apdu/utils/ByteArrayBuilder.ts @@ -7,8 +7,6 @@ import { ValueOverflowError, } from "./AppBuilderError"; -const MAX_8_BIT_UINT = 0xff; -const MAX_16_BIT_UINT = 0xffff; const MAX_32_BIT_UINT = 0xffffffff; /** @@ -32,7 +30,7 @@ export class ByteArrayBuilder { private data: Uint8Array = new Uint8Array(); private readonly errors: AppBuilderError[] = []; // Custom Error - constructor(private maxPayloadSize: number) {} + constructor(private maxPayloadSize: number = MAX_32_BIT_UINT) {} // ========== // Public API @@ -44,74 +42,100 @@ export class ByteArrayBuilder { */ build = (): Uint8Array => this.data; + /** + * Try to build a new payload instance with the current state of the builder + * if the builder don't contain any error. + * @returns {payload | undefined} - Returns a new payload instance or undefined + */ + tryBuild = (): Uint8Array | undefined => { + return this.hasErrors() ? undefined : this.data; + }; + /** * Add a 8-bit unsigned integer to the payload (max value 0xff = 255) - * @param value: number - The value to add to the data + * @param value: number | bigint - The value to add to the data * @returns {ByteArrayBuilder} - Returns the current instance of ByteArrayBuilder */ - add8BitUIntToData = (value: number): ByteArrayBuilder => { - if (value > MAX_8_BIT_UINT) { - this.errors.push( - new ValueOverflowError(value.toString(), MAX_8_BIT_UINT), - ); - return this; - } - - if (this.data.length >= this.maxPayloadSize) { - this.errors.push(new DataOverflowError(value.toString())); - return this; - } - - this.data = Uint8Array.from([...this.data, value & 0xff]); - return this; + add8BitUIntToData = (value: number | bigint): ByteArrayBuilder => { + return this.addNumberToData(value, 8n, false, false); }; /** * Add a 16-bit unsigned integer to the payload (max value 0xffff = 65535) - * @param value: number - The value to add to the data + * @param value: number | bigint - The value to add to the data + * @param bigEndian: boolean - True to encode in big endian, false for little endian * @returns {ByteArrayBuilder} - Returns the current instance of ByteArrayBuilder */ - add16BitUIntToData = (value: number): ByteArrayBuilder => { - if (value > MAX_16_BIT_UINT) { - this.errors.push( - new ValueOverflowError(value.toString(), MAX_16_BIT_UINT), - ); - return this; - } + add16BitUIntToData = ( + value: number | bigint, + bigEndian: boolean = true, + ): ByteArrayBuilder => { + return this.addNumberToData(value, 16n, false, bigEndian); + }; - if (this.getAvailablePayloadLength() < 2) { - this.errors.push(new DataOverflowError(value.toString())); - return this; - } + /** + * Add a 32-bit unsigned integer to the payload (max value 0xffffffff = 4294967295) + * @param value: number | bigint - The value to add to the data + * @param bigEndian: boolean - True to encode in big endian, false for little endian + * @returns {ByteArrayBuilder} - Returns the current instance of ByteArrayBuilder + */ + add32BitUIntToData = ( + value: number | bigint, + bigEndian: boolean = true, + ): ByteArrayBuilder => { + return this.addNumberToData(value, 32n, false, bigEndian); + }; - this.add8BitUIntToData((value >>> 8) & 0xff); - this.add8BitUIntToData(value & 0xff); - return this; + /** + * Add a 64-bit unsigned integer to the payload (max value 0xffffffffffffffff = 18446744073709551615) + * @param value: number | bigint - The value to add to the data + * @param bigEndian: boolean - True to encode in big endian, false for little endian + * @returns {ByteArrayBuilder} - Returns the current instance of ByteArrayBuilder + */ + add64BitUIntToData = ( + value: number | bigint, + bigEndian: boolean = true, + ): ByteArrayBuilder => { + return this.addNumberToData(value, 64n, false, bigEndian); }; /** - * Add a 32-bit unsigned integer to the payload (max value 0xffffffff = 4294967295) - * @param value: number - The value to add to the data + * Add a 16-bit signed integer to the payload (value between -0x8000 to 0x7fff) + * @param value: number | bigint - The value to add to the data + * @param bigEndian: boolean - True to encode in big endian, false for little endian * @returns {ByteArrayBuilder} - Returns the current instance of ByteArrayBuilder */ - add32BitUIntToData = (value: number): ByteArrayBuilder => { - if (value > MAX_32_BIT_UINT) { - this.errors.push( - new ValueOverflowError(value.toString(), MAX_32_BIT_UINT), - ); - return this; - } + add16BitIntToData = ( + value: number | bigint, + bigEndian: boolean = true, + ): ByteArrayBuilder => { + return this.addNumberToData(value, 16n, true, bigEndian); + }; - if (this.getAvailablePayloadLength() < 4) { - this.errors.push(new DataOverflowError(value.toString())); - return this; - } + /** + * Add a 32-bit signed integer to the payload (value between -0x80000000 to 0x7fffffff) + * @param value: number | bigint - The value to add to the data + * @param bigEndian: boolean - True to encode in big endian, false for little endian + * @returns {ByteArrayBuilder} - Returns the current instance of ByteArrayBuilder + */ + add32BitIntToData = ( + value: number | bigint, + bigEndian: boolean = true, + ): ByteArrayBuilder => { + return this.addNumberToData(value, 32n, true, bigEndian); + }; - this.add8BitUIntToData((value >>> 24) & 0xff); - this.add8BitUIntToData((value >>> 16) & 0xff); - this.add8BitUIntToData((value >>> 8) & 0xff); - this.add8BitUIntToData(value & 0xff); - return this; + /** + * Add a 64-bit signed integer to the payload (value between -0x8000000000000000 to 0x7fffffffffffffff) + * @param value: number | bigint - The value to add to the data + * @param bigEndian: boolean - True to encode in big endian, false for little endian + * @returns {ByteArrayBuilder} - Returns the current instance of ByteArrayBuilder + */ + add64BitIntToData = ( + value: number | bigint, + bigEndian: boolean = true, + ): ByteArrayBuilder => { + return this.addNumberToData(value, 64n, true, bigEndian); }; /** @@ -231,6 +255,12 @@ export class ByteArrayBuilder { */ getErrors = (): AppBuilderError[] => this.errors; + /** + * Verifies if the builder contains errors + * @returns {boolean} - Returns wether the builder contains errors or not + */ + hasErrors = (): boolean => this.errors.length !== 0; + // =========== // Private API // =========== @@ -249,4 +279,88 @@ export class ByteArrayBuilder { this.data.length + value.length + (hasLv ? 1 : 0) <= this.maxPayloadSize ); }; + + /** + * Add a number to the payload + * @param value: number | bigint - The value to add to the data + * @param sizeInBits: bigint - The number size in bits, for example 16 for a uint16 + * @param signed: boolean - Whether the value is signed or unsigned. + * @param bigEndian: boolean - True to encode in big endian, false for little endian + * @returns {ByteArrayBuilder} - Returns the current instance of ByteArrayBuilder + */ + private addNumberToData( + value: number | bigint, + sizeInBits: bigint, + signed: boolean, + bigEndian: boolean, + ): ByteArrayBuilder { + // Convert the number to two's complement and check its bounds + let converted = this.checkBoundsAndConvert(value, sizeInBits, signed); + if (converted === undefined) { + return this; + } + + // Compute the buffer + const sizeInBytes = Number(sizeInBits) / 8; + const buffer = new Uint8Array(sizeInBytes); + if (bigEndian) { + for (let i = sizeInBytes - 1; i >= 0; i--) { + buffer[i] = Number(converted & 0xffn); + converted >>= 8n; + } + } else { + for (let i = 0; i < sizeInBytes; i++) { + buffer[i] = Number(converted & 0xffn); + converted >>= 8n; + } + } + return this.addBufferToData(buffer); + } + + /** + * Checks the bounds of a signed or unsigned integer value and converts it to two's complement if it is signed and negative. + * @param value The value to check and convert. + * @param sizeInBits The size of the value in bits. + * @param signed Whether the value is signed or unsigned. + * @returns The converted value, or null if the value is out of bounds. + */ + private checkBoundsAndConvert( + value: number | bigint, + sizeInBits: bigint, + signed: boolean, + ): bigint | undefined { + // Normalize the value to a bigint + if (typeof value === "number") { + if (!Number.isInteger(value) || value > Number.MAX_SAFE_INTEGER) { + this.errors.push(new ValueOverflowError(value.toString())); + return; + } + value = BigInt(value); + } + + if (!signed) { + // Check if the value is within the bounds of an unsigned integer + const limit = 1n << sizeInBits; + if (value < 0 || value >= limit) { + this.errors.push(new ValueOverflowError(value.toString(), limit - 1n)); + return; + } + } else { + // Check if the value is within the bounds of a signed integer + const limit = 1n << (sizeInBits - 1n); + if (value >= limit || value < -limit) { + this.errors.push(new ValueOverflowError(value.toString(), limit - 1n)); + return; + } + + // Convert the value to two's complement if it is negative + // https://en.wikipedia.org/wiki/Two%27s_complement + if (value < 0n) { + const mask = (1n << sizeInBits) - 1n; + value = -value; + value = (~value & mask) + 1n; + } + } + return value; + } } diff --git a/packages/core/src/api/apdu/utils/ByteArrayParser.test.ts b/packages/core/src/api/apdu/utils/ByteArrayParser.test.ts index 7fe4815d2..40b2acc55 100644 --- a/packages/core/src/api/apdu/utils/ByteArrayParser.test.ts +++ b/packages/core/src/api/apdu/utils/ByteArrayParser.test.ts @@ -1,3 +1,5 @@ +import { hexaStringToBuffer } from "@api/utils/HexaString"; + import { ByteArrayParser } from "./ByteArrayParser"; const RESPONSE_ONE_BYTE = new Uint8Array([0x01]); @@ -54,6 +56,33 @@ let parser: ByteArrayParser; let response = RESPONSE_ONE_BYTE; describe("ByteArrayParser", () => { + const parserExtractNumber = ( + bigEndian: boolean, + sizeInBits: number, + signed: boolean, + ): bigint | number | undefined => { + if (signed) { + switch (sizeInBits) { + case 2: + return parser.extract16BitInt(bigEndian); + case 4: + return parser.extract32BitInt(bigEndian); + case 8: + return parser.extract64BitInt(bigEndian); + } + } else { + switch (sizeInBits) { + case 2: + return parser.extract16BitUInt(bigEndian); + case 4: + return parser.extract32BitUInt(bigEndian); + case 8: + return parser.extract64BitUInt(bigEndian); + } + } + return undefined; + }; + describe("clean", () => { beforeEach(() => { jest.resetAllMocks(); @@ -111,6 +140,53 @@ describe("ByteArrayParser", () => { } }); + it.each([ + [2, false, true, "ffff", 0xffff], + [2, true, true, "7fff", 0x7fff], + [2, true, true, "8000", -0x8000], + [4, false, true, "ffffffff", 0xffffffff], + [4, true, true, "7fffffff", 0x7fffffff], + [4, true, true, "80000000", -0x80000000], + [8, false, true, "ffffffffffffffff", 0xffffffffffffffffn], + [8, true, true, "7fffffffffffffff", 0x7fffffffffffffffn], + [8, true, true, "8000000000000000", -0x8000000000000000n], + ])( + "Extract a number to the limit: size %i, signed %s, bigEndian %s, buffer %s, expected %i", + (sizeInBits, signed, bigEndian, input, output) => { + parser = new ByteArrayParser(hexaStringToBuffer(input)!); + const result = parserExtractNumber(bigEndian, sizeInBits, signed); + expect(result).toStrictEqual(output); + }, + ); + + it.each([ + [2, false, true, "3302", 0x3302], + [2, false, false, "0233", 0x3302], + [2, true, true, "1068", 4200], + [2, true, true, "ef98", -4200], + [2, true, false, "6810", 4200], + [2, true, false, "98ef", -4200], + [4, false, true, "01234567", 0x01234567], + [4, false, false, "67452301", 0x01234567], + [4, true, true, "075bcd15", 123456789], + [4, true, true, "f8a432eb", -123456789], + [4, true, false, "15cd5b07", 123456789], + [4, true, false, "eb32a4f8", -123456789], + [8, false, true, "0032435442584447", 14147778004927559n], + [8, false, false, "4744584254433200", 14147778004927559n], + [8, true, true, "0032435442584447", 14147778004927559n], + [8, true, true, "ffcdbcabbda7bbb9", -14147778004927559n], + [8, true, false, "4744584254433200", 14147778004927559n], + [8, true, false, "b9bba7bdabbccdff", -14147778004927559n], + ])( + "Extract the following number: size %i, signed %s, bigEndian %s, buffer %s, expected %i", + (sizeInBits, signed, bigEndian, input, output) => { + parser = new ByteArrayParser(hexaStringToBuffer(input)!); + const result = parserExtractNumber(bigEndian, sizeInBits, signed); + expect(result).toStrictEqual(output); + }, + ); + it("Extract 16-bit & 32-bit number", () => { response = RESPONSE_ALL_BYTES; parser = new ByteArrayParser(response); diff --git a/packages/core/src/api/apdu/utils/ByteArrayParser.ts b/packages/core/src/api/apdu/utils/ByteArrayParser.ts index b131f98bc..e0d388541 100644 --- a/packages/core/src/api/apdu/utils/ByteArrayParser.ts +++ b/packages/core/src/api/apdu/utils/ByteArrayParser.ts @@ -45,31 +45,61 @@ export class ByteArrayParser { } /** - * Extract a 16-bit unsigned integer (Big Endian coding) from the response + * Extract a 16-bit unsigned integer from the response + * @param bigEndian: boolean - True to decode in big endian, false for little endian * @returns {number | undefined} - Returns the 16-bit unsigned integer extracted from the response */ - extract16BitUInt(): number | undefined { - if (this.outOfRange(2)) return; - let msb = this.extract8BitUInt(); - if (msb === undefined) return; - const lsb = this.extract8BitUInt(); - if (lsb === undefined) return; - msb *= 0x100; - return msb + lsb; + extract16BitUInt(bigEndian: boolean = true): number | undefined { + const value = this.extractNumber(16n, false, bigEndian); + return value === undefined ? undefined : Number(value); } /** - * Extract a 32-bit unsigned integer (Big Endian coding) from the response + * Extract a 16-bit signed integer from the response + * @param bigEndian: boolean - True to decode in big endian, false for little endian + * @returns {number | undefined} - Returns the 16-bit signed integer extracted from the response + */ + extract16BitInt(bigEndian: boolean = true): number | undefined { + const value = this.extractNumber(16n, true, bigEndian); + return value === undefined ? undefined : Number(value); + } + + /** + * Extract a 32-bit unsigned integer from the response + * @param bigEndian: boolean - True to decode in big endian, false for little endian * @returns {number | undefined} - Returns the 32-bit unsigned integer extracted from the response */ - extract32BitUInt(): number | undefined { - if (this.outOfRange(4)) return; - let msw = this.extract16BitUInt(); - if (msw === undefined) return; - const lsw = this.extract16BitUInt(); - if (lsw === undefined) return; - msw *= 0x10000; - return msw + lsw; + extract32BitUInt(bigEndian: boolean = true): number | undefined { + const value = this.extractNumber(32n, false, bigEndian); + return value === undefined ? undefined : Number(value); + } + + /** + * Extract a 32-bit signed integer from the response + * @param bigEndian: boolean - True to decode in big endian, false for little endian + * @returns {number | undefined} - Returns the 32-bit signed integer extracted from the response + */ + extract32BitInt(bigEndian: boolean = true): number | undefined { + const value = this.extractNumber(32n, true, bigEndian); + return value === undefined ? undefined : Number(value); + } + + /** + * Extract a 64-bit unsigned integer from the response + * @param bigEndian: boolean - True to decode in big endian, false for little endian + * @returns {number | undefined} - Returns the 64-bit unsigned integer extracted from the response + */ + extract64BitUInt(bigEndian: boolean = true): bigint | undefined { + return this.extractNumber(64n, false, bigEndian); + } + + /** + * Extract a 64-bit signed integer from the response + * @param bigEndian: boolean - True to decode in big endian, false for little endian + * @returns {number | undefined} - Returns the 64-bit signed integer extracted from the response + */ + extract64BitInt(bigEndian: boolean = true): bigint | undefined { + return this.extractNumber(64n, true, bigEndian); } /** @@ -189,4 +219,45 @@ export class ByteArrayParser { private outOfRange(length: number): boolean { return this.index + length > this.buffer.length; } + + /** + * Extract a number from the buffer + * @param sizeInBits: bigint - The number size in bits, for example 16 for a uint16 + * @param signed: boolean - True is the number can be signed and converted to two's compliment + * @param bigEndian: boolean - True to decode in big endian, false for little endian + * @returns {bigint | undefined} - Returns the number extracted from the buffer + */ + private extractNumber( + sizeInBits: bigint, + signed: boolean, + bigEndian: boolean, + ): bigint | undefined { + // Check the range + const sizeInBytes: number = Number(sizeInBits) / 8; + if (this.outOfRange(sizeInBytes)) return; + + // Compute the number + let value: bigint = 0n; + if (bigEndian) { + for (let i = 0; i < sizeInBytes; i++) { + value = (value << 8n) | BigInt(this.buffer[i + this.index]!); + } + } else { + for (let i = sizeInBytes - 1; i >= 0; i--) { + value = (value << 8n) | BigInt(this.buffer[i + this.index]!); + } + } + + // Convert the value to two's complement if it is negative + // https://en.wikipedia.org/wiki/Two%27s_complement + if (signed) { + const limit = 1n << (sizeInBits - 1n); + if (value & limit) { + value -= limit << 1n; + } + } + + this.index += sizeInBytes; + return value; + } } From 663b82a40f6969ead2de65c966d02a476ee096d4 Mon Sep 17 00:00:00 2001 From: Pierre Aoun Date: Mon, 9 Sep 2024 18:47:29 +0200 Subject: [PATCH 4/9] :sparkles: (keyring-btc): Implement varint parser and builder --- .../src/internal/utils/Varint.test.ts | 73 +++++++++++++++++++ .../keyring-btc/src/internal/utils/Varint.ts | 59 +++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 packages/signer/keyring-btc/src/internal/utils/Varint.test.ts create mode 100644 packages/signer/keyring-btc/src/internal/utils/Varint.ts diff --git a/packages/signer/keyring-btc/src/internal/utils/Varint.test.ts b/packages/signer/keyring-btc/src/internal/utils/Varint.test.ts new file mode 100644 index 000000000..ea5e7247a --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/utils/Varint.test.ts @@ -0,0 +1,73 @@ +import { ByteArrayParser, hexaStringToBuffer } from "@ledgerhq/device-sdk-core"; + +import { encodeVarint, extractVarint } from "./Varint"; + +describe("Varint tests", () => { + it("Decode empty buffer", () => { + const parser = new ByteArrayParser(Uint8Array.from([])); + const extracted = extractVarint(parser); + expect(extracted.isJust()).toStrictEqual(false); + }); + + it("Decode out of bound bigint", () => { + const parser = new ByteArrayParser( + Uint8Array.from([0xff, 0x58, 0xc1, 0x59, 0x7d, 0xa1, 0x83, 0xf5, 0x4b]), + ); + const extracted = extractVarint(parser); + expect(extracted.isJust()).toStrictEqual(false); + }); + + it("Decode with buffer too small", () => { + const parser = new ByteArrayParser(Uint8Array.from([0xff, 0x58, 0xc1])); + const extracted = extractVarint(parser); + expect(extracted.isJust()).toStrictEqual(false); + }); + + it("Encode negative number", () => { + const encoded = encodeVarint(-1); + expect(encoded.isJust()).toStrictEqual(false); + }); + + it("Encode an unsafe number", () => { + const encoded = encodeVarint(Number.MAX_SAFE_INTEGER + 1); + expect(encoded.isJust()).toStrictEqual(false); + }); + + it("Encode an out of bounds big bigint", () => { + const encoded = encodeVarint(BigInt("0x10000000000000000")); + expect(encoded.isJust()).toStrictEqual(false); + }); + + // Examples taken from https://wiki.bitcoinsv.io/index.php/VarInt + it.each([ + [0xbb, "0xBB"], + [0xff, "0xFDFF00"], + [0x3419, "0xFD1934"], + [0xdc4591, "0xFE9145DC00"], + [0x80081e5, "0xFEE5810008"], + [0xb4da564e2857, "0xFF57284E56DAB40000"], + [0x4bf583a17d59c158n, "0xFF58C1597DA183F54B"], + ])("Encode varint %d into buffer %s", (value, buffer) => { + const encoded = encodeVarint(value); + expect(encoded.isJust()).toStrictEqual(true); + expect(encoded.unsafeCoerce()).toStrictEqual(hexaStringToBuffer(buffer)!); + }); + + // Examples taken from https://wiki.bitcoinsv.io/index.php/VarInt + it.each([ + ["0xBB", 0xbb, 1], + ["0xFDFF00", 0xff, 3], + ["0xFD1934", 0x3419, 3], + ["0xFE9145DC00", 0xdc4591, 5], + ["0xFEE5810008", 0x80081e5, 5], + ["0xFF57284E56DAB40000", 0xb4da564e2857, 9], + ])( + "Extract from buffer %s the expected varint %d of size %d", + (buffer, value, sizeInBytes) => { + const parser = new ByteArrayParser(hexaStringToBuffer(buffer)!); + const extracted = extractVarint(parser); + expect(extracted.isJust()).toStrictEqual(true); + expect(extracted.unsafeCoerce()).toStrictEqual({ value, sizeInBytes }); + }, + ); +}); diff --git a/packages/signer/keyring-btc/src/internal/utils/Varint.ts b/packages/signer/keyring-btc/src/internal/utils/Varint.ts new file mode 100644 index 000000000..237a97173 --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/utils/Varint.ts @@ -0,0 +1,59 @@ +import { ByteArrayBuilder, ByteArrayParser } from "@ledgerhq/device-sdk-core"; +import { Just, Maybe, Nothing } from "purify-ts"; + +/** + * As described here: https://wiki.bitcoinsv.io/index.php/VarInt + * + * - value <= 0xFC: value as uint8 + * - 0xFC < value <= 0xFFFF: 0xFD + value as uint16 LE + * - 0xFFFF < value <= 0xFFFFFFFF: 0xFE + value as uint32 LE + * - 0xFFFFFFFF < value <= 0xFFFFFFFFFFFFFFFF: 0xFF + value as uint64 LE + */ +export type Varint = { + value: number; + sizeInBytes: number; +}; + +export function extractVarint(parser: ByteArrayParser): Maybe { + const prefix = parser.extract8BitUInt(); + if (prefix === undefined) { + return Nothing; + } else if (prefix <= 0xfc) { + return Just({ value: prefix, sizeInBytes: 1 }); + } else if (prefix === 0xfd) { + return Maybe.fromNullable(parser.extract16BitUInt(false)).map((value) => ({ + value, + sizeInBytes: 3, + })); + } else if (prefix === 0xfe) { + return Maybe.fromNullable(parser.extract32BitUInt(false)).map((value) => ({ + value, + sizeInBytes: 5, + })); + } else { + return Maybe.fromNullable(parser.extract64BitUInt(false)).chain((value) => { + if (value > Number.MAX_SAFE_INTEGER) { + return Nothing; + } else { + return Just({ value: Number(value), sizeInBytes: 9 }); + } + }); + } +} + +export function encodeVarint(value: number | bigint): Maybe { + const builder = new ByteArrayBuilder(); + if (value <= 0xfc) { + builder.add8BitUIntToData(value); + } else if (value <= 0xffff) { + builder.add8BitUIntToData(0xfd); + builder.add16BitUIntToData(value, false); + } else if (value <= 0xffffffff) { + builder.add8BitUIntToData(0xfe); + builder.add32BitUIntToData(value, false); + } else { + builder.add8BitUIntToData(0xff); + builder.add64BitUIntToData(value, false); + } + return Maybe.fromNullable(builder.tryBuild()); +} From 1f1485be90f073d73f7013d6b755f1234e481844 Mon Sep 17 00:00:00 2001 From: Pierre Aoun Date: Tue, 3 Sep 2024 14:30:21 +0200 Subject: [PATCH 5/9] :sparkles: (keyring-btc): Implement merkle tree abstractions --- .changeset/calm-months-live.md | 6 + packages/core/src/api/index.ts | 6 +- .../core/src/api/utils/HexaString.test.ts | 19 +- packages/core/src/api/utils/HexaString.ts | 6 + packages/signer/keyring-btc/package.json | 2 + .../merkle-tree/di/merkleTreeModule.test.ts | 19 ++ .../merkle-tree/di/merkleTreeModule.ts | 21 ++ .../merkle-tree/di/merkleTreeTypes.ts | 4 + .../model/DefaultMerkleMap.test.ts | 135 ++++++++ .../merkle-tree/model/DefaultMerkleMap.ts | 72 +++++ .../model/DefaultMerkleTree.test.ts | 288 ++++++++++++++++++ .../merkle-tree/model/DefaultMerkleTree.ts | 124 ++++++++ .../src/internal/merkle-tree/model/Hasher.ts | 9 + .../internal/merkle-tree/model/MerkleMap.ts | 7 + .../internal/merkle-tree/model/MerkleTree.ts | 11 + .../src/internal/merkle-tree/model/Node.ts | 20 ++ .../service/DefaultMerkleMapService.ts | 21 ++ .../service/DefaultMerkleTreeService.ts | 14 + .../merkle-tree/service/MerkleMapService.ts | 7 + .../merkle-tree/service/MerkleTreeService.ts | 5 + pnpm-lock.yaml | 6 + 21 files changed, 800 insertions(+), 2 deletions(-) create mode 100644 .changeset/calm-months-live.md create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeModule.test.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeModule.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeTypes.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.test.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.test.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/Hasher.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleMap.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleTree.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/Node.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleMapService.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleTreeService.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapService.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeService.ts diff --git a/.changeset/calm-months-live.md b/.changeset/calm-months-live.md new file mode 100644 index 000000000..224cec86d --- /dev/null +++ b/.changeset/calm-months-live.md @@ -0,0 +1,6 @@ +--- +"@ledgerhq/keyring-btc": patch +"@ledgerhq/device-sdk-core": patch +--- + +Implement MerkleTree and MerkleMap services diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts index 833e1710b..beacc5e0b 100644 --- a/packages/core/src/api/index.ts +++ b/packages/core/src/api/index.ts @@ -118,4 +118,8 @@ export { DeviceSessionStateType, } from "@api/device-session/DeviceSessionState"; export { type SdkError } from "@api/Error"; -export { hexaStringToBuffer, isHexaString } from "@api/utils/HexaString"; +export { + bufferToHexaString, + hexaStringToBuffer, + isHexaString, +} from "@api/utils/HexaString"; diff --git a/packages/core/src/api/utils/HexaString.test.ts b/packages/core/src/api/utils/HexaString.test.ts index df7f85f39..698c87911 100644 --- a/packages/core/src/api/utils/HexaString.test.ts +++ b/packages/core/src/api/utils/HexaString.test.ts @@ -1,4 +1,8 @@ -import { hexaStringToBuffer, isHexaString } from "./HexaString"; +import { + bufferToHexaString, + hexaStringToBuffer, + isHexaString, +} from "./HexaString"; describe("HexaString", () => { describe("isHexaString function", () => { @@ -138,4 +142,17 @@ describe("HexaString", () => { expect(result).toStrictEqual(new Uint8Array([0x0a, 0x35])); }); }); + + describe("bufferToHexaString function", () => { + it("should convert a buffer into a hexa string", () => { + // GIVEN + const value = Uint8Array.from([0, 1, 2, 0xff, 0xfe]); + + // WHEN + const result = bufferToHexaString(value); + + // THEN + expect(result).toStrictEqual("0x000102fffe"); + }); + }); }); diff --git a/packages/core/src/api/utils/HexaString.ts b/packages/core/src/api/utils/HexaString.ts index 05868d375..d342248e8 100644 --- a/packages/core/src/api/utils/HexaString.ts +++ b/packages/core/src/api/utils/HexaString.ts @@ -23,3 +23,9 @@ export const hexaStringToBuffer = (value: string): Uint8Array | null => { } return new Uint8Array(bytes); }; + +export const bufferToHexaString = (value: Uint8Array): HexaString => { + return `0x${Array.from(value, (byte) => + byte.toString(16).padStart(2, "0"), + ).join("")}`; +}; diff --git a/packages/signer/keyring-btc/package.json b/packages/signer/keyring-btc/package.json index 4c0c7c5e0..fd20ff728 100644 --- a/packages/signer/keyring-btc/package.json +++ b/packages/signer/keyring-btc/package.json @@ -42,7 +42,9 @@ "test:coverage": "pnpm test -- --coverage" }, "dependencies": { + "@types/crypto-js": "^4.2.2", "bitcoinjs-lib": "^6.1.6", + "crypto-js": "^4.2.0", "inversify": "^6.0.2", "inversify-logger-middleware": "^3.1.0", "purify-ts": "^2.1.0", diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeModule.test.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeModule.test.ts new file mode 100644 index 000000000..b1072856b --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeModule.test.ts @@ -0,0 +1,19 @@ +import { Container } from "inversify"; + +import { merkleTreeModuleFactory } from "./merkleTreeModule"; + +describe("MerkleTreeModuleFactory", () => { + describe("Default", () => { + let container: Container; + let mod: ReturnType; + beforeEach(() => { + mod = merkleTreeModuleFactory(); + container = new Container(); + container.load(mod); + }); + + it("should return the merkle tree service module", () => { + expect(mod).toBeDefined(); + }); + }); +}); diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeModule.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeModule.ts new file mode 100644 index 000000000..d1c736f65 --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeModule.ts @@ -0,0 +1,21 @@ +import { ContainerModule } from "inversify"; + +import { merkleTreeTypes } from "@internal/merkle-tree/di/merkleTreeTypes"; +import { DefaultMerkleMapService } from "@internal/merkle-tree/service/DefaultMerkleMapService"; +import { DefaultMerkleTreeService } from "@internal/merkle-tree/service/DefaultMerkleTreeService"; + +export const merkleTreeModuleFactory = () => + new ContainerModule( + ( + bind, + _unbind, + _isBound, + _rebind, + _unbindAsync, + _onActivation, + _onDeactivation, + ) => { + bind(merkleTreeTypes.MerkleTreeService).to(DefaultMerkleTreeService); + bind(merkleTreeTypes.MerkleMapService).to(DefaultMerkleMapService); + }, + ); diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeTypes.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeTypes.ts new file mode 100644 index 000000000..557749cbf --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeTypes.ts @@ -0,0 +1,4 @@ +export const merkleTreeTypes = { + MerkleTreeService: Symbol.for("MerkleTreeService"), + MerkleMapService: Symbol.for("MerkleMapService"), +}; diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.test.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.test.ts new file mode 100644 index 000000000..bd0c562ba --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.test.ts @@ -0,0 +1,135 @@ +import { type MerkleTreeService } from "@internal/merkle-tree/service/MerkleTreeService"; + +import { DefaultMerkleMap } from "./DefaultMerkleMap"; + +describe("DefaultMerkleMap", () => { + const TEST_KEYS = [ + Uint8Array.from([0, 1, 2, 3]), + Uint8Array.from([0, 1, 2, 4]), + Uint8Array.from([0, 1, 2, 5]), + Uint8Array.from([0, 2, 2, 3]), + Uint8Array.from([1, 1, 2, 3]), + ]; + + const TEST_VALUES = [ + Uint8Array.from([0, 0]), + Uint8Array.from([0xff, 0xff, 0xff, 0xff]), + Uint8Array.from([0, 1, 2, 5]), + Uint8Array.from([0, 0xff]), + Uint8Array.from([0xfe, 57]), + ]; + + const mockCreateMerkleTree = jest.fn(); + const mockMerkleTree: MerkleTreeService = { + create: mockCreateMerkleTree, + }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it("Invalid value count", () => { + const maybeMap = DefaultMerkleMap.create( + TEST_KEYS, + [Uint8Array.from([0]), ...TEST_VALUES], + mockMerkleTree, + ); + expect(maybeMap.isJust()).toStrictEqual(false); + }); + + it("Invalid ordering", () => { + const maybeMap = DefaultMerkleMap.create( + [Uint8Array.from([0, 1, 2, 5]), ...TEST_KEYS], + [Uint8Array.from([0]), ...TEST_VALUES], + mockMerkleTree, + ); + expect(maybeMap.isJust()).toStrictEqual(false); + }); + + it("Dupplicate key", () => { + const maybeMap = DefaultMerkleMap.create( + [Uint8Array.from([0, 1, 2, 3]), ...TEST_KEYS], + [Uint8Array.from([0]), ...TEST_VALUES], + mockMerkleTree, + ); + expect(maybeMap.isJust()).toStrictEqual(false); + }); + + it("Get map commitment", () => { + mockCreateMerkleTree + .mockReturnValueOnce({ + getRoot: () => Uint8Array.from([42, 43]), + size: () => TEST_KEYS.length, + }) + .mockReturnValueOnce({ + getRoot: () => Uint8Array.from([44, 45]), + size: () => TEST_VALUES.length, + }); + + const maybeMap = DefaultMerkleMap.create( + TEST_KEYS, + TEST_VALUES, + mockMerkleTree, + ); + expect(maybeMap.isJust()).toStrictEqual(true); + const map = maybeMap.unsafeCoerce(); + expect(map.getCommitment()).toStrictEqual( + Uint8Array.from([5, 42, 43, 44, 45]), + ); + + expect(mockCreateMerkleTree).toHaveBeenCalledWith(TEST_KEYS); + expect(mockCreateMerkleTree).toHaveBeenCalledWith(TEST_VALUES); + }); + + it("Get map commitment, with mid size", () => { + mockCreateMerkleTree + .mockReturnValueOnce({ + getRoot: () => Uint8Array.from([42, 43]), + size: () => 0xdc4591, + }) + .mockReturnValueOnce({ + getRoot: () => Uint8Array.from([44, 45]), + size: () => 0xdc4591, + }); + + const maybeMap = DefaultMerkleMap.create( + TEST_KEYS, + TEST_VALUES, + mockMerkleTree, + ); + expect(maybeMap.isJust()).toStrictEqual(true); + const map = maybeMap.unsafeCoerce(); + + // example from https://wiki.bitcoinsv.io/index.php/VarInt + expect(map.getCommitment()).toStrictEqual( + Uint8Array.from([0xfe, 0x91, 0x45, 0xdc, 0x00, 42, 43, 44, 45]), + ); + }); + + it("Get map commitment, with big size", () => { + mockCreateMerkleTree + .mockReturnValueOnce({ + getRoot: () => Uint8Array.from([42, 43]), + size: () => 0xb4da564e2857, + }) + .mockReturnValueOnce({ + getRoot: () => Uint8Array.from([44, 45]), + size: () => 0xb4da564e2857, + }); + + const maybeMap = DefaultMerkleMap.create( + TEST_KEYS, + TEST_VALUES, + mockMerkleTree, + ); + expect(maybeMap.isJust()).toStrictEqual(true); + const map = maybeMap.unsafeCoerce(); + + // example from https://wiki.bitcoinsv.io/index.php/VarInt + expect(map.getCommitment()).toStrictEqual( + Uint8Array.from([ + 0xff, 0x57, 0x28, 0x4e, 0x56, 0xda, 0xb4, 0x00, 0x00, 42, 43, 44, 45, + ]), + ); + }); +}); diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.ts new file mode 100644 index 000000000..d3dccce02 --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.ts @@ -0,0 +1,72 @@ +import { bufferToHexaString } from "@ledgerhq/device-sdk-core"; +import { Just, Maybe, Nothing } from "purify-ts"; + +import { type MerkleTreeService } from "@internal/merkle-tree/service/MerkleTreeService"; +import { encodeVarint } from "@internal/utils/Varint"; + +import { type MerkleMap } from "./MerkleMap"; +import { type MerkleTree } from "./MerkleTree"; + +/** + * This implements "Merkelized Maps", documented at + * https://github.com/LedgerHQ/app-bitcoin-new/blob/master/doc/merkle.md#merkleized-maps + * + * A merkelized map consist of two merkle trees, one for the keys of + * a map and one for the values of the same map, thus the two merkle + * trees have the same shape. The commitment is the number elements + * in the map followed by the keys' merkle root followed by the + * values' merkle root. + */ +export class DefaultMerkleMap implements MerkleMap { + private constructor( + private keysTree: MerkleTree, + private valuesTree: MerkleTree, + ) {} + + getKeys(): MerkleTree { + return this.keysTree; + } + getValues(): MerkleTree { + return this.valuesTree; + } + + /** + * @param keys Sorted list of distinct keys + * @param values values, in corresponding order as the keys, and of equal length + */ + static create( + keys: Uint8Array[], + values: Uint8Array[], + merkleTreeService: MerkleTreeService, + ): Maybe { + // Sanity check: keys and values should have the same length + if (keys.length != values.length) { + return Nothing; + } + + // Sanity check: verify that keys are actually sorted and with no duplicates + for (let i = 0; i < keys.length - 1; i++) { + if (bufferToHexaString(keys[i]!) >= bufferToHexaString(keys[i + 1]!)) { + return Nothing; + } + } + + // Create merkle trees for both keys and values + const keysTree = merkleTreeService.create(keys); + const valuesTree = merkleTreeService.create(values); + return Just(new DefaultMerkleMap(keysTree, valuesTree)); + } + + /** + * Get the commitment for that merkle map + */ + getCommitment(): Uint8Array { + // Safe to extract the result since an array size cannot overflow a ts number + const size = encodeVarint(this.keysTree.size()).unsafeCoerce(); + return Uint8Array.from([ + ...size, + ...this.keysTree.getRoot(), + ...this.valuesTree.getRoot(), + ]); + } +} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.test.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.test.ts new file mode 100644 index 000000000..6af4414a6 --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.test.ts @@ -0,0 +1,288 @@ +import { hexaStringToBuffer } from "@ledgerhq/device-sdk-core"; +import { Just, Nothing } from "purify-ts"; + +import { Sha256Hasher } from "@internal/merkle-tree/model/Hasher"; + +import { DefaultMerkleTree } from "./DefaultMerkleTree"; + +const EMPTY_TREE = { + data: [], + leaves: [], + nodes: [], + root: hexaStringToBuffer( + "0000000000000000000000000000000000000000000000000000000000000000", + ), +}; + +const ONE_LEAF_TREE = { + data: [Uint8Array.from([42, 43])], + leaves: [Uint8Array.from([0, 42, 43])], + nodes: [], + root: hexaStringToBuffer( + "8a707e40cad3d1070b0a1c87dd362cdf4d546e6d2a7aaf155c798932f71de4ad", + ), +}; + +const TINY_TREE = { + data: [Uint8Array.from([0, 1, 2, 3, 4]), Uint8Array.from([5, 6, 7, 8, 9])], + leaves: [ + Uint8Array.from([0, 0, 1, 2, 3, 4]), + Uint8Array.from([0, 5, 6, 7, 8, 9]), + ], + nodes: [ + [ + hexaStringToBuffer( + "9d1cf1e12fa64e20ccd2a5387504b99e89e24ef55dba8399637c3f5b6615c0ad", + )!, + hexaStringToBuffer( + "7dc17a7849e10103f73b8fa5a4b829f6292a1a59272e0e0c9def83a9c0f86de1", + )!, + ], + ], + root: hexaStringToBuffer( + "dc65c307a70fb21f4c8ea0cdfc5ff3291adae1db9ce5410b93dcb43c5eddd793", + )!, +}; + +const SMALL_TREE = { + data: [ + Uint8Array.from([0]), + Uint8Array.from([1]), + Uint8Array.from([2]), + Uint8Array.from([3]), + ], + leaves: [ + Uint8Array.from([0, 0]), + Uint8Array.from([0, 1]), + Uint8Array.from([0, 2]), + Uint8Array.from([0, 3]), + ], + nodes: [ + [ + hexaStringToBuffer( + "96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7", + )!, + hexaStringToBuffer( + "b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2", + )!, + hexaStringToBuffer( + "fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f", + )!, + hexaStringToBuffer( + "583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + )!, + ], + [ + hexaStringToBuffer( + "a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a", + )!, + hexaStringToBuffer( + "52c56b473e5246933e7852989cd9feba3b38f078742b93afff1e65ed46797825", + )!, + ], + ], + root: hexaStringToBuffer( + "9bcd51240af4005168f033121ba85be5a6ed4f0e6a5fac262066729b8fbfdecb", + )!, +}; + +const BIG_TREE = { + data: [ + Uint8Array.from([0]), + Uint8Array.from([1]), + Uint8Array.from([2]), + Uint8Array.from([3]), + Uint8Array.from([4]), + Uint8Array.from([5]), + Uint8Array.from([6]), + Uint8Array.from([7]), + Uint8Array.from([8]), + Uint8Array.from([9]), + Uint8Array.from([10]), + ], + leaves: [ + Uint8Array.from([0, 0]), + Uint8Array.from([0, 1]), + Uint8Array.from([0, 2]), + Uint8Array.from([0, 3]), + Uint8Array.from([0, 4]), + Uint8Array.from([0, 5]), + Uint8Array.from([0, 6]), + Uint8Array.from([0, 7]), + Uint8Array.from([0, 8]), + Uint8Array.from([0, 9]), + Uint8Array.from([0, 10]), + ], + nodes: [ + [ + hexaStringToBuffer( + "96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7", + )!, + hexaStringToBuffer( + "b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2", + )!, + hexaStringToBuffer( + "fcf0a6c700dd13e274b6fba8deea8dd9b26e4eedde3495717cac8408c9c5177f", + )!, + hexaStringToBuffer( + "583c7dfb7b3055d99465544032a571e10a134b1b6f769422bbb71fd7fa167a5d", + )!, + hexaStringToBuffer( + "4f35212d12f9ad2036492c95f1fe79baf4ec7bd9bef3dffa7579f2293ff546a4", + )!, + hexaStringToBuffer( + "9f1afa4dc124cba73134e82ff50f17c8f7164257c79fed9a13f5943a6acb8e3d", + )!, + hexaStringToBuffer( + "40d88127d4d31a3891f41598eeed41174e5bc89b1eb9bbd66a8cbfc09956a3fd", + )!, + hexaStringToBuffer( + "2ecd8a6b7d2845546659ad4cf443533cf921b19dc81fa83934e83821b4dfdcb7", + )!, + hexaStringToBuffer( + "b4c43b50bf245bd727623e3c775a8fcfb8d823d00b57dd65f7f79dd33f126315", + )!, + hexaStringToBuffer( + "c87479cd656e7e3ad6bd8db402e8027df454b2b0c42ff29e093458beb98a23d4", + )!, + hexaStringToBuffer( + "67ebbd370daa02ba9aadd05d8e091e862d0d8bcadafdf2a22360240a42fe922e", + )!, + ], + [ + hexaStringToBuffer( + "a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a", + )!, + hexaStringToBuffer( + "52c56b473e5246933e7852989cd9feba3b38f078742b93afff1e65ed46797825", + )!, + hexaStringToBuffer( + "4b8c129ed14cce2c08cfc6766db7f8cdb133b5f698b8de3d5890ea7ff7f0a8d1", + )!, + hexaStringToBuffer( + "bbb0feb32f648c73fe170518bcec1f675af1b780dc23d6fbf30b745c1ca5fa11", + )!, + hexaStringToBuffer( + "f7e08e9a9e87822bd79a0bf24d14c7a431be807336bd3c50ccb2d249b2a91404", + )!, + hexaStringToBuffer( + "67ebbd370daa02ba9aadd05d8e091e862d0d8bcadafdf2a22360240a42fe922e", + )!, + ], + [ + hexaStringToBuffer( + "9bcd51240af4005168f033121ba85be5a6ed4f0e6a5fac262066729b8fbfdecb", + )!, + hexaStringToBuffer( + "c1fe42b33ebb8e8a7e4a90abc481c7434e2be02cff2f6a18d7ffab4f1e25891b", + )!, + hexaStringToBuffer( + "e4b894272eb88bed1830cbef82d1c7aee37612a9e7131a5bdd0ae1a6e8f3ff50", + )!, + ], + [ + hexaStringToBuffer( + "ef7f49b620f6c7ea9b963a214da34b5021c6ded8ed57734380a311ab726aa907", + )!, + hexaStringToBuffer( + "e4b894272eb88bed1830cbef82d1c7aee37612a9e7131a5bdd0ae1a6e8f3ff50", + )!, + ], + ], + root: hexaStringToBuffer( + "e177ad5a8a17108dad67c70a51266681aa02b9e2b7ad6a0357585ba4289982ac", + )!, +}; + +describe("DefaultMerkleTree", () => { + it("Get small tree root and leaves", () => { + const tree = new DefaultMerkleTree(SMALL_TREE.data, Sha256Hasher); + expect(tree.getRoot()).toStrictEqual(SMALL_TREE.root); + expect(tree.size()).toStrictEqual(SMALL_TREE.leaves.length); + expect(tree.getLeaves().map((l) => l.value)).toStrictEqual( + SMALL_TREE.leaves, + ); + expect(tree.getLeaves().map((l) => l.hash)).toStrictEqual( + SMALL_TREE.nodes[0], + ); + SMALL_TREE.nodes[0]!.forEach((hash, index) => { + expect(tree.getLeafHash(index)).toStrictEqual(Just(hash)); + }); + expect(tree.getLeafHash(-1)).toStrictEqual(Nothing); + expect(tree.getLeafHash(SMALL_TREE.nodes[0]!.length)).toStrictEqual( + Nothing, + ); + }); + + it("Get big tree root and leaves", () => { + const tree = new DefaultMerkleTree(BIG_TREE.data, Sha256Hasher); + expect(tree.getRoot()).toStrictEqual(BIG_TREE.root); + expect(tree.size()).toStrictEqual(BIG_TREE.leaves.length); + expect(tree.getLeaves().map((l) => l.value)).toStrictEqual(BIG_TREE.leaves); + expect(tree.getLeaves().map((l) => l.hash)).toStrictEqual( + BIG_TREE.nodes[0], + ); + BIG_TREE.nodes[0]!.forEach((hash, index) => { + expect(tree.getLeafHash(index)).toStrictEqual(Just(hash)); + }); + expect(tree.getLeafHash(-1)).toStrictEqual(Nothing); + expect(tree.getLeafHash(BIG_TREE.nodes[0]!.length)).toStrictEqual(Nothing); + }); + + it("Get small tree merkle proof", () => { + const tree = new DefaultMerkleTree(SMALL_TREE.data, Sha256Hasher); + const maybeProof = tree.getProof(2); + expect(maybeProof.isJust()).toStrictEqual(true); + const proof = maybeProof.unsafeCoerce(); + + expect(proof).toStrictEqual([ + SMALL_TREE.nodes[0]![3], + SMALL_TREE.nodes[1]![0], + ]); + }); + + it("Get big tree merkle proof", () => { + const tree = new DefaultMerkleTree(BIG_TREE.data, Sha256Hasher); + const maybeProof = tree.getProof(2); + expect(maybeProof.isJust()).toStrictEqual(true); + const proof = maybeProof.unsafeCoerce(); + + expect(proof).toStrictEqual([ + BIG_TREE.nodes[0]![3], + BIG_TREE.nodes[1]![0], + BIG_TREE.nodes[2]![1], + BIG_TREE.nodes[3]![1], + ]); + }); + + it("Get out-of-bound merkle proof", () => { + const tree = new DefaultMerkleTree(SMALL_TREE.data, Sha256Hasher); + let maybeProof = tree.getProof(-1); + expect(maybeProof.isJust()).toStrictEqual(false); + maybeProof = tree.getProof(5); + expect(maybeProof.isJust()).toStrictEqual(false); + }); + + it("Empty tree", () => { + const tree = new DefaultMerkleTree(EMPTY_TREE.data, Sha256Hasher); + expect(tree.getRoot()).toStrictEqual(EMPTY_TREE.root); + const proof = tree.getProof(0); + expect(proof.isJust()).toStrictEqual(false); + }); + + it("One-leaf tree", () => { + const tree = new DefaultMerkleTree(ONE_LEAF_TREE.data, Sha256Hasher); + expect(tree.getRoot()).toStrictEqual(ONE_LEAF_TREE.root); + const proof = tree.getProof(0); + expect(proof.isJust()).toStrictEqual(true); + expect(proof.unsafeCoerce()).toStrictEqual([]); + }); + + it("Tiny tree", () => { + const tree = new DefaultMerkleTree(TINY_TREE.data, Sha256Hasher); + expect(tree.getRoot()).toStrictEqual(TINY_TREE.root); + const proof = tree.getProof(0); + expect(proof.isJust()).toStrictEqual(true); + expect(proof.unsafeCoerce()).toStrictEqual([TINY_TREE.nodes[0]![1]]); + }); +}); diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.ts new file mode 100644 index 000000000..23f64d0ec --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.ts @@ -0,0 +1,124 @@ +import { Just, Maybe } from "purify-ts"; + +import { type Hasher } from "@internal/merkle-tree/model/Hasher"; +import { Leaf, Node } from "@internal/merkle-tree/model/Node"; + +import { type MerkleTree } from "./MerkleTree"; + +const HASH_LEAF_PREFIX = 0; +const HASH_NODE_PREFIX = 1; + +/** + * This class implements the merkle tree used by Ledger Bitcoin app v2+, + * which is documented at + * https://github.com/LedgerHQ/app-bitcoin-new/blob/master/doc/merkle.md + */ +export class DefaultMerkleTree implements MerkleTree { + private root: Node | Leaf; + private leaves: Leaf[]; + + constructor(leaves: Uint8Array[], hasher: Hasher) { + // Leaves are hashed with a LEAF prefix to prevent second pre-image attacks + leaves = leaves.map((leaf) => Uint8Array.from([HASH_LEAF_PREFIX, ...leaf])); + const tree = buildTree( + leaves.map((leaf) => new Leaf(leaf, hasher(leaf))), + hasher, + ); + this.root = tree.root; + this.leaves = tree.leaves; + } + + // Get the merkle tree root hash + getRoot(): Uint8Array { + return this.root.hash; + } + + // Get the tree size + size(): number { + return this.leaves.length; + } + + // Get the leaes of that tree + getLeaves(): Leaf[] { + return this.leaves; + } + + // Get the hash of a given leaf index + getLeafHash(index: number): Maybe { + return Maybe.fromNullable(this.leaves[index]?.hash); + } + + // Get the merkle proof for a given leaf index + getProof(index: number): Maybe { + return Maybe.fromNullable(this.leaves[index]).map((leaf) => + proveNode(leaf), + ); + } +} + +/** + * A merkle proof is composed of the sibling node at each stage of the tree. + * It contains all the nodes necessary to compute the root from the given leaf. + */ +function proveNode(node: Node | Leaf): Uint8Array[] { + return node.parent.caseOf({ + Just: (parent) => + parent.leftChild === node + ? [parent.rightChild.hash, ...proveNode(parent)] + : [parent.leftChild.hash, ...proveNode(parent)], + Nothing: () => [], + }); +} + +/** + * Create a merkle tree from a list of leaves. + * This is a recursive function which split the leaves in 2 subtrees, call itself on each subtree, + * then compute the hash of the parent node. + */ +function buildTree( + leaves: Leaf[], + hasher: Hasher, +): { + root: Node | Leaf; + leaves: Leaf[]; +} { + if (leaves.length == 0) { + // Empty tree only should have a hash full of zeros + return { + root: new Leaf(new Uint8Array(), Uint8Array.from(Array(32).fill(0))), + leaves: [], + }; + } else if (leaves.length == 1) { + // 1 child means we're at the leaf level, nothing to compute + return { root: leaves[0]!, leaves }; + } + // At least 2 children: compute the tree + const leftCount = highestPowerOf2LessThan(leaves.length); + const leftBranch = buildTree(leaves.slice(0, leftCount), hasher); + const rightBranch = buildTree(leaves.slice(leftCount), hasher); + const leftChild = leftBranch.root; + const rightChild = rightBranch.root; + // Nodes are hashed with a NODE prefix to prevent second pre-image attacks + const hash = hasher( + Uint8Array.from([HASH_NODE_PREFIX, ...leftChild.hash, ...rightChild.hash]), + ); + const node = new Node(leftChild, rightChild, hash); + leftChild.parent = Just(node); + rightChild.parent = Just(node); + return { root: node, leaves: [...leftBranch.leaves, ...rightBranch.leaves] }; +} + +function highestPowerOf2LessThan(n: number): number { + if (n < 2) { + // Should not be possible + throw Error("Expected n >= 2"); + } + if (isPowerOf2(n)) { + return n / 2; + } + return 1 << Math.floor(Math.log2(n)); +} + +function isPowerOf2(n: number): boolean { + return (n & (n - 1)) === 0; +} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/Hasher.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/Hasher.ts new file mode 100644 index 000000000..533910cda --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/Hasher.ts @@ -0,0 +1,9 @@ +import { hexaStringToBuffer } from "@ledgerhq/device-sdk-core"; +import * as CryptoJS from "crypto-js"; + +export type Hasher = (buffer: Uint8Array) => Uint8Array; + +export const Sha256Hasher: Hasher = (buffer) => { + const hash = CryptoJS.SHA256(CryptoJS.lib.WordArray.create(buffer)); + return hexaStringToBuffer(hash.toString())!; +}; diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleMap.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleMap.ts new file mode 100644 index 000000000..f2052caee --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleMap.ts @@ -0,0 +1,7 @@ +import { MerkleTree } from "./MerkleTree"; + +export interface MerkleMap { + getKeys(): MerkleTree; + getValues(): MerkleTree; + getCommitment(): Uint8Array; +} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleTree.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleTree.ts new file mode 100644 index 000000000..c944e71f3 --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleTree.ts @@ -0,0 +1,11 @@ +import { Maybe } from "purify-ts"; + +import { Leaf } from "@internal/merkle-tree/model/Node"; + +export interface MerkleTree { + size(): number; + getRoot(): Uint8Array; + getLeaves(): Leaf[]; + getLeafHash(index: number): Maybe; + getProof(index: number): Maybe; +} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/Node.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/Node.ts new file mode 100644 index 000000000..150598587 --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/Node.ts @@ -0,0 +1,20 @@ +import { Maybe, Nothing } from "purify-ts"; + +export class Node { + public parent: Maybe = Nothing; + + constructor( + public leftChild: Node | Leaf, + public rightChild: Node | Leaf, + public hash: Uint8Array, + ) {} +} + +export class Leaf { + public parent: Maybe = Nothing; + + constructor( + public value: Uint8Array, + public hash: Uint8Array, + ) {} +} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleMapService.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleMapService.ts new file mode 100644 index 000000000..0a7750112 --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleMapService.ts @@ -0,0 +1,21 @@ +import { inject, injectable } from "inversify"; +import { Maybe } from "purify-ts"; + +import { merkleTreeTypes } from "@internal/merkle-tree/di/merkleTreeTypes"; +import { DefaultMerkleMap } from "@internal/merkle-tree/model/DefaultMerkleMap"; +import { type MerkleMap } from "@internal/merkle-tree/model/MerkleMap"; + +import { type MerkleMapService } from "./MerkleMapService"; +import { type MerkleTreeService } from "./MerkleTreeService"; + +@injectable() +export class DefaultMerkleMapService implements MerkleMapService { + constructor( + @inject(merkleTreeTypes.MerkleTreeService) + private merkleTreeService: MerkleTreeService, + ) {} + + create(keys: Uint8Array[], values: Uint8Array[]): Maybe { + return DefaultMerkleMap.create(keys, values, this.merkleTreeService); + } +} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleTreeService.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleTreeService.ts new file mode 100644 index 000000000..eb736e7bc --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleTreeService.ts @@ -0,0 +1,14 @@ +import { injectable } from "inversify"; + +import { DefaultMerkleTree } from "@internal/merkle-tree/model/DefaultMerkleTree"; +import { Sha256Hasher } from "@internal/merkle-tree/model/Hasher"; +import { type MerkleTree } from "@internal/merkle-tree/model/MerkleTree"; + +import { type MerkleTreeService } from "./MerkleTreeService"; + +@injectable() +export class DefaultMerkleTreeService implements MerkleTreeService { + create(leaves: Uint8Array[]): MerkleTree { + return new DefaultMerkleTree(leaves, Sha256Hasher); + } +} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapService.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapService.ts new file mode 100644 index 000000000..8555daf08 --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapService.ts @@ -0,0 +1,7 @@ +import { Maybe } from "purify-ts"; + +import { type MerkleMap } from "@internal/merkle-tree/model/MerkleMap"; + +export interface MerkleMapService { + create(keys: Uint8Array[], values: Uint8Array[]): Maybe; +} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeService.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeService.ts new file mode 100644 index 000000000..6b645e1de --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeService.ts @@ -0,0 +1,5 @@ +import { type MerkleTree } from "@internal/merkle-tree/model/MerkleTree"; + +export interface MerkleTreeService { + create(leaves: Uint8Array[]): MerkleTree; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8889e6c0b..3dc3b4dfd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -272,9 +272,15 @@ importers: packages/signer/keyring-btc: dependencies: + '@types/crypto-js': + specifier: ^4.2.2 + version: 4.2.2 bitcoinjs-lib: specifier: ^6.1.6 version: 6.1.6 + crypto-js: + specifier: ^4.2.0 + version: 4.2.0 inversify: specifier: ^6.0.2 version: 6.0.2 From a90a606aafbbddc2b862b822ea4f54aefee2e5bd Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Thu, 19 Sep 2024 14:09:38 +0200 Subject: [PATCH 6/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(keyring-btc):=20Refac?= =?UTF-8?q?or=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove unnecessary interfaces - create HasherService and Sha256 implem - Create builders for merkle tree and map - replace maybe with either - add Leaf model file --- .../merkle-tree/di/merkleTreeModule.ts | 10 +- .../merkle-tree/di/merkleTreeTypes.ts | 5 +- .../model/DefaultMerkleMap.test.ts | 135 ------------------ .../merkle-tree/model/DefaultMerkleMap.ts | 72 ---------- .../merkle-tree/model/DefaultMerkleTree.ts | 124 ---------------- .../src/internal/merkle-tree/model/Hasher.ts | 9 -- .../src/internal/merkle-tree/model/Leaf.ts | 12 ++ .../merkle-tree/model/MerkleMap.test.ts | 63 ++++++++ .../internal/merkle-tree/model/MerkleMap.ts | 36 ++++- .../internal/merkle-tree/model/MerkleTree.ts | 61 ++++++-- .../src/internal/merkle-tree/model/Node.ts | 11 +- .../service/DefaultMerkleMapService.ts | 21 --- .../service/DefaultMerkleTreeService.ts | 14 -- .../merkle-tree/service/HasherService.ts | 3 + .../service/MerkleMapBuilder.test.ts | 73 ++++++++++ .../merkle-tree/service/MerkleMapBuilder.ts | 42 ++++++ .../merkle-tree/service/MerkleMapService.ts | 7 - .../MerkleTreeBuilder.test.ts} | 30 ++-- .../merkle-tree/service/MerkleTreeBuilder.ts | 89 ++++++++++++ .../merkle-tree/service/MerkleTreeService.ts | 5 - .../service/Sha256HasherService.ts | 11 ++ 21 files changed, 406 insertions(+), 427 deletions(-) delete mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.test.ts delete mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.ts delete mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.ts delete mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/Hasher.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/Leaf.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleMap.test.ts delete mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleMapService.ts delete mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleTreeService.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/HasherService.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapBuilder.test.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapBuilder.ts delete mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapService.ts rename packages/signer/keyring-btc/src/internal/merkle-tree/{model/DefaultMerkleTree.test.ts => service/MerkleTreeBuilder.test.ts} (91%) create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeBuilder.ts delete mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeService.ts create mode 100644 packages/signer/keyring-btc/src/internal/merkle-tree/service/Sha256HasherService.ts diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeModule.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeModule.ts index d1c736f65..8a7645cf7 100644 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeModule.ts +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeModule.ts @@ -1,8 +1,9 @@ import { ContainerModule } from "inversify"; import { merkleTreeTypes } from "@internal/merkle-tree/di/merkleTreeTypes"; -import { DefaultMerkleMapService } from "@internal/merkle-tree/service/DefaultMerkleMapService"; -import { DefaultMerkleTreeService } from "@internal/merkle-tree/service/DefaultMerkleTreeService"; +import { MerkleMapBuilder } from "@internal/merkle-tree/service/MerkleMapBuilder"; +import { MerkleTreeBuilder } from "@internal/merkle-tree/service/MerkleTreeBuilder"; +import { Sha256HasherService } from "@internal/merkle-tree/service/Sha256HasherService"; export const merkleTreeModuleFactory = () => new ContainerModule( @@ -15,7 +16,8 @@ export const merkleTreeModuleFactory = () => _onActivation, _onDeactivation, ) => { - bind(merkleTreeTypes.MerkleTreeService).to(DefaultMerkleTreeService); - bind(merkleTreeTypes.MerkleMapService).to(DefaultMerkleMapService); + bind(merkleTreeTypes.HasherService).to(Sha256HasherService); + bind(merkleTreeTypes.MerkleTreeBuilder).to(MerkleTreeBuilder); + bind(merkleTreeTypes.MerkleMapBuilder).to(MerkleMapBuilder); }, ); diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeTypes.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeTypes.ts index 557749cbf..04b4bc62d 100644 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeTypes.ts +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/di/merkleTreeTypes.ts @@ -1,4 +1,5 @@ export const merkleTreeTypes = { - MerkleTreeService: Symbol.for("MerkleTreeService"), - MerkleMapService: Symbol.for("MerkleMapService"), + HasherService: Symbol.for("HasherService"), + MerkleTreeBuilder: Symbol.for("MerkleTreeBuilder"), + MerkleMapBuilder: Symbol.for("MerkleMapBuilder"), }; diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.test.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.test.ts deleted file mode 100644 index bd0c562ba..000000000 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { type MerkleTreeService } from "@internal/merkle-tree/service/MerkleTreeService"; - -import { DefaultMerkleMap } from "./DefaultMerkleMap"; - -describe("DefaultMerkleMap", () => { - const TEST_KEYS = [ - Uint8Array.from([0, 1, 2, 3]), - Uint8Array.from([0, 1, 2, 4]), - Uint8Array.from([0, 1, 2, 5]), - Uint8Array.from([0, 2, 2, 3]), - Uint8Array.from([1, 1, 2, 3]), - ]; - - const TEST_VALUES = [ - Uint8Array.from([0, 0]), - Uint8Array.from([0xff, 0xff, 0xff, 0xff]), - Uint8Array.from([0, 1, 2, 5]), - Uint8Array.from([0, 0xff]), - Uint8Array.from([0xfe, 57]), - ]; - - const mockCreateMerkleTree = jest.fn(); - const mockMerkleTree: MerkleTreeService = { - create: mockCreateMerkleTree, - }; - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it("Invalid value count", () => { - const maybeMap = DefaultMerkleMap.create( - TEST_KEYS, - [Uint8Array.from([0]), ...TEST_VALUES], - mockMerkleTree, - ); - expect(maybeMap.isJust()).toStrictEqual(false); - }); - - it("Invalid ordering", () => { - const maybeMap = DefaultMerkleMap.create( - [Uint8Array.from([0, 1, 2, 5]), ...TEST_KEYS], - [Uint8Array.from([0]), ...TEST_VALUES], - mockMerkleTree, - ); - expect(maybeMap.isJust()).toStrictEqual(false); - }); - - it("Dupplicate key", () => { - const maybeMap = DefaultMerkleMap.create( - [Uint8Array.from([0, 1, 2, 3]), ...TEST_KEYS], - [Uint8Array.from([0]), ...TEST_VALUES], - mockMerkleTree, - ); - expect(maybeMap.isJust()).toStrictEqual(false); - }); - - it("Get map commitment", () => { - mockCreateMerkleTree - .mockReturnValueOnce({ - getRoot: () => Uint8Array.from([42, 43]), - size: () => TEST_KEYS.length, - }) - .mockReturnValueOnce({ - getRoot: () => Uint8Array.from([44, 45]), - size: () => TEST_VALUES.length, - }); - - const maybeMap = DefaultMerkleMap.create( - TEST_KEYS, - TEST_VALUES, - mockMerkleTree, - ); - expect(maybeMap.isJust()).toStrictEqual(true); - const map = maybeMap.unsafeCoerce(); - expect(map.getCommitment()).toStrictEqual( - Uint8Array.from([5, 42, 43, 44, 45]), - ); - - expect(mockCreateMerkleTree).toHaveBeenCalledWith(TEST_KEYS); - expect(mockCreateMerkleTree).toHaveBeenCalledWith(TEST_VALUES); - }); - - it("Get map commitment, with mid size", () => { - mockCreateMerkleTree - .mockReturnValueOnce({ - getRoot: () => Uint8Array.from([42, 43]), - size: () => 0xdc4591, - }) - .mockReturnValueOnce({ - getRoot: () => Uint8Array.from([44, 45]), - size: () => 0xdc4591, - }); - - const maybeMap = DefaultMerkleMap.create( - TEST_KEYS, - TEST_VALUES, - mockMerkleTree, - ); - expect(maybeMap.isJust()).toStrictEqual(true); - const map = maybeMap.unsafeCoerce(); - - // example from https://wiki.bitcoinsv.io/index.php/VarInt - expect(map.getCommitment()).toStrictEqual( - Uint8Array.from([0xfe, 0x91, 0x45, 0xdc, 0x00, 42, 43, 44, 45]), - ); - }); - - it("Get map commitment, with big size", () => { - mockCreateMerkleTree - .mockReturnValueOnce({ - getRoot: () => Uint8Array.from([42, 43]), - size: () => 0xb4da564e2857, - }) - .mockReturnValueOnce({ - getRoot: () => Uint8Array.from([44, 45]), - size: () => 0xb4da564e2857, - }); - - const maybeMap = DefaultMerkleMap.create( - TEST_KEYS, - TEST_VALUES, - mockMerkleTree, - ); - expect(maybeMap.isJust()).toStrictEqual(true); - const map = maybeMap.unsafeCoerce(); - - // example from https://wiki.bitcoinsv.io/index.php/VarInt - expect(map.getCommitment()).toStrictEqual( - Uint8Array.from([ - 0xff, 0x57, 0x28, 0x4e, 0x56, 0xda, 0xb4, 0x00, 0x00, 42, 43, 44, 45, - ]), - ); - }); -}); diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.ts deleted file mode 100644 index d3dccce02..000000000 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleMap.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { bufferToHexaString } from "@ledgerhq/device-sdk-core"; -import { Just, Maybe, Nothing } from "purify-ts"; - -import { type MerkleTreeService } from "@internal/merkle-tree/service/MerkleTreeService"; -import { encodeVarint } from "@internal/utils/Varint"; - -import { type MerkleMap } from "./MerkleMap"; -import { type MerkleTree } from "./MerkleTree"; - -/** - * This implements "Merkelized Maps", documented at - * https://github.com/LedgerHQ/app-bitcoin-new/blob/master/doc/merkle.md#merkleized-maps - * - * A merkelized map consist of two merkle trees, one for the keys of - * a map and one for the values of the same map, thus the two merkle - * trees have the same shape. The commitment is the number elements - * in the map followed by the keys' merkle root followed by the - * values' merkle root. - */ -export class DefaultMerkleMap implements MerkleMap { - private constructor( - private keysTree: MerkleTree, - private valuesTree: MerkleTree, - ) {} - - getKeys(): MerkleTree { - return this.keysTree; - } - getValues(): MerkleTree { - return this.valuesTree; - } - - /** - * @param keys Sorted list of distinct keys - * @param values values, in corresponding order as the keys, and of equal length - */ - static create( - keys: Uint8Array[], - values: Uint8Array[], - merkleTreeService: MerkleTreeService, - ): Maybe { - // Sanity check: keys and values should have the same length - if (keys.length != values.length) { - return Nothing; - } - - // Sanity check: verify that keys are actually sorted and with no duplicates - for (let i = 0; i < keys.length - 1; i++) { - if (bufferToHexaString(keys[i]!) >= bufferToHexaString(keys[i + 1]!)) { - return Nothing; - } - } - - // Create merkle trees for both keys and values - const keysTree = merkleTreeService.create(keys); - const valuesTree = merkleTreeService.create(values); - return Just(new DefaultMerkleMap(keysTree, valuesTree)); - } - - /** - * Get the commitment for that merkle map - */ - getCommitment(): Uint8Array { - // Safe to extract the result since an array size cannot overflow a ts number - const size = encodeVarint(this.keysTree.size()).unsafeCoerce(); - return Uint8Array.from([ - ...size, - ...this.keysTree.getRoot(), - ...this.valuesTree.getRoot(), - ]); - } -} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.ts deleted file mode 100644 index 23f64d0ec..000000000 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { Just, Maybe } from "purify-ts"; - -import { type Hasher } from "@internal/merkle-tree/model/Hasher"; -import { Leaf, Node } from "@internal/merkle-tree/model/Node"; - -import { type MerkleTree } from "./MerkleTree"; - -const HASH_LEAF_PREFIX = 0; -const HASH_NODE_PREFIX = 1; - -/** - * This class implements the merkle tree used by Ledger Bitcoin app v2+, - * which is documented at - * https://github.com/LedgerHQ/app-bitcoin-new/blob/master/doc/merkle.md - */ -export class DefaultMerkleTree implements MerkleTree { - private root: Node | Leaf; - private leaves: Leaf[]; - - constructor(leaves: Uint8Array[], hasher: Hasher) { - // Leaves are hashed with a LEAF prefix to prevent second pre-image attacks - leaves = leaves.map((leaf) => Uint8Array.from([HASH_LEAF_PREFIX, ...leaf])); - const tree = buildTree( - leaves.map((leaf) => new Leaf(leaf, hasher(leaf))), - hasher, - ); - this.root = tree.root; - this.leaves = tree.leaves; - } - - // Get the merkle tree root hash - getRoot(): Uint8Array { - return this.root.hash; - } - - // Get the tree size - size(): number { - return this.leaves.length; - } - - // Get the leaes of that tree - getLeaves(): Leaf[] { - return this.leaves; - } - - // Get the hash of a given leaf index - getLeafHash(index: number): Maybe { - return Maybe.fromNullable(this.leaves[index]?.hash); - } - - // Get the merkle proof for a given leaf index - getProof(index: number): Maybe { - return Maybe.fromNullable(this.leaves[index]).map((leaf) => - proveNode(leaf), - ); - } -} - -/** - * A merkle proof is composed of the sibling node at each stage of the tree. - * It contains all the nodes necessary to compute the root from the given leaf. - */ -function proveNode(node: Node | Leaf): Uint8Array[] { - return node.parent.caseOf({ - Just: (parent) => - parent.leftChild === node - ? [parent.rightChild.hash, ...proveNode(parent)] - : [parent.leftChild.hash, ...proveNode(parent)], - Nothing: () => [], - }); -} - -/** - * Create a merkle tree from a list of leaves. - * This is a recursive function which split the leaves in 2 subtrees, call itself on each subtree, - * then compute the hash of the parent node. - */ -function buildTree( - leaves: Leaf[], - hasher: Hasher, -): { - root: Node | Leaf; - leaves: Leaf[]; -} { - if (leaves.length == 0) { - // Empty tree only should have a hash full of zeros - return { - root: new Leaf(new Uint8Array(), Uint8Array.from(Array(32).fill(0))), - leaves: [], - }; - } else if (leaves.length == 1) { - // 1 child means we're at the leaf level, nothing to compute - return { root: leaves[0]!, leaves }; - } - // At least 2 children: compute the tree - const leftCount = highestPowerOf2LessThan(leaves.length); - const leftBranch = buildTree(leaves.slice(0, leftCount), hasher); - const rightBranch = buildTree(leaves.slice(leftCount), hasher); - const leftChild = leftBranch.root; - const rightChild = rightBranch.root; - // Nodes are hashed with a NODE prefix to prevent second pre-image attacks - const hash = hasher( - Uint8Array.from([HASH_NODE_PREFIX, ...leftChild.hash, ...rightChild.hash]), - ); - const node = new Node(leftChild, rightChild, hash); - leftChild.parent = Just(node); - rightChild.parent = Just(node); - return { root: node, leaves: [...leftBranch.leaves, ...rightBranch.leaves] }; -} - -function highestPowerOf2LessThan(n: number): number { - if (n < 2) { - // Should not be possible - throw Error("Expected n >= 2"); - } - if (isPowerOf2(n)) { - return n / 2; - } - return 1 << Math.floor(Math.log2(n)); -} - -function isPowerOf2(n: number): boolean { - return (n & (n - 1)) === 0; -} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/Hasher.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/Hasher.ts deleted file mode 100644 index 533910cda..000000000 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/model/Hasher.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { hexaStringToBuffer } from "@ledgerhq/device-sdk-core"; -import * as CryptoJS from "crypto-js"; - -export type Hasher = (buffer: Uint8Array) => Uint8Array; - -export const Sha256Hasher: Hasher = (buffer) => { - const hash = CryptoJS.SHA256(CryptoJS.lib.WordArray.create(buffer)); - return hexaStringToBuffer(hash.toString())!; -}; diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/Leaf.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/Leaf.ts new file mode 100644 index 000000000..d04e027e9 --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/Leaf.ts @@ -0,0 +1,12 @@ +import { Maybe, Nothing } from "purify-ts"; + +import { Node } from "@internal/merkle-tree/model/Node"; + +export class Leaf { + public parent: Maybe = Nothing; + + constructor( + public value: Uint8Array, + public hash: Uint8Array, + ) {} +} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleMap.test.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleMap.test.ts new file mode 100644 index 000000000..6ba0692ea --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleMap.test.ts @@ -0,0 +1,63 @@ +import { MerkleTreeBuilder } from "@internal/merkle-tree/service/MerkleTreeBuilder"; + +import { MerkleMap } from "./MerkleMap"; +import { MerkleTree } from "./MerkleTree"; + +const KEYS = [ + Uint8Array.from([0, 1, 2, 3]), + Uint8Array.from([0, 1, 2, 4]), + Uint8Array.from([0, 1, 2, 5]), + Uint8Array.from([0, 2, 2, 3]), + Uint8Array.from([1, 1, 2, 3]), +]; + +const VALUES = [ + Uint8Array.from([0, 0]), + Uint8Array.from([0xff, 0xff, 0xff, 0xff]), + Uint8Array.from([0, 1, 2, 5]), + Uint8Array.from([0, 0xff]), + Uint8Array.from([0xfe, 57]), +]; + +const COMMITMENT_RESULT = Uint8Array.from([ + 0x05, 0x01, 0x01, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x01, 0x02, + 0x04, 0x01, 0x00, 0x00, 0x01, 0x02, 0x05, 0x00, 0x00, 0x02, 0x02, 0x03, 0x00, + 0x01, 0x01, 0x02, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x01, 0x00, 0x00, 0x01, 0x02, 0x05, 0x00, 0x00, 0xff, 0x00, 0xfe, + 0x39, +]); + +describe("MerkleMap", () => { + const hasherService = { hash: (data: Uint8Array) => data }; + const merkleTreeBuilder = new MerkleTreeBuilder(hasherService); + + it("should be defined", () => { + expect(MerkleMap).toBeDefined(); + }); + + it("should create a MerkleMap instance", () => { + // GIVEN + const keys = merkleTreeBuilder.build(KEYS); + const values = merkleTreeBuilder.build(VALUES); + + // WHEN + const merkleMap = new MerkleMap(keys, values); + + // THEN + expect(merkleMap).toBeInstanceOf(MerkleMap); + expect(merkleMap.keys).toBeInstanceOf(MerkleTree); + expect(merkleMap.values).toBeInstanceOf(MerkleTree); + }); + + it("should return commitment", () => { + // GIVEN + const keys = merkleTreeBuilder.build(KEYS); + const values = merkleTreeBuilder.build(VALUES); + + // WHEN + const merkleMap = new MerkleMap(keys, values); + + // THEN + expect(merkleMap.getCommitment()).toEqual(COMMITMENT_RESULT); + }); +}); diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleMap.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleMap.ts index f2052caee..dc1c9b8d0 100644 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleMap.ts +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleMap.ts @@ -1,7 +1,33 @@ -import { MerkleTree } from "./MerkleTree"; +import { encodeVarint } from "@internal/utils/Varint"; -export interface MerkleMap { - getKeys(): MerkleTree; - getValues(): MerkleTree; - getCommitment(): Uint8Array; +import { type MerkleTree } from "./MerkleTree"; + +/** + * This implements "Merkelized Maps", documented at + * https://github.com/LedgerHQ/app-bitcoin-new/blob/master/doc/merkle.md#merkleized-maps + * + * A merkelized map consist of two merkle trees, one for the keys of + * a map and one for the values of the same map, thus the two merkle + * trees have the same shape. The commitment is the number elements + * in the map followed by the keys' merkle root followed by the + * values' merkle root. + */ +export class MerkleMap { + constructor( + public keys: MerkleTree, + public values: MerkleTree, + ) {} + + /** + * Get the commitment for that merkle map + */ + getCommitment(): Uint8Array { + // Safe to extract the result since an array size cannot overflow a ts number + const size = encodeVarint(this.keys.size()).unsafeCoerce(); + return Uint8Array.from([ + ...size, + ...this.keys.getRoot(), + ...this.values.getRoot(), + ]); + } } diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleTree.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleTree.ts index c944e71f3..f4f64df65 100644 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleTree.ts +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/MerkleTree.ts @@ -1,11 +1,56 @@ import { Maybe } from "purify-ts"; -import { Leaf } from "@internal/merkle-tree/model/Node"; - -export interface MerkleTree { - size(): number; - getRoot(): Uint8Array; - getLeaves(): Leaf[]; - getLeafHash(index: number): Maybe; - getProof(index: number): Maybe; +import { Leaf } from "@internal/merkle-tree/model/Leaf"; +import { Node } from "@internal/merkle-tree/model/Node"; + +/** + * This class implements the merkle tree used by Ledger Bitcoin app v2+, + * which is documented at + * https://github.com/LedgerHQ/app-bitcoin-new/blob/master/doc/merkle.md + */ +export class MerkleTree { + constructor( + private root: Node | Leaf, + private leaves: Leaf[], + ) {} + + // Get the merkle tree root hash + getRoot(): Uint8Array { + return this.root.hash; + } + + // Get the tree size + size(): number { + return this.leaves.length; + } + + // Get the leaes of that tree + getLeaves(): Leaf[] { + return this.leaves; + } + + // Get the hash of a given leaf index + getLeafHash(index: number): Maybe { + return Maybe.fromNullable(this.leaves[index]?.hash); + } + + // Get the merkle proof for a given leaf index + getProof(index: number): Maybe { + return Maybe.fromNullable(this.leaves[index]).map((leaf) => + this.proveNode(leaf), + ); + } + /** + * A merkle proof is composed of the sibling node at each stage of the tree. + * It contains all the nodes necessary to compute the root from the given leaf. + */ + private proveNode(node: Node | Leaf): Uint8Array[] { + return node.parent.caseOf({ + Just: (parent) => + parent.leftChild === node + ? [parent.rightChild.hash, ...this.proveNode(parent)] + : [parent.leftChild.hash, ...this.proveNode(parent)], + Nothing: () => [], + }); + } } diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/Node.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/model/Node.ts index 150598587..f135f5cb5 100644 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/model/Node.ts +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/model/Node.ts @@ -1,5 +1,7 @@ import { Maybe, Nothing } from "purify-ts"; +import { Leaf } from "./Leaf"; + export class Node { public parent: Maybe = Nothing; @@ -9,12 +11,3 @@ export class Node { public hash: Uint8Array, ) {} } - -export class Leaf { - public parent: Maybe = Nothing; - - constructor( - public value: Uint8Array, - public hash: Uint8Array, - ) {} -} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleMapService.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleMapService.ts deleted file mode 100644 index 0a7750112..000000000 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleMapService.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { inject, injectable } from "inversify"; -import { Maybe } from "purify-ts"; - -import { merkleTreeTypes } from "@internal/merkle-tree/di/merkleTreeTypes"; -import { DefaultMerkleMap } from "@internal/merkle-tree/model/DefaultMerkleMap"; -import { type MerkleMap } from "@internal/merkle-tree/model/MerkleMap"; - -import { type MerkleMapService } from "./MerkleMapService"; -import { type MerkleTreeService } from "./MerkleTreeService"; - -@injectable() -export class DefaultMerkleMapService implements MerkleMapService { - constructor( - @inject(merkleTreeTypes.MerkleTreeService) - private merkleTreeService: MerkleTreeService, - ) {} - - create(keys: Uint8Array[], values: Uint8Array[]): Maybe { - return DefaultMerkleMap.create(keys, values, this.merkleTreeService); - } -} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleTreeService.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleTreeService.ts deleted file mode 100644 index eb736e7bc..000000000 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/service/DefaultMerkleTreeService.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { injectable } from "inversify"; - -import { DefaultMerkleTree } from "@internal/merkle-tree/model/DefaultMerkleTree"; -import { Sha256Hasher } from "@internal/merkle-tree/model/Hasher"; -import { type MerkleTree } from "@internal/merkle-tree/model/MerkleTree"; - -import { type MerkleTreeService } from "./MerkleTreeService"; - -@injectable() -export class DefaultMerkleTreeService implements MerkleTreeService { - create(leaves: Uint8Array[]): MerkleTree { - return new DefaultMerkleTree(leaves, Sha256Hasher); - } -} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/HasherService.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/HasherService.ts new file mode 100644 index 000000000..9ad2592ee --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/HasherService.ts @@ -0,0 +1,3 @@ +export interface HasherService { + hash: (buffer: Uint8Array) => Uint8Array; +} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapBuilder.test.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapBuilder.test.ts new file mode 100644 index 000000000..bac199600 --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapBuilder.test.ts @@ -0,0 +1,73 @@ +import { MerkleTreeBuilder } from "@internal/merkle-tree/service/MerkleTreeBuilder"; + +import { MerkleMapBuilder } from "./MerkleMapBuilder"; + +describe("MerkleMapBuilder", () => { + const TEST_KEYS = [ + Uint8Array.from([0, 1, 2, 3]), + Uint8Array.from([0, 1, 2, 4]), + Uint8Array.from([0, 1, 2, 5]), + Uint8Array.from([0, 2, 2, 3]), + Uint8Array.from([1, 1, 2, 3]), + ]; + + const TEST_VALUES = [ + Uint8Array.from([0, 0]), + Uint8Array.from([0xff, 0xff, 0xff, 0xff]), + Uint8Array.from([0, 1, 2, 5]), + Uint8Array.from([0, 0xff]), + Uint8Array.from([0xfe, 57]), + ]; + + const mockCreateMerkleTree = jest.fn(); + const mockMerkleTree: MerkleTreeBuilder = { + build: mockCreateMerkleTree, + } as unknown as MerkleTreeBuilder; + let builder: MerkleMapBuilder; + + beforeEach(() => { + jest.resetAllMocks(); + builder = new MerkleMapBuilder(mockMerkleTree); + }); + + it("Invalid value count", () => { + const eitherMap = builder.build(TEST_KEYS, [ + Uint8Array.from([0]), + ...TEST_VALUES, + ]); + expect(eitherMap.isRight()).toStrictEqual(false); + }); + + it("Invalid ordering", () => { + const eitherMap = builder.build( + [Uint8Array.from([0, 1, 2, 5]), ...TEST_KEYS], + [Uint8Array.from([0]), ...TEST_VALUES], + ); + expect(eitherMap.isRight()).toStrictEqual(false); + }); + + it("Dupplicate key", () => { + const eitherMap = builder.build( + [Uint8Array.from([0, 1, 2, 3]), ...TEST_KEYS], + [Uint8Array.from([0]), ...TEST_VALUES], + ); + expect(eitherMap.isRight()).toStrictEqual(false); + }); + + it("correct value", () => { + mockCreateMerkleTree + .mockReturnValueOnce({ + getRoot: () => Uint8Array.from([42, 43]), + size: () => TEST_KEYS.length, + }) + .mockReturnValueOnce({ + getRoot: () => Uint8Array.from([44, 45]), + size: () => TEST_VALUES.length, + }); + + const eitherMap = builder.build(TEST_KEYS, TEST_VALUES); + expect(eitherMap.isRight()).toStrictEqual(true); + expect(mockCreateMerkleTree).toHaveBeenCalledWith(TEST_KEYS); + expect(mockCreateMerkleTree).toHaveBeenCalledWith(TEST_VALUES); + }); +}); diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapBuilder.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapBuilder.ts new file mode 100644 index 000000000..b92f8b19c --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapBuilder.ts @@ -0,0 +1,42 @@ +import { bufferToHexaString } from "@ledgerhq/device-sdk-core"; +import { inject, injectable } from "inversify"; +import { Either, Left, Right } from "purify-ts"; + +import { merkleTreeTypes } from "@internal/merkle-tree/di/merkleTreeTypes"; +import { MerkleMap } from "@internal/merkle-tree/model/MerkleMap"; + +import { type MerkleTreeBuilder } from "./MerkleTreeBuilder"; + +@injectable() +export class MerkleMapBuilder { + constructor( + @inject(merkleTreeTypes.MerkleTreeBuilder) + private merkleTreeBuilder: MerkleTreeBuilder, + ) {} + + /** + * @param keys Sorted list of distinct keys + * @param values values, in corresponding order as the keys, and of equal length + */ + public build( + keys: Uint8Array[], + values: Uint8Array[], + ): Either { + // Sanity check: keys and values should have the same length + if (keys.length != values.length) { + return Left(new Error("Keys and values should have the same length")); + } + + // Sanity check: verify that keys are actually sorted and with no duplicates + for (let i = 0; i < keys.length - 1; i++) { + if (bufferToHexaString(keys[i]!) >= bufferToHexaString(keys[i + 1]!)) { + return Left(new Error("Keys should be sorted and distinct")); + } + } + + // Create merkle trees for both keys and values + const keysTree = this.merkleTreeBuilder.build(keys); + const valuesTree = this.merkleTreeBuilder.build(values); + return Right(new MerkleMap(keysTree, valuesTree)); + } +} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapService.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapService.ts deleted file mode 100644 index 8555daf08..000000000 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapService.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Maybe } from "purify-ts"; - -import { type MerkleMap } from "@internal/merkle-tree/model/MerkleMap"; - -export interface MerkleMapService { - create(keys: Uint8Array[], values: Uint8Array[]): Maybe; -} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.test.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeBuilder.test.ts similarity index 91% rename from packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.test.ts rename to packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeBuilder.test.ts index 6af4414a6..ddfabd438 100644 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/model/DefaultMerkleTree.test.ts +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeBuilder.test.ts @@ -1,9 +1,8 @@ import { hexaStringToBuffer } from "@ledgerhq/device-sdk-core"; import { Just, Nothing } from "purify-ts"; -import { Sha256Hasher } from "@internal/merkle-tree/model/Hasher"; - -import { DefaultMerkleTree } from "./DefaultMerkleTree"; +import { MerkleTreeBuilder } from "./MerkleTreeBuilder"; +import { Sha256HasherService } from "./Sha256HasherService"; const EMPTY_TREE = { data: [], @@ -194,9 +193,16 @@ const BIG_TREE = { )!, }; -describe("DefaultMerkleTree", () => { +describe("MerkleTreeBuilder", () => { + let builder: MerkleTreeBuilder; + + beforeEach(() => { + jest.resetAllMocks(); + builder = new MerkleTreeBuilder(new Sha256HasherService()); + }); + it("Get small tree root and leaves", () => { - const tree = new DefaultMerkleTree(SMALL_TREE.data, Sha256Hasher); + const tree = builder.build(SMALL_TREE.data); expect(tree.getRoot()).toStrictEqual(SMALL_TREE.root); expect(tree.size()).toStrictEqual(SMALL_TREE.leaves.length); expect(tree.getLeaves().map((l) => l.value)).toStrictEqual( @@ -215,7 +221,7 @@ describe("DefaultMerkleTree", () => { }); it("Get big tree root and leaves", () => { - const tree = new DefaultMerkleTree(BIG_TREE.data, Sha256Hasher); + const tree = builder.build(BIG_TREE.data); expect(tree.getRoot()).toStrictEqual(BIG_TREE.root); expect(tree.size()).toStrictEqual(BIG_TREE.leaves.length); expect(tree.getLeaves().map((l) => l.value)).toStrictEqual(BIG_TREE.leaves); @@ -230,7 +236,7 @@ describe("DefaultMerkleTree", () => { }); it("Get small tree merkle proof", () => { - const tree = new DefaultMerkleTree(SMALL_TREE.data, Sha256Hasher); + const tree = builder.build(SMALL_TREE.data); const maybeProof = tree.getProof(2); expect(maybeProof.isJust()).toStrictEqual(true); const proof = maybeProof.unsafeCoerce(); @@ -242,7 +248,7 @@ describe("DefaultMerkleTree", () => { }); it("Get big tree merkle proof", () => { - const tree = new DefaultMerkleTree(BIG_TREE.data, Sha256Hasher); + const tree = builder.build(BIG_TREE.data); const maybeProof = tree.getProof(2); expect(maybeProof.isJust()).toStrictEqual(true); const proof = maybeProof.unsafeCoerce(); @@ -256,7 +262,7 @@ describe("DefaultMerkleTree", () => { }); it("Get out-of-bound merkle proof", () => { - const tree = new DefaultMerkleTree(SMALL_TREE.data, Sha256Hasher); + const tree = builder.build(SMALL_TREE.data); let maybeProof = tree.getProof(-1); expect(maybeProof.isJust()).toStrictEqual(false); maybeProof = tree.getProof(5); @@ -264,14 +270,14 @@ describe("DefaultMerkleTree", () => { }); it("Empty tree", () => { - const tree = new DefaultMerkleTree(EMPTY_TREE.data, Sha256Hasher); + const tree = builder.build(EMPTY_TREE.data); expect(tree.getRoot()).toStrictEqual(EMPTY_TREE.root); const proof = tree.getProof(0); expect(proof.isJust()).toStrictEqual(false); }); it("One-leaf tree", () => { - const tree = new DefaultMerkleTree(ONE_LEAF_TREE.data, Sha256Hasher); + const tree = builder.build(ONE_LEAF_TREE.data); expect(tree.getRoot()).toStrictEqual(ONE_LEAF_TREE.root); const proof = tree.getProof(0); expect(proof.isJust()).toStrictEqual(true); @@ -279,7 +285,7 @@ describe("DefaultMerkleTree", () => { }); it("Tiny tree", () => { - const tree = new DefaultMerkleTree(TINY_TREE.data, Sha256Hasher); + const tree = builder.build(TINY_TREE.data); expect(tree.getRoot()).toStrictEqual(TINY_TREE.root); const proof = tree.getProof(0); expect(proof.isJust()).toStrictEqual(true); diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeBuilder.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeBuilder.ts new file mode 100644 index 000000000..647389abf --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeBuilder.ts @@ -0,0 +1,89 @@ +import { inject, injectable } from "inversify"; +import { Just } from "purify-ts"; + +import { merkleTreeTypes } from "@internal/merkle-tree/di/merkleTreeTypes"; +import { Leaf } from "@internal/merkle-tree/model/Leaf"; +import { MerkleTree } from "@internal/merkle-tree/model/MerkleTree"; +import { Node } from "@internal/merkle-tree/model/Node"; + +import { type HasherService } from "./HasherService"; + +const HASH_LEAF_PREFIX = 0; +const HASH_NODE_PREFIX = 1; + +@injectable() +export class MerkleTreeBuilder { + constructor( + @inject(merkleTreeTypes.HasherService) private hasher: HasherService, + ) {} + + build(leaves: Uint8Array[]): MerkleTree { + // Leaves are hashed with a LEAF prefix to prevent second pre-image attacks + leaves = leaves.map((leaf) => Uint8Array.from([HASH_LEAF_PREFIX, ...leaf])); + const tree: { + root: Node | Leaf; + leaves: Leaf[]; + } = this.buildTree( + leaves.map((leaf) => new Leaf(leaf, this.hasher.hash(leaf))), + ); + + return new MerkleTree(tree.root, tree.leaves); + } + + /** + * Create a merkle tree from a list of leaves. + * This is a recursive function which split the leaves in 2 subtrees, call itself on each subtree, + * then compute the hash of the parent node. + */ + private buildTree(leaves: Leaf[]): { + root: Node | Leaf; + leaves: Leaf[]; + } { + if (leaves.length == 0) { + // Empty tree only should have a hash full of zeros + return { + root: new Leaf(new Uint8Array(), Uint8Array.from(Array(32).fill(0))), + leaves: [], + }; + } else if (leaves.length == 1) { + // 1 child means we're at the leaf level, nothing to compute + return { root: leaves[0]!, leaves }; + } + // At least 2 children: compute the tree + const leftCount = this.highestPowerOf2LessThan(leaves.length); + const leftBranch = this.buildTree(leaves.slice(0, leftCount)); + const rightBranch = this.buildTree(leaves.slice(leftCount)); + const leftChild = leftBranch.root; + const rightChild = rightBranch.root; + // Nodes are hashed with a NODE prefix to prevent second pre-image attacks + const hash = this.hasher.hash( + Uint8Array.from([ + HASH_NODE_PREFIX, + ...leftChild.hash, + ...rightChild.hash, + ]), + ); + const node = new Node(leftChild, rightChild, hash); + leftChild.parent = Just(node); + rightChild.parent = Just(node); + return { + root: node, + leaves: [...leftBranch.leaves, ...rightBranch.leaves], + }; + } + + private highestPowerOf2LessThan(n: number): number { + if (n < 2) { + // Should not be possible + throw Error("Expected n >= 2"); + } + if (this.isPowerOf2(n)) { + return n / 2; + } + return 1 << Math.floor(Math.log2(n)); + } + + private isPowerOf2(n: number): boolean { + return (n & (n - 1)) === 0; + } +} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeService.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeService.ts deleted file mode 100644 index 6b645e1de..000000000 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeService.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { type MerkleTree } from "@internal/merkle-tree/model/MerkleTree"; - -export interface MerkleTreeService { - create(leaves: Uint8Array[]): MerkleTree; -} diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/Sha256HasherService.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/Sha256HasherService.ts new file mode 100644 index 000000000..02cc83566 --- /dev/null +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/Sha256HasherService.ts @@ -0,0 +1,11 @@ +import { hexaStringToBuffer } from "@ledgerhq/device-sdk-core"; +import { lib, SHA256 } from "crypto-js"; + +import { HasherService } from "./HasherService"; + +export class Sha256HasherService implements HasherService { + hash(buffer: Uint8Array): Uint8Array { + const hash = SHA256(lib.WordArray.create(buffer)); + return hexaStringToBuffer(hash.toString())!; + } +} From 5b76072242c712cc87548e6967aff7dbc914aa9c Mon Sep 17 00:00:00 2001 From: Louis Aussedat Date: Tue, 24 Sep 2024 10:05:32 +0200 Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=91=B7=20(keyring-btc):=20Rename=20co?= =?UTF-8?q?re=20to=20device=20management=20kit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/calm-months-live.md | 2 +- .changeset/tender-rings-leave.md | 2 +- .../src/internal/merkle-tree/service/MerkleMapBuilder.ts | 2 +- .../internal/merkle-tree/service/MerkleTreeBuilder.test.ts | 2 +- .../src/internal/merkle-tree/service/Sha256HasherService.ts | 2 +- .../signer/keyring-btc/src/internal/utils/Varint.test.ts | 5 ++++- packages/signer/keyring-btc/src/internal/utils/Varint.ts | 5 ++++- 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.changeset/calm-months-live.md b/.changeset/calm-months-live.md index 224cec86d..5d081f2d9 100644 --- a/.changeset/calm-months-live.md +++ b/.changeset/calm-months-live.md @@ -1,6 +1,6 @@ --- "@ledgerhq/keyring-btc": patch -"@ledgerhq/device-sdk-core": patch +"@ledgerhq/device-management-kit": patch --- Implement MerkleTree and MerkleMap services diff --git a/.changeset/tender-rings-leave.md b/.changeset/tender-rings-leave.md index 698f206da..731e8428b 100644 --- a/.changeset/tender-rings-leave.md +++ b/.changeset/tender-rings-leave.md @@ -1,5 +1,5 @@ --- -"@ledgerhq/device-sdk-core": patch +"@ledgerhq/device-management-kit": patch --- Add ListDeviceSessions use case diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapBuilder.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapBuilder.ts index b92f8b19c..87d809c7b 100644 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapBuilder.ts +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleMapBuilder.ts @@ -1,4 +1,4 @@ -import { bufferToHexaString } from "@ledgerhq/device-sdk-core"; +import { bufferToHexaString } from "@ledgerhq/device-management-kit"; import { inject, injectable } from "inversify"; import { Either, Left, Right } from "purify-ts"; diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeBuilder.test.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeBuilder.test.ts index ddfabd438..c9c5b0057 100644 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeBuilder.test.ts +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/MerkleTreeBuilder.test.ts @@ -1,4 +1,4 @@ -import { hexaStringToBuffer } from "@ledgerhq/device-sdk-core"; +import { hexaStringToBuffer } from "@ledgerhq/device-management-kit"; import { Just, Nothing } from "purify-ts"; import { MerkleTreeBuilder } from "./MerkleTreeBuilder"; diff --git a/packages/signer/keyring-btc/src/internal/merkle-tree/service/Sha256HasherService.ts b/packages/signer/keyring-btc/src/internal/merkle-tree/service/Sha256HasherService.ts index 02cc83566..df87f691a 100644 --- a/packages/signer/keyring-btc/src/internal/merkle-tree/service/Sha256HasherService.ts +++ b/packages/signer/keyring-btc/src/internal/merkle-tree/service/Sha256HasherService.ts @@ -1,4 +1,4 @@ -import { hexaStringToBuffer } from "@ledgerhq/device-sdk-core"; +import { hexaStringToBuffer } from "@ledgerhq/device-management-kit"; import { lib, SHA256 } from "crypto-js"; import { HasherService } from "./HasherService"; diff --git a/packages/signer/keyring-btc/src/internal/utils/Varint.test.ts b/packages/signer/keyring-btc/src/internal/utils/Varint.test.ts index ea5e7247a..a4c9f5ff9 100644 --- a/packages/signer/keyring-btc/src/internal/utils/Varint.test.ts +++ b/packages/signer/keyring-btc/src/internal/utils/Varint.test.ts @@ -1,4 +1,7 @@ -import { ByteArrayParser, hexaStringToBuffer } from "@ledgerhq/device-sdk-core"; +import { + ByteArrayParser, + hexaStringToBuffer, +} from "@ledgerhq/device-management-kit"; import { encodeVarint, extractVarint } from "./Varint"; diff --git a/packages/signer/keyring-btc/src/internal/utils/Varint.ts b/packages/signer/keyring-btc/src/internal/utils/Varint.ts index 237a97173..b5a3f1500 100644 --- a/packages/signer/keyring-btc/src/internal/utils/Varint.ts +++ b/packages/signer/keyring-btc/src/internal/utils/Varint.ts @@ -1,4 +1,7 @@ -import { ByteArrayBuilder, ByteArrayParser } from "@ledgerhq/device-sdk-core"; +import { + ByteArrayBuilder, + ByteArrayParser, +} from "@ledgerhq/device-management-kit"; import { Just, Maybe, Nothing } from "purify-ts"; /** From f80eb22b94ca80c7295050d983ab733c88aeb48c Mon Sep 17 00:00:00 2001 From: Kien Nguyen Date: Tue, 24 Sep 2024 11:31:14 +0200 Subject: [PATCH 8/9] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20(jfrog)=20[NO-ISSUE]:?= =?UTF-8?q?=20Rename=20changeset=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/tender-rings-leave.md | 2 +- .github/workflows/release.yml | 12 ++++++++++++ packages/core/package.json | 2 +- packages/signer/context-module/package.json | 2 +- packages/signer/keyring-btc/package.json | 2 +- packages/signer/keyring-eth/package.json | 2 +- packages/trusted-apps/package.json | 2 +- packages/ui/package.json | 2 +- 8 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.changeset/tender-rings-leave.md b/.changeset/tender-rings-leave.md index 698f206da..731e8428b 100644 --- a/.changeset/tender-rings-leave.md +++ b/.changeset/tender-rings-leave.md @@ -1,5 +1,5 @@ --- -"@ledgerhq/device-sdk-core": patch +"@ledgerhq/device-management-kit": patch --- Add ListDeviceSessions use case diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 28a55493d..5dfd54bca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,6 +44,9 @@ jobs: registry=https://${NPM_REGISTRY}/ //${NPM_REGISTRY}/:_authToken=${NPM_REGISTRY_TOKEN} EOF + + - name: Create dist directory to store tarball + run: mkdir -p dist - name: Publish id: changesets @@ -55,6 +58,15 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.CI_BOT_TOKEN }} + - name: Check dist directory to store tarball + if: steps.changesets.outputs.published == 'true' + run: | + echo "Checking directory" + ls -al + + echo "Checking dist directory" + ls -al dist + - name: Attest tarball if: steps.changesets.outputs.published == 'true' uses: LedgerHQ/actions-security/actions/attest@actions/attest-1 diff --git a/packages/core/package.json b/packages/core/package.json index 688e31bcb..4c81bfaac 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,7 +32,7 @@ "dev:cjs": "concurrently \"tsc --watch -p tsconfig.cjs.json\" \"tsc-alias --watch -p tsconfig.cjs.json\"", "lint": "eslint", "lint:fix": "pnpm lint --fix", - "postpack": "find . -name '*.tgz' -exec mv ../../dist/ \\; 2> /dev/null", + "postpack": "find . -name '*.tgz' -exec mv ../../dist/ \\; ", "prettier": "prettier . --check", "prettier:fix": "prettier . --write", "test": "jest", diff --git a/packages/signer/context-module/package.json b/packages/signer/context-module/package.json index 5eff5293f..30e809467 100644 --- a/packages/signer/context-module/package.json +++ b/packages/signer/context-module/package.json @@ -33,7 +33,7 @@ "dev:cjs": "concurrently \"tsc --watch -p tsconfig.cjs.json\" \"tsc-alias --watch -p tsconfig.cjs.json\"", "lint": "eslint", "lint:fix": "pnpm lint --fix", - "postpack": "find . -name '*.tgz' -exec mv ../../../dist/ \\; 2> /dev/null", + "postpack": "find . -name '*.tgz' -exec mv ../../../dist/ \\; ", "prettier": "prettier . --check", "prettier:fix": "prettier . --write", "test": "jest", diff --git a/packages/signer/keyring-btc/package.json b/packages/signer/keyring-btc/package.json index a5e72dd4b..a5be6fb50 100644 --- a/packages/signer/keyring-btc/package.json +++ b/packages/signer/keyring-btc/package.json @@ -35,7 +35,7 @@ "dev:cjs": "concurrently \"tsc --watch -p tsconfig.cjs.json\" \"tsc-alias --watch -p tsconfig.cjs.json\"", "lint": "eslint", "lint:fix": "pnpm lint --fix", - "postpack": "find . -name '*.tgz' -exec mv ../../../dist/ \\; 2> /dev/null", + "postpack": "find . -name '*.tgz' -exec mv ../../../dist/ \\; ", "prettier": "prettier . --check", "prettier:fix": "prettier . --write", "typecheck": "tsc --noEmit", diff --git a/packages/signer/keyring-eth/package.json b/packages/signer/keyring-eth/package.json index 7e24cc713..eb20aeabe 100644 --- a/packages/signer/keyring-eth/package.json +++ b/packages/signer/keyring-eth/package.json @@ -35,7 +35,7 @@ "dev:cjs": "concurrently \"tsc --watch -p tsconfig.cjs.json\" \"tsc-alias --watch -p tsconfig.cjs.json\"", "lint": "eslint", "lint:fix": "pnpm lint --fix", - "postpack": "find . -name '*.tgz' -exec mv ../../../dist/ \\; 2> /dev/null", + "postpack": "find . -name '*.tgz' -exec mv ../../../dist/ \\; ", "prettier": "prettier . --check", "prettier:fix": "prettier . --write", "typecheck": "tsc --noEmit", diff --git a/packages/trusted-apps/package.json b/packages/trusted-apps/package.json index 9cf1100ae..44500d9f9 100644 --- a/packages/trusted-apps/package.json +++ b/packages/trusted-apps/package.json @@ -35,7 +35,7 @@ "dev:cjs": "concurrently \"tsc --watch -p tsconfig.cjs.json\" \"tsc-alias --watch -p tsconfig.cjs.json\"", "lint": "eslint", "lint:fix": "pnpm lint --fix", - "postpack": "find . -name '*.tgz' -exec mv ../../dist/ \\; 2> /dev/null", + "postpack": "find . -name '*.tgz' -exec mv ../../dist/ \\; ", "prettier": "prettier . --check", "prettier:fix": "prettier . --write", "typecheck": "tsc --noEmit", diff --git a/packages/ui/package.json b/packages/ui/package.json index 1ddf2bbfa..64212759d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -35,7 +35,7 @@ "dev:cjs": "concurrently \"tsc --watch -p tsconfig.cjs.json\" \"tsc-alias --watch -p tsconfig.cjs.json\"", "lint": "eslint", "lint:fix": "pnpm lint --fix", - "postpack": "find . -name '*.tgz' -exec mv ../../dist/ \\; 2> /dev/null", + "postpack": "find . -name '*.tgz' -exec mv ../../dist/ \\; ", "prettier": "prettier . --check", "prettier:fix": "prettier . --write", "typecheck": "tsc --noEmit", From d5729215b8652d205124695c312f19fc88bdf3b0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Sep 2024 10:16:59 +0000 Subject: [PATCH 9/9] Version Packages --- .changeset/blue-boats-drop.md | 5 -- .changeset/blue-pandas-wash.md | 5 -- .changeset/brave-squids-obey.md | 5 -- .changeset/brown-lizards-juggle.md | 5 -- .changeset/calm-months-live.md | 6 -- .changeset/chatty-bats-sleep.md | 5 -- .changeset/chatty-kings-attend.md | 5 -- .changeset/chatty-mice-hear.md | 5 -- .changeset/chatty-waves-live.md | 5 -- .changeset/cuddly-gifts-repeat.md | 5 -- .changeset/cuddly-impalas-sing.md | 5 -- .changeset/gentle-apples-help.md | 5 -- .changeset/gentle-experts-impress.md | 5 -- .changeset/gentle-zebras-raise.md | 5 -- .changeset/good-oranges-move.md | 5 -- .changeset/happy-lions-eat.md | 5 -- .changeset/hip-teachers-punch.md | 5 -- .changeset/hot-cheetahs-wonder.md | 6 -- .changeset/hungry-parents-chew.md | 5 -- .changeset/khaki-roses-cover.md | 5 -- .changeset/late-flies-smile.md | 5 -- .changeset/light-timers-lie.md | 5 -- .changeset/lucky-keys-explode.md | 5 -- .changeset/nervous-phones-cross.md | 5 -- .changeset/new-guests-judge.md | 5 -- .changeset/nine-cycles-clean.md | 5 -- .changeset/ninety-shirts-beg.md | 5 -- .changeset/odd-ties-count.md | 5 -- .changeset/old-ads-deny.md | 5 -- .changeset/old-cups-cheat.md | 5 -- .changeset/perfect-deers-sneeze.md | 9 --- .changeset/proud-flies-type.md | 5 -- .changeset/proud-turtles-tease.md | 6 -- .changeset/rare-tips-stare.md | 5 -- .changeset/real-crews-switch.md | 5 -- .changeset/rotten-bobcats-dance.md | 5 -- .changeset/rude-ads-kick.md | 5 -- .changeset/selfish-months-decide.md | 5 -- .changeset/serious-snails-deny.md | 5 -- .changeset/seven-beans-poke.md | 5 -- .changeset/silent-apes-protect.md | 5 -- .changeset/sixty-hotels-cheat.md | 6 -- .changeset/slow-eggs-act.md | 5 -- .changeset/strong-terms-brake.md | 5 -- .changeset/tall-hairs-cheer.md | 5 -- .changeset/tall-rockets-mate.md | 5 -- .changeset/tender-rings-leave.md | 5 -- .changeset/thick-zoos-travel.md | 5 -- .changeset/thin-socks-fold.md | 5 -- .changeset/tricky-grapes-count.md | 5 -- .changeset/unlucky-pears-sort.md | 5 -- .changeset/violet-clouds-compete.md | 6 -- .changeset/witty-dancers-boil.md | 5 -- .changeset/witty-plums-search.md | 7 --- apps/sample/CHANGELOG.md | 28 +++++++++ apps/sample/package.json | 2 +- packages/core/CHANGELOG.md | 40 ++++++++++++ packages/core/package.json | 2 +- packages/signer/context-module/CHANGELOG.md | 20 ++++++ packages/signer/context-module/package.json | 2 +- packages/signer/keyring-btc/CHANGELOG.md | 18 ++++++ packages/signer/keyring-btc/package.json | 2 +- packages/signer/keyring-eth/CHANGELOG.md | 67 +++++++++++++++++++++ packages/signer/keyring-eth/package.json | 2 +- 64 files changed, 178 insertions(+), 286 deletions(-) delete mode 100644 .changeset/blue-boats-drop.md delete mode 100644 .changeset/blue-pandas-wash.md delete mode 100644 .changeset/brave-squids-obey.md delete mode 100644 .changeset/brown-lizards-juggle.md delete mode 100644 .changeset/calm-months-live.md delete mode 100644 .changeset/chatty-bats-sleep.md delete mode 100644 .changeset/chatty-kings-attend.md delete mode 100644 .changeset/chatty-mice-hear.md delete mode 100644 .changeset/chatty-waves-live.md delete mode 100644 .changeset/cuddly-gifts-repeat.md delete mode 100644 .changeset/cuddly-impalas-sing.md delete mode 100644 .changeset/gentle-apples-help.md delete mode 100644 .changeset/gentle-experts-impress.md delete mode 100644 .changeset/gentle-zebras-raise.md delete mode 100644 .changeset/good-oranges-move.md delete mode 100644 .changeset/happy-lions-eat.md delete mode 100644 .changeset/hip-teachers-punch.md delete mode 100644 .changeset/hot-cheetahs-wonder.md delete mode 100644 .changeset/hungry-parents-chew.md delete mode 100644 .changeset/khaki-roses-cover.md delete mode 100644 .changeset/late-flies-smile.md delete mode 100644 .changeset/light-timers-lie.md delete mode 100644 .changeset/lucky-keys-explode.md delete mode 100644 .changeset/nervous-phones-cross.md delete mode 100644 .changeset/new-guests-judge.md delete mode 100644 .changeset/nine-cycles-clean.md delete mode 100644 .changeset/ninety-shirts-beg.md delete mode 100644 .changeset/odd-ties-count.md delete mode 100644 .changeset/old-ads-deny.md delete mode 100644 .changeset/old-cups-cheat.md delete mode 100644 .changeset/perfect-deers-sneeze.md delete mode 100644 .changeset/proud-flies-type.md delete mode 100644 .changeset/proud-turtles-tease.md delete mode 100644 .changeset/rare-tips-stare.md delete mode 100644 .changeset/real-crews-switch.md delete mode 100644 .changeset/rotten-bobcats-dance.md delete mode 100644 .changeset/rude-ads-kick.md delete mode 100644 .changeset/selfish-months-decide.md delete mode 100644 .changeset/serious-snails-deny.md delete mode 100644 .changeset/seven-beans-poke.md delete mode 100644 .changeset/silent-apes-protect.md delete mode 100644 .changeset/sixty-hotels-cheat.md delete mode 100644 .changeset/slow-eggs-act.md delete mode 100644 .changeset/strong-terms-brake.md delete mode 100644 .changeset/tall-hairs-cheer.md delete mode 100644 .changeset/tall-rockets-mate.md delete mode 100644 .changeset/tender-rings-leave.md delete mode 100644 .changeset/thick-zoos-travel.md delete mode 100644 .changeset/thin-socks-fold.md delete mode 100644 .changeset/tricky-grapes-count.md delete mode 100644 .changeset/unlucky-pears-sort.md delete mode 100644 .changeset/violet-clouds-compete.md delete mode 100644 .changeset/witty-dancers-boil.md delete mode 100644 .changeset/witty-plums-search.md create mode 100644 packages/signer/context-module/CHANGELOG.md create mode 100644 packages/signer/keyring-btc/CHANGELOG.md create mode 100644 packages/signer/keyring-eth/CHANGELOG.md diff --git a/.changeset/blue-boats-drop.md b/.changeset/blue-boats-drop.md deleted file mode 100644 index 344db5746..000000000 --- a/.changeset/blue-boats-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -add GetAddressCommand implementation diff --git a/.changeset/blue-pandas-wash.md b/.changeset/blue-pandas-wash.md deleted file mode 100644 index 39b216dd4..000000000 --- a/.changeset/blue-pandas-wash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement SetPluginCommand diff --git a/.changeset/brave-squids-obey.md b/.changeset/brave-squids-obey.md deleted file mode 100644 index 2972a6222..000000000 --- a/.changeset/brave-squids-obey.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement SendEIP712FilteringCommand diff --git a/.changeset/brown-lizards-juggle.md b/.changeset/brown-lizards-juggle.md deleted file mode 100644 index 6484c16b2..000000000 --- a/.changeset/brown-lizards-juggle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement SignEIP712Command diff --git a/.changeset/calm-months-live.md b/.changeset/calm-months-live.md deleted file mode 100644 index 5d081f2d9..000000000 --- a/.changeset/calm-months-live.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@ledgerhq/keyring-btc": patch -"@ledgerhq/device-management-kit": patch ---- - -Implement MerkleTree and MerkleMap services diff --git a/.changeset/chatty-bats-sleep.md b/.changeset/chatty-bats-sleep.md deleted file mode 100644 index 3836ffd6e..000000000 --- a/.changeset/chatty-bats-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement ProvideTokenInformationCommand diff --git a/.changeset/chatty-kings-attend.md b/.changeset/chatty-kings-attend.md deleted file mode 100644 index a8020c3cd..000000000 --- a/.changeset/chatty-kings-attend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-sdk-sample": patch ---- - -Implement keyring getAddress diff --git a/.changeset/chatty-mice-hear.md b/.changeset/chatty-mice-hear.md deleted file mode 100644 index 9a5c0be62..000000000 --- a/.changeset/chatty-mice-hear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-sdk-sample": patch ---- - -Add SignTypedData to Ethereum Keyring diff --git a/.changeset/chatty-waves-live.md b/.changeset/chatty-waves-live.md deleted file mode 100644 index 13974c707..000000000 --- a/.changeset/chatty-waves-live.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-sdk-sample": patch ---- - -Add ListAppsWithMetadataDeviceAction in sample app diff --git a/.changeset/cuddly-gifts-repeat.md b/.changeset/cuddly-gifts-repeat.md deleted file mode 100644 index 0a355254a..000000000 --- a/.changeset/cuddly-gifts-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-sdk-sample": patch ---- - -Implement SignTransactionUseCase in the Ethereum Keyring part diff --git a/.changeset/cuddly-impalas-sing.md b/.changeset/cuddly-impalas-sing.md deleted file mode 100644 index e97e8b2c1..000000000 --- a/.changeset/cuddly-impalas-sing.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/keyring-btc": patch ---- - -Implement GetExtendedPublicKeyCommand diff --git a/.changeset/gentle-apples-help.md b/.changeset/gentle-apples-help.md deleted file mode 100644 index 9d23da574..000000000 --- a/.changeset/gentle-apples-help.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-sdk-sample": patch ---- - -Added a block to display an example of UI for the device actions, next to the logs. diff --git a/.changeset/gentle-experts-impress.md b/.changeset/gentle-experts-impress.md deleted file mode 100644 index b582aba97..000000000 --- a/.changeset/gentle-experts-impress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Add README for device-signer-kit-ethereum module diff --git a/.changeset/gentle-zebras-raise.md b/.changeset/gentle-zebras-raise.md deleted file mode 100644 index a27f4568c..000000000 --- a/.changeset/gentle-zebras-raise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Add transaction serialized to transaction mapper result diff --git a/.changeset/good-oranges-move.md b/.changeset/good-oranges-move.md deleted file mode 100644 index 624645e8b..000000000 --- a/.changeset/good-oranges-move.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement ProvideNFTInformationCommand diff --git a/.changeset/happy-lions-eat.md b/.changeset/happy-lions-eat.md deleted file mode 100644 index 54dfb0fcf..000000000 --- a/.changeset/happy-lions-eat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-management-kit": patch ---- - -Improve code visibility diff --git a/.changeset/hip-teachers-punch.md b/.changeset/hip-teachers-punch.md deleted file mode 100644 index 5ed3f4510..000000000 --- a/.changeset/hip-teachers-punch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement GetChallengeCommand diff --git a/.changeset/hot-cheetahs-wonder.md b/.changeset/hot-cheetahs-wonder.md deleted file mode 100644 index aaa2d68de..000000000 --- a/.changeset/hot-cheetahs-wonder.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch -"@ledgerhq/device-sdk-sample": patch ---- - -Implement sign message diff --git a/.changeset/hungry-parents-chew.md b/.changeset/hungry-parents-chew.md deleted file mode 100644 index cd014b588..000000000 --- a/.changeset/hungry-parents-chew.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement ProvideTransactionContextTask diff --git a/.changeset/khaki-roses-cover.md b/.changeset/khaki-roses-cover.md deleted file mode 100644 index dc435c37a..000000000 --- a/.changeset/khaki-roses-cover.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-sdk-sample": patch ---- - -Remove device session if device not connected diff --git a/.changeset/late-flies-smile.md b/.changeset/late-flies-smile.md deleted file mode 100644 index 6f5a604e5..000000000 --- a/.changeset/late-flies-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement SendEIP712StructImplemCommand diff --git a/.changeset/light-timers-lie.md b/.changeset/light-timers-lie.md deleted file mode 100644 index 6af746550..000000000 --- a/.changeset/light-timers-lie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-management-kit": patch ---- - -Fix wrong dependency declaration for @statelyai/inspect (from devDeps to deps diff --git a/.changeset/lucky-keys-explode.md b/.changeset/lucky-keys-explode.md deleted file mode 100644 index 7b371868c..000000000 --- a/.changeset/lucky-keys-explode.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/keyring-btc": patch ---- - -Implement GetMasterFingerprintCommand diff --git a/.changeset/nervous-phones-cross.md b/.changeset/nervous-phones-cross.md deleted file mode 100644 index a6a40d665..000000000 --- a/.changeset/nervous-phones-cross.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Add BuildTransactionContextTask diff --git a/.changeset/new-guests-judge.md b/.changeset/new-guests-judge.md deleted file mode 100644 index d4b705307..000000000 --- a/.changeset/new-guests-judge.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-management-kit": minor ---- - -Add new device actions to core: ListApps, GoToDashboard, GetDeviceStatus diff --git a/.changeset/nine-cycles-clean.md b/.changeset/nine-cycles-clean.md deleted file mode 100644 index 1ab073a6f..000000000 --- a/.changeset/nine-cycles-clean.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-sdk-sample": patch ---- - -Small refacto on the sample app + integration of the device-signer-kit-ethereum's getAddress diff --git a/.changeset/ninety-shirts-beg.md b/.changeset/ninety-shirts-beg.md deleted file mode 100644 index 44401a46a..000000000 --- a/.changeset/ninety-shirts-beg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-management-kit": minor ---- - -Add ManagerApi service to core diff --git a/.changeset/odd-ties-count.md b/.changeset/odd-ties-count.md deleted file mode 100644 index 489abefb5..000000000 --- a/.changeset/odd-ties-count.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-management-kit": minor ---- - -Add new ListApps command to SDK core diff --git a/.changeset/old-ads-deny.md b/.changeset/old-ads-deny.md deleted file mode 100644 index 485a232cd..000000000 --- a/.changeset/old-ads-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/context-module": minor ---- - -Bump ethers to v6.13.2 diff --git a/.changeset/old-cups-cheat.md b/.changeset/old-cups-cheat.md deleted file mode 100644 index 01dbce8dd..000000000 --- a/.changeset/old-cups-cheat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Add Set External Plugin command diff --git a/.changeset/perfect-deers-sneeze.md b/.changeset/perfect-deers-sneeze.md deleted file mode 100644 index 6cfc83b00..000000000 --- a/.changeset/perfect-deers-sneeze.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@ledgerhq/context-module": patch -"@ledgerhq/keyring-btc": patch -"@ledgerhq/device-signer-kit-ethereum": patch -"@ledgerhq/device-management-kit": patch -"@ledgerhq/device-sdk-sample": patch ---- - -Rename packages diff --git a/.changeset/proud-flies-type.md b/.changeset/proud-flies-type.md deleted file mode 100644 index aa01f11f8..000000000 --- a/.changeset/proud-flies-type.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement ProvideDomainNameCommand diff --git a/.changeset/proud-turtles-tease.md b/.changeset/proud-turtles-tease.md deleted file mode 100644 index ee04802a3..000000000 --- a/.changeset/proud-turtles-tease.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@ledgerhq/device-management-kit": patch -"@ledgerhq/device-sdk-sample": patch ---- - -Add support of Ledger Flex diff --git a/.changeset/rare-tips-stare.md b/.changeset/rare-tips-stare.md deleted file mode 100644 index ac02e4e6b..000000000 --- a/.changeset/rare-tips-stare.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-management-kit": patch ---- - -Device reconnection on app change diff --git a/.changeset/real-crews-switch.md b/.changeset/real-crews-switch.md deleted file mode 100644 index 755cf5201..000000000 --- a/.changeset/real-crews-switch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": minor ---- - -Implement SignTransactionUseCase diff --git a/.changeset/rotten-bobcats-dance.md b/.changeset/rotten-bobcats-dance.md deleted file mode 100644 index 75de89eed..000000000 --- a/.changeset/rotten-bobcats-dance.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-management-kit": patch ---- - -Added a new "generic" DeviceAction `SendCommandInAppDeviceAction` diff --git a/.changeset/rude-ads-kick.md b/.changeset/rude-ads-kick.md deleted file mode 100644 index fd8bd3d45..000000000 --- a/.changeset/rude-ads-kick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement TransactionMapperService diff --git a/.changeset/selfish-months-decide.md b/.changeset/selfish-months-decide.md deleted file mode 100644 index d3db28255..000000000 --- a/.changeset/selfish-months-decide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Bump ethers to v6.13.2 diff --git a/.changeset/serious-snails-deny.md b/.changeset/serious-snails-deny.md deleted file mode 100644 index 4b5c48f48..000000000 --- a/.changeset/serious-snails-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement SignTransactionDeviceAction diff --git a/.changeset/seven-beans-poke.md b/.changeset/seven-beans-poke.md deleted file mode 100644 index 381ff22dc..000000000 --- a/.changeset/seven-beans-poke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Add SignPersonalMessage command diff --git a/.changeset/silent-apes-protect.md b/.changeset/silent-apes-protect.md deleted file mode 100644 index ee16552a1..000000000 --- a/.changeset/silent-apes-protect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-management-kit": patch ---- - -Add sign-personal-message user interaction diff --git a/.changeset/sixty-hotels-cheat.md b/.changeset/sixty-hotels-cheat.md deleted file mode 100644 index cb153108d..000000000 --- a/.changeset/sixty-hotels-cheat.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch -"@ledgerhq/device-management-kit": patch ---- - -DSDK-420 Implement the EIP712 TypedData parser service diff --git a/.changeset/slow-eggs-act.md b/.changeset/slow-eggs-act.md deleted file mode 100644 index f06298dfc..000000000 --- a/.changeset/slow-eggs-act.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/context-module": patch ---- - -Improve code visibility and update command implementations diff --git a/.changeset/strong-terms-brake.md b/.changeset/strong-terms-brake.md deleted file mode 100644 index dc4ec9b99..000000000 --- a/.changeset/strong-terms-brake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/context-module": patch ---- - -Update readme file diff --git a/.changeset/tall-hairs-cheer.md b/.changeset/tall-hairs-cheer.md deleted file mode 100644 index 37aae89c3..000000000 --- a/.changeset/tall-hairs-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/keyring-btc": patch ---- - -Create keyring-btc package diff --git a/.changeset/tall-rockets-mate.md b/.changeset/tall-rockets-mate.md deleted file mode 100644 index 514bfda48..000000000 --- a/.changeset/tall-rockets-mate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-management-kit": patch ---- - -Add exports from api, add add32BitUintToData for ApduBuilder diff --git a/.changeset/tender-rings-leave.md b/.changeset/tender-rings-leave.md deleted file mode 100644 index 731e8428b..000000000 --- a/.changeset/tender-rings-leave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-management-kit": patch ---- - -Add ListDeviceSessions use case diff --git a/.changeset/thick-zoos-travel.md b/.changeset/thick-zoos-travel.md deleted file mode 100644 index 9d771c023..000000000 --- a/.changeset/thick-zoos-travel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement SendEIP712StructDefinitionCommand diff --git a/.changeset/thin-socks-fold.md b/.changeset/thin-socks-fold.md deleted file mode 100644 index 077913416..000000000 --- a/.changeset/thin-socks-fold.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Add SendSignTransactionTask and simplify SendTransactionCommand diff --git a/.changeset/tricky-grapes-count.md b/.changeset/tricky-grapes-count.md deleted file mode 100644 index 35db5be10..000000000 --- a/.changeset/tricky-grapes-count.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implementation of getAddress using SendCommandInAppDeviceAction and the GetAddress command diff --git a/.changeset/unlucky-pears-sort.md b/.changeset/unlucky-pears-sort.md deleted file mode 100644 index 3794b4dfb..000000000 --- a/.changeset/unlucky-pears-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-management-kit": minor ---- - -Add ListAppsWithMetadata device action diff --git a/.changeset/violet-clouds-compete.md b/.changeset/violet-clouds-compete.md deleted file mode 100644 index 9a88f3743..000000000 --- a/.changeset/violet-clouds-compete.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": minor -"@ledgerhq/device-management-kit": minor ---- - -Use of CommandResult return type in commands diff --git a/.changeset/witty-dancers-boil.md b/.changeset/witty-dancers-boil.md deleted file mode 100644 index 0e18dc527..000000000 --- a/.changeset/witty-dancers-boil.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@ledgerhq/device-signer-kit-ethereum": patch ---- - -Implement SignTransactionCommand diff --git a/.changeset/witty-plums-search.md b/.changeset/witty-plums-search.md deleted file mode 100644 index b4643c774..000000000 --- a/.changeset/witty-plums-search.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@ledgerhq/context-module": patch -"@ledgerhq/device-signer-kit-ethereum": patch -"@ledgerhq/device-management-kit": patch ---- - -add HexaString to handle `0x${string}` type diff --git a/apps/sample/CHANGELOG.md b/apps/sample/CHANGELOG.md index 2b23b08e2..7747dafae 100644 --- a/apps/sample/CHANGELOG.md +++ b/apps/sample/CHANGELOG.md @@ -1,5 +1,33 @@ # @ledgerhq/device-sdk-sample +## 0.1.2 + +### Patch Changes + +- [#147](https://github.com/LedgerHQ/device-sdk-ts/pull/147) [`2893f92`](https://github.com/LedgerHQ/device-sdk-ts/commit/2893f92e023741ef33e72dd5bc40e18b42052ca8) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Implement keyring getAddress + +- [#208](https://github.com/LedgerHQ/device-sdk-ts/pull/208) [`38be97b`](https://github.com/LedgerHQ/device-sdk-ts/commit/38be97ba986efd206d2cc465fc5ce39bc9146070) Thanks [@paoun-ledger](https://github.com/paoun-ledger)! - Add SignTypedData to Ethereum Keyring + +- [#161](https://github.com/LedgerHQ/device-sdk-ts/pull/161) [`f290f0e`](https://github.com/LedgerHQ/device-sdk-ts/commit/f290f0ee2ffd899ba63c965d8d511904174cc008) Thanks [@valpinkman](https://github.com/valpinkman)! - Add ListAppsWithMetadataDeviceAction in sample app + +- [#230](https://github.com/LedgerHQ/device-sdk-ts/pull/230) [`3e91677`](https://github.com/LedgerHQ/device-sdk-ts/commit/3e916777aea17cf4902050dee87896e0d7e971b4) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Implement SignTransactionUseCase in the Ethereum Keyring part + +- [#281](https://github.com/LedgerHQ/device-sdk-ts/pull/281) [`6f3d793`](https://github.com/LedgerHQ/device-sdk-ts/commit/6f3d793c305db8a2b217b0d8cc221d5a3340c418) Thanks [@ofreyssinet-ledger](https://github.com/ofreyssinet-ledger)! - Added a block to display an example of UI for the device actions, next to the logs. + +- [#209](https://github.com/LedgerHQ/device-sdk-ts/pull/209) [`c5b5cc1`](https://github.com/LedgerHQ/device-sdk-ts/commit/c5b5cc11d0b0dfec4e1e76ecd98d4ad09a6c9d89) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Implement sign message + +- [#121](https://github.com/LedgerHQ/device-sdk-ts/pull/121) [`8e35d51`](https://github.com/LedgerHQ/device-sdk-ts/commit/8e35d515e3bea5e98319a619a053fa90437f6024) Thanks [@jdabbech-ledger](https://github.com/jdabbech-ledger)! - Remove device session if device not connected + +- [#156](https://github.com/LedgerHQ/device-sdk-ts/pull/156) [`a25f529`](https://github.com/LedgerHQ/device-sdk-ts/commit/a25f529ed08206d38d00026a3589bbbaa21075bc) Thanks [@ofreyssinet-ledger](https://github.com/ofreyssinet-ledger)! - Small refacto on the sample app + integration of the device-signer-kit-ethereum's getAddress + +- [#290](https://github.com/LedgerHQ/device-sdk-ts/pull/290) [`6dd1534`](https://github.com/LedgerHQ/device-sdk-ts/commit/6dd153414d0041795e0c145bc70bb5247af7ad92) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Rename packages + +- [#169](https://github.com/LedgerHQ/device-sdk-ts/pull/169) [`d9e0164`](https://github.com/LedgerHQ/device-sdk-ts/commit/d9e0164d69bede69269d0989c24a8631b9a0875d) Thanks [@ofreyssinet-ledger](https://github.com/ofreyssinet-ledger)! - Add support of Ledger Flex + +- Updated dependencies [[`fd022bc`](https://github.com/LedgerHQ/device-sdk-ts/commit/fd022bc4a3c6b84fcfedadb0b618ab0f3f1bf43c), [`a31c298`](https://github.com/LedgerHQ/device-sdk-ts/commit/a31c29851efd6b4eca4503a17b04857788f003b9), [`25cd8f2`](https://github.com/LedgerHQ/device-sdk-ts/commit/25cd8f2bd647e5d69783e17178a958eedd1d3836), [`7a7113e`](https://github.com/LedgerHQ/device-sdk-ts/commit/7a7113e64707364d3873281cb97f74de06c7a2ae), [`1f1485b`](https://github.com/LedgerHQ/device-sdk-ts/commit/1f1485be90f073d73f7013d6b755f1234e481844), [`77a4938`](https://github.com/LedgerHQ/device-sdk-ts/commit/77a4938d34df103e41e8efa203ac4fc793d9d420), [`74ea2fc`](https://github.com/LedgerHQ/device-sdk-ts/commit/74ea2fc64e51e5206d1ef5b86ee27cfc6a12edc1), [`463132c`](https://github.com/LedgerHQ/device-sdk-ts/commit/463132c253fa7b55f6d7dd5bf3d2ed0e78866bd0), [`5c68f48`](https://github.com/LedgerHQ/device-sdk-ts/commit/5c68f48c25edde30416598bb2152ba5077820724), [`899d151`](https://github.com/LedgerHQ/device-sdk-ts/commit/899d15152c2cf67b19cb6ca83dc1fbbd0e79ae27), [`69e1ad1`](https://github.com/LedgerHQ/device-sdk-ts/commit/69e1ad154eaedc15135765d3095ad9979bf8baf0), [`c5b5cc1`](https://github.com/LedgerHQ/device-sdk-ts/commit/c5b5cc11d0b0dfec4e1e76ecd98d4ad09a6c9d89), [`16b84b0`](https://github.com/LedgerHQ/device-sdk-ts/commit/16b84b04413ad9602f1dad6b8229d8d0afec185b), [`b5d81da`](https://github.com/LedgerHQ/device-sdk-ts/commit/b5d81da73ff4c5ee9fdce231f218dcfd40e28083), [`41892b3`](https://github.com/LedgerHQ/device-sdk-ts/commit/41892b3dbd27c71b091d4c8203286702a81f380b), [`43eb989`](https://github.com/LedgerHQ/device-sdk-ts/commit/43eb989569e14c9f61356099545204a71c2032a8), [`c6822ba`](https://github.com/LedgerHQ/device-sdk-ts/commit/c6822ba275946200333a8e64f240bf52c62e649c), [`0ef0626`](https://github.com/LedgerHQ/device-sdk-ts/commit/0ef06260b4cf87c3cb41fe2819e8efd849b2f336), [`f708627`](https://github.com/LedgerHQ/device-sdk-ts/commit/f708627965617b40951016448b8f90d71c19a2f8), [`fd25324`](https://github.com/LedgerHQ/device-sdk-ts/commit/fd253248a05504d4f2a3b7310bd62132c12d05f7), [`6dd1534`](https://github.com/LedgerHQ/device-sdk-ts/commit/6dd153414d0041795e0c145bc70bb5247af7ad92), [`899d151`](https://github.com/LedgerHQ/device-sdk-ts/commit/899d15152c2cf67b19cb6ca83dc1fbbd0e79ae27), [`d9e0164`](https://github.com/LedgerHQ/device-sdk-ts/commit/d9e0164d69bede69269d0989c24a8631b9a0875d), [`3b59289`](https://github.com/LedgerHQ/device-sdk-ts/commit/3b592899168ecedfa3698041b77e09764c1cf4d7), [`25a7e5c`](https://github.com/LedgerHQ/device-sdk-ts/commit/25a7e5c60622858ecc6941eb12d68288ebf68e37), [`a25f529`](https://github.com/LedgerHQ/device-sdk-ts/commit/a25f529ed08206d38d00026a3589bbbaa21075bc), [`dc23eba`](https://github.com/LedgerHQ/device-sdk-ts/commit/dc23eba464db8e7a19e60fe2f9ffb6254f8b2886), [`efe5167`](https://github.com/LedgerHQ/device-sdk-ts/commit/efe51677c3adcd858c497c2ae48061c9cb2ec460), [`c148662`](https://github.com/LedgerHQ/device-sdk-ts/commit/c14866288678a334cfc9fdeb271ef4e0b3c03061), [`2a44494`](https://github.com/LedgerHQ/device-sdk-ts/commit/2a4449444d7fecc874f393b2bfc2942fa6cd7c15), [`c5b5cc1`](https://github.com/LedgerHQ/device-sdk-ts/commit/c5b5cc11d0b0dfec4e1e76ecd98d4ad09a6c9d89), [`5018129`](https://github.com/LedgerHQ/device-sdk-ts/commit/501812904cbb7eb519651b4c8dbb613198e1e89c), [`2893f92`](https://github.com/LedgerHQ/device-sdk-ts/commit/2893f92e023741ef33e72dd5bc40e18b42052ca8), [`f25bb8f`](https://github.com/LedgerHQ/device-sdk-ts/commit/f25bb8feec3e733d1ebb13b2d7c7ea08e61fae3e), [`48a348a`](https://github.com/LedgerHQ/device-sdk-ts/commit/48a348ae1a275722303f2fc9380863fff25d375f), [`64341a7`](https://github.com/LedgerHQ/device-sdk-ts/commit/64341a7630f6e691fecb3db3c608dc5cd7983f36), [`a25f529`](https://github.com/LedgerHQ/device-sdk-ts/commit/a25f529ed08206d38d00026a3589bbbaa21075bc), [`73825aa`](https://github.com/LedgerHQ/device-sdk-ts/commit/73825aaa5869c9026bd1a5a1b142a74a9484662f), [`8cba13a`](https://github.com/LedgerHQ/device-sdk-ts/commit/8cba13a3fb720ecd15b2464c45be30fc9851bd0a), [`861f9c5`](https://github.com/LedgerHQ/device-sdk-ts/commit/861f9c56b7b10034df156e369400dfd614b545f1), [`861f9c5`](https://github.com/LedgerHQ/device-sdk-ts/commit/861f9c56b7b10034df156e369400dfd614b545f1)]: + - @ledgerhq/device-signer-kit-ethereum@1.0.0 + - @ledgerhq/device-management-kit@0.4.0 + ## 0.1.1 ### Patch Changes diff --git a/apps/sample/package.json b/apps/sample/package.json index 6289642ea..598641d7c 100644 --- a/apps/sample/package.json +++ b/apps/sample/package.json @@ -1,6 +1,6 @@ { "name": "@ledgerhq/device-sdk-sample", - "version": "0.1.1", + "version": "0.1.2", "private": true, "scripts": { "dev": "next dev", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 372332440..625c7d1e7 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,45 @@ # @ledgerhq/device-management-kit +## 0.4.0 + +### Minor Changes + +- [#124](https://github.com/LedgerHQ/device-sdk-ts/pull/124) [`c6822ba`](https://github.com/LedgerHQ/device-sdk-ts/commit/c6822ba275946200333a8e64f240bf52c62e649c) Thanks [@valpinkman](https://github.com/valpinkman)! - Add new device actions to core: ListApps, GoToDashboard, GetDeviceStatus + +- [#161](https://github.com/LedgerHQ/device-sdk-ts/pull/161) [`0ef0626`](https://github.com/LedgerHQ/device-sdk-ts/commit/0ef06260b4cf87c3cb41fe2819e8efd849b2f336) Thanks [@valpinkman](https://github.com/valpinkman)! - Add ManagerApi service to core + +- [#111](https://github.com/LedgerHQ/device-sdk-ts/pull/111) [`f708627`](https://github.com/LedgerHQ/device-sdk-ts/commit/f708627965617b40951016448b8f90d71c19a2f8) Thanks [@valpinkman](https://github.com/valpinkman)! - Add new ListApps command to SDK core + +- [#161](https://github.com/LedgerHQ/device-sdk-ts/pull/161) [`73825aa`](https://github.com/LedgerHQ/device-sdk-ts/commit/73825aaa5869c9026bd1a5a1b142a74a9484662f) Thanks [@valpinkman](https://github.com/valpinkman)! - Add ListAppsWithMetadata device action + +- [#172](https://github.com/LedgerHQ/device-sdk-ts/pull/172) [`8cba13a`](https://github.com/LedgerHQ/device-sdk-ts/commit/8cba13a3fb720ecd15b2464c45be30fc9851bd0a) Thanks [@jdabbech-ledger](https://github.com/jdabbech-ledger)! - Use of CommandResult return type in commands + +### Patch Changes + +- [#270](https://github.com/LedgerHQ/device-sdk-ts/pull/270) [`1f1485b`](https://github.com/LedgerHQ/device-sdk-ts/commit/1f1485be90f073d73f7013d6b755f1234e481844) Thanks [@paoun-ledger](https://github.com/paoun-ledger)! - Implement MerkleTree and MerkleMap services + +- [#174](https://github.com/LedgerHQ/device-sdk-ts/pull/174) [`899d151`](https://github.com/LedgerHQ/device-sdk-ts/commit/899d15152c2cf67b19cb6ca83dc1fbbd0e79ae27) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Improve code visibility + +- [#284](https://github.com/LedgerHQ/device-sdk-ts/pull/284) [`41892b3`](https://github.com/LedgerHQ/device-sdk-ts/commit/41892b3dbd27c71b091d4c8203286702a81f380b) Thanks [@valpinkman](https://github.com/valpinkman)! - Fix wrong dependency declaration for @statelyai/inspect (from devDeps to deps + +- [#290](https://github.com/LedgerHQ/device-sdk-ts/pull/290) [`6dd1534`](https://github.com/LedgerHQ/device-sdk-ts/commit/6dd153414d0041795e0c145bc70bb5247af7ad92) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Rename packages + +- [#169](https://github.com/LedgerHQ/device-sdk-ts/pull/169) [`d9e0164`](https://github.com/LedgerHQ/device-sdk-ts/commit/d9e0164d69bede69269d0989c24a8631b9a0875d) Thanks [@ofreyssinet-ledger](https://github.com/ofreyssinet-ledger)! - Add support of Ledger Flex + +- [#121](https://github.com/LedgerHQ/device-sdk-ts/pull/121) [`3b59289`](https://github.com/LedgerHQ/device-sdk-ts/commit/3b592899168ecedfa3698041b77e09764c1cf4d7) Thanks [@jdabbech-ledger](https://github.com/jdabbech-ledger)! - Device reconnection on app change + +- [#156](https://github.com/LedgerHQ/device-sdk-ts/pull/156) [`a25f529`](https://github.com/LedgerHQ/device-sdk-ts/commit/a25f529ed08206d38d00026a3589bbbaa21075bc) Thanks [@ofreyssinet-ledger](https://github.com/ofreyssinet-ledger)! - Added a new "generic" DeviceAction `SendCommandInAppDeviceAction` + +- [#209](https://github.com/LedgerHQ/device-sdk-ts/pull/209) [`c5b5cc1`](https://github.com/LedgerHQ/device-sdk-ts/commit/c5b5cc11d0b0dfec4e1e76ecd98d4ad09a6c9d89) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Add sign-personal-message user interaction + +- [#186](https://github.com/LedgerHQ/device-sdk-ts/pull/186) [`5018129`](https://github.com/LedgerHQ/device-sdk-ts/commit/501812904cbb7eb519651b4c8dbb613198e1e89c) Thanks [@paoun-ledger](https://github.com/paoun-ledger)! - DSDK-420 Implement the EIP712 TypedData parser service + +- [#147](https://github.com/LedgerHQ/device-sdk-ts/pull/147) [`2893f92`](https://github.com/LedgerHQ/device-sdk-ts/commit/2893f92e023741ef33e72dd5bc40e18b42052ca8) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Add exports from api, add add32BitUintToData for ApduBuilder + +- [#303](https://github.com/LedgerHQ/device-sdk-ts/pull/303) [`f25bb8f`](https://github.com/LedgerHQ/device-sdk-ts/commit/f25bb8feec3e733d1ebb13b2d7c7ea08e61fae3e) Thanks [@valpinkman](https://github.com/valpinkman)! - Add ListDeviceSessions use case + +- [#159](https://github.com/LedgerHQ/device-sdk-ts/pull/159) [`861f9c5`](https://github.com/LedgerHQ/device-sdk-ts/commit/861f9c56b7b10034df156e369400dfd614b545f1) Thanks [@aussedatlo](https://github.com/aussedatlo)! - add HexaString to handle `0x${string}` type + ## 0.3.0 ### Minor Changes diff --git a/packages/core/package.json b/packages/core/package.json index 4c81bfaac..978436b83 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@ledgerhq/device-management-kit", - "version": "0.3.0", + "version": "0.4.0", "license": "MIT", "exports": { ".": { diff --git a/packages/signer/context-module/CHANGELOG.md b/packages/signer/context-module/CHANGELOG.md new file mode 100644 index 000000000..1282f5dd2 --- /dev/null +++ b/packages/signer/context-module/CHANGELOG.md @@ -0,0 +1,20 @@ +# @ledgerhq/context-module + +## 1.0.0 + +### Minor Changes + +- [#203](https://github.com/LedgerHQ/device-sdk-ts/pull/203) [`efe5167`](https://github.com/LedgerHQ/device-sdk-ts/commit/efe51677c3adcd858c497c2ae48061c9cb2ec460) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Bump ethers to v6.13.2 + +### Patch Changes + +- [#290](https://github.com/LedgerHQ/device-sdk-ts/pull/290) [`6dd1534`](https://github.com/LedgerHQ/device-sdk-ts/commit/6dd153414d0041795e0c145bc70bb5247af7ad92) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Rename packages + +- [#216](https://github.com/LedgerHQ/device-sdk-ts/pull/216) [`16b84b0`](https://github.com/LedgerHQ/device-sdk-ts/commit/16b84b04413ad9602f1dad6b8229d8d0afec185b) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Improve code visibility and update command implementations + +- [#170](https://github.com/LedgerHQ/device-sdk-ts/pull/170) [`e073436`](https://github.com/LedgerHQ/device-sdk-ts/commit/e0734365a2cedc79aa7786038d5f47880fba4319) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Update readme file + +- [#159](https://github.com/LedgerHQ/device-sdk-ts/pull/159) [`861f9c5`](https://github.com/LedgerHQ/device-sdk-ts/commit/861f9c56b7b10034df156e369400dfd614b545f1) Thanks [@aussedatlo](https://github.com/aussedatlo)! - add HexaString to handle `0x${string}` type + +- Updated dependencies [[`1f1485b`](https://github.com/LedgerHQ/device-sdk-ts/commit/1f1485be90f073d73f7013d6b755f1234e481844), [`899d151`](https://github.com/LedgerHQ/device-sdk-ts/commit/899d15152c2cf67b19cb6ca83dc1fbbd0e79ae27), [`41892b3`](https://github.com/LedgerHQ/device-sdk-ts/commit/41892b3dbd27c71b091d4c8203286702a81f380b), [`c6822ba`](https://github.com/LedgerHQ/device-sdk-ts/commit/c6822ba275946200333a8e64f240bf52c62e649c), [`0ef0626`](https://github.com/LedgerHQ/device-sdk-ts/commit/0ef06260b4cf87c3cb41fe2819e8efd849b2f336), [`f708627`](https://github.com/LedgerHQ/device-sdk-ts/commit/f708627965617b40951016448b8f90d71c19a2f8), [`6dd1534`](https://github.com/LedgerHQ/device-sdk-ts/commit/6dd153414d0041795e0c145bc70bb5247af7ad92), [`d9e0164`](https://github.com/LedgerHQ/device-sdk-ts/commit/d9e0164d69bede69269d0989c24a8631b9a0875d), [`3b59289`](https://github.com/LedgerHQ/device-sdk-ts/commit/3b592899168ecedfa3698041b77e09764c1cf4d7), [`a25f529`](https://github.com/LedgerHQ/device-sdk-ts/commit/a25f529ed08206d38d00026a3589bbbaa21075bc), [`c5b5cc1`](https://github.com/LedgerHQ/device-sdk-ts/commit/c5b5cc11d0b0dfec4e1e76ecd98d4ad09a6c9d89), [`5018129`](https://github.com/LedgerHQ/device-sdk-ts/commit/501812904cbb7eb519651b4c8dbb613198e1e89c), [`2893f92`](https://github.com/LedgerHQ/device-sdk-ts/commit/2893f92e023741ef33e72dd5bc40e18b42052ca8), [`f25bb8f`](https://github.com/LedgerHQ/device-sdk-ts/commit/f25bb8feec3e733d1ebb13b2d7c7ea08e61fae3e), [`73825aa`](https://github.com/LedgerHQ/device-sdk-ts/commit/73825aaa5869c9026bd1a5a1b142a74a9484662f), [`8cba13a`](https://github.com/LedgerHQ/device-sdk-ts/commit/8cba13a3fb720ecd15b2464c45be30fc9851bd0a), [`861f9c5`](https://github.com/LedgerHQ/device-sdk-ts/commit/861f9c56b7b10034df156e369400dfd614b545f1)]: + - @ledgerhq/device-management-kit@0.4.0 diff --git a/packages/signer/context-module/package.json b/packages/signer/context-module/package.json index 30e809467..95606445a 100644 --- a/packages/signer/context-module/package.json +++ b/packages/signer/context-module/package.json @@ -1,6 +1,6 @@ { "name": "@ledgerhq/context-module", - "version": "0.1.0", + "version": "1.0.0", "license": "MIT", "private": true, "exports": { diff --git a/packages/signer/keyring-btc/CHANGELOG.md b/packages/signer/keyring-btc/CHANGELOG.md new file mode 100644 index 000000000..7fdc6b6cc --- /dev/null +++ b/packages/signer/keyring-btc/CHANGELOG.md @@ -0,0 +1,18 @@ +# @ledgerhq/keyring-btc + +## 1.0.0 + +### Patch Changes + +- [#270](https://github.com/LedgerHQ/device-sdk-ts/pull/270) [`1f1485b`](https://github.com/LedgerHQ/device-sdk-ts/commit/1f1485be90f073d73f7013d6b755f1234e481844) Thanks [@paoun-ledger](https://github.com/paoun-ledger)! - Implement MerkleTree and MerkleMap services + +- [#282](https://github.com/LedgerHQ/device-sdk-ts/pull/282) [`c03eb92`](https://github.com/LedgerHQ/device-sdk-ts/commit/c03eb9206e4e4c106cc01a205ff830b2ab271e66) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Implement GetExtendedPublicKeyCommand + +- [#279](https://github.com/LedgerHQ/device-sdk-ts/pull/279) [`70770c6`](https://github.com/LedgerHQ/device-sdk-ts/commit/70770c6b4396dbec115cda4b2d40579ca108aeae) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Implement GetMasterFingerprintCommand + +- [#290](https://github.com/LedgerHQ/device-sdk-ts/pull/290) [`6dd1534`](https://github.com/LedgerHQ/device-sdk-ts/commit/6dd153414d0041795e0c145bc70bb5247af7ad92) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Rename packages + +- [#269](https://github.com/LedgerHQ/device-sdk-ts/pull/269) [`9a3afe4`](https://github.com/LedgerHQ/device-sdk-ts/commit/9a3afe42b6a70bbbea396324deb5052002c54042) Thanks [@paoun-ledger](https://github.com/paoun-ledger)! - Create keyring-btc package + +- Updated dependencies [[`1f1485b`](https://github.com/LedgerHQ/device-sdk-ts/commit/1f1485be90f073d73f7013d6b755f1234e481844), [`899d151`](https://github.com/LedgerHQ/device-sdk-ts/commit/899d15152c2cf67b19cb6ca83dc1fbbd0e79ae27), [`41892b3`](https://github.com/LedgerHQ/device-sdk-ts/commit/41892b3dbd27c71b091d4c8203286702a81f380b), [`c6822ba`](https://github.com/LedgerHQ/device-sdk-ts/commit/c6822ba275946200333a8e64f240bf52c62e649c), [`0ef0626`](https://github.com/LedgerHQ/device-sdk-ts/commit/0ef06260b4cf87c3cb41fe2819e8efd849b2f336), [`f708627`](https://github.com/LedgerHQ/device-sdk-ts/commit/f708627965617b40951016448b8f90d71c19a2f8), [`6dd1534`](https://github.com/LedgerHQ/device-sdk-ts/commit/6dd153414d0041795e0c145bc70bb5247af7ad92), [`d9e0164`](https://github.com/LedgerHQ/device-sdk-ts/commit/d9e0164d69bede69269d0989c24a8631b9a0875d), [`3b59289`](https://github.com/LedgerHQ/device-sdk-ts/commit/3b592899168ecedfa3698041b77e09764c1cf4d7), [`a25f529`](https://github.com/LedgerHQ/device-sdk-ts/commit/a25f529ed08206d38d00026a3589bbbaa21075bc), [`c5b5cc1`](https://github.com/LedgerHQ/device-sdk-ts/commit/c5b5cc11d0b0dfec4e1e76ecd98d4ad09a6c9d89), [`5018129`](https://github.com/LedgerHQ/device-sdk-ts/commit/501812904cbb7eb519651b4c8dbb613198e1e89c), [`2893f92`](https://github.com/LedgerHQ/device-sdk-ts/commit/2893f92e023741ef33e72dd5bc40e18b42052ca8), [`f25bb8f`](https://github.com/LedgerHQ/device-sdk-ts/commit/f25bb8feec3e733d1ebb13b2d7c7ea08e61fae3e), [`73825aa`](https://github.com/LedgerHQ/device-sdk-ts/commit/73825aaa5869c9026bd1a5a1b142a74a9484662f), [`8cba13a`](https://github.com/LedgerHQ/device-sdk-ts/commit/8cba13a3fb720ecd15b2464c45be30fc9851bd0a), [`861f9c5`](https://github.com/LedgerHQ/device-sdk-ts/commit/861f9c56b7b10034df156e369400dfd614b545f1)]: + - @ledgerhq/device-management-kit@0.4.0 diff --git a/packages/signer/keyring-btc/package.json b/packages/signer/keyring-btc/package.json index 8d7dd2e75..d345806a3 100644 --- a/packages/signer/keyring-btc/package.json +++ b/packages/signer/keyring-btc/package.json @@ -1,6 +1,6 @@ { "name": "@ledgerhq/keyring-btc", - "version": "0.0.1", + "version": "1.0.0", "license": "MIT", "main": "lib/cjs/index.js", "types": "lib/cjs/index.d.ts", diff --git a/packages/signer/keyring-eth/CHANGELOG.md b/packages/signer/keyring-eth/CHANGELOG.md new file mode 100644 index 000000000..180d1b266 --- /dev/null +++ b/packages/signer/keyring-eth/CHANGELOG.md @@ -0,0 +1,67 @@ +# @ledgerhq/device-signer-kit-ethereum + +## 1.0.0 + +### Minor Changes + +- [#228](https://github.com/LedgerHQ/device-sdk-ts/pull/228) [`25a7e5c`](https://github.com/LedgerHQ/device-sdk-ts/commit/25a7e5c60622858ecc6941eb12d68288ebf68e37) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Implement SignTransactionUseCase + +- [#172](https://github.com/LedgerHQ/device-sdk-ts/pull/172) [`8cba13a`](https://github.com/LedgerHQ/device-sdk-ts/commit/8cba13a3fb720ecd15b2464c45be30fc9851bd0a) Thanks [@jdabbech-ledger](https://github.com/jdabbech-ledger)! - Use of CommandResult return type in commands + +### Patch Changes + +- [#147](https://github.com/LedgerHQ/device-sdk-ts/pull/147) [`fd022bc`](https://github.com/LedgerHQ/device-sdk-ts/commit/fd022bc4a3c6b84fcfedadb0b618ab0f3f1bf43c) Thanks [@aussedatlo](https://github.com/aussedatlo)! - add GetAddressCommand implementation + +- [#185](https://github.com/LedgerHQ/device-sdk-ts/pull/185) [`a31c298`](https://github.com/LedgerHQ/device-sdk-ts/commit/a31c29851efd6b4eca4503a17b04857788f003b9) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Implement SetPluginCommand + +- [#191](https://github.com/LedgerHQ/device-sdk-ts/pull/191) [`25cd8f2`](https://github.com/LedgerHQ/device-sdk-ts/commit/25cd8f2bd647e5d69783e17178a958eedd1d3836) Thanks [@paoun-ledger](https://github.com/paoun-ledger)! - Implement SendEIP712FilteringCommand + +- [#189](https://github.com/LedgerHQ/device-sdk-ts/pull/189) [`7a7113e`](https://github.com/LedgerHQ/device-sdk-ts/commit/7a7113e64707364d3873281cb97f74de06c7a2ae) Thanks [@paoun-ledger](https://github.com/paoun-ledger)! - Implement SignEIP712Command + +- [#171](https://github.com/LedgerHQ/device-sdk-ts/pull/171) [`77a4938`](https://github.com/LedgerHQ/device-sdk-ts/commit/77a4938d34df103e41e8efa203ac4fc793d9d420) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Implement ProvideTokenInformationCommand + +- [#231](https://github.com/LedgerHQ/device-sdk-ts/pull/231) [`74ea2fc`](https://github.com/LedgerHQ/device-sdk-ts/commit/74ea2fc64e51e5206d1ef5b86ee27cfc6a12edc1) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Add README for device-signer-kit-ethereum module + +- [#160](https://github.com/LedgerHQ/device-sdk-ts/pull/160) [`463132c`](https://github.com/LedgerHQ/device-sdk-ts/commit/463132c253fa7b55f6d7dd5bf3d2ed0e78866bd0) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Add transaction serialized to transaction mapper result + +- [#183](https://github.com/LedgerHQ/device-sdk-ts/pull/183) [`5c68f48`](https://github.com/LedgerHQ/device-sdk-ts/commit/5c68f48c25edde30416598bb2152ba5077820724) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Implement ProvideNFTInformationCommand + +- [#150](https://github.com/LedgerHQ/device-sdk-ts/pull/150) [`69e1ad1`](https://github.com/LedgerHQ/device-sdk-ts/commit/69e1ad154eaedc15135765d3095ad9979bf8baf0) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Implement GetChallengeCommand + +- [#209](https://github.com/LedgerHQ/device-sdk-ts/pull/209) [`c5b5cc1`](https://github.com/LedgerHQ/device-sdk-ts/commit/c5b5cc11d0b0dfec4e1e76ecd98d4ad09a6c9d89) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Implement sign message + +- [#216](https://github.com/LedgerHQ/device-sdk-ts/pull/216) [`16b84b0`](https://github.com/LedgerHQ/device-sdk-ts/commit/16b84b04413ad9602f1dad6b8229d8d0afec185b) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Implement ProvideTransactionContextTask + +- [#195](https://github.com/LedgerHQ/device-sdk-ts/pull/195) [`b5d81da`](https://github.com/LedgerHQ/device-sdk-ts/commit/b5d81da73ff4c5ee9fdce231f218dcfd40e28083) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Implement SendEIP712StructImplemCommand + +- [#215](https://github.com/LedgerHQ/device-sdk-ts/pull/215) [`43eb989`](https://github.com/LedgerHQ/device-sdk-ts/commit/43eb989569e14c9f61356099545204a71c2032a8) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Add BuildTransactionContextTask + +- [#197](https://github.com/LedgerHQ/device-sdk-ts/pull/197) [`fd25324`](https://github.com/LedgerHQ/device-sdk-ts/commit/fd253248a05504d4f2a3b7310bd62132c12d05f7) Thanks [@jdabbech-ledger](https://github.com/jdabbech-ledger)! - Add Set External Plugin command + +- [#290](https://github.com/LedgerHQ/device-sdk-ts/pull/290) [`6dd1534`](https://github.com/LedgerHQ/device-sdk-ts/commit/6dd153414d0041795e0c145bc70bb5247af7ad92) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Rename packages + +- [#174](https://github.com/LedgerHQ/device-sdk-ts/pull/174) [`899d151`](https://github.com/LedgerHQ/device-sdk-ts/commit/899d15152c2cf67b19cb6ca83dc1fbbd0e79ae27) Thanks [@jiyuzhuang](https://github.com/jiyuzhuang)! - Implement ProvideDomainNameCommand + +- [#148](https://github.com/LedgerHQ/device-sdk-ts/pull/148) [`dc23eba`](https://github.com/LedgerHQ/device-sdk-ts/commit/dc23eba464db8e7a19e60fe2f9ffb6254f8b2886) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Implement TransactionMapperService + +- [#203](https://github.com/LedgerHQ/device-sdk-ts/pull/203) [`efe5167`](https://github.com/LedgerHQ/device-sdk-ts/commit/efe51677c3adcd858c497c2ae48061c9cb2ec460) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Bump ethers to v6.13.2 + +- [#227](https://github.com/LedgerHQ/device-sdk-ts/pull/227) [`c148662`](https://github.com/LedgerHQ/device-sdk-ts/commit/c14866288678a334cfc9fdeb271ef4e0b3c03061) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Implement SignTransactionDeviceAction + +- [#176](https://github.com/LedgerHQ/device-sdk-ts/pull/176) [`2a44494`](https://github.com/LedgerHQ/device-sdk-ts/commit/2a4449444d7fecc874f393b2bfc2942fa6cd7c15) Thanks [@jdabbech-ledger](https://github.com/jdabbech-ledger)! - Add SignPersonalMessage command + +- [#186](https://github.com/LedgerHQ/device-sdk-ts/pull/186) [`5018129`](https://github.com/LedgerHQ/device-sdk-ts/commit/501812904cbb7eb519651b4c8dbb613198e1e89c) Thanks [@paoun-ledger](https://github.com/paoun-ledger)! - DSDK-420 Implement the EIP712 TypedData parser service + +- [#187](https://github.com/LedgerHQ/device-sdk-ts/pull/187) [`48a348a`](https://github.com/LedgerHQ/device-sdk-ts/commit/48a348ae1a275722303f2fc9380863fff25d375f) Thanks [@paoun-ledger](https://github.com/paoun-ledger)! - Implement SendEIP712StructDefinitionCommand + +- [#210](https://github.com/LedgerHQ/device-sdk-ts/pull/210) [`64341a7`](https://github.com/LedgerHQ/device-sdk-ts/commit/64341a7630f6e691fecb3db3c608dc5cd7983f36) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Add SendSignTransactionTask and simplify SendTransactionCommand + +- [#156](https://github.com/LedgerHQ/device-sdk-ts/pull/156) [`a25f529`](https://github.com/LedgerHQ/device-sdk-ts/commit/a25f529ed08206d38d00026a3589bbbaa21075bc) Thanks [@ofreyssinet-ledger](https://github.com/ofreyssinet-ledger)! - Implementation of getAddress using SendCommandInAppDeviceAction and the GetAddress command + +- [#159](https://github.com/LedgerHQ/device-sdk-ts/pull/159) [`861f9c5`](https://github.com/LedgerHQ/device-sdk-ts/commit/861f9c56b7b10034df156e369400dfd614b545f1) Thanks [@aussedatlo](https://github.com/aussedatlo)! - Implement SignTransactionCommand + +- [#159](https://github.com/LedgerHQ/device-sdk-ts/pull/159) [`861f9c5`](https://github.com/LedgerHQ/device-sdk-ts/commit/861f9c56b7b10034df156e369400dfd614b545f1) Thanks [@aussedatlo](https://github.com/aussedatlo)! - add HexaString to handle `0x${string}` type + +- Updated dependencies [[`1f1485b`](https://github.com/LedgerHQ/device-sdk-ts/commit/1f1485be90f073d73f7013d6b755f1234e481844), [`899d151`](https://github.com/LedgerHQ/device-sdk-ts/commit/899d15152c2cf67b19cb6ca83dc1fbbd0e79ae27), [`41892b3`](https://github.com/LedgerHQ/device-sdk-ts/commit/41892b3dbd27c71b091d4c8203286702a81f380b), [`c6822ba`](https://github.com/LedgerHQ/device-sdk-ts/commit/c6822ba275946200333a8e64f240bf52c62e649c), [`0ef0626`](https://github.com/LedgerHQ/device-sdk-ts/commit/0ef06260b4cf87c3cb41fe2819e8efd849b2f336), [`f708627`](https://github.com/LedgerHQ/device-sdk-ts/commit/f708627965617b40951016448b8f90d71c19a2f8), [`efe5167`](https://github.com/LedgerHQ/device-sdk-ts/commit/efe51677c3adcd858c497c2ae48061c9cb2ec460), [`6dd1534`](https://github.com/LedgerHQ/device-sdk-ts/commit/6dd153414d0041795e0c145bc70bb5247af7ad92), [`d9e0164`](https://github.com/LedgerHQ/device-sdk-ts/commit/d9e0164d69bede69269d0989c24a8631b9a0875d), [`3b59289`](https://github.com/LedgerHQ/device-sdk-ts/commit/3b592899168ecedfa3698041b77e09764c1cf4d7), [`a25f529`](https://github.com/LedgerHQ/device-sdk-ts/commit/a25f529ed08206d38d00026a3589bbbaa21075bc), [`c5b5cc1`](https://github.com/LedgerHQ/device-sdk-ts/commit/c5b5cc11d0b0dfec4e1e76ecd98d4ad09a6c9d89), [`5018129`](https://github.com/LedgerHQ/device-sdk-ts/commit/501812904cbb7eb519651b4c8dbb613198e1e89c), [`16b84b0`](https://github.com/LedgerHQ/device-sdk-ts/commit/16b84b04413ad9602f1dad6b8229d8d0afec185b), [`e073436`](https://github.com/LedgerHQ/device-sdk-ts/commit/e0734365a2cedc79aa7786038d5f47880fba4319), [`2893f92`](https://github.com/LedgerHQ/device-sdk-ts/commit/2893f92e023741ef33e72dd5bc40e18b42052ca8), [`f25bb8f`](https://github.com/LedgerHQ/device-sdk-ts/commit/f25bb8feec3e733d1ebb13b2d7c7ea08e61fae3e), [`73825aa`](https://github.com/LedgerHQ/device-sdk-ts/commit/73825aaa5869c9026bd1a5a1b142a74a9484662f), [`8cba13a`](https://github.com/LedgerHQ/device-sdk-ts/commit/8cba13a3fb720ecd15b2464c45be30fc9851bd0a), [`861f9c5`](https://github.com/LedgerHQ/device-sdk-ts/commit/861f9c56b7b10034df156e369400dfd614b545f1)]: + - @ledgerhq/device-management-kit@0.4.0 + - @ledgerhq/context-module@1.0.0 diff --git a/packages/signer/keyring-eth/package.json b/packages/signer/keyring-eth/package.json index eb20aeabe..6db5c9dbb 100644 --- a/packages/signer/keyring-eth/package.json +++ b/packages/signer/keyring-eth/package.json @@ -1,6 +1,6 @@ { "name": "@ledgerhq/device-signer-kit-ethereum", - "version": "0.0.1", + "version": "1.0.0", "license": "MIT", "main": "lib/cjs/index.js", "types": "lib/cjs/index.d.ts",