UNPKG

@ledgerhq/hw-app-str

Version:
199 lines 8.21 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 __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const bip32_path_1 = __importDefault(require("bip32-path")); const errors_1 = require("./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_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) */ class Str { transport; constructor(transport, scrambleKey = "l0v") { this.transport = transport; transport.decorateAppAPIMethods(this, [ "getAppConfiguration", "getPublicKey", "signTransaction", "signSorobanAuthorization", "signHash", ], scrambleKey); } /** * 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() { 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 raw public key for a given BIP 32 path. * * @param path a path in BIP 32 format * @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.rawPublicKey) */ async getPublicKey(path, display = false) { 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. * * @param path a path in BIP 32 format * @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) */ async signTransaction(path, transaction) { 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 Soroban authorization. * * @param path a path in BIP 32 format * @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.signSorobanAuthorization("44'/148'/0'", hashIdPreimage).then(o => o.signature) */ async signSorobanAuthorization(path, hashIdPreimage) { const pathBuffer = pathToBuffer(path); const payload = Buffer.concat([pathBuffer, hashIdPreimage]); const resp = await this.sendToDevice(INS_SIGN_SOROBAN_AUTHORIZATION, payload); return { signature: resp }; } /** * 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, hash) { const pathBuffer = pathToBuffer(path); const payload = Buffer.concat([pathBuffer, hash]); const resp = await this.sendToDevice(INS_SIGN_HASH, payload); return { signature: resp }; } async sendToDevice(instruction, payload) { let response = 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); } } exports.default = Str; const remapErrors = e => { if (e) { switch (e.statusCode) { case SW_DENY: return new errors_1.StellarUserRefusedError("User refused the request", undefined, { cause: e }); case SW_DATA_PARSING_FAIL: return new errors_1.StellarDataParsingFailedError("Unable to parse the provided data", undefined, { cause: e, }); case SW_HASH_SIGNING_MODE_NOT_ENABLED: return new errors_1.StellarHashSigningNotEnabledError("Hash signing not allowed. Have you enabled it in the app settings?", undefined, { cause: e }); case SW_DATA_TOO_LARGE: return new errors_1.StellarDataTooLargeError("The provided data is too large for the device to process", undefined, { cause: e }); } } return e; }; const pathToBuffer = (originalPath) => { const path = originalPath .split("/") .map(value => (value.endsWith("'") || value.endsWith("h") ? value : `${value}'`)) .join("/"); const pathNums = bip32_path_1.default.fromString(path).toPathArray(); return serializePath(pathNums); }; const serializePath = (path) => { 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; }; __exportStar(require("./errors"), exports); //# sourceMappingURL=Str.js.map