UNPKG

namillum

Version:
212 lines (186 loc) 6.13 kB
import TransportU2F from '@ledgerhq/hw-transport-u2f' import TransportWebUSB from '@ledgerhq/hw-transport-webusb' import { sha256 } from 'js-sha256' import { encodeTransactionProto } from '@zilliqa-js/account/dist/util' import { BN, Long } from '@zilliqa-js/util' const CLA = 0xE0 const INS = { getVersion: 0x01, getPublickKey: 0x02, getPublicAddress: 0x02, signTxn: 0x04, signHash: 0x08 } const PubKeyByteLen = 33 const SigByteLen = 64 const HashByteLen = 32 const Bech32AddrLen = 'zil'.length + 1 + 32 + 6 /** * Zilliqa API * * @example * import Zil from "@ledgerhq/hw-app-zil"; * const zil = new Zil(transport) */ export default class Zilliqa { static async create () { const isUSB = await TransportWebUSB.isSupported() try { if (isUSB) { const transport = await TransportWebUSB.create() return transport } } catch { // } return TransportU2F.create() } constructor (transport, scrambleKey = 'w0w') { this.transport = transport transport.decorateAppAPIMethods( this, ['getVersion', 'getPublicKey', 'getPublicAddress', 'signHash', 'signTxn'], scrambleKey ) } getVersion () { const P1 = 0x00 const P2 = 0x00 return this.transport.send(CLA, INS.getVersion, P1, P2).then((response) => { let version = 'v' for (let i = 0; i < 3; ++i) { version += parseInt('0x' + response[i]) if (i !== 2) { version += '.' } } return { version } }) } getPublicKey (index) { const P1 = 0x00 const P2 = 0x00 const payload = Buffer.alloc(4) payload.writeInt32LE(index) return this.transport .send(CLA, INS.getPublickKey, P1, P2, payload) .then((response) => { // The first PubKeyByteLen bytes are the public address. const publicKey = response.toString('hex').slice(0, PubKeyByteLen * 2) return { publicKey } }) } getPublicAddress (index) { const P1 = 0x00 const P2 = 0x01 const payload = Buffer.alloc(4) payload.writeInt32LE(index) return this.transport .send(CLA, INS.getPublicAddress, P1, P2, payload) .then((response) => { // After the first PubKeyByteLen bytes, the remaining is the bech32 address string. const pubAddr = response .slice(PubKeyByteLen, PubKeyByteLen + Bech32AddrLen) .toString('utf-8') const publicKey = response.toString('hex').slice(0, PubKeyByteLen * 2) return { pubAddr, publicKey } }) } signTxn (keyIndex, txnParams) { // https://github.com/Zilliqa/Zilliqa-JavaScript-Library/tree/dev/packages/zilliqa-js-account#interfaces const P1 = 0x00 const P2 = 0x00 const indexBytes = Buffer.alloc(4) indexBytes.writeInt32LE(keyIndex) // Convert to Zilliqa types if (!(txnParams.amount instanceof BN)) { txnParams.amount = new BN(txnParams.amount) } if (!(txnParams.gasPrice instanceof BN)) { txnParams.gasPrice = new BN(txnParams.gasPrice) } if (!(txnParams.gasLimit instanceof Long)) { txnParams.gasLimit = Long.fromNumber(txnParams.gasLimit) } let txnBytes = encodeTransactionProto(txnParams) const STREAM_LEN = 128 // Stream in batches of STREAM_LEN bytes each. let txn1Bytes if (txnBytes.length > STREAM_LEN) { txn1Bytes = txnBytes.slice(0, STREAM_LEN) txnBytes = txnBytes.slice(STREAM_LEN, undefined) } else { txn1Bytes = txnBytes txnBytes = Buffer.alloc(0) } const txn1SizeBytes = Buffer.alloc(4) txn1SizeBytes.writeInt32LE(txn1Bytes.length) const hostBytesLeftBytes = Buffer.alloc(4) hostBytesLeftBytes.writeInt32LE(txnBytes.length) // See signTxn.c:handleSignTxn() for sequence details of payload. // 1. 4 bytes for indexBytes. // 2. 4 bytes for hostBytesLeftBytes. // 3. 4 bytes for txn1SizeBytes (number of bytes being sent now). // 4. txn1Bytes of actual data. const payload = Buffer.concat([ indexBytes, hostBytesLeftBytes, txn1SizeBytes, txn1Bytes ]) const transport = this.transport return transport .send(CLA, INS.signTxn, P1, P2, payload) .then(function cb (response) { // Keep streaming data into the device till we run out of it. // See signTxn.c:istream_callback() for how this is used. // Each time the bytes sent consists of: // 1. 4-bytes of hostBytesLeftBytes. // 2. 4-bytes of txnNSizeBytes (number of bytes being sent now). // 3. txnNBytes of actual data. if (txnBytes.length > 0) { let txnNBytes if (txnBytes.length > STREAM_LEN) { txnNBytes = txnBytes.slice(0, STREAM_LEN) txnBytes = txnBytes.slice(STREAM_LEN, undefined) } else { txnNBytes = txnBytes txnBytes = Buffer.alloc(0) } const txnNSizeBytes = Buffer.alloc(4) txnNSizeBytes.writeInt32LE(txnNBytes.length) hostBytesLeftBytes.writeInt32LE(txnBytes.length) const payload = Buffer.concat([ hostBytesLeftBytes, txnNSizeBytes, txnNBytes ]) return transport.send(CLA, INS.signTxn, P1, P2, payload).then(cb) } return response }) .then((result) => { return (result.toString('hex').slice(0, SigByteLen * 2)) }) } signHash(keyIndex, message) { const P1 = 0x00; const P2 = 0x00; const hashStr = sha256(message) let indexBytes = Buffer.alloc(4); indexBytes.writeInt32LE(keyIndex); const hashBytes = Buffer.from(hashStr, "hex"); let hashLen = hashBytes.length; if (hashLen <= 0) { throw Error(`Hash length ${hashLen} is invalid`); } if (hashLen > HashByteLen) { hashBytes.slice(0, HashByteLen); } const payload = Buffer.concat([indexBytes, hashBytes]); return this.transport .send(CLA, INS.signHash, P1, P2, payload) .then((result) => { return (result.toString('hex').slice(0, SigByteLen * 2)); }); } }