UNPKG

@ledgerhq/hw-app-btc

Version:
340 lines • 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getDefaultVersions = exports.getZcashBranchId = void 0; exports.createTransaction = createTransaction; const logs_1 = require("@ledgerhq/logs"); const hashPublicKey_1 = require("./hashPublicKey"); const getWalletPublicKey_1 = require("./getWalletPublicKey"); const getTrustedInput_1 = require("./getTrustedInput"); const startUntrustedHashTransactionInput_1 = require("./startUntrustedHashTransactionInput"); const serializeTransaction_1 = require("./serializeTransaction"); const getTrustedInputBIP143_1 = require("./getTrustedInputBIP143"); const compressPublicKey_1 = require("./compressPublicKey"); const signTransaction_1 = require("./signTransaction"); const finalizeInput_1 = require("./finalizeInput"); const getAppAndVersion_1 = require("./getAppAndVersion"); const constants_1 = require("./constants"); const shouldUseTrustedInputForSegwit_1 = require("./shouldUseTrustedInputForSegwit"); const defaultsSignTransaction = { lockTime: constants_1.DEFAULT_LOCKTIME, sigHashType: constants_1.SIGHASH_ALL, segwit: false, additionals: [], onDeviceStreaming: _e => { }, onDeviceSignatureGranted: () => { }, onDeviceSignatureRequested: () => { }, }; const getZcashBranchId = (blockHeight) => { const branchId = Buffer.alloc(4); if (!blockHeight || blockHeight >= constants_1.ZCASH_ACTIVATION_HEIGHTS.NU6_1) { // NOTE: null and undefined should default to latest version branchId.writeUInt32LE(0x4dec4df0, 0); } else if (blockHeight >= constants_1.ZCASH_ACTIVATION_HEIGHTS.NU6) { branchId.writeUInt32LE(0xc8e71055, 0); } else if (blockHeight >= constants_1.ZCASH_ACTIVATION_HEIGHTS.NU5) { branchId.writeUInt32LE(0xc2d6d0b4, 0); } else if (blockHeight >= constants_1.ZCASH_ACTIVATION_HEIGHTS.CANOPY) { branchId.writeUInt32LE(0xe9ff75a6, 0); } else if (blockHeight >= constants_1.ZCASH_ACTIVATION_HEIGHTS.HEARTWOOD) { branchId.writeUInt32LE(0xf5b9230b, 0); } else if (blockHeight >= constants_1.ZCASH_ACTIVATION_HEIGHTS.BLOSSOM) { branchId.writeUInt32LE(0x2bb40e60, 0); } else if (blockHeight >= constants_1.ZCASH_ACTIVATION_HEIGHTS.SAPLING) { branchId.writeUInt32LE(0x76b809bb, 0); } else { branchId.writeUInt32LE(0x5ba81b19, 0); } return branchId; }; exports.getZcashBranchId = getZcashBranchId; const getDefaultVersions = ({ isZcash, sapling, isDecred, expiryHeight, }) => { const defaultVersion = Buffer.alloc(4); if (!!expiryHeight && !isDecred) { if (isZcash) { defaultVersion.writeUInt32LE(0x80000005, 0); } else if (sapling) { defaultVersion.writeUInt32LE(0x80000004, 0); } else { defaultVersion.writeUInt32LE(0x80000003, 0); } } else { defaultVersion.writeUInt32LE(1, 0); } return { defaultVersion }; }; exports.getDefaultVersions = getDefaultVersions; async function createTransaction(transport, arg) { const signTx = { ...defaultsSignTransaction, ...arg }; const { inputs, associatedKeysets, blockHeight, changePath, outputScriptHex, lockTime, sigHashType, segwit, additionals, expiryHeight, onDeviceStreaming, onDeviceSignatureGranted, onDeviceSignatureRequested, } = signTx; let useTrustedInputForSegwit = signTx.useTrustedInputForSegwit; if (useTrustedInputForSegwit === undefined) { try { const a = await (0, getAppAndVersion_1.getAppAndVersion)(transport); useTrustedInputForSegwit = (0, shouldUseTrustedInputForSegwit_1.shouldUseTrustedInputForSegwit)(a); } catch (e) { if (e.statusCode === 0x6d00) { useTrustedInputForSegwit = false; } else { throw e; } } } // loop: 0 or 1 (before and after) // i: index of the input being streamed // i goes on 0...n, inluding n. in order for the progress value to go to 1 // we normalize the 2 loops to make a global percentage const notify = (loop, i) => { const { length } = inputs; if (length < 3) return; // there is not enough significant event to worth notifying (aka just use a spinner) const index = length * loop + i; const total = 2 * length; const progress = index / total; onDeviceStreaming({ progress, total, index, }); }; const isDecred = additionals.includes("decred"); const isZcash = additionals.includes("zcash"); const sapling = additionals.includes("sapling"); const bech32 = segwit && additionals.includes("bech32"); const useBip143 = segwit || (!!additionals && (additionals.includes("abc") || additionals.includes("gold") || additionals.includes("bip143"))) || (!!expiryHeight && !isDecred); // Inputs are provided as arrays of [transaction, output_index, optional redeem script, optional sequence] // associatedKeysets are provided as arrays of [path] const lockTimeBuffer = Buffer.alloc(4); lockTimeBuffer.writeUInt32LE(lockTime, 0); const nullScript = Buffer.alloc(0); const nullPrevout = Buffer.alloc(0); const { defaultVersion } = (0, exports.getDefaultVersions)({ isZcash, sapling, isDecred, expiryHeight, }); // Default version to 2 for XST not to have timestamp const trustedInputs = []; const regularOutputs = []; const signatures = []; const publicKeys = []; let firstRun = true; const resuming = false; const targetTransaction = { inputs: [], version: defaultVersion, timestamp: Buffer.alloc(0), }; const outputScript = Buffer.from(outputScriptHex, "hex"); notify(0, 0); // first pass on inputs to get trusted inputs for (const input of inputs) { if (!resuming) { if (isZcash) { input[0].consensusBranchId = (0, exports.getZcashBranchId)(input[4]); } const trustedInput = useBip143 && !useTrustedInputForSegwit ? (0, getTrustedInputBIP143_1.getTrustedInputBIP143)(input[1], input[0], additionals) : await (0, getTrustedInput_1.getTrustedInput)(transport, input[1], input[0], additionals); (0, logs_1.log)("hw", "got trustedInput=" + trustedInput); const sequence = Buffer.alloc(4); sequence.writeUInt32LE(input.length >= 4 && typeof input[3] === "number" ? input[3] : constants_1.DEFAULT_SEQUENCE, 0); trustedInputs.push({ trustedInput: true, value: Buffer.from(trustedInput, "hex"), sequence, }); } const { outputs } = input[0]; const index = input[1]; if (outputs && index <= outputs.length - 1) { regularOutputs.push(outputs[index]); } if (expiryHeight && !isDecred) { targetTransaction.nVersionGroupId = Buffer.from( // nVersionGroupId is 0x26A7270A for zcash NU5 upgrade // refer to https://github.com/zcash/zcash/blob/master/src/primitives/transaction.h isZcash ? [0x0a, 0x27, 0xa7, 0x26] : sapling ? [0x85, 0x20, 0x2f, 0x89] : [0x70, 0x82, 0xc4, 0x03]); targetTransaction.nExpiryHeight = expiryHeight; // For sapling : valueBalance (8), nShieldedSpend (1), nShieldedOutput (1), nJoinSplit (1) // Overwinter : use nJoinSplit (1) targetTransaction.extraData = Buffer.from(sapling ? [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] : [0x00]); } else if (isDecred) { targetTransaction.nExpiryHeight = expiryHeight; } } targetTransaction.inputs = inputs.map((input, idx) => { const sequence = Buffer.alloc(4); sequence.writeUInt32LE(input.length >= 4 && typeof input[3] === "number" ? input[3] : constants_1.DEFAULT_SEQUENCE, 0); return { script: isZcash ? regularOutputs[idx].script : nullScript, prevout: nullPrevout, sequence, }; }); if (!resuming) { // Collect public keys const result = []; for (let i = 0; i < inputs.length; i++) { const r = await (0, getWalletPublicKey_1.getWalletPublicKey)(transport, { path: associatedKeysets[i], }); notify(0, i + 1); result.push(r); } for (let i = 0; i < result.length; i++) { publicKeys.push((0, compressPublicKey_1.compressPublicKey)(Buffer.from(result[i].publicKey, "hex"))); } } onDeviceSignatureRequested(); targetTransaction.consensusBranchId = (0, exports.getZcashBranchId)(blockHeight); if (useBip143) { // Do the first run with all inputs await (0, startUntrustedHashTransactionInput_1.startUntrustedHashTransactionInput)(transport, true, targetTransaction, trustedInputs, true, !!expiryHeight, additionals, useTrustedInputForSegwit); if (!resuming && changePath) { await (0, finalizeInput_1.provideOutputFullChangePath)(transport, changePath); } await (0, finalizeInput_1.hashOutputFull)(transport, outputScript); } if (!!expiryHeight && !isDecred) { await (0, signTransaction_1.signTransaction)(transport, "", lockTime, constants_1.SIGHASH_ALL, expiryHeight); } // Do the second run with the individual transaction for (let i = 0; i < inputs.length; i++) { const input = inputs[i]; const script = inputs[i].length >= 3 && typeof input[2] === "string" ? Buffer.from(input[2], "hex") : !segwit ? regularOutputs[i].script : Buffer.concat([ Buffer.from([constants_1.OP_DUP, constants_1.OP_HASH160, constants_1.HASH_SIZE]), (0, hashPublicKey_1.hashPublicKey)(publicKeys[i]), Buffer.from([constants_1.OP_EQUALVERIFY, constants_1.OP_CHECKSIG]), ]); const pseudoTX = Object.assign({}, targetTransaction); const pseudoTrustedInputs = useBip143 ? [trustedInputs[i]] : trustedInputs; if (useBip143) { pseudoTX.inputs = [{ ...pseudoTX.inputs[i], script }]; } else { pseudoTX.inputs[i].script = script; } await (0, startUntrustedHashTransactionInput_1.startUntrustedHashTransactionInput)(transport, !useBip143 && firstRun, pseudoTX, pseudoTrustedInputs, useBip143, !!expiryHeight && !isDecred, additionals, useTrustedInputForSegwit); if (!useBip143) { if (!resuming && changePath) { await (0, finalizeInput_1.provideOutputFullChangePath)(transport, changePath); } await (0, finalizeInput_1.hashOutputFull)(transport, outputScript, additionals); } if (firstRun) { onDeviceSignatureGranted(); notify(1, 0); } const signature = await (0, signTransaction_1.signTransaction)(transport, associatedKeysets[i], lockTime, sigHashType, expiryHeight, additionals); notify(1, i + 1); signatures.push(signature); targetTransaction.inputs[i].script = nullScript; if (firstRun) { firstRun = false; } } targetTransaction.version = defaultVersion; targetTransaction.consensusBranchId = (0, exports.getZcashBranchId)(blockHeight); // Populate the final input scripts for (let i = 0; i < inputs.length; i++) { if (segwit) { targetTransaction.witness = Buffer.alloc(0); if (!bech32) { targetTransaction.inputs[i].script = Buffer.concat([ Buffer.from("160014", "hex"), (0, hashPublicKey_1.hashPublicKey)(publicKeys[i]), ]); } } else { const signatureSize = Buffer.alloc(1); const keySize = Buffer.alloc(1); signatureSize[0] = signatures[i].length; keySize[0] = publicKeys[i].length; targetTransaction.inputs[i].script = Buffer.concat([ signatureSize, signatures[i], keySize, publicKeys[i], ]); } const offset = useBip143 && !useTrustedInputForSegwit ? 0 : 4; targetTransaction.inputs[i].prevout = trustedInputs[i].value.slice(offset, offset + 0x24); } targetTransaction.locktime = lockTimeBuffer; let result = Buffer.concat([ (0, serializeTransaction_1.serializeTransaction)(targetTransaction, false, targetTransaction.timestamp, additionals), outputScript, ]); if (segwit && !isDecred) { let witness = Buffer.alloc(0); for (let i = 0; i < inputs.length; i++) { const tmpScriptData = Buffer.concat([ Buffer.from("02", "hex"), Buffer.from([signatures[i].length]), signatures[i], Buffer.from([publicKeys[i].length]), publicKeys[i], ]); witness = Buffer.concat([witness, tmpScriptData]); } result = Buffer.concat([result, witness]); } // from to https://zips.z.cash/zip-0225, zcash is different with other coins, the lock_time and nExpiryHeight fields are before the inputs and outputs if (!isZcash) { result = Buffer.concat([result, lockTimeBuffer]); if (expiryHeight) { result = Buffer.concat([ result, targetTransaction.nExpiryHeight || Buffer.alloc(0), targetTransaction.extraData || Buffer.alloc(0), ]); } } if (isDecred) { let decredWitness = Buffer.from([targetTransaction.inputs.length]); inputs.forEach((input, inputIndex) => { decredWitness = Buffer.concat([ decredWitness, Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), Buffer.from([0x00, 0x00, 0x00, 0x00]), //Block height Buffer.from([0xff, 0xff, 0xff, 0xff]), //Block index Buffer.from([targetTransaction.inputs[inputIndex].script.length]), targetTransaction.inputs[inputIndex].script, ]); }); result = Buffer.concat([result, decredWitness]); } if (isZcash) { result = Buffer.concat([result, Buffer.from([0x00, 0x00, 0x00])]); } return result.toString("hex"); } //# sourceMappingURL=createTransaction.js.map