UNPKG

@ledgerhq/hw-app-btc

Version:
195 lines • 8.41 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTrustedInputRaw = getTrustedInputRaw; exports.getTrustedInput = getTrustedInput; exports.sendSapling = sendSapling; exports.sendOrchard = sendOrchard; const invariant_1 = __importDefault(require("invariant")); const constants_1 = require("./constants"); const varint_1 = require("./varint"); async function getTrustedInputRaw(transport, transactionData, indexLookup) { let data; let firstRound = false; if (typeof indexLookup === "number") { firstRound = true; const prefix = Buffer.alloc(4); prefix.writeUInt32BE(indexLookup, 0); data = Buffer.concat([prefix, transactionData], transactionData.length + 4); } else { data = transactionData; } const trustedInput = await transport.send(0xe0, 0x42, firstRound ? 0x00 : 0x80, 0x00, data); const res = trustedInput.slice(0, trustedInput.length - 2).toString("hex"); return res; } async function getTrustedInput(transport, indexLookup, transaction, additionals = []) { const { inputs, outputs, locktime, nExpiryHeight, extraData } = transaction; if (!outputs || !locktime) { throw new Error("getTrustedInput: locktime & outputs is expected"); } const isDecred = additionals.includes("decred"); const processScriptBlocks = async (script, sequence) => { const seq = sequence || Buffer.alloc(0); const scriptBlocks = []; let offset = 0; while (offset !== script.length) { const blockSize = script.length - offset > constants_1.MAX_SCRIPT_BLOCK ? constants_1.MAX_SCRIPT_BLOCK : script.length - offset; if (offset + blockSize !== script.length) { scriptBlocks.push(script.slice(offset, offset + blockSize)); } else { scriptBlocks.push(Buffer.concat([script.slice(offset, offset + blockSize), seq])); } offset += blockSize; } // Handle case when no script length: we still want to pass the sequence // relatable: https://github.com/LedgerHQ/ledger-live-desktop/issues/1386 if (script.length === 0) { scriptBlocks.push(seq); } let res; for (const scriptBlock of scriptBlocks) { res = await getTrustedInputRaw(transport, scriptBlock); } return res; }; const processWholeScriptBlock = block => getTrustedInputRaw(transport, block); const isZcash = additionals.includes("zcash"); // NOTE: this isn't necessary as consensusBranchId is only set when the transaction is a zcash tx // but better safe than sorry const zCashConsensusBranchId = transaction.consensusBranchId || Buffer.alloc(0); await getTrustedInputRaw(transport, Buffer.concat([ transaction.version, transaction.timestamp || Buffer.alloc(0), transaction.nVersionGroupId || Buffer.alloc(0), isZcash ? zCashConsensusBranchId : Buffer.alloc(0), (0, varint_1.createVarint)(inputs.length), ]), indexLookup); for (const input of inputs) { const treeField = isDecred ? input.tree || Buffer.from([0x00]) : Buffer.alloc(0); const data = Buffer.concat([input.prevout, treeField, (0, varint_1.createVarint)(input.script.length)]); await getTrustedInputRaw(transport, data); // iteration (eachSeries) ended // TODO notify progress // deferred.notify("input"); await (isDecred ? processWholeScriptBlock(Buffer.concat([input.script, input.sequence])) : processScriptBlocks(input.script, input.sequence)); } await getTrustedInputRaw(transport, (0, varint_1.createVarint)(outputs.length)); for (const output of outputs) { const data = Buffer.concat([ output.amount, isDecred ? Buffer.from([0x00, 0x00]) : Buffer.alloc(0), //Version script (0, varint_1.createVarint)(output.script.length), output.script, ]); await getTrustedInputRaw(transport, data); } if (isZcash) { const data = Buffer.concat([ (0, varint_1.createVarint)(transaction.sapling?.vSpendsSapling.length || 0), (0, varint_1.createVarint)(transaction.sapling?.vOutputSapling.length || 0), (0, varint_1.createVarint)(transaction.orchard?.vActions.length || 0), ]); await getTrustedInputRaw(transport, data); if (transaction.sapling) { await sendSapling(transport, transaction.sapling); } if (transaction.orchard) { await sendOrchard(transport, transaction.orchard); } } const endData = []; if (nExpiryHeight && nExpiryHeight.length > 0) { endData.push(nExpiryHeight); } if (extraData && extraData.length > 0) { endData.push(extraData); } let extraPart; if (endData.length) { const data = Buffer.concat(endData); extraPart = isDecred ? data : Buffer.concat([(0, varint_1.createVarint)(data.length), data]); } const res = await processScriptBlocks(Buffer.concat([locktime, extraPart || Buffer.alloc(0)])); (0, invariant_1.default)(res, "missing result in processScriptBlocks"); return res; } async function sendSapling(transport, sapling) { const saplingData = Buffer.concat([sapling.valueBalanceSapling, sapling.anchorSapling]); await getTrustedInputRaw(transport, saplingData); // send spends for (const spend of sapling.vSpendsSapling) { const spendData = Buffer.concat([spend.cv, spend.nullifier, spend.rk]); await getTrustedInputRaw(transport, spendData); } for (const output of sapling.vOutputSapling) { const outputData = Buffer.concat([ output.cmu, output.ephemeralKey, output.encCiphertext.slice(0, 52), // https://zips.z.cash/zip-0244 ]); await getTrustedInputRaw(transport, outputData); } // send outputs memo for (const output of sapling.vOutputSapling) { // Send memo and noncompact ciphertext in 128-byte blocks // https://zips.z.cash/zip-0244 for (let i = 0; i < 4; i++) { const start = 52 + i * 128; const end = start + 128; const outputData = Buffer.concat([output.encCiphertext.slice(start, end)]); await getTrustedInputRaw(transport, outputData); } } // send outputs noncompact for (const output of sapling.vOutputSapling) { const outputData = Buffer.concat([ output.cv, output.encCiphertext.slice(564, constants_1.zCashEncCiphertextSize), // https://zips.z.cash/zip-0244 output.outCiphertext, ]); await getTrustedInputRaw(transport, outputData); } } async function sendOrchard(transport, orchard) { // compact digest data for (const action of orchard.vActions) { const actionData = Buffer.concat([ action.nullifier, action.cmx, action.ephemeralKey, action.encCiphertext.slice(0, 52), // https://zips.z.cash/zip-0244 ]); await getTrustedInputRaw(transport, actionData); } // memo digest data for (const action of orchard.vActions) { // Send memo and noncompact ciphertext in 128-byte blocks // https://zips.z.cash/zip-0244 for (let i = 0; i < 4; i++) { const start = 52 + i * 128; const end = start + 128; const outputData = Buffer.concat([action.encCiphertext.slice(start, end)]); await getTrustedInputRaw(transport, outputData); } } // noncompact for (const action of orchard.vActions) { const actionData = Buffer.concat([ action.cv, action.rk, action.encCiphertext.slice(564, constants_1.zCashEncCiphertextSize), // https://zips.z.cash/zip-0244 action.outCiphertext, ]); await getTrustedInputRaw(transport, actionData); } const orchardData = Buffer.concat([orchard.flags, orchard.valueBalance, orchard.anchor]); await getTrustedInputRaw(transport, orchardData); } //# sourceMappingURL=getTrustedInput.js.map