@ledgerhq/hw-app-btc
Version:
Ledger Hardware Wallet Bitcoin Application API
160 lines (145 loc) • 4.35 kB
text/typescript
import { log } from "@ledgerhq/logs";
import type { Transaction, TransactionInput, TransactionOutput } from "./types";
import { getVarint } from "./varint";
import { formatTransactionDebug } from "./debug";
export function splitTransaction(
transactionHex: string,
isSegwitSupported: boolean | null | undefined = false,
hasExtraData = false,
additionals: Array<string> = [],
): Transaction {
const inputs: TransactionInput[] = [];
const outputs: TransactionOutput[] = [];
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 = 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 = 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 = 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 = getVarint(transaction, offset);
offset += varint[1];
const script = transaction.slice(offset, offset + varint[0]);
offset += varint[0];
outputs.push({
amount,
script,
});
}
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) {
extraData = transaction.slice(offset);
}
//Get witnesses for Decred
if (isDecred) {
varint = 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 = getVarint(transaction, offset);
offset += varint[1];
const script = transaction.slice(offset, offset + varint[0]);
offset += varint[0];
inputs[i].script = script;
}
}
const t: Transaction = {
version,
inputs,
outputs,
locktime,
witness: witnessScript,
timestamp,
nVersionGroupId,
nExpiryHeight,
extraData,
};
log("btc", `splitTransaction ${transactionHex}:\n${formatTransactionDebug(t)}`);
return t;
}