UNPKG

@zondax/ledger-substrate

Version:

TS / Node API for Substrate/Polkadot based apps running on Ledger devices

440 lines 22.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PolkadotGenericApp = void 0; /** ****************************************************************************** * (c) 2019 - 2024 Zondax AG * * 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. ******************************************************************************* */ const axios_1 = __importDefault(require("axios")); const ledger_js_1 = __importStar(require("@zondax/ledger-js")); const common_1 = require("./common"); class PolkadotGenericApp extends ledger_js_1.default { /** * Constructs a new PolkadotGenericApp instance. * @param transport - The transport instance. * @param txMetadataChainId - The chain ID in the transaction metadata service. * @param txMetadataSrvUrl - The optional transaction metadata service URL. * @throws {Error} - If the transport is not defined. */ constructor(transport, txMetadataChainId, txMetadataSrvUrl) { super(transport, PolkadotGenericApp._params); this.txMetadataSrvUrl = txMetadataSrvUrl; this.txMetadataChainId = txMetadataChainId; if (!this.transport) { throw new Error('Transport has not been defined'); } } /** * Retrieves transaction metadata from the metadata service. * @param txBlob - The transaction blob. * @param txMetadataChainId - The optional chain ID for the transaction metadata service. This value temporarily overrides the one set in the constructor. * @param txMetadataSrvUrl - The optional URL for the transaction metadata service. This value temporarily overrides the one set in the constructor. * @returns The transaction metadata. * @throws {ResponseError} - If the txMetadataSrvUrl is not defined. */ async getTxMetadata(txBlob, txMetadataChainId, txMetadataSrvUrl) { const txMetadataChainIdVal = txMetadataChainId ?? this.txMetadataChainId; const txMetadataSrvUrlVal = txMetadataSrvUrl ?? this.txMetadataSrvUrl; if (!txMetadataChainIdVal) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.GenericError, 'txMetadataSrvUrl is not defined or is empty. The use of the method requires access to a metadata shortening service.'); } if (!txMetadataSrvUrlVal) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.GenericError, 'txMetadataChainId is not defined or is empty. These values are configured in the metadata shortening service. Check the corresponding configuration in the service.'); } const resp = await axios_1.default.post(txMetadataSrvUrlVal, { txBlob: txBlob.toString('hex'), chain: { id: txMetadataChainIdVal }, }); let txMetadata = resp.data.txMetadata; if (txMetadata.slice(0, 2) === '0x') { txMetadata = txMetadata.slice(2); } return Buffer.from(txMetadata, 'hex'); } /** * @deprecated Use getAddressEcdsa or getAddressEd25519 instead. This method will be removed in a future version. * Retrieves the address for a given BIP44 path and SS58 prefix. * @param bip44Path - The BIP44 path. * @param ss58prefix - The SS58 prefix, must be an integer up to 65535. * @param showAddrInDevice - Whether to show the address on the device. * @param scheme - The scheme to use for the address. Default is ED25519. * @returns The address response. * @throws {ResponseError} If the response from the device indicates an error. */ async getAddress(bip44Path, ss58prefix, showAddrInDevice = false, scheme = 0 /* SCHEME.ED25519 */) { // needs to be integer, and up to 65535 if (!Number.isInteger(ss58prefix) || ss58prefix < 0 || ss58prefix >> 16 !== 0) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.ConditionsOfUseNotSatisfied, `Unexpected ss58prefix ${ss58prefix}. Needs to be a non-negative integer up to 2^16`); } if (scheme != 2 /* SCHEME.ECDSA */ && scheme != 0 /* SCHEME.ED25519 */) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.ConditionsOfUseNotSatisfied, `Unexpected scheme ${scheme}. Needs to be ECDSA (2) or ED25519 (0)`); } const bip44PathBuffer = this.serializePath(bip44Path); const prefixBuffer = Buffer.alloc(2); prefixBuffer.writeUInt16LE(ss58prefix); let payload = bip44PathBuffer; if (scheme === 0 /* SCHEME.ED25519 */) { payload = Buffer.concat([payload, prefixBuffer]); } const p1 = showAddrInDevice ? 1 /* P1_VALUES.SHOW_ADDRESS_IN_DEVICE */ : 0 /* P1_VALUES.ONLY_RETRIEVE */; try { const responseBuffer = await this.transport.send(this.CLA, this.INS.GET_ADDR, p1, scheme ?? 0 /* SCHEME.ED25519 */, payload); const response = (0, ledger_js_1.processResponse)(responseBuffer); const currentScheme = (scheme ?? 0 /* SCHEME.ED25519 */); const pubKeyLen = currentScheme === 2 /* SCHEME.ECDSA */ ? common_1.ECDSA_PUBKEY_LEN : common_1.ED25519_PUBKEY_LEN; const pubKey = response.readBytes(pubKeyLen).toString('hex'); let address = ''; if (currentScheme === 2 /* SCHEME.ECDSA */) { address = response.readBytes(response.length()).toString('hex'); } else { address = response.readBytes(response.length()).toString('ascii'); } return { pubKey, address, }; } catch (e) { throw (0, ledger_js_1.processErrorResponse)(e); } } /** * Retrieves the address for a given BIP44 path using the ECDSA scheme. * @param bip44Path - The BIP44 path. * @param showAddrInDevice - Whether to show the address on the device. * @returns The address response. * @throws {ResponseError} If the response from the device indicates an error. */ async getAddressEcdsa(bip44Path, showAddrInDevice = false) { return this.getAddress(bip44Path, 0, showAddrInDevice, 2 /* SCHEME.ECDSA */); } /** * Retrieves the address for a given BIP44 path and SS58 prefix using the ED25519 scheme. * @param bip44Path - The BIP44 path. * @param ss58prefix - The SS58 prefix. * @param showAddrInDevice - Whether to show the address on the device. * @returns The address response. * @throws {ResponseError} If the response from the device indicates an error. */ async getAddressEd25519(bip44Path, ss58prefix, showAddrInDevice = false) { return this.getAddress(bip44Path, ss58prefix, showAddrInDevice); } splitBufferToChunks(message, chunkSize) { const chunks = []; const buffer = Buffer.from(message); for (let i = 0; i < buffer.length; i += chunkSize) { let end = i + chunkSize; if (i > buffer.length) { end = buffer.length; } chunks.push(buffer.subarray(i, end)); } return chunks; } getSignReqChunks(path, txBlob, metadata) { const chunks = []; const bip44Path = this.serializePath(path); const blobLen = Buffer.alloc(2); blobLen.writeUInt16LE(txBlob.length); chunks.push(Buffer.concat([bip44Path, blobLen])); if (metadata == null) { chunks.push(...this.splitBufferToChunks(txBlob, this.CHUNK_SIZE)); } else { chunks.push(...this.splitBufferToChunks(Buffer.concat([txBlob, metadata]), this.CHUNK_SIZE)); } return chunks; } /** * Signs a transaction blob. * @param path - The BIP44 path. * @param ins - The instruction for signing. * @param blob - The transaction blob. * @param metadata - The optional metadata. * @throws {ResponseError} If the response from the device indicates an error. * @returns The response containing the signature and status. */ async signImplEd25519(path, ins, blob, metadata) { const chunks = this.getSignReqChunks(path, blob, metadata); try { let result = await this.sendGenericChunk(ins, 0 /* SCHEME.ED25519 */, 1, chunks.length, chunks[0]); for (let i = 1; i < chunks.length; i += 1) { result = await this.sendGenericChunk(ins, 0 /* SCHEME.ED25519 */, 1 + i, chunks.length, chunks[i]); } return { signature: result.readBytes(result.length()), }; } catch (e) { throw (0, ledger_js_1.processErrorResponse)(e); } } /** * Signs a transaction blob using the ECDSA scheme. * @param path - The BIP44 path. * @param ins - The instruction for signing. * @param blob - The transaction blob. * @param metadata - The optional metadata. * @throws {ResponseError} If the response from the device indicates an error. * @returns The response containing the signature and status. For ECDSA, the signature is in RSV format: * - R: First 32 bytes (signature.slice(0, 32)) * - S: Next 32 bytes (signature.slice(32, 64)) * - V: Last byte (signature.slice(64, 65)) * @see parseEcdsaSignature - Use this utility function to easily parse the signature into R, S, V components */ async signImplEcdsa(path, ins, blob, metadata) { const chunks = this.getSignReqChunks(path, blob, metadata); try { let result = await this.sendGenericChunk(ins, 2 /* SCHEME.ECDSA */, 1, chunks.length, chunks[0]); for (let i = 1; i < chunks.length; i += 1) { result = await this.sendGenericChunk(ins, 2 /* SCHEME.ECDSA */, 1 + i, chunks.length, chunks[i]); } return { signature: result.readBytes(result.length()), }; } catch (e) { throw (0, ledger_js_1.processErrorResponse)(e); } } /** * @deprecated Use signEcdsa or signEd25519 instead. This method will be removed in a future version. * Signs a transaction blob retrieving the correct metadata from a metadata service. * @param path - The BIP44 path. * @param txBlob - The transaction blob. * @param scheme - The scheme to use for the signing. Default is ED25519. * @throws {ResponseError} If the response from the device indicates an error. * @returns The response containing the signature and status. */ async sign(path, txBlob, scheme = 0 /* SCHEME.ED25519 */) { if (scheme != 2 /* SCHEME.ECDSA */ && scheme != 0 /* SCHEME.ED25519 */) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.ConditionsOfUseNotSatisfied, `Unexpected scheme ${scheme}. Needs to be ECDSA (2) or ED25519 (0)`); } if (scheme === 2 /* SCHEME.ECDSA */) { return await this.signEcdsa(path, txBlob); } return await this.signEd25519(path, txBlob); } /** * Signs a transaction blob using the ED25519 scheme. * @param path - The BIP44 path. * @param txBlob - The transaction blob. * @throws {ResponseError} If the response from the device indicates an error. * @returns The response containing the signature and status. */ async signEd25519(path, txBlob) { if (!this.txMetadataSrvUrl) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.GenericError, 'txMetadataSrvUrl is not defined or is empty. The use of the method requires access to a metadata shortening service.'); } if (!this.txMetadataChainId) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.GenericError, 'txMetadataChainId is not defined or is empty. These values are configured in the metadata shortening service. Check the corresponding configuration in the service.'); } const txMetadata = await this.getTxMetadata(txBlob); return await this.signImplEd25519(path, this.INS.SIGN, txBlob, txMetadata); } /** * Signs a transaction blob using the ECDSA scheme. * @param path - The BIP44 path. * @param txBlob - The transaction blob. * @throws {ResponseError} If the response from the device indicates an error. * @returns The response containing the signature and status. For ECDSA, the signature is in RSV format: * - R: First 32 bytes (signature.slice(0, 32)) * - S: Next 32 bytes (signature.slice(32, 64)) * - V: Last byte (signature.slice(64, 65)) * @see parseEcdsaSignature - Use this utility function to easily parse the signature into R, S, V components */ async signEcdsa(path, txBlob) { if (!this.txMetadataSrvUrl) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.GenericError, 'txMetadataSrvUrl is not defined or is empty. The use of the method requires access to a metadata shortening service.'); } if (!this.txMetadataChainId) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.GenericError, 'txMetadataChainId is not defined or is empty. These values are configured in the metadata shortening service. Check the corresponding configuration in the service.'); } const txMetadata = await this.getTxMetadata(txBlob); return await this.signImplEcdsa(path, this.INS.SIGN, txBlob, txMetadata); } /** * Signs a transaction blob with provided metadata. * @param path - The BIP44 path. * @param txBlob - The transaction blob. * @param txMetadataChainId - The optional chain ID for the transaction metadata service. This value temporarily overrides the one set in the constructor. * @param txMetadataSrvUrl - The optional URL for the transaction metadata service. This value temporarily overrides the one set in the constructor. * @throws {ResponseError} If the response from the device indicates an error. * @returns The response containing the signature and status. */ async signMigration(path, txBlob, txMetadataChainId, txMetadataSrvUrl) { if (!this.txMetadataSrvUrl) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.GenericError, 'txMetadataSrvUrl is not defined or is empty. The use of the method requires access to a metadata shortening service.'); } if (!this.txMetadataChainId) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.GenericError, 'txMetadataChainId is not defined or is empty. These values are configured in the metadata shortening service. Check the corresponding configuration in the service.'); } const txMetadata = await this.getTxMetadata(txBlob, txMetadataChainId, txMetadataSrvUrl); return await this.signImplEd25519(path, this.INS.SIGN, txBlob, txMetadata); } /** * @deprecated Use signRawEcdsa or signRawEd25519 instead. This method will be removed in a future version. * Signs a raw transaction blob. * @param path - The BIP44 path. * @param txBlob - The transaction blob. * @param scheme - The scheme to use for the signing. Default is ED25519. * @throws {ResponseError} If the response from the device indicates an error. * @returns The response containing the signature and status. */ async signRaw(path, txBlob, scheme = 0 /* SCHEME.ED25519 */) { if (scheme != 2 /* SCHEME.ECDSA */ && scheme != 0 /* SCHEME.ED25519 */) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.ConditionsOfUseNotSatisfied, `Unexpected scheme ${scheme}. Needs to be ECDSA (2) or ED25519 (0)`); } if (scheme === 2 /* SCHEME.ECDSA */) { return await this.signRawEcdsa(path, txBlob); } return await this.signRawEd25519(path, txBlob); } /** * Signs a raw transaction blob using the ED25519 scheme. * @param path - The BIP44 path. * @param txBlob - The transaction blob. * @throws {ResponseError} If the response from the device indicates an error. * @returns The response containing the signature and status. */ async signRawEd25519(path, txBlob) { return await this.signImplEd25519(path, this.INS.SIGN_RAW, txBlob); } /** * Signs a raw transaction blob using the ECDSA scheme. * @param path - The BIP44 path. * @param txBlob - The transaction blob. * @throws {ResponseError} If the response from the device indicates an error. * @returns The response containing the signature and status. For ECDSA, the signature is in RSV format: * - R: First 32 bytes (signature.slice(0, 32)) * - S: Next 32 bytes (signature.slice(32, 64)) * - V: Last byte (signature.slice(64, 65)) * @see parseEcdsaSignature - Use this utility function to easily parse the signature into R, S, V components */ async signRawEcdsa(path, txBlob) { return await this.signImplEcdsa(path, this.INS.SIGN_RAW, txBlob); } /** * @deprecated Use signWithMetadataEd25519 or signWithMetadataEcdsa instead. This method will be removed in a future version. * [Expert-only Method] Signs a transaction blob with provided metadata (this could be used also with a migration app) * @param path - The BIP44 path. * @param txBlob - The transaction blob. * @param txMetadata - The transaction metadata. * @throws {ResponseError} If the response from the device indicates an error. * @returns The response containing the signature and status. */ async signWithMetadata(path, txBlob, txMetadata, scheme = 0 /* SCHEME.ED25519 */) { if (scheme != 2 /* SCHEME.ECDSA */ && scheme != 0 /* SCHEME.ED25519 */) { throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.ConditionsOfUseNotSatisfied, `Unexpected scheme ${scheme}. Needs to be ECDSA (2) or ED25519 (0)`); } if (scheme === 2 /* SCHEME.ECDSA */) { return await this.signWithMetadataEcdsa(path, txBlob, txMetadata); } return await this.signWithMetadataEd25519(path, txBlob, txMetadata); } /** * Signs a transaction blob with provided metadata using the ECDSA scheme. * @param path - The BIP44 path. * @param txBlob - The transaction blob. * @param txMetadata - The transaction metadata. * @throws {ResponseError} If the response from the device indicates an error. * @returns The response containing the signature and status. For ECDSA, the signature is in RSV format: * - R: First 32 bytes (signature.slice(0, 32)) * - S: Next 32 bytes (signature.slice(32, 64)) * - V: Last byte (signature.slice(64, 65)) * @see parseEcdsaSignature - Use this utility function to easily parse the signature into R, S, V components */ async signWithMetadataEcdsa(path, txBlob, txMetadata) { return await this.signImplEcdsa(path, this.INS.SIGN, txBlob, txMetadata); } /** * Signs a transaction blob with provided metadata using the ED25519 scheme. * @param path - The BIP44 path. * @param txBlob - The transaction blob. * @param txMetadata - The transaction metadata. * @throws {ResponseError} If the response from the device indicates an error. * @returns The response containing the signature and status. */ async signWithMetadataEd25519(path, txBlob, txMetadata) { return await this.signImplEd25519(path, this.INS.SIGN, txBlob, txMetadata); } /** * Utility function to convert ECDSA signature response into RSV structure * @param signature - The ECDSA signature buffer from the device response * @returns Object containing R, S, and V components of the ECDSA signature * @throws {Error} If signature length is not 65 bytes (expected for ECDSA RSV format) */ static parseEcdsaSignature(signature) { if (signature.length !== 65) { throw new Error('Invalid ECDSA signature length. Expected 65 bytes for RSV format'); } return { r: signature.slice(0, 32).toString('hex'), s: signature.slice(32, 64).toString('hex'), v: signature.slice(64, 65).toString('hex'), }; } } exports.PolkadotGenericApp = PolkadotGenericApp; PolkadotGenericApp._INS = { GET_VERSION: 0x00, GET_ADDR: 0x01, SIGN: 0x02, SIGN_RAW: 0x03, }; PolkadotGenericApp._params = { cla: 0xf9, ins: { ...PolkadotGenericApp._INS }, p1Values: { ONLY_RETRIEVE: 0x00, SHOW_ADDRESS_IN_DEVICE: 0x01 }, chunkSize: 250, requiredPathLengths: [5], }; //# sourceMappingURL=generic_app.js.map