import { Transport as LedgerTransport } from "@ledgerhq/hw-transport"; import { Transport } from "./contracts"; import { splitPath, splitToChunks, writePathsToBuffer } from "./utils"; export default class STH implements Transport { readonly IDENTIFIER = 0xe0; readonly OP_GET_PUBLIC_KEY = 0x02; readonly OP_SIGN_TRANSACTION = 0x04; readonly OP_GET_VERSION = 0x06; readonly OP_SIGN_MESSAGE = 0x08; readonly ALG_SECP256K1 = 0x40; readonly CHUNK_SIZE = 255; readonly PAYLOAD_MAX = 255 * 4; private transport: LedgerTransport; constructor (transport: LedgerTransport) { this.transport = transport; this.transport.decorateAppAPIMethods(this, [ "getAddress", "signTransaction", "getAppConfiguration" ], "w0w"); } public async getVersion (): Promise { const response = await this.transport.send( this.IDENTIFIER, this.OP_GET_VERSION, 0x00, 0x00 ); return `${response[1]}.${response[2]}.${response[3]}`; } public async getPublicKey (path: string): Promise { const paths = splitPath(path); const buffer = Buffer.alloc(1 + (paths.length * 4)); writePathsToBuffer(paths, buffer); const response = await this.transport.send( this.IDENTIFIER, this.OP_GET_PUBLIC_KEY, 0x00, this.ALG_SECP256K1, buffer ); return response.slice(1, 1 + response[0]).toString("hex"); } public async signTransaction (path: string, hex: Buffer): Promise { return this.sign(path, hex, this.OP_SIGN_TRANSACTION); } public async signMessage (path: string, hex: Buffer): Promise { return this.sign(path, hex, this.OP_SIGN_MESSAGE); } private async sign (path: string, hex: Buffer, operation: number): Promise { if (hex.length > this.PAYLOAD_MAX) { throw new Error("Payload is too large"); } const paths = splitPath(path); const hexChunks = splitToChunks(hex, (this.CHUNK_SIZE * 2) - ((2 + (paths.length * 4)) * 2)); const toSend = []; for (const index in hexChunks) { const chunk = hexChunks[index]; const buffer = Buffer.alloc(index === "0" ? 1 + (paths.length * 4) : 0); if (index === "0") { writePathsToBuffer(paths, buffer); } toSend.push(Buffer.concat([buffer, chunk])); } const promises = []; for (const index in toSend) { const data = toSend[index]; let chunkPart = 0x00; if (toSend.length === 1 && index === "0") { chunkPart = 0x80; } else if (toSend.length > 1 && index === (toSend.length - 1).toString()) { chunkPart = 0x81; } try { const promise = await this.transport.send( this.IDENTIFIER, operation, chunkPart, this.ALG_SECP256K1, data ); promises.push(promise); } catch (error) { throw new Error(`Could not sign transaction: ${error.message}`); } } const response = await Promise.all(promises); if (!response.length) { throw new Error("No response"); } return response.map(r => r.slice(0, r.length - 2).toString('hex')).join(""); } }