UNPKG

@ledgerhq/hw-app-algorand

Version:
150 lines (135 loc) 4.33 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 type Transport from "@ledgerhq/hw-transport"; import BIPPath from "bip32-path"; import { UserRefusedOnDevice } from "@ledgerhq/errors"; import { encodeAddress } from "./utils"; const CHUNK_SIZE = 250; // const P1_FIRST = 0x00; const P1_MORE = 0x80; const P1_WITH_ACCOUNT_ID = 0x01; const P2_LAST = 0x00; const P2_MORE = 0x80; const SW_OK = 0x9000; const SW_CANCEL = 0x6986; const P1_WITH_REQUEST_USER_APPROVAL = 0x80; // algo spec const CLA = 0x80; const INS_GET_PUBLIC_KEY = 0x03; const INS_SIGN_MSGPACK = 0x08; /** * Algorand API * * @example * import Algorand from "@ledgerhq/hw-app-algorand"; * const algo = new Algorand(transport) */ export default class Algorand { transport: Transport; constructor(transport: Transport) { this.transport = transport; transport.decorateAppAPIMethods(this, ["getAddress", "sign"], "ALGO"); } /** * get Algorant address for a given BIP 32 path. * @param path a path in BIP 32 format * @option boolDisplay optionally enable or not the display * @return an object with a publicKey, address and (optionally) chainCode * @example * cosmos.getAddress("44'/283'/0'/0/0").then(o => o.address) */ getAddress( path: string, boolDisplay?: boolean, ): Promise<{ publicKey: string; address: string; }> { const bipPath = BIPPath.fromString(path).toPathArray(); const buf = Buffer.alloc(4); buf.writeUInt32BE(bipPath[2], 0); return this.transport .send(CLA, INS_GET_PUBLIC_KEY, boolDisplay ? P1_WITH_REQUEST_USER_APPROVAL : 0, 0, buf, [ SW_OK, ]) .then(response => { const buffer = Buffer.from(response.slice(0, 32)); const publicKey = buffer.toString("hex"); const address = encodeAddress(buffer); return { publicKey, address, }; }); } foreach<T, A>(arr: T[], callback: (arg0: T, arg1: number) => Promise<A>): Promise<A[]> { function iterate(index, array, result) { if (index >= array.length) { return result; } else return callback(array[index], index).then(function (res) { result.push(res); return iterate(index + 1, array, result); }); } return Promise.resolve().then(() => iterate(0, arr, [])); } async sign( path: string, message: string, ): Promise<{ signature: null | Buffer; }> { const bipPath = BIPPath.fromString(path).toPathArray(); const buf = Buffer.alloc(4); buf.writeUInt32BE(bipPath[2], 0); const chunks: Buffer[] = []; const buffer = Buffer.concat([buf, Buffer.from(message, "hex")]); for (let i = 0; i < buffer.length; i += CHUNK_SIZE) { let end = i + CHUNK_SIZE; if (i > buffer.length) { end = buffer.length; } chunks.push(buffer.slice(i, end)); } let response: any = {}; return this.foreach(chunks, (data, j) => this.transport .send( CLA, INS_SIGN_MSGPACK, j === 0 ? P1_WITH_ACCOUNT_ID : P1_MORE, j + 1 === chunks.length ? P2_LAST : P2_MORE, data, [SW_OK, SW_CANCEL], ) .then(apduResponse => (response = apduResponse)), ).then(() => { const errorCodeData = response; if (errorCodeData === 0x6986) { throw new UserRefusedOnDevice(); } let signature = null; if (response.length > 0) { signature = response.slice(0, response.length); } return { signature: signature, }; }); } }