@ledgerhq/hw-app-btc
Version:
Ledger Hardware Wallet Bitcoin Application API
132 lines (108 loc) • 4.02 kB
text/typescript
import invariant from "invariant";
import type Transport from "@ledgerhq/hw-transport";
import type { Transaction } from "./types";
import { MAX_SCRIPT_BLOCK } from "./constants";
import { createVarint } from "./varint";
export async function getTrustedInputRaw(
transport: Transport,
transactionData: Buffer,
indexLookup?: number | null | undefined,
): Promise<string> {
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;
}
export async function getTrustedInput(
transport: Transport,
indexLookup: number,
transaction: Transaction,
additionals: Array<string> = [],
): Promise<string> {
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?: Buffer) => {
const seq = sequence || Buffer.alloc(0);
const scriptBlocks: Buffer[] = [];
let offset = 0;
while (offset !== script.length) {
const blockSize =
script.length - offset > MAX_SCRIPT_BLOCK ? 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);
await getTrustedInputRaw(
transport,
Buffer.concat([
transaction.version,
transaction.timestamp || Buffer.alloc(0),
transaction.nVersionGroupId || Buffer.alloc(0),
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, 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, createVarint(outputs.length));
for (const output of outputs) {
const data = Buffer.concat([
output.amount,
isDecred ? Buffer.from([0x00, 0x00]) : Buffer.alloc(0), //Version script
createVarint(output.script.length),
output.script,
]);
await getTrustedInputRaw(transport, data);
}
const endData: Buffer[] = [];
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([createVarint(data.length), data]);
}
const res = await processScriptBlocks(Buffer.concat([locktime, extraPart || Buffer.alloc(0)]));
invariant(res, "missing result in processScriptBlocks");
return res;
}