@taquito/ledger-signer
Version:
Ledger hardware wallet signer integration for Taquito.
188 lines (187 loc) • 9.04 kB
JavaScript
"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;