ledger-stellar-sdk
Version:
Ledger Hardware Wallet Stellar JavaScript bindings.
192 lines • 7.81 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const base_1 = require("@scure/base");
const crc_1 = __importDefault(require("crc"));
/**
* @typedef {object} Signature
* @property {Buffer} signature - The signature
*/
/**
* @typedef {object} PublicKey
* @property {string} publicKey - Encoded public key
* @property {Buffer} rawPublicKey - Raw public key
*/
/**
* @typedef {object} AppConfiguration
* @property {string} version - The version of the Stellar app installed on the device
* @property {boolean} hashSigningEnabled - Whether hash signing is enabled
*/
/**
* Ledger Hardware Wallet Stellar JavaScript bindings.
*/
class Stellar {
/**
* @param {Transport} transport - The Ledger transport to use
* @param {string} [scrambleKey=w0w] - A string that will be used to scramble the device communication
* @example
* ```typescript
* import TransportWebUSB from "@ledgerhq/hw-transport-webusb";
* import LedgerStellarApi from "ledger-stellar-sdk";
*
* const transport = await TransportWebUSB.create();
* const stellar = new LedgerStellarApi(transport);
* ```
*/
constructor(transport, scrambleKey = "w0w") {
this.transport = transport;
transport.decorateAppAPIMethods(this, ["getPublicKey", "signTransaction", "signHash", "getAppConfiguration"], scrambleKey);
}
/**
* Get Stellar public key for a given account index.
*
* @param {number} accountIndex - It is part of key derivation path: `m/44'/148'/accountIndex'`
* @param {boolean} [display=false] - If set to "true", the public key will be displayed on the Ledger device and the user will be asked to confirm, otherwise it will not
* @returns {PublicKey} an object with a publicKey and rawPublicKey.
* @example
* ```typescript
* const response = stellar.getPublicKey(0, true)
* ```
*/
async getPublicKey(accountIndex, display = false) {
const paths = getStellarPath(accountIndex);
const buffer = Buffer.alloc(1 + paths.length * 4);
buffer[0] = paths.length;
paths.forEach((element, index) => {
buffer.writeUInt32BE(element, 1 + 4 * index);
});
const response = await this.transport.send(0xe0, 0x02, 0x00, display ? 0x01 : 0x00, buffer);
const rawPublicKey = response.subarray(0, 32);
const publicKey = encodeEd25519PublicKey(rawPublicKey);
return { publicKey, rawPublicKey };
}
/**
* Sign the given transaction.
*
* @param {number} accountIndex - It is part of key derivation path: `m/44'/148'/accountIndex'`
* @param {Buffer} transaction - The transaction to sign. It consists of network id and transaction envelope, if you are using `stellar-sdk`, you can use `transaction.signatureBase()` to get the value
* @returns {Signature} the signature
*/
async signTransaction(accountIndex, transaction) {
const paths = getStellarPath(accountIndex);
let response;
let offset = 0;
while (offset !== transaction.length) {
const isFirstChunk = offset === 0;
const maxChunkSize = isFirstChunk ? 150 - 1 - paths.length * 4 : 150;
const chunkSize = offset + maxChunkSize > transaction.length ? transaction.length - offset : maxChunkSize;
const buffer = Buffer.alloc(isFirstChunk ? 1 + paths.length * 4 + chunkSize : chunkSize);
if (isFirstChunk) {
buffer[0] = paths.length;
paths.forEach((element, index) => {
buffer.writeUInt32BE(element, 1 + 4 * index);
});
transaction.copy(buffer, 1 + 4 * paths.length, offset, offset + chunkSize);
}
else {
transaction.copy(buffer, 0, offset, offset + chunkSize);
}
const isLastChunk = offset + chunkSize === transaction.length;
response = await this.transport.send(0xe0, 0x04, isFirstChunk ? 0x00 : 0x80, isLastChunk ? 0x00 : 0x80, buffer);
offset += chunkSize;
}
if (!response) {
throw new Error("response is empty");
}
const signature = response.subarray(0, 64);
return { signature };
}
/**
* Sign the given hash.
*
* It is intended for signing transactions not supported by the Ledger Stellar
* app and should be avoided as much as possible.
*
* @param {number} accountIndex - It is part of key derivation path: `m/44'/148'/accountIndex'`
* @param {string|Buffer} hash - The hash to sign
* @returns {Signature} the signature
* @example
* ```typescript
* const response = stellar.signHash(0, "4b480b455a7ee154c33651819e3ce2ceb6bcd9dda78887777c4d2718c5cd04cd")
* ```
*/
async signHash(accountIndex, hash) {
if (typeof hash === "string") {
hash = Buffer.from(hash.startsWith("0x") ? hash.slice(2) : hash, "hex");
}
if (Buffer.byteLength(hash) !== 32) {
throw new Error("hash must be 32 bytes");
}
const paths = getStellarPath(accountIndex);
const buffer = Buffer.alloc(1 + paths.length * 4 + 32);
buffer[0] = paths.length;
paths.forEach((element, index) => {
buffer.writeUInt32BE(element, 1 + 4 * index);
});
const offset = 1 + 4 * paths.length;
hash.copy(buffer, offset);
const response = await this.transport.send(0xe0, 0x08, 0x00, 0x00, buffer);
const signature = response.subarray(0, 64);
return { signature };
}
/**
* Get the configuration of the Ledger Stellar app installed on the hardware device.
*
* @returns {AppConfiguration} an object with the version and the flag to indicate whether hash signing is enabled
* @example
* ```typescript
* const response = await stellar.getAppConfiguration();
* ```
*/
async getAppConfiguration() {
const response = await this.transport.send(0xe0, 0x06, 0x00, 0x00);
const hashSigningEnabled = response[0] === 1;
const version = `${response[1]}.${response[2]}.${response[3]}`;
return { version, hashSigningEnabled };
}
}
exports.default = Stellar;
/**
* Build a Stellar path.
*
* @private
* @param {number} accountIndex - It is part of key derivation path: `m/44'/148'/accountIndex'`
* @returns {Array<number>} the path
*/
function getStellarPath(accountIndex) {
if (accountIndex < 0 || accountIndex > 2 ** 32 - 1) {
throw new Error("Invalid account index");
}
const initValue = 0x80000000;
return [initValue + 44, initValue + 148, initValue + accountIndex];
}
/**
* Computes the CRC16-XModem checksum of `payload` in little-endian order.
*
* @private
* @param {Buffer} payload - The payload to checksum
* @returns {Buffer} the checksum
*/
function calculateChecksum(payload) {
const checksum = Buffer.alloc(2);
checksum.writeUInt16LE(crc_1.default.crc16xmodem(payload), 0);
return checksum;
}
/**
* Encode a raw public key to a Stellar public key.
*
* @private
* @param {Buffer} data - The raw public key
* @returns {string} the Stellar public key
*/
function encodeEd25519PublicKey(data) {
const versionByte = 6 << 3; // G (when encoded in base32)
const versionBuffer = Buffer.from([versionByte]);
const payload = Buffer.concat([versionBuffer, data]);
const checksum = calculateChecksum(payload);
const unencoded = Buffer.concat([payload, checksum]);
return base_1.base32.encode(unencoded);
}
//# sourceMappingURL=index.js.map