@ledgerhq/hw-app-btc
Version:
Ledger Hardware Wallet Bitcoin Application API
316 lines • 11.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.splitTransaction = splitTransaction;
const logs_1 = require("@ledgerhq/logs");
const varint_1 = require("./varint");
const debug_1 = require("./debug");
const constants_1 = require("./constants");
function splitTransaction(transactionHex, isSegwitSupported = false, hasExtraData = false, additionals = []) {
const inputs = [];
const outputs = [];
let witness = false;
let offset = 0;
const timestamp = Buffer.alloc(0);
let nExpiryHeight = Buffer.alloc(0);
let nVersionGroupId = Buffer.alloc(0);
let extraData = Buffer.alloc(0);
let witnessScript, locktime;
const isDecred = additionals.includes("decred");
const isZencash = additionals.includes("zencash");
const isZcash = additionals.includes("zcash");
const transaction = Buffer.from(transactionHex, "hex");
const version = transaction.slice(offset, offset + 4);
const overwinter = version.equals(Buffer.from([0x03, 0x00, 0x00, 0x80])) ||
version.equals(Buffer.from([0x04, 0x00, 0x00, 0x80])) ||
version.equals(Buffer.from([0x05, 0x00, 0x00, 0x80]));
const isZcashv5 = isZcash && version.equals(Buffer.from([0x05, 0x00, 0x00, 0x80]));
offset += 4;
if (isSegwitSupported &&
transaction[offset] === 0 &&
transaction[offset + 1] !== 0 &&
!isZencash) {
offset += 2;
witness = true;
}
if (overwinter) {
nVersionGroupId = transaction.slice(offset, 4 + offset);
offset += 4;
}
if (isZcashv5) {
locktime = transaction.slice(offset + 4, offset + 8);
nExpiryHeight = transaction.slice(offset + 8, offset + 12);
offset += 12;
}
let varint = (0, varint_1.getVarint)(transaction, offset);
const numberInputs = varint[0];
offset += varint[1];
for (let i = 0; i < numberInputs; i++) {
const prevout = transaction.slice(offset, offset + 36);
offset += 36;
let script = Buffer.alloc(0);
let tree = Buffer.alloc(0);
//No script for decred, it has a witness
if (!isDecred) {
varint = (0, varint_1.getVarint)(transaction, offset);
offset += varint[1];
script = transaction.slice(offset, offset + varint[0]);
offset += varint[0];
}
else {
//Tree field
tree = transaction.slice(offset, offset + 1);
offset += 1;
}
const sequence = transaction.slice(offset, offset + 4);
offset += 4;
inputs.push({
prevout,
script,
sequence,
tree,
});
}
varint = (0, varint_1.getVarint)(transaction, offset);
const numberOutputs = varint[0];
offset += varint[1];
for (let i = 0; i < numberOutputs; i++) {
const amount = transaction.slice(offset, offset + 8);
offset += 8;
if (isDecred) {
//Script version
offset += 2;
}
varint = (0, varint_1.getVarint)(transaction, offset);
offset += varint[1];
const script = transaction.slice(offset, offset + varint[0]);
offset += varint[0];
outputs.push({
amount,
script,
});
}
let sapling;
let orchard;
if (hasExtraData) {
if (isZcashv5) {
({ sapling, offset } = splitSaplingPart(transaction, offset));
({ orchard, offset } = splitOrchardPart(transaction, offset));
extraData = transaction.subarray(offset);
}
}
if (witness) {
witnessScript = transaction.slice(offset, -4);
locktime = transaction.slice(transaction.length - 4);
}
else if (!isZcashv5) {
locktime = transaction.slice(offset, offset + 4);
}
offset += 4;
if ((overwinter || isDecred) && !isZcashv5) {
nExpiryHeight = transaction.slice(offset, offset + 4);
offset += 4;
}
if (hasExtraData) {
if (!isZcashv5) {
extraData = transaction.slice(offset);
}
}
//Get witnesses for Decred
if (isDecred) {
varint = (0, varint_1.getVarint)(transaction, offset);
offset += varint[1];
if (varint[0] !== numberInputs) {
throw new Error("splitTransaction: incoherent number of witnesses");
}
for (let i = 0; i < numberInputs; i++) {
//amount
offset += 8;
//block height
offset += 4;
//block index
offset += 4;
//Script size
varint = (0, varint_1.getVarint)(transaction, offset);
offset += varint[1];
const script = transaction.slice(offset, offset + varint[0]);
offset += varint[0];
inputs[i].script = script;
}
}
const t = {
version,
inputs,
outputs,
locktime,
witness: witnessScript,
timestamp,
nVersionGroupId,
nExpiryHeight,
extraData,
sapling,
orchard,
};
(0, logs_1.log)("btc", `splitTransaction ${transactionHex}:\n${(0, debug_1.formatTransactionDebug)(t)}`);
return t;
}
/**
* Splits the Sapling part of a Zcash v5 transaction buffer according to https://zips.z.cash/zip-0225
*/
function splitSaplingPart(transaction, offset) {
let varint = (0, varint_1.getVarint)(transaction, offset);
const nSpendsSapling = varint[0];
offset += varint[1];
const vSpendsSapling = [];
for (let i = 0; i < nSpendsSapling; i++) {
const cv = transaction.slice(offset, offset + 32);
offset += 32;
const nullifier = transaction.slice(offset, offset + 32);
offset += 32;
const rk = transaction.slice(offset, offset + 32);
offset += 32;
vSpendsSapling.push({
cv,
nullifier,
rk,
});
}
varint = (0, varint_1.getVarint)(transaction, offset);
const nOutputsSapling = varint[0];
offset += varint[1];
const vOutputSapling = [];
for (let i = 0; i < nOutputsSapling; i++) {
const cv = transaction.slice(offset, offset + 32);
offset += 32;
const cmu = transaction.slice(offset, offset + 32);
offset += 32;
const ephemeralKey = transaction.slice(offset, offset + 32);
offset += 32;
const encCiphertext = transaction.slice(offset, offset + constants_1.zCashEncCiphertextSize);
offset += constants_1.zCashEncCiphertextSize;
const outCiphertext = transaction.slice(offset, offset + constants_1.zCashOutCiphertextSize);
offset += constants_1.zCashOutCiphertextSize;
vOutputSapling.push({
cv,
cmu,
ephemeralKey,
encCiphertext,
outCiphertext,
});
}
let valueBalanceSapling = Buffer.alloc(0);
if (nSpendsSapling + nOutputsSapling > 0) {
valueBalanceSapling = transaction.slice(offset, offset + 8);
offset += 8;
}
let anchorSapling = Buffer.alloc(0);
if (nSpendsSapling > 0) {
anchorSapling = transaction.slice(offset, offset + 32);
offset += 32;
}
let vSpendProofsSapling = Buffer.alloc(0);
let vSpendAuthSigsSapling = Buffer.alloc(0);
if (nSpendsSapling > 0) {
vSpendProofsSapling = transaction.slice(offset, offset + constants_1.zCashProofsSaplingSize * nSpendsSapling);
offset += constants_1.zCashProofsSaplingSize * nSpendsSapling;
vSpendAuthSigsSapling = transaction.slice(offset, offset + 64 * nSpendsSapling);
offset += 64 * nSpendsSapling;
}
let vOutputProofsSapling = Buffer.alloc(0);
if (nOutputsSapling > 0) {
vOutputProofsSapling = transaction.slice(offset, offset + constants_1.zCashProofsSaplingSize * nOutputsSapling);
offset += constants_1.zCashProofsSaplingSize * nOutputsSapling;
}
let bindingSigSapling = Buffer.alloc(0);
if (nSpendsSapling + nOutputsSapling > 0) {
bindingSigSapling = transaction.slice(offset, offset + 64);
offset += 64;
}
let sapling;
if (nSpendsSapling + nOutputsSapling > 0) {
sapling = {
nSpendsSapling,
vSpendsSapling,
nOutputsSapling,
vOutputSapling,
valueBalanceSapling,
anchorSapling,
vSpendProofsSapling,
vSpendAuthSigsSapling,
vOutputProofsSapling,
bindingSigSapling,
};
}
return { sapling, offset };
}
/**
* Splits the Orchard part of a Zcash v5 transaction buffer according to https://zips.z.cash/zip-0225
*/
function splitOrchardPart(transaction, offset) {
// orchard
let varint = (0, varint_1.getVarint)(transaction, offset);
const nActionsOrchard = varint[0];
offset += varint[1];
let orchard;
if (nActionsOrchard > 0) {
const actionsOrchard = [];
for (let i = 0; i < nActionsOrchard; i++) {
const cv = transaction.subarray(offset, offset + 32);
offset += 32;
const nullifier = transaction.subarray(offset, offset + 32);
offset += 32;
const rk = transaction.subarray(offset, offset + 32);
offset += 32;
const cmx = transaction.subarray(offset, offset + 32);
offset += 32;
const ephemeralKey = transaction.subarray(offset, offset + 32);
offset += 32;
const encCiphertext = transaction.subarray(offset, offset + constants_1.zCashEncCiphertextSize);
offset += constants_1.zCashEncCiphertextSize;
const outCiphertext = transaction.subarray(offset, offset + constants_1.zCashOutCiphertextSize);
offset += constants_1.zCashOutCiphertextSize;
const action = {
cv,
nullifier,
rk,
cmx,
ephemeralKey,
encCiphertext,
outCiphertext,
};
actionsOrchard.push(action);
}
// flag field
const flagsOrchard = transaction.subarray(offset, offset + 1);
offset += 1;
// value balance orchard
const valueBalanceOrchard = transaction.subarray(offset, offset + 8);
offset += 8;
const anchorOrchard = transaction.subarray(offset, offset + 32);
offset += 32;
// read the size of proof
varint = (0, varint_1.getVarint)(transaction, offset);
const sizeProofsOrchard = transaction.subarray(offset, offset + varint[1]);
offset += varint[1];
// proof field
const proofsOrchard = transaction.subarray(offset, offset + varint[0]);
offset += varint[0];
// vSpendAuthSigsOrchard field
const vSpendAuthSigsOrchard = transaction.subarray(offset, offset + nActionsOrchard * 64);
offset += nActionsOrchard * 64;
// bindingSigOrchard
const bindingSigOrchard = transaction.subarray(offset, offset + 64);
offset += 64;
orchard = {
vActions: actionsOrchard,
flags: flagsOrchard,
valueBalance: valueBalanceOrchard,
anchor: anchorOrchard,
sizeProofs: sizeProofsOrchard,
proofs: proofsOrchard,
vSpendsAuthSigs: vSpendAuthSigsOrchard,
bindingSig: bindingSigOrchard,
};
}
return { orchard, offset };
}
//# sourceMappingURL=splitTransaction.js.map