UNPKG

@taquito/ledger-signer

Version:

Ledger hardware wallet signer integration for Taquito.

188 lines (187 loc) 9.04 kB
"use strict"; /** * @packageDocumentation * @module @taquito/ledger-signer */ Object.defineProperty(exports, "__esModule", { value: true }); exports.LedgerSigner = exports.VERSION = exports.HDPathTemplate = exports.InvalidDerivationTypeError = exports.DerivationType = exports.InvalidDerivationPathError = void 0; const buffer_1 = require("buffer"); const utils_1 = require("@taquito/utils"); const utils_2 = require("./utils"); const blake2_js_1 = require("@noble/hashes/blake2.js"); const errors_1 = require("./errors"); const core_1 = require("@taquito/core"); var core_2 = require("@taquito/core"); Object.defineProperty(exports, "InvalidDerivationPathError", { enumerable: true, get: function () { return core_2.InvalidDerivationPathError; } }); var DerivationType; (function (DerivationType) { DerivationType[DerivationType["ED25519"] = 0] = "ED25519"; DerivationType[DerivationType["SECP256K1"] = 1] = "SECP256K1"; DerivationType[DerivationType["P256"] = 2] = "P256"; DerivationType[DerivationType["BIP32_ED25519"] = 3] = "BIP32_ED25519"; })(DerivationType || (exports.DerivationType = DerivationType = {})); var errors_2 = require("./errors"); Object.defineProperty(exports, "InvalidDerivationTypeError", { enumerable: true, get: function () { return errors_2.InvalidDerivationTypeError; } }); const HDPathTemplate = (account) => { return `44'/1729'/${account}'/0'`; }; exports.HDPathTemplate = HDPathTemplate; var version_1 = require("./version"); Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return version_1.VERSION; } }); /** * * Implementation of the Signer interface that will allow signing operation from a Ledger Nano device * * @param transport A transport instance from LedgerJS libraries depending on the platform used (e.g. Web, Node) * @param path The ledger derivation path (default is "44'/1729'/0'/0'") * @param prompt Whether to prompt the ledger for public key (default is true) * @param derivationType The value which defines the curve to use (DerivationType.ED25519(default), DerivationType.SECP256K1, DerivationType.P256, DerivationType.BIP32_ED25519) * * @example * ``` * import TransportNodeHid from "@ledgerhq/hw-transport-node-hid"; * const transport = await TransportNodeHid.create(); * const ledgerSigner = new LedgerSigner(transport, "44'/1729'/0'/0'", false, DerivationType.ED25519); * ``` * * @example * ``` * import TransportU2F from "@ledgerhq/hw-transport-u2f"; * const transport = await TransportU2F.create(); * const ledgerSigner = new LedgerSigner(transport, "44'/1729'/0'/0'", true, DerivationType.SECP256K1); * ``` * * @example * ``` * import TransportU2F from "@ledgerhq/hw-transport-u2f"; * const transport = await TransportU2F.create(); * const ledgerSigner = new LedgerSigner(transport, "44'/1729'/6'/0'", true, DerivationType.BIP32_ED25519); * ``` */ class LedgerSigner { constructor(transport, path = "44'/1729'/0'/0'", prompt = true, derivationType = DerivationType.ED25519) { this.transport = transport; this.path = path; this.prompt = prompt; this.derivationType = derivationType; // constants for APDU requests (https://github.com/obsidiansystems/ledger-app-tezos/blob/master/APDUs.md) this.CLA = 0x80; // Instruction class (always 0x80) this.INS_GET_PUBLIC_KEY = 0x02; // Instruction code to get the ledger’s internal public key without prompt this.INS_PROMPT_PUBLIC_KEY = 0x03; // Instruction code to get the ledger’s internal public key with prompt this.INS_SIGN = 0x04; // Sign a message with the ledger’s key this.FIRST_MESSAGE_SEQUENCE = 0x00; this.LAST_MESSAGE_SEQUENCE = 0x81; this.OTHER_MESSAGE_SEQUENCE = 0x01; this.transport.setScrambleKey('XTZ'); if (!path.startsWith(`44'/1729'`)) { throw new core_1.InvalidDerivationPathError(path, `expecting prefix "44'/1729'".`); } if (!Object.values(DerivationType).includes(derivationType)) { throw new errors_1.InvalidDerivationTypeError(derivationType.toString()); } } async publicKeyHash() { if (!this._publicKeyHash) { await this.publicKey(); } if (this._publicKeyHash) { return this._publicKeyHash; } throw new errors_1.PublicKeyHashRetrievalError(); } async publicKey() { if (this._publicKey) { return this._publicKey; } const responseLedger = await this.getLedgerPublicKey(); const publicKeyLength = responseLedger[0]; const rawPublicKey = responseLedger.slice(1, 1 + publicKeyLength); const compressedPublicKey = (0, utils_2.compressPublicKey)(rawPublicKey, this.derivationType); const prefixes = this.getPrefixes(); const publicKey = (0, utils_1.b58Encode)(compressedPublicKey, prefixes.prefPk); const publicKeyHash = (0, utils_1.b58Encode)((0, blake2_js_1.blake2b)(compressedPublicKey, { dkLen: 20 }), prefixes.prefPkh); this._publicKey = publicKey; this._publicKeyHash = publicKeyHash; return publicKey; } async getLedgerPublicKey() { try { let ins = this.INS_PROMPT_PUBLIC_KEY; if (this.prompt === false) { ins = this.INS_GET_PUBLIC_KEY; } const responseLedger = await this.transport.send(this.CLA, ins, this.FIRST_MESSAGE_SEQUENCE, this.derivationType, (0, utils_2.transformPathToBuffer)(this.path)); return responseLedger; } catch (error) { throw new errors_1.PublicKeyRetrievalError(error); } } async secretKey() { throw new core_1.ProhibitedActionError('Secret key cannot be exposed'); } async sign(bytes, watermark) { const watermarkedBytes = (0, utils_2.appendWatermark)(bytes, watermark); const watermarkedBytes2buff = buffer_1.Buffer.from(watermarkedBytes, 'hex'); let messageToSend = []; messageToSend.push((0, utils_2.transformPathToBuffer)(this.path)); messageToSend = (0, utils_2.chunkOperation)(messageToSend, watermarkedBytes2buff); const ledgerResponse = await this.signWithLedger(messageToSend); let signature; if (this.derivationType === DerivationType.ED25519 || this.derivationType === DerivationType.BIP32_ED25519) { signature = ledgerResponse.slice(0, ledgerResponse.length - 2).toString('hex'); } else { if (!(0, utils_2.validateResponse)(ledgerResponse)) { throw new errors_1.InvalidLedgerResponseError('Invalid signature return by ledger unable to parse the response'); } const idxLengthRVal = 3; // Third element of response is length of r value const rValue = (0, utils_2.extractValue)(idxLengthRVal, ledgerResponse); const idxLengthSVal = rValue.idxValueStart + rValue.length + 1; const sValue = (0, utils_2.extractValue)(idxLengthSVal, ledgerResponse); const signatureBuffer = buffer_1.Buffer.concat([rValue.buffer, sValue.buffer]); signature = signatureBuffer.toString('hex'); } return { bytes, sig: (0, utils_1.b58Encode)(signature, utils_1.PrefixV2.GenericSignature), prefixSig: (0, utils_1.b58Encode)(signature, this.getPrefixes().prefSig), sbytes: bytes + signature, }; } async signWithLedger(message) { // first element of the message represents the path let ledgerResponse = await this.transport.send(this.CLA, this.INS_SIGN, this.FIRST_MESSAGE_SEQUENCE, this.derivationType, message[0]); for (let i = 1; i < message.length; i++) { const p1 = i === message.length - 1 ? this.LAST_MESSAGE_SEQUENCE : this.OTHER_MESSAGE_SEQUENCE; ledgerResponse = await this.transport.send(this.CLA, this.INS_SIGN, p1, this.derivationType, message[i]); } return ledgerResponse; } getPrefixes() { if (this.derivationType === DerivationType.ED25519 || this.derivationType === DerivationType.BIP32_ED25519) { return { prefPk: utils_1.PrefixV2.Ed25519PublicKey, prefPkh: utils_1.PrefixV2.Ed25519PublicKeyHash, prefSig: utils_1.PrefixV2.Ed25519Signature, }; } else if (this.derivationType === DerivationType.SECP256K1) { return { prefPk: utils_1.PrefixV2.Secp256k1PublicKey, prefPkh: utils_1.PrefixV2.Secp256k1PublicKeyHash, prefSig: utils_1.PrefixV2.Secp256k1Signature, }; } else { return { prefPk: utils_1.PrefixV2.P256PublicKey, prefPkh: utils_1.PrefixV2.P256PublicKeyHash, prefSig: utils_1.PrefixV2.P256Signature, }; } } } exports.LedgerSigner = LedgerSigner;