UNPKG

@ledgerhq/hw-app-aptos

Version:
150 lines 6.2 kB
/******************************************************************************** * Ledger Node JS API * (c) 2017-2018 Ledger * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ import { sha3_256 as sha3Hash } from "@noble/hashes/sha3"; import { StatusCodes } from "@ledgerhq/errors"; import { bip32asBuffer } from "./bip32"; const MAX_APDU_LEN = 255; const P1_NON_CONFIRM = 0x00; const P1_CONFIRM = 0x01; const P1_START = 0x00; const P2_MORE = 0x80; const P2_LAST = 0x00; const LEDGER_CLA = 0x5b; const INS = { GET_VERSION: 0x03, GET_PUBLIC_KEY: 0x05, SIGN_TX: 0x06, }; /** * Aptos API * * @example * import Transport from "@ledgerhq/hw-transport"; * import Aptos from "@ledgerhq/hw-app-aptos"; * * function establishConnection() { * return Transport.create() * .then(transport => new Aptos(transport)); * } * * function fetchAddress(aptosClient) { * return aptosClient.getAddress("44'/144'/0'/0/0"); * } * * function signTransaction(aptosClient, deviceData, seqNo, buffer) { * * const transactionBlob = encode(buffer); * * console.log('Sending transaction to device for approval...'); * return aptosClient.signTransaction("44'/144'/0'/0/0", transactionBlob); * } * * function prepareAndSign(aptosClient, seqNo) { * return fetchAddress(aptosClient) * .then(deviceData => signTransaction(aptosClient, deviceData, seqNo)); * } * * establishConnection() * .then(aptos => prepareAndSign(aptos, 123, payload)) * .then(signature => console.log(`Signature: ${signature}`)) * .catch(e => console.log(`An error occurred (${e.message})`)); */ export default class Aptos { transport; constructor(transport, scrambleKey = "aptos") { this.transport = transport; transport.decorateAppAPIMethods(this, ["getVersion", "getAddress"], scrambleKey); } async getVersion() { const [major, minor, patch] = await this.sendToDevice(INS.GET_VERSION, P1_NON_CONFIRM, P2_LAST, Buffer.alloc(0)); return { version: `${major}.${minor}.${patch}`, }; } /** * get Aptos address for a given BIP 32 path. * * @param path a path in BIP 32 format * @param display optionally enable or not the display * @return an object with a publicKey, address and (optionally) chainCode * @example * const result = await aptos.getAddress("44'/144'/0'/0/0"); * const { publicKey, address } = result; */ async getAddress(path, display = false) { const pathBuffer = bip32asBuffer(path); const responseBuffer = await this.sendToDevice(INS.GET_PUBLIC_KEY, display ? P1_CONFIRM : P1_NON_CONFIRM, P2_LAST, pathBuffer); let offset = 1; const pubKeyLen = responseBuffer.subarray(0, offset)[0] - 1; const pubKeyBuffer = responseBuffer.subarray(++offset, (offset += pubKeyLen)); const chainCodeLen = responseBuffer.subarray(offset, ++offset)[0]; const chainCodeBuffer = responseBuffer.subarray(offset, offset + chainCodeLen); const address = "0x" + this.publicKeyToAddress(pubKeyBuffer).toString("hex"); return { publicKey: pubKeyBuffer, chainCode: chainCodeBuffer, address, }; } /** * sign a Aptos transaction with a given BIP 32 path * * * @param path a path in BIP 32 format * @param txBuffer the buffer to be signed for transaction * @return a signature as hex string * @example * const signature = await aptos.signTransaction("44'/144'/0'/0/0", "12000022800000002400000002614000000001315D3468400000000000000C73210324E5F600B52BB3D9246D49C4AB1722BA7F32B7A3E4F9F2B8A1A28B9118CC36C48114F31B152151B6F42C1D61FE4139D34B424C8647D183142ECFC1831F6E979C6DA907E88B1CAD602DB59E2F"); */ async signTransaction(path, txBuffer) { const pathBuffer = bip32asBuffer(path); await this.sendToDevice(INS.SIGN_TX, P1_START, P2_MORE, pathBuffer); const responseBuffer = await this.sendToDevice(INS.SIGN_TX, 1, P2_LAST, txBuffer); const signatureLen = responseBuffer[0]; const signatureBuffer = responseBuffer.subarray(1, 1 + signatureLen); return { signature: signatureBuffer }; } // send chunked if payload size exceeds maximum for a call async sendToDevice(instruction, p1, p2, payload) { const acceptStatusList = [StatusCodes.OK]; let payloadOffset = 0; if (payload.length > MAX_APDU_LEN) { while (payload.length - payloadOffset > MAX_APDU_LEN) { const buf = payload.subarray(payloadOffset, (payloadOffset += MAX_APDU_LEN)); const reply = await this.transport.send(LEDGER_CLA, instruction, p1++, P2_MORE, buf, acceptStatusList); this.throwOnFailure(reply); } } const buf = payload.subarray(payloadOffset); const reply = await this.transport.send(LEDGER_CLA, instruction, p1, p2, buf, acceptStatusList); this.throwOnFailure(reply); return reply.subarray(0, reply.length - 2); } publicKeyToAddress(pubKey) { const hash = sha3Hash.create(); hash.update(pubKey); hash.update("\x00"); return Buffer.from(hash.digest()); } throwOnFailure(reply) { // transport makes sure reply has a valid length const status = reply.readUInt16BE(reply.length - 2); if (status !== StatusCodes.OK) { throw new Error(`Failure with status code: 0x${status.toString(16)}`); } } } //# sourceMappingURL=Aptos.js.map