diff --git a/.changeset/fair-scissors-sparkle.md b/.changeset/fair-scissors-sparkle.md new file mode 100644 index 00000000000..e727ab4b475 --- /dev/null +++ b/.changeset/fair-scissors-sparkle.md @@ -0,0 +1,6 @@ +--- +"@ledgerhq/hw-app-str": major +"@ledgerhq/live-common": patch +--- + +Refactor `hw-app-str` and add `signSorobanAuthorization`. Please check the changelog and documentation of "@ledgerhq/hw-app-str" for more information. diff --git a/libs/ledger-live-common/src/families/stellar/bridge.integration.test.ts b/libs/ledger-live-common/src/families/stellar/bridge.integration.test.ts index d84e8eb4e49..65670460b93 100644 --- a/libs/ledger-live-common/src/families/stellar/bridge.integration.test.ts +++ b/libs/ledger-live-common/src/families/stellar/bridge.integration.test.ts @@ -27,17 +27,17 @@ const dataset: DatasetTest = { { name: "stellar seed 1", apdus: ` - => e002000017038000002c8000009480000000766961206c756d696e61 + => e00200000d038000002c8000009480000000 <= 27c586f8499294c64d57f8d7956eef4431de58ab20e1c88001f6cf131c97d6f39000 - => e002000017038000002c8000009480000001766961206c756d696e61 + => e00200000d038000002c8000009480000001 <= 1174242cc3e722e843ac37db3a745897941396d486456e303001b06b417db1f89000 - => e002000017038000002c8000009480000002766961206c756d696e61 + => e00200000d038000002c8000009480000002 <= 8636fa7a5a5bb9fe4fb2615f04425f54dc74c16fefc1325958c9719ee03ef5379000 - => e002000017038000002c8000009480000003766961206c756d696e61 + => e00200000d038000002c8000009480000003 <= 0f052ff4b74726a6f668380927c3d23e9c16d538cb6c272add871e069336bead9000 - => e002000017038000002c8000009480000004766961206c756d696e61 + => e00200000d038000002c8000009480000004 <= 60c75356c268ff0158eeca556526830761327693a93cf4754020fadbe04d0f2b9000 - => e002000017038000002c8000009480000005766961206c756d696e61 + => e00200000d038000002c8000009480000005 <= 124516f8ffb161c9492486e54b4432a2c11e4817414dea54fb8bde13b5ac49439000 `, }, diff --git a/libs/ledger-live-common/src/families/stellar/hw-getAddress.ts b/libs/ledger-live-common/src/families/stellar/hw-getAddress.ts index 63a8c3f4d88..6f69aa1a031 100644 --- a/libs/ledger-live-common/src/families/stellar/hw-getAddress.ts +++ b/libs/ledger-live-common/src/families/stellar/hw-getAddress.ts @@ -1,13 +1,15 @@ import Stellar from "@ledgerhq/hw-app-str"; import type { Resolver } from "../../hw/getAddress/types"; +import { StrKey } from "@stellar/stellar-sdk"; const resolver: Resolver = async (transport, { path, verify }) => { const stellar = new Stellar(transport); - const r = await stellar.getPublicKey(path, false, verify); + const { rawPublicKey } = await stellar.getPublicKey(path, verify); + const publicKey = StrKey.encodeEd25519PublicKey(rawPublicKey); return { path, - address: r.publicKey, - publicKey: r.raw.toString("hex"), + address: publicKey, + publicKey: rawPublicKey.toString("hex"), }; }; diff --git a/libs/ledgerjs/packages/hw-app-str/CHANGELOG.md b/libs/ledgerjs/packages/hw-app-str/CHANGELOG.md index 83696e863c8..0a77b837a2f 100644 --- a/libs/ledgerjs/packages/hw-app-str/CHANGELOG.md +++ b/libs/ledgerjs/packages/hw-app-str/CHANGELOG.md @@ -1,5 +1,20 @@ # @ledgerhq/hw-app-str +## 7.0.0 + +### Major Changes + +- `Str.getPublicKey`'s function signature has changed. Previously, it was `getPublicKey(path: string, boolValidate?: boolean, boolDisplay?: boolean): Promise<{ publicKey: string; raw: Buffer; }>` and now it is `async getPublicKey(path: string, display = false): Promise<{ rawPublicKey: Buffer }>` +- `Str.signTransaction` will no longer automatically fallback to `Str.signHash`. If you want to sign a hash, you have to call `Str.signHash` directly. +- Removed the fixed limit on the maximum length of the transaction in `Str.signTransaction`. Currently, if the transaction is too large for the device to handle, `StellarUserRefusedError` will be thrown. +- Add `Str.signSorobanAuthorization` method to sign Stellar Soroban authorization. +- `Str.getAppConfiguration` now returns `maxDataSize`, it represents the maximum size of the data that the device can processed. +- Add error classes for better error handling, check the documentation for more information: + - `StellarUserRefusedError` + - `StellarHashSigningNotEnabledError` + - `StellarDataTooLargeError` + - `StellarDataParsingFailedError` + ## 6.28.6 ### Patch Changes diff --git a/libs/ledgerjs/packages/hw-app-str/README.md b/libs/ledgerjs/packages/hw-app-str/README.md index 7071e24b85c..b528a680971 100644 --- a/libs/ledgerjs/packages/hw-app-str/README.md +++ b/libs/ledgerjs/packages/hw-app-str/README.md @@ -1,62 +1,14 @@ -## Ledger Stellar app API +[GitHub](https://github.com/LedgerHQ/ledger-live/), +[Ledger Devs Discord](https://developers.ledger.com/discord-pro), +[Developer Portal](https://developers.ledger.com/) -## Usage +## @ledgerhq/hw-app-str +Ledger Hardware Wallet Stellar JavaScript bindings. -```js -// when using "@ledgerhq/hw-transport-node-hid" library you need to go to -// Settings -> Browser support in ledger stellar app and set this setting to 'No' -import Transport from "@ledgerhq/hw-transport-node-hid"; -// import Transport from "@ledgerhq/hw-transport-u2f"; // for browser -import Str from "@ledgerhq/hw-app-str"; -import * as StellarSdk from "@stellar/stellar-sdk"; - -const getStrAppVersion = async () => { - const transport = await Transport.create(); - const str = new Str(transport); - const result = await str.getAppConfiguration(); - return result.version; -} -getStrAppVersion().then(v => console.log(v)); - -const getStrPublicKey = async () => { - const transport = await Transport.create(); - const str = new Str(transport); - const result = await str.getPublicKey("44'/148'/0'"); - return result.publicKey; -}; -let publicKey; -getStrPublicKey().then(pk => { - console.log(pk); - publicKey = pk; -}); - -const signStrTransaction = async (publicKey) => { - const transaction = new StellarSdk.TransactionBuilder({accountId: () => publicKey, sequenceNumber: () => '1234', incrementSequenceNumber: () => null}) - .addOperation(StellarSdk.Operation.createAccount({ - source: publicKey, - destination: 'GBLYVYCCCRYTZTWTWGOMJYKEGQMTH2U3X4R4NUI7CUGIGEJEKYD5S5OJ', // SATIS5GR33FXKM7HVWZ2UQO33GM66TVORZUEF2HPUQ3J7K634CTOAWQ7 - startingBalance: '11.331', - })) - .build(); - const transport = await Transport.create(); - const str = new Str(transport); - const result = await str.signTransaction("44'/148'/0'", transaction.signatureBase()); - - // add signature to transaction - const keyPair = StellarSdk.Keypair.fromPublicKey(publicKey); - const hint = keyPair.signatureHint(); - const decorated = new StellarSdk.xdr.DecoratedSignature({hint: hint, signature: result.signature}); - transaction.signatures.push(decorated); - - return transaction; -} -signStrTransaction(publicKey).then(transaction => console.log(transaction.toEnvelope().toXDR().toString('base64'))); -``` - ---- +*** ## Are you adding Ledger support to your software wallet? @@ -64,7 +16,159 @@ You may be using this package to communicate with the Stellar Nano App. For a smooth and quick integration: -- See the developers’ documentation on the [Developer Portal](https://developers.ledger.com/docs/transport/overview/) and -- Go on [Discord](https://developers.ledger.com/discord-pro/) to chat with developer support and the developer community. +* See the developers’ documentation on the [Developer Portal](https://developers.ledger.com/docs/transport/overview/) and +* Go on [Discord](https://developers.ledger.com/discord-pro/) to chat with developer support and the developer community. + +*** + +## Errors handling + +All functions may throw an error, it's important to handle the errors properly. + +We have written corresponding classes for exceptions that developers should actively handle, you can find them in the [API](#api) section. + +*** + +## API + + + +#### Table of Contents + +* [StellarHashSigningNotEnabledError](#stellarhashsigningnotenablederror) +* [StellarDataParsingFailedError](#stellardataparsingfailederror) +* [StellarUserRefusedError](#stellaruserrefusederror) +* [StellarDataTooLargeError](#stellardatatoolargeerror) +* [Str](#str) + * [Parameters](#parameters) + * [Examples](#examples) + * [getAppConfiguration](#getappconfiguration) + * [Examples](#examples-1) + * [getPublicKey](#getpublickey) + * [Parameters](#parameters-1) + * [Examples](#examples-2) + * [signTransaction](#signtransaction) + * [Parameters](#parameters-2) + * [Examples](#examples-3) + * [signSorobanAuthorization](#signsorobanauthorization) + * [Parameters](#parameters-3) + * [Examples](#examples-4) + * [signHash](#signhash) + * [Parameters](#parameters-4) + * [Examples](#examples-5) + +### StellarHashSigningNotEnabledError + +Error thrown when hash signing is not enabled on the device. + +### StellarDataParsingFailedError + +Error thrown when data parsing fails. + +For example, when parsing the transaction fails, this error is thrown. + +### StellarUserRefusedError + +Error thrown when the user refuses the request on the device. + +### StellarDataTooLargeError + +Error thrown when the data is too large to be processed by the device. + +### Str + +Stellar API + +#### Parameters + +* `transport` **Transport** a transport for sending commands to a device +* `scrambleKey` a scramble key (optional, default `"l0v"`) + +#### Examples + +```javascript +import Str from "@ledgerhq/hw-app-str"; +const str = new Str(transport) +``` + +#### getAppConfiguration + +Get Stellar application configuration. + +##### Examples + +```javascript +str.getAppConfiguration().then(o => o.version) +``` + +Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<{version: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), hashSigningEnabled: [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean), maxDataSize: [number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)?}>** an object with the application configuration, including the version, +whether hash signing is enabled, and the maximum data size in bytes that the device can sign. + +#### getPublicKey + +Get Stellar raw public key for a given BIP 32 path. + +##### Parameters + +* `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** a path in BIP 32 format +* `display` if true, the device will ask the user to confirm the address on the device, if false, it will return the raw public key directly (optional, default `false`) + +##### Examples + +```javascript +str.getPublicKey("44'/148'/0'").then(o => o.rawPublicKey) +``` + +Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<{rawPublicKey: [Buffer](https://nodejs.org/api/buffer.html)}>** an object with the raw ed25519 public key. +If you want to convert it to string, you can use [StrKey.encodeEd25519PublicKey](https://stellar.github.io/js-stellar-base/StrKey.html#.encodeEd25519PublicKey) + +#### signTransaction + +Sign a Stellar transaction. + +##### Parameters + +* `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** a path in BIP 32 format +* `transaction` **[Buffer](https://nodejs.org/api/buffer.html)** [signature base](https://stellar.github.io/js-stellar-base/Transaction.html#signatureBase) of the transaction to sign + +##### Examples + +```javascript +str.signTransaction("44'/148'/0'", signatureBase).then(o => o.signature) +``` + +Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<{signature: [Buffer](https://nodejs.org/api/buffer.html)}>** an object with the signature + +#### signSorobanAuthorization + +Sign a Stellar Soroban authorization. + +##### Parameters + +* `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** a path in BIP 32 format +* `hashIdPreimage` **[Buffer](https://nodejs.org/api/buffer.html)** the [Soroban authorization hashIdPreimage](https://github.com/stellar/stellar-xdr/blob/1a04392432dacc0092caaeae22a600ea1af3c6a5/Stellar-transaction.x#L702-L709) to sign + +##### Examples + +```javascript +str.signSorobanAuthorization("44'/148'/0'", hashIdPreimage).then(o => o.signature) +``` + +Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<{signature: [Buffer](https://nodejs.org/api/buffer.html)}>** an object with the signature + +#### signHash + +Sign a hash. + +##### Parameters + +* `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** a path in BIP 32 format +* `hash` **[Buffer](https://nodejs.org/api/buffer.html)** the hash to sign + +##### Examples + +```javascript +str.signHash("44'/148'/0'", hash).then(o => o.signature) +``` ---- +Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<{signature: [Buffer](https://nodejs.org/api/buffer.html)}>** an object with the signature diff --git a/libs/ledgerjs/packages/hw-app-str/package.json b/libs/ledgerjs/packages/hw-app-str/package.json index 8f420732fb9..cee98faabc9 100644 --- a/libs/ledgerjs/packages/hw-app-str/package.json +++ b/libs/ledgerjs/packages/hw-app-str/package.json @@ -1,6 +1,6 @@ { "name": "@ledgerhq/hw-app-str", - "version": "6.28.6", + "version": "7.0.0", "description": "Ledger Hardware Wallet Stellar Application API", "keywords": [ "Ledger", @@ -28,9 +28,8 @@ "license": "Apache-2.0", "dependencies": { "@ledgerhq/hw-transport": "workspace:^", - "base32.js": "^0.1.0", - "sha.js": "^2.3.6", - "tweetnacl": "^1.0.3" + "@ledgerhq/errors": "workspace:^", + "bip32-path": "^0.4.2" }, "devDependencies": { "@ledgerhq/hw-transport-mocker": "workspace:^", @@ -48,6 +47,7 @@ "build": "tsc && tsc -m ES6 --outDir lib-es", "prewatch": "pnpm build", "watch": "tsc --watch", + "doc": "documentation readme src/** --section=API --pe ts --re ts --re d.ts", "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache", "lint:fix": "pnpm lint --fix", "test": "jest", diff --git a/libs/ledgerjs/packages/hw-app-str/src/Str.ts b/libs/ledgerjs/packages/hw-app-str/src/Str.ts index 0bbd91e5287..9089a96f662 100644 --- a/libs/ledgerjs/packages/hw-app-str/src/Str.ts +++ b/libs/ledgerjs/packages/hw-app-str/src/Str.ts @@ -15,301 +15,235 @@ * limitations under the License. ********************************************************************************/ import type Transport from "@ledgerhq/hw-transport"; +import BIPPath from "bip32-path"; import { - splitPath, - foreach, - encodeEd25519PublicKey, - verifyEd25519Signature, - checkStellarBip32Path, - hash, -} from "./utils"; + StellarHashSigningNotEnabledError, + StellarDataParsingFailedError, + StellarUserRefusedError, + StellarDataTooLargeError, +} from "./errors"; + const CLA = 0xe0; +const P1_FIRST = 0x00; +const P1_MORE = 0x80; +const P2_LAST = 0x00; +const P2_MORE = 0x80; +const P2_NON_CONFIRM = 0x00; // for getPublicKey +const P2_CONFIRM = 0x01; // for getPublicKey + const INS_GET_PK = 0x02; const INS_SIGN_TX = 0x04; const INS_GET_CONF = 0x06; -const INS_SIGN_TX_HASH = 0x08; -const INS_KEEP_ALIVE = 0x10; -const APDU_MAX_SIZE = 150; -const P1_FIRST_APDU = 0x00; -const P1_MORE_APDU = 0x80; -const P2_LAST_APDU = 0x00; -const P2_MORE_APDU = 0x80; -const SW_OK = 0x9000; -const SW_CANCEL = 0x6985; -const SW_UNKNOWN_OP = 0x6c24; -const SW_MULTI_OP = 0x6c25; -const SW_NOT_ALLOWED = 0x6c66; -const SW_UNSUPPORTED = 0x6d00; -const SW_KEEP_ALIVE = 0x6e02; -const TX_MAX_SIZE = 1540; +const INS_SIGN_HASH = 0x08; +const INS_SIGN_SOROBAN_AUTHORIZATION = 0x0a; + +const APDU_MAX_PAYLOAD = 255; + +const SW_DENY = 0x6985; +const SW_HASH_SIGNING_MODE_NOT_ENABLED = 0x6c66; +const SW_DATA_TOO_LARGE = 0xb004; +const SW_DATA_PARSING_FAIL = 0xb005; + /** * Stellar API * + * @param transport a transport for sending commands to a device + * @param scrambleKey a scramble key + * * @example * import Str from "@ledgerhq/hw-app-str"; * const str = new Str(transport) */ - export default class Str { - transport: Transport; + private transport: Transport; constructor(transport: Transport, scrambleKey = "l0v") { this.transport = transport; transport.decorateAppAPIMethods( this, - ["getAppConfiguration", "getPublicKey", "signTransaction", "signHash"], + [ + "getAppConfiguration", + "getPublicKey", + "signTransaction", + "signSorobanAuthorization", + "signHash", + ], scrambleKey, ); } - getAppConfiguration(): Promise<{ + /** + * Get Stellar application configuration. + * + * @returns an object with the application configuration, including the version, + * whether hash signing is enabled, and the maximum data size in bytes that the device can sign. + * @example + * str.getAppConfiguration().then(o => o.version) + */ + async getAppConfiguration(): Promise<{ version: string; + hashSigningEnabled: boolean; + maxDataSize?: number; }> { - return this.transport.send(CLA, INS_GET_CONF, 0x00, 0x00).then(response => { - const multiOpsEnabled = response[0] === 0x01 || response[1] < 0x02; - const version = "" + response[1] + "." + response[2] + "." + response[3]; - return { - version: version, - multiOpsEnabled: multiOpsEnabled, - }; - }); + const resp = await this.sendToDevice(INS_GET_CONF, Buffer.alloc(0)); + const [hashSigningEnabled, major, minor, patch, maxDataSizeHi, maxDataSizeLo] = resp; + return { + hashSigningEnabled: hashSigningEnabled === 0x01, + version: `${major}.${minor}.${patch}`, + maxDataSize: resp.length > 4 ? (maxDataSizeHi << 8) | maxDataSizeLo : undefined, // For compatibility with older app, let's remove this in the future + }; } /** - * get Stellar public key for a given BIP 32 path. + * Get Stellar raw public key for a given BIP 32 path. + * * @param path a path in BIP 32 format - * @option boolValidate optionally enable key pair validation - * @option boolDisplay optionally enable or not the display - * @return an object with the publicKey (using XLM public key format) and - * the raw ed25519 public key. + * @param display if true, the device will ask the user to confirm the address on the device, if false, it will return the raw public key directly + * @return an object with the raw ed25519 public key. + * If you want to convert it to string, you can use {@link https://stellar.github.io/js-stellar-base/StrKey.html#.encodeEd25519PublicKey StrKey.encodeEd25519PublicKey} * @example - * str.getPublicKey("44'/148'/0'").then(o => o.publicKey) + * str.getPublicKey("44'/148'/0'").then(o => o.rawPublicKey) */ - getPublicKey( - path: string, - boolValidate?: boolean, - boolDisplay?: boolean, - ): Promise<{ - publicKey: string; - raw: Buffer; - }> { - checkStellarBip32Path(path); - const apdus: Buffer[] = []; - let response; - const pathElts = splitPath(path); - const buffer = Buffer.alloc(1 + pathElts.length * 4); - buffer[0] = pathElts.length; - pathElts.forEach((element, index) => { - buffer.writeUInt32BE(element, 1 + 4 * index); - }); - const verifyMsg = Buffer.from("via lumina", "ascii"); - apdus.push(Buffer.concat([buffer, verifyMsg])); - let keepAlive = false; - return foreach(apdus, data => - this.transport - .send( - CLA, - keepAlive ? INS_KEEP_ALIVE : INS_GET_PK, - boolValidate ? 0x01 : 0x00, - boolDisplay ? 0x01 : 0x00, - data, - [SW_OK, SW_KEEP_ALIVE], - ) - .then(apduResponse => { - const status = Buffer.from(apduResponse.slice(apduResponse.length - 2)).readUInt16BE(0); - - if (status === SW_KEEP_ALIVE) { - keepAlive = true; - apdus.push(Buffer.alloc(0)); - } - - response = apduResponse; - }), - ).then(() => { - // response = Buffer.from(response, 'hex'); - let offset = 0; - const rawPublicKey = response.slice(offset, offset + 32); - offset += 32; - const publicKey = encodeEd25519PublicKey(rawPublicKey); - - if (boolValidate) { - const signature = response.slice(offset, offset + 64); - - if (!verifyEd25519Signature(verifyMsg, signature, rawPublicKey)) { - throw new Error("Bad signature. Keypair is invalid. Please report this."); - } - } - - return { - publicKey: publicKey, - raw: rawPublicKey, - }; - }); + async getPublicKey(path: string, display = false): Promise<{ rawPublicKey: Buffer }> { + const pathBuffer = pathToBuffer(path); + const p2 = display ? P2_CONFIRM : P2_NON_CONFIRM; + try { + const data = await this.transport.send(CLA, INS_GET_PK, P1_FIRST, p2, pathBuffer); + return { rawPublicKey: data.slice(0, -2) }; + } catch (e) { + throw remapErrors(e); + } } /** - * sign a Stellar transaction. + * Sign a Stellar transaction. + * * @param path a path in BIP 32 format - * @param transaction signature base of the transaction to sign - * @return an object with the signature and the status + * @param transaction {@link https://stellar.github.io/js-stellar-base/Transaction.html#signatureBase signature base} of the transaction to sign + * @return an object with the signature * @example * str.signTransaction("44'/148'/0'", signatureBase).then(o => o.signature) */ - signTransaction( + async signTransaction( path: string, transaction: Buffer, ): Promise<{ signature: Buffer; }> { - checkStellarBip32Path(path); - - if (transaction.length > TX_MAX_SIZE) { - throw new Error( - "Transaction too large: max = " + TX_MAX_SIZE + "; actual = " + transaction.length, - ); - } - - const apdus: Buffer[] = []; - let response; - const pathElts = splitPath(path); - const bufferSize = 1 + pathElts.length * 4; - const buffer = Buffer.alloc(bufferSize); - buffer[0] = pathElts.length; - pathElts.forEach(function (element, index) { - buffer.writeUInt32BE(element, 1 + 4 * index); - }); - let chunkSize = APDU_MAX_SIZE - bufferSize; - - if (transaction.length <= chunkSize) { - // it fits in a single apdu - apdus.push(Buffer.concat([buffer, transaction])); - } else { - // we need to send multiple apdus to transmit the entire transaction - let chunk = Buffer.alloc(chunkSize); - let offset = 0; - transaction.copy(chunk, 0, offset, chunkSize); - apdus.push(Buffer.concat([buffer, chunk])); - offset += chunkSize; - - while (offset < transaction.length) { - const remaining = transaction.length - offset; - chunkSize = remaining < APDU_MAX_SIZE ? remaining : APDU_MAX_SIZE; - chunk = Buffer.alloc(chunkSize); - transaction.copy(chunk, 0, offset, offset + chunkSize); - offset += chunkSize; - apdus.push(chunk); - } - } - - let keepAlive = false; - return foreach(apdus, (data, i) => - this.transport - .send( - CLA, - keepAlive ? INS_KEEP_ALIVE : INS_SIGN_TX, - i === 0 ? P1_FIRST_APDU : P1_MORE_APDU, - i === apdus.length - 1 ? P2_LAST_APDU : P2_MORE_APDU, - data, - [SW_OK, SW_CANCEL, SW_UNKNOWN_OP, SW_MULTI_OP, SW_KEEP_ALIVE], - ) - .then(apduResponse => { - const status = Buffer.from(apduResponse.slice(apduResponse.length - 2)).readUInt16BE(0); - - if (status === SW_KEEP_ALIVE) { - keepAlive = true; - apdus.push(Buffer.alloc(0)); - } - - response = apduResponse; - }), - ).then(() => { - const status = Buffer.from(response.slice(response.length - 2)).readUInt16BE(0); - - if (status === SW_OK) { - const signature = Buffer.from(response.slice(0, response.length - 2)); - return { - signature: signature, - }; - } else if (status === SW_UNKNOWN_OP) { - // pre-v2 app version: fall back on hash signing - return this.signHash_private(path, hash(transaction)); - } else if (status === SW_MULTI_OP) { - // multi-operation transaction: attempt hash signing - return this.signHash_private(path, hash(transaction)); - } else { - throw new Error("Transaction approval request was rejected"); - } - }); + const pathBuffer = pathToBuffer(path); + const payload = Buffer.concat([pathBuffer, transaction]); + const resp = await this.sendToDevice(INS_SIGN_TX, payload); + return { signature: resp }; } /** - * sign a Stellar transaction hash. + * Sign a Stellar Soroban authorization. + * * @param path a path in BIP 32 format - * @param hash hash of the transaction to sign + * @param hashIdPreimage the {@link https://github.com/stellar/stellar-xdr/blob/1a04392432dacc0092caaeae22a600ea1af3c6a5/Stellar-transaction.x#L702-L709 Soroban authorization hashIdPreimage} to sign * @return an object with the signature * @example - * str.signHash("44'/148'/0'", hash).then(o => o.signature) + * str.signSorobanAuthorization("44'/148'/0'", hashIdPreimage).then(o => o.signature) */ - signHash( + async signSorobanAuthorization( path: string, - hash: Buffer, + hashIdPreimage: Buffer, ): Promise<{ signature: Buffer; }> { - checkStellarBip32Path(path); - return this.signHash_private(path, hash); + const pathBuffer = pathToBuffer(path); + const payload = Buffer.concat([pathBuffer, hashIdPreimage]); + const resp = await this.sendToDevice(INS_SIGN_SOROBAN_AUTHORIZATION, payload); + return { signature: resp }; } - signHash_private( + /** + * Sign a hash. + * + * @param path a path in BIP 32 format + * @param hash the hash to sign + * @return an object with the signature + * @example + * str.signHash("44'/148'/0'", hash).then(o => o.signature) + */ + async signHash( path: string, hash: Buffer, ): Promise<{ signature: Buffer; }> { - const apdus: Buffer[] = []; - let response; - const pathElts = splitPath(path); - const buffer = Buffer.alloc(1 + pathElts.length * 4); - buffer[0] = pathElts.length; - pathElts.forEach(function (element, index) { - buffer.writeUInt32BE(element, 1 + 4 * index); - }); - apdus.push(Buffer.concat([buffer, hash])); - let keepAlive = false; - return foreach(apdus, data => - this.transport - .send(CLA, keepAlive ? INS_KEEP_ALIVE : INS_SIGN_TX_HASH, 0x00, 0x00, data, [ - SW_OK, - SW_CANCEL, - SW_NOT_ALLOWED, - SW_UNSUPPORTED, - SW_KEEP_ALIVE, - ]) - .then(apduResponse => { - const status = Buffer.from(apduResponse.slice(apduResponse.length - 2)).readUInt16BE(0); - - if (status === SW_KEEP_ALIVE) { - keepAlive = true; - apdus.push(Buffer.alloc(0)); - } - - response = apduResponse; - }), - ).then(() => { - const status = Buffer.from(response.slice(response.length - 2)).readUInt16BE(0); + const pathBuffer = pathToBuffer(path); + const payload = Buffer.concat([pathBuffer, hash]); + const resp = await this.sendToDevice(INS_SIGN_HASH, payload); + return { signature: resp }; + } - if (status === SW_OK) { - const signature = Buffer.from(response.slice(0, response.length - 2)); - return { - signature: signature, - }; - } else if (status === SW_CANCEL) { - throw new Error("Transaction approval request was rejected"); - } else if (status === SW_UNSUPPORTED) { - throw new Error("Hash signing is not supported"); - } else { - throw new Error("Hash signing not allowed. Have you enabled it in the app settings?"); + private async sendToDevice(instruction: number, payload: Buffer) { + let response: Buffer = Buffer.alloc(0); + let remaining = payload.length; + // eslint-disable-next-line no-constant-condition + while (true) { + const chunkSize = remaining > APDU_MAX_PAYLOAD ? APDU_MAX_PAYLOAD : remaining; + const p1 = remaining === payload.length ? P1_FIRST : P1_MORE; + const p2 = remaining - chunkSize === 0 ? P2_LAST : P2_MORE; + const chunk = payload.slice( + payload.length - remaining, + payload.length - remaining + chunkSize, + ); + response = await this.transport.send(CLA, instruction, p1, p2, chunk).catch(e => { + throw remapErrors(e); + }); + remaining -= chunkSize; + if (remaining === 0) { + break; } - }); + } + return response.slice(0, -2); } } + +const remapErrors = e => { + if (e) { + switch (e.statusCode) { + case SW_DENY: + return new StellarUserRefusedError("User refused the request", undefined, { cause: e }); + case SW_DATA_PARSING_FAIL: + return new StellarDataParsingFailedError("Unable to parse the provided data", undefined, { + cause: e, + }); + case SW_HASH_SIGNING_MODE_NOT_ENABLED: + return new StellarHashSigningNotEnabledError( + "Hash signing not allowed. Have you enabled it in the app settings?", + undefined, + { cause: e }, + ); + case SW_DATA_TOO_LARGE: + return new StellarDataTooLargeError( + "The provided data is too large for the device to process", + undefined, + { cause: e }, + ); + } + } + return e; +}; + +const pathToBuffer = (originalPath: string) => { + const path = originalPath + .split("/") + .map(value => (value.endsWith("'") || value.endsWith("h") ? value : `${value}'`)) + .join("/"); + const pathNums: number[] = BIPPath.fromString(path).toPathArray(); + return serializePath(pathNums); +}; + +const serializePath = (path: number[]) => { + const buf = Buffer.alloc(1 + path.length * 4); + buf.writeUInt8(path.length, 0); + for (const [i, num] of path.entries()) { + buf.writeUInt32BE(num, 1 + i * 4); + } + return buf; +}; + +export * from "./errors"; diff --git a/libs/ledgerjs/packages/hw-app-str/src/errors.ts b/libs/ledgerjs/packages/hw-app-str/src/errors.ts new file mode 100644 index 00000000000..d8aea838b60 --- /dev/null +++ b/libs/ledgerjs/packages/hw-app-str/src/errors.ts @@ -0,0 +1,27 @@ +import { createCustomErrorClass } from "@ledgerhq/errors"; + +/** + * Error thrown when hash signing is not enabled on the device. + */ +export const StellarHashSigningNotEnabledError = createCustomErrorClass( + "StellarHashSigningNotEnabledError", +); + +/** + * Error thrown when data parsing fails. + * + * For example, when parsing the transaction fails, this error is thrown. + */ +export const StellarDataParsingFailedError = createCustomErrorClass( + "StellarDataParsingFailedError", +); + +/** + * Error thrown when the user refuses the request on the device. + */ +export const StellarUserRefusedError = createCustomErrorClass("StellarUserRefusedError"); + +/** + * Error thrown when the data is too large to be processed by the device. + */ +export const StellarDataTooLargeError = createCustomErrorClass("StellarDataTooLargeError"); diff --git a/libs/ledgerjs/packages/hw-app-str/src/utils.ts b/libs/ledgerjs/packages/hw-app-str/src/utils.ts deleted file mode 100644 index 4c89c7cd64d..00000000000 --- a/libs/ledgerjs/packages/hw-app-str/src/utils.ts +++ /dev/null @@ -1,111 +0,0 @@ -/******************************************************************************** - * Ledger Node JS API - * (c) 2017-2018 Ledger - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ********************************************************************************/ -import base32 from "base32.js"; -import nacl from "tweetnacl"; -import { sha256 } from "sha.js"; -// TODO use bip32-path library -export function splitPath(path: string): number[] { - const result: number[] = []; - const components = path.split("/"); - components.forEach(element => { - let number = parseInt(element, 10); - - if (isNaN(number)) { - return; // FIXME shouldn't it throws instead? - } - - if (element.length > 1 && element[element.length - 1] === "'") { - number += 0x80000000; - } - - result.push(number); - }); - return result; -} -export function foreach( - arr: T[], - callback: (arg0: T, arg1: number) => Promise, -): Promise { - function iterate(index, array, result) { - if (index >= array.length) { - return result; - } else { - return callback(array[index], index).then(function (res) { - result.push(res); - return iterate(index + 1, array, result); - }); - } - } - - return Promise.resolve().then(() => iterate(0, arr, [])); -} -export function crc16xmodem(buf: Buffer, previous?: number): number { - let crc = typeof previous !== "undefined" ? ~~previous : 0x0; - - for (let index = 0; index < buf.length; index++) { - const byte = buf[index]; - let code = (crc >>> 8) & 0xff; - code ^= byte & 0xff; - code ^= code >>> 4; - crc = (crc << 8) & 0xffff; - crc ^= code; - code = (code << 5) & 0xffff; - crc ^= code; - code = (code << 7) & 0xffff; - crc ^= code; - } - - return crc; -} -export function encodeEd25519PublicKey(rawPublicKey: Buffer): string { - const versionByte = 6 << 3; // 'G' - - const data = Buffer.from(rawPublicKey); - const versionBuffer = Buffer.from([versionByte]); - const payload = Buffer.concat([versionBuffer, data]); - const checksum = Buffer.alloc(2); - checksum.writeUInt16LE(crc16xmodem(payload), 0); - const unencoded = Buffer.concat([payload, checksum]); - return base32.encode(unencoded); -} -export function verifyEd25519Signature( - data: Buffer, - signature: Buffer, - publicKey: Buffer, -): boolean { - return nacl.sign.detached.verify( - new Uint8Array(data.toJSON().data), - new Uint8Array(signature.toJSON().data), - new Uint8Array(publicKey.toJSON().data), - ); -} -export function hash(data: Buffer) { - const hasher = new sha256(); - hasher.update(data, "utf8"); - return hasher.digest(); -} -export function checkStellarBip32Path(path: string): void { - path.split("/").forEach(function (element) { - if (!element.toString().endsWith("'")) { - throw new Error( - "Detected a non-hardened path element in requested BIP32 path." + - " Non-hardended paths are not supported at this time. Please use an all-hardened path." + - " Example: 44'/148'/0'", - ); - } - }); -} diff --git a/libs/ledgerjs/packages/hw-app-str/tests/Str.test.ts b/libs/ledgerjs/packages/hw-app-str/tests/Str.test.ts index 25a88d4f07f..9c27c9ee92d 100644 --- a/libs/ledgerjs/packages/hw-app-str/tests/Str.test.ts +++ b/libs/ledgerjs/packages/hw-app-str/tests/Str.test.ts @@ -3,53 +3,358 @@ import { RecordStore, } from "@ledgerhq/hw-transport-mocker"; import Str from "../src/Str"; +import { StellarHashSigningNotEnabledError, StellarDataParsingFailedError, StellarUserRefusedError, StellarDataTooLargeError } from "../src/errors"; +import { TransportStatusError } from "@ledgerhq/errors"; -test("getAppConfiguration", async () => { +test("getAppConfiguration (hash signing disabled)", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` => e006000000 - <= 000300019000 + <= 00050401061a9000 `) ); const str = new Str(transport); const result = await str.getAppConfiguration(); - expect(result).toEqual({ multiOpsEnabled: false, version: "3.0.1" }); + expect(result).toEqual({ hashSigningEnabled: false, maxDataSize: 1562, version: "5.4.1" }); }); -test("getPublicKey", async () => { +test("getAppConfiguration (hash signing enabled)", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` - => e002010117038000002c8000009480000000766961206c756d696e61 - <= 7691d85048acc4ed085d9061ce0948bbdf7de6a92b790aaf241d31b7dcaa423881b9f7cb3bd2fad4f0fdab9da1407e8e85f702fa58584fba3104e4549b85ca8046d73a4010870bc4765eff7e0bafcfe91390c4475ba3fcc598750758ed770e0f9000 + => e006000000 + <= 01050401061a9000 + `) + ); + const str = new Str(transport); + const result = await str.getAppConfiguration(); + expect(result).toEqual({ hashSigningEnabled: true, maxDataSize: 1562, version: "5.4.1" }); +}); + +test("getAppConfiguration (old version app < 5.4.1)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e006000000 + <= 000504019000 + `) + ); + const str = new Str(transport); + const result = await str.getAppConfiguration(); + expect(result).toStrictEqual({ hashSigningEnabled: false, maxDataSize: undefined, version: "5.4.1" }); +}); + +test("getPublicKey (without confirm)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00200000d038000002c8000009480000000 + <= 9a222500cf47b03d05edec04ed3294cece1de727ccadb401f47d6b4b230e81a09000 + `) + ); + const str = new Str(transport); + const { rawPublicKey } = await str.getPublicKey("44'/148'/0'", false); + expect(rawPublicKey.toString("hex")).toEqual( + "9a222500cf47b03d05edec04ed3294cece1de727ccadb401f47d6b4b230e81a0" + ); +}); + +test("getPublicKey (with confirm)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00200010d038000002c8000009480000000 + <= 9a222500cf47b03d05edec04ed3294cece1de727ccadb401f47d6b4b230e81a09000 `) ); const str = new Str(transport); - const { publicKey, raw } = await str.getPublicKey("44'/148'/0'", true, true); - expect(publicKey).toEqual( - "GB3JDWCQJCWMJ3IILWIGDTQJJC5567PGVEVXSCVPEQOTDN64VJBDQBYX" + const { rawPublicKey } = await str.getPublicKey("44'/148'/0'", true); + expect(rawPublicKey.toString("hex")).toEqual( + "9a222500cf47b03d05edec04ed3294cece1de727ccadb401f47d6b4b230e81a0" ); - expect(raw.toString("hex")).toEqual( - "7691d85048acc4ed085d9061ce0948bbdf7de6a92b790aaf241d31b7dcaa4238" +}); + +test("getPublicKey (with confirm and rejected)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00200010d038000002c8000009480000000 + <= 6985 + `) ); + const str = new Str(transport); + await expect(str.getPublicKey("44'/148'/0'", true)).rejects.toThrow(StellarUserRefusedError); }); -test("signTransaction", async () => { +test("signHash (hash signing enabled)", async () => { const transport = await openTransportReplayer( RecordStore.fromString(` - => e004008096038000002c80000094800000007ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a979000000020000000020da998b75e42b1f7f85d075c127f5b246df12ad96f010bcf7f76f72b16e57130000006400c5b4a5000000190000000000000000000000010000000000000001000000009541f02746240c1e9f3843d28e56f0a583ecd27502fb0f4a27d4d0922f + => e00800002d038000002c8000009480000000a85c933d37847689825acbdfeb50e66d791814abf03b9be4bd6450abb6c99616 + <= bd4ddd948dd1b27d92672d968eba50b8822eacebf67142f77eda5c5ee9c569c048c90b074a677470984bb203417e911d5b1d74270c2a82772cb80893fca1d70b9000 + `) + ); + const str = new Str(transport); + const hash = Buffer.from("a85c933d37847689825acbdfeb50e66d791814abf03b9be4bd6450abb6c99616", "hex"); + const { signature } = await str.signHash("44'/148'/0'", hash); + const result = signature.toString("hex"); + expect(result).toEqual( + "bd4ddd948dd1b27d92672d968eba50b8822eacebf67142f77eda5c5ee9c569c048c90b074a677470984bb203417e911d5b1d74270c2a82772cb80893fca1d70b" + ); +}) + +test("signHash (hash signing disabled)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00800002d038000002c8000009480000000a85c933d37847689825acbdfeb50e66d791814abf03b9be4bd6450abb6c99616 + <= 6c66 + `) + ); + const str = new Str(transport); + const hash = Buffer.from("a85c933d37847689825acbdfeb50e66d791814abf03b9be4bd6450abb6c99616", "hex"); + await expect(str.signHash("44'/148'/0'", hash)).rejects.toThrow(StellarHashSigningNotEnabledError); +}) + +test("signHash (rejected)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00800002d038000002c8000009480000000a85c933d37847689825acbdfeb50e66d791814abf03b9be4bd6450abb6c99616 + <= 6985 + `) + ); + const str = new Str(transport); + const hash = Buffer.from("a85c933d37847689825acbdfeb50e66d791814abf03b9be4bd6450abb6c99616", "hex"); + await expect(str.signHash("44'/148'/0'", hash)).rejects.toThrow(StellarUserRefusedError); +}) + +test("signTransaction (size exceeds APDU_MAX_PAYLOAD)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e0040080ff038000002c80000094800000007ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000200000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd000071d7002d9fed000000010000000000000000000000010000000100000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd00000018000000020000024c0061736d0100000001150460027e7e017e60037e7e7e017e6000017e600000021904016c01300000016c01310000016c015f0001016c013800000305040203030305030100100619037f01418080c0000b7f00418080c0000b7f00418080 + <= 9000 + => e0048080ffc0000b073505066d656d6f7279020009696e6372656d656e740004015f00070a5f5f646174615f656e6403010b5f5f686561705f6261736503020aa70104920102017f017e41002100024002400240428ebad0af86d43942021080808080004201520d00428ebad0af86d4394202108180808000220142ff01834204520d012001422088a721000b200041016a2200450d01428ebad0af86d4392000ad422086420484220142021082808080001a4284808080a0064284808080c00c1083808080001a20010f0b00000b108580808000000b0900108680808000000b040000000b02000b00730e636f6e74726163747370656376300000000000000040496e + <= 9000 + => e0048080ff6372656d656e7420696e6372656d656e747320616e20696e7465726e616c20636f756e7465722c20616e642072657475726e73207468652076616c75652e00000009696e6372656d656e74000000000000000000000100000004001e11636f6e7472616374656e766d657461763000000000000000140000003900730e636f6e74726163746d65746176300000000000000005727376657200000000000006312e37332e3000000000000000000008727373646b7665720000003332302e302e302d72633223303939323431336639623035653562666231663837326263653939653839643931323962326536310000000000000000010000000000000001 <= 9000 - => e004800013e064a200000000000000000098968000000000 - <= 79e6da561676d16f17e91ad0dbbe917e3da0fffe660aa9f277669385960b0aec8dcf002b7305b329cc02f2eabd2f20320dee4828b412ed2850b9771ffb23920d9000 + => e00480003c0000000713e16858bde4ab50a006dbf07172288f3ec19d8640d1a853016c60c15c2511170000000000186636000002b000000000000000000000000c + <= 83968afc83b45d0672f5b5259a7bab68ee030f6ffda0c2f3fd550e3b5dd40b3a6bee1f246e760523ab6bd434f9e11f237ffd16bc8f18bb58a59eb94abe1a5a0c9000 + `) + ); + const str = new Str(transport); + const transaction = Buffer.from( + "7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000200000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd000071d7002d9fed000000010000000000000000000000010000000100000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd00000018000000020000024c0061736d0100000001150460027e7e017e60037e7e7e017e6000017e600000021904016c01300000016c01310000016c015f0001016c013800000305040203030305030100100619037f01418080c0000b7f00418080c0000b7f00418080c0000b073505066d656d6f7279020009696e6372656d656e740004015f00070a5f5f646174615f656e6403010b5f5f686561705f6261736503020aa70104920102017f017e41002100024002400240428ebad0af86d43942021080808080004201520d00428ebad0af86d4394202108180808000220142ff01834204520d012001422088a721000b200041016a2200450d01428ebad0af86d4392000ad422086420484220142021082808080001a4284808080a0064284808080c00c1083808080001a20010f0b00000b108580808000000b0900108680808000000b040000000b02000b00730e636f6e74726163747370656376300000000000000040496e6372656d656e7420696e6372656d656e747320616e20696e7465726e616c20636f756e7465722c20616e642072657475726e73207468652076616c75652e00000009696e6372656d656e74000000000000000000000100000004001e11636f6e7472616374656e766d657461763000000000000000140000003900730e636f6e74726163746d65746176300000000000000005727376657200000000000006312e37332e3000000000000000000008727373646b7665720000003332302e302e302d726332233039393234313366396230356535626662316638373262636539396538396439313239623265363100000000000000000100000000000000010000000713e16858bde4ab50a006dbf07172288f3ec19d8640d1a853016c60c15c2511170000000000186636000002b000000000000000000000000c", + "hex" + ); + const { signature } = await str.signTransaction("44'/148'/0'", transaction); + const result = signature.toString("hex"); + expect(result).toEqual( + "83968afc83b45d0672f5b5259a7bab68ee030f6ffda0c2f3fd550e3b5dd40b3a6bee1f246e760523ab6bd434f9e11f237ffd16bc8f18bb58a59eb94abe1a5a0c" + ); +}); + +test("signTransaction (size does not exceed APDU_MAX_PAYLOAD)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e0040000ff038000002c80000094800000007ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000200000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd000071d7002d9fed000000010000000000000000000000010000000100000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd00000018000000020000024c0061736d0100000001150460027e7e017e60037e7e7e017e6000017e600000021904016c01300000016c01310000016c015f0001016c013800000305040203030305030100100619037f01418080c0000b7f00418080c0000b7f00418080 + <= f30f5d707a6dbde681f9604832391295719130790578ecaca0b1d37a68923b15714b36f4507e56aff2d0e3256ed5af496a6b021896e13dca00a8283e426860089000 `) ); const str = new Str(transport); const transaction = Buffer.from( - "7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a979000000020000000020da998b75e42b1f7f85d075c127f5b246df12ad96f010bcf7f76f72b16e57130000006400c5b4a5000000190000000000000000000000010000000000000001000000009541f02746240c1e9f3843d28e56f0a583ecd27502fb0f4a27d4d0922fe064a200000000000000000098968000000000", + "7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000200000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd000071d7002d9fed000000010000000000000000000000010000000100000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd00000018000000020000024c0061736d0100000001150460027e7e017e60037e7e7e017e6000017e600000021904016c01300000016c01310000016c015f0001016c013800000305040203030305030100100619037f01418080c0000b7f00418080c0000b7f00418080", "hex" ); const { signature } = await str.signTransaction("44'/148'/0'", transaction); const result = signature.toString("hex"); expect(result).toEqual( - "79e6da561676d16f17e91ad0dbbe917e3da0fffe660aa9f277669385960b0aec8dcf002b7305b329cc02f2eabd2f20320dee4828b412ed2850b9771ffb23920d" + "f30f5d707a6dbde681f9604832391295719130790578ecaca0b1d37a68923b15714b36f4507e56aff2d0e3256ed5af496a6b021896e13dca00a8283e42686008" + ); +}); + +test("signTransaction (size too long)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e0040080ff038000002c80000094800000007ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000200000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd000071d7002d9fed000000010000000000000000000000010000000100000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd00000018000000020000024c0061736d0100000001150460027e7e017e60037e7e7e017e6000017e600000021904016c01300000016c01310000016c015f0001016c013800000305040203030305030100100619037f01418080c0000b7f00418080c0000b7f00418080 + <= 9000 + => e0048080ffc0000b073505066d656d6f7279020009696e6372656d656e740004015f00070a5f5f646174615f656e6403010b5f5f686561705f6261736503020aa70104920102017f017e41002100024002400240428ebad0af86d43942021080808080004201520d00428ebad0af86d4394202108180808000220142ff01834204520d012001422088a721000b200041016a2200450d01428ebad0af86d4392000ad422086420484220142021082808080001a4284808080a0064284808080c00c1083808080001a20010f0b00000b108580808000000b0900108680808000000b040000000b02000b00730e636f6e74726163747370656376300000000000000040496e + <= 9000 + => e0048080ff6372656d656e7420696e6372656d656e747320616e20696e7465726e616c20636f756e7465722c20616e642072657475726e73207468652076616c75652e00000009696e6372656d656e74000000000000000000000100000004001e11636f6e7472616374656e766d657461763000000000000000140000003900730e636f6e74726163746d65746176300000000000000005727376657200000000000006312e37332e3000000000000000000008727373646b7665720000003332302e302e302d72633223303939323431336639623035653562666231663837326263653939653839643931323962326536310000000000000000010000000000000001 + <= b004 + `) + ); + const str = new Str(transport); + const transaction = Buffer.from( + "7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000200000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd000071d7002d9fed000000010000000000000000000000010000000100000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd00000018000000020000024c0061736d0100000001150460027e7e017e60037e7e7e017e6000017e600000021904016c01300000016c01310000016c015f0001016c013800000305040203030305030100100619037f01418080c0000b7f00418080c0000b7f00418080c0000b073505066d656d6f7279020009696e6372656d656e740004015f00070a5f5f646174615f656e6403010b5f5f686561705f6261736503020aa70104920102017f017e41002100024002400240428ebad0af86d43942021080808080004201520d00428ebad0af86d4394202108180808000220142ff01834204520d012001422088a721000b200041016a2200450d01428ebad0af86d4392000ad422086420484220142021082808080001a4284808080a0064284808080c00c1083808080001a20010f0b00000b108580808000000b0900108680808000000b040000000b02000b00730e636f6e74726163747370656376300000000000000040496e6372656d656e7420696e6372656d656e747320616e20696e7465726e616c20636f756e7465722c20616e642072657475726e73207468652076616c75652e00000009696e6372656d656e74000000000000000000000100000004001e11636f6e7472616374656e766d657461763000000000000000140000003900730e636f6e74726163746d65746176300000000000000005727376657200000000000006312e37332e3000000000000000000008727373646b7665720000003332302e302e302d726332233039393234313366396230356535626662316638373262636539396538396439313239623265363100000000000000000100000000000000010000000713e16858bde4ab50a006dbf07172288f3ec19d8640d1a853016c60c15c2511170000000000186636000002b000000000000000000000000c", + "hex" + ); + await expect(str.signTransaction("44'/148'/0'", transaction)).rejects.toThrow(StellarDataTooLargeError); +}); + +test("signTransaction (rejected)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e0040080ff038000002c80000094800000007ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000200000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd000071d7002d9fed000000010000000000000000000000010000000100000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd00000018000000020000024c0061736d0100000001150460027e7e017e60037e7e7e017e6000017e600000021904016c01300000016c01310000016c015f0001016c013800000305040203030305030100100619037f01418080c0000b7f00418080c0000b7f00418080 + <= 9000 + => e0048080ffc0000b073505066d656d6f7279020009696e6372656d656e740004015f00070a5f5f646174615f656e6403010b5f5f686561705f6261736503020aa70104920102017f017e41002100024002400240428ebad0af86d43942021080808080004201520d00428ebad0af86d4394202108180808000220142ff01834204520d012001422088a721000b200041016a2200450d01428ebad0af86d4392000ad422086420484220142021082808080001a4284808080a0064284808080c00c1083808080001a20010f0b00000b108580808000000b0900108680808000000b040000000b02000b00730e636f6e74726163747370656376300000000000000040496e + <= 9000 + => e0048080ff6372656d656e7420696e6372656d656e747320616e20696e7465726e616c20636f756e7465722c20616e642072657475726e73207468652076616c75652e00000009696e6372656d656e74000000000000000000000100000004001e11636f6e7472616374656e766d657461763000000000000000140000003900730e636f6e74726163746d65746176300000000000000005727376657200000000000006312e37332e3000000000000000000008727373646b7665720000003332302e302e302d72633223303939323431336639623035653562666231663837326263653939653839643931323962326536310000000000000000010000000000000001 + <= 9000 + => e00480003c0000000713e16858bde4ab50a006dbf07172288f3ec19d8640d1a853016c60c15c2511170000000000186636000002b000000000000000000000000c + <= 6985 + `) + ); + const str = new Str(transport); + const transaction = Buffer.from( + "7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000200000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd000071d7002d9fed000000010000000000000000000000010000000100000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd00000018000000020000024c0061736d0100000001150460027e7e017e60037e7e7e017e6000017e600000021904016c01300000016c01310000016c015f0001016c013800000305040203030305030100100619037f01418080c0000b7f00418080c0000b7f00418080c0000b073505066d656d6f7279020009696e6372656d656e740004015f00070a5f5f646174615f656e6403010b5f5f686561705f6261736503020aa70104920102017f017e41002100024002400240428ebad0af86d43942021080808080004201520d00428ebad0af86d4394202108180808000220142ff01834204520d012001422088a721000b200041016a2200450d01428ebad0af86d4392000ad422086420484220142021082808080001a4284808080a0064284808080c00c1083808080001a20010f0b00000b108580808000000b0900108680808000000b040000000b02000b00730e636f6e74726163747370656376300000000000000040496e6372656d656e7420696e6372656d656e747320616e20696e7465726e616c20636f756e7465722c20616e642072657475726e73207468652076616c75652e00000009696e6372656d656e74000000000000000000000100000004001e11636f6e7472616374656e766d657461763000000000000000140000003900730e636f6e74726163746d65746176300000000000000005727376657200000000000006312e37332e3000000000000000000008727373646b7665720000003332302e302e302d726332233039393234313366396230356535626662316638373262636539396538396439313239623265363100000000000000000100000000000000010000000713e16858bde4ab50a006dbf07172288f3ec19d8640d1a853016c60c15c2511170000000000186636000002b000000000000000000000000c", + "hex" + ); + await expect(str.signTransaction("44'/148'/0'", transaction)).rejects.toThrow(StellarUserRefusedError); +}); + +test("signTransaction (parse data failed)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e0040000ff038000002c80000094800000007ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000200000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd000071d7002d9fed000000010000000000000000000000010000000100000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd00000018000000020000024c0061736d0100000001150460027e7e017e60037e7e7e017e6000017e600000021904016c01300000016c01310000016c015f0001016c013800000305040203030305030100100619037f01418080c0000b7f00418080c0000b7f00418080 + <= b005 + `) + ); + const str = new Str(transport); + const transaction = Buffer.from( + "7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000200000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd000071d7002d9fed000000010000000000000000000000010000000100000000e93388bbfd2fbd11806dd0bd59cea9079e7cc70ce7b1e154f114cdfe4e466ecd00000018000000020000024c0061736d0100000001150460027e7e017e60037e7e7e017e6000017e600000021904016c01300000016c01310000016c015f0001016c013800000305040203030305030100100619037f01418080c0000b7f00418080c0000b7f00418080", + "hex" + ); + await expect(str.signTransaction("44'/148'/0'", transaction)).rejects.toThrow(StellarDataParsingFailedError); +}); + +test("signSorobanAuthorization (size exceeds APDU_MAX_PAYLOAD)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00a0080ff038000002c8000009480000000000000097ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000049756d450210c89f0000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000020000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55 + <= 9000 + => e00a8080ffa8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000000000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079 + <= 9000 + => e00a800037a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a0000000000000000000000026944050000000000 + <= 2e45c6d44fe613c3d7b38c9f7147bd70dc58cf7e1badb80d33632dcdafb90944674096da05dfe0f185da86535a6ca1a1fcef852a19ae99b22e4438ca5c43b9089000 + `) + ); + const str = new Str(transport); + const hashIdPreimage = Buffer.from( + "000000097ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000049756d450210c89f0000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000020000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000000000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a0000000000000000000000026944050000000000", + "hex" + ); + const { signature } = await str.signSorobanAuthorization("44'/148'/0'", hashIdPreimage); + const result = signature.toString("hex"); + expect(result).toEqual( + "2e45c6d44fe613c3d7b38c9f7147bd70dc58cf7e1badb80d33632dcdafb90944674096da05dfe0f185da86535a6ca1a1fcef852a19ae99b22e4438ca5c43b908" ); }); + +test("signSorobanAuthorization (size does not exceed APDU_MAX_PAYLOAD)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00a0000fd038000002c8000009480000000000000097ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000049756d450210c89f0000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000020000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d50 + <= 2e45c6d44fe613c3d7b38c9f7147bd70dc58cf7e1badb80d33632dcdafb90944674096da05dfe0f185da86535a6ca1a1fcef852a19ae99b22e4438ca5c43b9089000 + `) + ); + const str = new Str(transport); + const hashIdPreimage = Buffer.from( + "000000097ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000049756d450210c89f0000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000020000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d50", + "hex" + ); + const { signature } = await str.signSorobanAuthorization("44'/148'/0'", hashIdPreimage); + const result = signature.toString("hex"); + expect(result).toEqual( + "2e45c6d44fe613c3d7b38c9f7147bd70dc58cf7e1badb80d33632dcdafb90944674096da05dfe0f185da86535a6ca1a1fcef852a19ae99b22e4438ca5c43b908" + ); +}); + +test("signSorobanAuthorization (size too long)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00a0080ff038000002c8000009480000000000000097ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000049756d450210c89f0000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000020000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55 + <= 9000 + => e00a8080ffa8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000000000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079 + <= b004 + `) + ); + const str = new Str(transport); + const hashIdPreimage = Buffer.from( + "000000097ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000049756d450210c89f0000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000020000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000000000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a0000000000000000000000026944050000000000", + "hex" + ); + await expect(str.signSorobanAuthorization("44'/148'/0'", hashIdPreimage)).rejects.toThrow(StellarDataTooLargeError); +}); + +test("signSorobanAuthorization (rejected)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00a0000fd038000002c8000009480000000000000097ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000049756d450210c89f0000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000020000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d50 + <= 6985 + `) + ); + const str = new Str(transport); + const hashIdPreimage = Buffer.from( + "000000097ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000049756d450210c89f0000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000020000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d50", + "hex" + ); + await expect(str.signSorobanAuthorization("44'/148'/0'", hashIdPreimage)).rejects.toThrow(StellarUserRefusedError); +}); + +test("signSorobanAuthorization (parse data failed)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00a0080ff038000002c8000009480000000000000097ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000049756d450210c89f0000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000020000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55 + <= 9000 + => e00a8080ffa8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000000000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079 + <= 9000 + => e00a800037a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a0000000000000000000000026944050000000000 + <= b005 + `) + ); + const str = new Str(transport); + const hashIdPreimage = Buffer.from( + "000000097ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000049756d450210c89f0000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000020000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a00000000000000000000000269440500000000000000000000000001d7928b72c2703ccfeaf7eb9ff4ef4d504a55a8b979fc9b450ea2c842b4d1ce61000000087472616e7366657200000003000000120000000000000000acd0adc778238000bdb6bad22e919923897f7a87e310cef1505e6c0b0b71542500000012000000000000000079a5a13baf5571637a2157c6affb7491d7c4454eecd2b0b7e40e67023f096fac0000000a0000000000000000000000026944050000000000", + "hex" + ); + await expect(str.signSorobanAuthorization("44'/148'/0'", hashIdPreimage)).rejects.toThrow(StellarDataParsingFailedError); +}); + +test("pathToBuffer (44'/148'/19773')", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00200000d038000002c8000009480004d3d + <= 04becf1537ae24f24d326368a2ad8a09b06cb0671e96e6878d4e623aab4bf6e79000 + `) + ); + const str = new Str(transport); + const { rawPublicKey } = await str.getPublicKey("44'/148'/19773'", false); + expect(rawPublicKey.toString("hex")).toEqual( + "04becf1537ae24f24d326368a2ad8a09b06cb0671e96e6878d4e623aab4bf6e7" + ); +}); + +test("pathToBuffer (44'/148'/19773)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00200000d038000002c8000009480004d3d + <= 04becf1537ae24f24d326368a2ad8a09b06cb0671e96e6878d4e623aab4bf6e79000 + `) + ); + const str = new Str(transport); + const { rawPublicKey } = await str.getPublicKey("44'/148'/19773", false); + expect(rawPublicKey.toString("hex")).toEqual( + "04becf1537ae24f24d326368a2ad8a09b06cb0671e96e6878d4e623aab4bf6e7" + ); +}); + +test("remapErrors (unexpected)", async () => { + const transport = await openTransportReplayer( + RecordStore.fromString(` + => e00200000d038000002c8000009480004d3d + <= b001 + `) + ); + const str = new Str(transport); + try { + await str.getPublicKey("44'/148'/19773", false); + } catch (error) { + expect(error).toBeInstanceOf(TransportStatusError); + if (error instanceof TransportStatusError) { + expect(error.statusCode).toBe(0xb001); + } + } +}); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e88f5237d97..3d9741df7c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3960,18 +3960,15 @@ importers: libs/ledgerjs/packages/hw-app-str: dependencies: + '@ledgerhq/errors': + specifier: workspace:^ + version: link:../errors '@ledgerhq/hw-transport': specifier: workspace:^ version: link:../hw-transport - base32.js: - specifier: ^0.1.0 - version: 0.1.0 - sha.js: - specifier: ^2.3.6 - version: 2.4.11 - tweetnacl: - specifier: ^1.0.3 - version: 1.0.3 + bip32-path: + specifier: ^0.4.2 + version: 0.4.2 devDependencies: '@ledgerhq/hw-transport-mocker': specifier: workspace:^ @@ -12375,16 +12372,10 @@ packages: metro: '*' metro-core: '*' peerDependenciesMeta: - '@expo/metro-config': - optional: true - glob: - optional: true metro: optional: true metro-core: optional: true - minimatch: - optional: true dependencies: '@babel/runtime': 7.24.1 '@expo/code-signing-certificates': 0.0.5 @@ -17458,9 +17449,6 @@ packages: /@react-native-community/cli-tools@12.3.6: resolution: {integrity: sha512-FPEvZn19UTMMXUp/piwKZSh8cMEfO8G3KDtOwo53O347GTcwNrKjgZGtLSPELBX2gr+YlzEft3CoRv2Qmo83fQ==} - peerDependenciesMeta: - find-up: - optional: true dependencies: appdirsjs: 1.2.7 chalk: 4.1.2 @@ -25442,9 +25430,6 @@ packages: /ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependenciesMeta: - ajv: - optional: true dependencies: ajv: 8.11.2 @@ -32411,7 +32396,6 @@ packages: is-hex-prefixed: 1.0.0 strip-hex-prefix: 1.0.0 dev: false - bundledDependencies: false /ev-emitter@1.1.1: resolution: {integrity: sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q==} @@ -32648,8 +32632,6 @@ packages: peerDependenciesMeta: expo-constants: optional: true - expo-file-system: - optional: true expo-modules-core: optional: true react: @@ -32681,8 +32663,6 @@ packages: peerDependenciesMeta: expo-constants: optional: true - expo-file-system: - optional: true expo-modules-core: optional: true react: @@ -32712,8 +32692,6 @@ packages: react: '*' react-native: '*' peerDependenciesMeta: - expo-constants: - optional: true expo-file-system: optional: true expo-modules-core: @@ -33334,8 +33312,6 @@ packages: react: '*' react-native: '*' peerDependenciesMeta: - expo-modules-autolinking: - optional: true expo-modules-core: optional: true react: @@ -41486,9 +41462,6 @@ packages: /metro-transform-worker@0.80.8: resolution: {integrity: sha512-+4FG3TQk3BTbNqGkFb2uCaxYTfsbuFOCKMMURbwu0ehCP8ZJuTUramkaNZoATS49NSAkRgUltgmBa4YaKZ5mqw==} engines: {node: '>=18'} - peerDependenciesMeta: - metro-minify-terser: - optional: true dependencies: '@babel/core': 7.24.3 '@babel/generator': 7.24.1