@ledgerhq/hw-app-btc
Version:
Ledger Hardware Wallet Bitcoin Application API
469 lines • 17.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PsbtV2 = exports.NoSuchEntry = exports.psbtOut = exports.psbtIn = exports.psbtGlobal = void 0;
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
const buffertools_1 = require("../buffertools");
var psbtGlobal;
(function (psbtGlobal) {
psbtGlobal[psbtGlobal["TX_VERSION"] = 2] = "TX_VERSION";
psbtGlobal[psbtGlobal["FALLBACK_LOCKTIME"] = 3] = "FALLBACK_LOCKTIME";
psbtGlobal[psbtGlobal["INPUT_COUNT"] = 4] = "INPUT_COUNT";
psbtGlobal[psbtGlobal["OUTPUT_COUNT"] = 5] = "OUTPUT_COUNT";
psbtGlobal[psbtGlobal["TX_MODIFIABLE"] = 6] = "TX_MODIFIABLE";
psbtGlobal[psbtGlobal["VERSION"] = 251] = "VERSION";
})(psbtGlobal || (exports.psbtGlobal = psbtGlobal = {}));
var psbtIn;
(function (psbtIn) {
psbtIn[psbtIn["NON_WITNESS_UTXO"] = 0] = "NON_WITNESS_UTXO";
psbtIn[psbtIn["WITNESS_UTXO"] = 1] = "WITNESS_UTXO";
psbtIn[psbtIn["PARTIAL_SIG"] = 2] = "PARTIAL_SIG";
psbtIn[psbtIn["SIGHASH_TYPE"] = 3] = "SIGHASH_TYPE";
psbtIn[psbtIn["REDEEM_SCRIPT"] = 4] = "REDEEM_SCRIPT";
psbtIn[psbtIn["BIP32_DERIVATION"] = 6] = "BIP32_DERIVATION";
psbtIn[psbtIn["FINAL_SCRIPTSIG"] = 7] = "FINAL_SCRIPTSIG";
psbtIn[psbtIn["FINAL_SCRIPTWITNESS"] = 8] = "FINAL_SCRIPTWITNESS";
psbtIn[psbtIn["PREVIOUS_TXID"] = 14] = "PREVIOUS_TXID";
psbtIn[psbtIn["OUTPUT_INDEX"] = 15] = "OUTPUT_INDEX";
psbtIn[psbtIn["SEQUENCE"] = 16] = "SEQUENCE";
psbtIn[psbtIn["TAP_KEY_SIG"] = 19] = "TAP_KEY_SIG";
psbtIn[psbtIn["TAP_BIP32_DERIVATION"] = 22] = "TAP_BIP32_DERIVATION";
})(psbtIn || (exports.psbtIn = psbtIn = {}));
var psbtOut;
(function (psbtOut) {
psbtOut[psbtOut["REDEEM_SCRIPT"] = 0] = "REDEEM_SCRIPT";
psbtOut[psbtOut["BIP_32_DERIVATION"] = 2] = "BIP_32_DERIVATION";
psbtOut[psbtOut["AMOUNT"] = 3] = "AMOUNT";
psbtOut[psbtOut["SCRIPT"] = 4] = "SCRIPT";
psbtOut[psbtOut["TAP_BIP32_DERIVATION"] = 7] = "TAP_BIP32_DERIVATION";
})(psbtOut || (exports.psbtOut = psbtOut = {}));
const PSBT_MAGIC_BYTES = Buffer.from([0x70, 0x73, 0x62, 0x74, 0xff]);
class NoSuchEntry extends Error {
}
exports.NoSuchEntry = NoSuchEntry;
/**
* Implements Partially Signed Bitcoin Transaction version 2, BIP370, as
* documented at https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki
* and https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
*
* A psbt is a data structure that can carry all relevant information about a
* transaction through all stages of the signing process. From constructing an
* unsigned transaction to extracting the final serialized transaction ready for
* broadcast.
*
* This implementation is limited to what's needed in ledgerjs to carry out its
* duties, which means that support for features like multisig or taproot script
* path spending are not implemented. Specifically, it supports p2pkh,
* p2wpkhWrappedInP2sh, p2wpkh and p2tr key path spending.
*
* This class is made purposefully dumb, so it's easy to add support for
* complemantary fields as needed in the future.
*/
class PsbtV2 {
globalMap = new Map();
inputMaps = [];
outputMaps = [];
setGlobalTxVersion(version) {
this.setGlobal(psbtGlobal.TX_VERSION, uint32LE(version));
}
getGlobalTxVersion() {
return this.getGlobal(psbtGlobal.TX_VERSION).readUInt32LE(0);
}
setGlobalFallbackLocktime(locktime) {
this.setGlobal(psbtGlobal.FALLBACK_LOCKTIME, uint32LE(locktime));
}
getGlobalFallbackLocktime() {
return this.getGlobalOptional(psbtGlobal.FALLBACK_LOCKTIME)?.readUInt32LE(0);
}
setGlobalInputCount(inputCount) {
this.setGlobal(psbtGlobal.INPUT_COUNT, varint(inputCount));
}
getGlobalInputCount() {
return fromVarint(this.getGlobal(psbtGlobal.INPUT_COUNT));
}
setGlobalOutputCount(outputCount) {
this.setGlobal(psbtGlobal.OUTPUT_COUNT, varint(outputCount));
}
getGlobalOutputCount() {
return fromVarint(this.getGlobal(psbtGlobal.OUTPUT_COUNT));
}
setGlobalTxModifiable(byte) {
this.setGlobal(psbtGlobal.TX_MODIFIABLE, byte);
}
getGlobalTxModifiable() {
return this.getGlobalOptional(psbtGlobal.TX_MODIFIABLE);
}
setGlobalPsbtVersion(psbtVersion) {
this.setGlobal(psbtGlobal.VERSION, uint32LE(psbtVersion));
}
getGlobalPsbtVersion() {
return this.getGlobal(psbtGlobal.VERSION).readUInt32LE(0);
}
setInputNonWitnessUtxo(inputIndex, transaction) {
this.setInput(inputIndex, psbtIn.NON_WITNESS_UTXO, b(), transaction);
}
getInputNonWitnessUtxo(inputIndex) {
return this.getInputOptional(inputIndex, psbtIn.NON_WITNESS_UTXO, b());
}
setInputWitnessUtxo(inputIndex, amount, scriptPubKey) {
const buf = new buffertools_1.BufferWriter();
buf.writeSlice(amount);
buf.writeVarSlice(scriptPubKey);
this.setInput(inputIndex, psbtIn.WITNESS_UTXO, b(), buf.buffer());
}
getInputWitnessUtxo(inputIndex) {
const utxo = this.getInputOptional(inputIndex, psbtIn.WITNESS_UTXO, b());
if (!utxo)
return undefined;
const buf = new buffertools_1.BufferReader(utxo);
return { amount: buf.readSlice(8), scriptPubKey: buf.readVarSlice() };
}
setInputPartialSig(inputIndex, pubkey, signature) {
this.setInput(inputIndex, psbtIn.PARTIAL_SIG, pubkey, signature);
}
getInputPartialSig(inputIndex, pubkey) {
return this.getInputOptional(inputIndex, psbtIn.PARTIAL_SIG, pubkey);
}
setInputSighashType(inputIndex, sigHashtype) {
this.setInput(inputIndex, psbtIn.SIGHASH_TYPE, b(), uint32LE(sigHashtype));
}
getInputSighashType(inputIndex) {
const result = this.getInputOptional(inputIndex, psbtIn.SIGHASH_TYPE, b());
if (!result)
return undefined;
return result.readUInt32LE(0);
}
setInputRedeemScript(inputIndex, redeemScript) {
this.setInput(inputIndex, psbtIn.REDEEM_SCRIPT, b(), redeemScript);
}
getInputRedeemScript(inputIndex) {
return this.getInputOptional(inputIndex, psbtIn.REDEEM_SCRIPT, b());
}
setInputBip32Derivation(inputIndex, pubkey, masterFingerprint, path) {
if (pubkey.length != 33)
throw new Error("Invalid pubkey length: " + pubkey.length);
this.setInput(inputIndex, psbtIn.BIP32_DERIVATION, pubkey, this.encodeBip32Derivation(masterFingerprint, path));
}
getInputBip32Derivation(inputIndex, pubkey) {
const buf = this.getInputOptional(inputIndex, psbtIn.BIP32_DERIVATION, pubkey);
if (!buf)
return undefined;
return this.decodeBip32Derivation(buf);
}
setInputFinalScriptsig(inputIndex, scriptSig) {
this.setInput(inputIndex, psbtIn.FINAL_SCRIPTSIG, b(), scriptSig);
}
getInputFinalScriptsig(inputIndex) {
return this.getInputOptional(inputIndex, psbtIn.FINAL_SCRIPTSIG, b());
}
setInputFinalScriptwitness(inputIndex, scriptWitness) {
this.setInput(inputIndex, psbtIn.FINAL_SCRIPTWITNESS, b(), scriptWitness);
}
getInputFinalScriptwitness(inputIndex) {
return this.getInput(inputIndex, psbtIn.FINAL_SCRIPTWITNESS, b());
}
setInputPreviousTxId(inputIndex, txid) {
this.setInput(inputIndex, psbtIn.PREVIOUS_TXID, b(), txid);
}
getInputPreviousTxid(inputIndex) {
return this.getInput(inputIndex, psbtIn.PREVIOUS_TXID, b());
}
setInputOutputIndex(inputIndex, outputIndex) {
this.setInput(inputIndex, psbtIn.OUTPUT_INDEX, b(), uint32LE(outputIndex));
}
getInputOutputIndex(inputIndex) {
return this.getInput(inputIndex, psbtIn.OUTPUT_INDEX, b()).readUInt32LE(0);
}
setInputSequence(inputIndex, sequence) {
this.setInput(inputIndex, psbtIn.SEQUENCE, b(), uint32LE(sequence));
}
getInputSequence(inputIndex) {
return this.getInputOptional(inputIndex, psbtIn.SEQUENCE, b())?.readUInt32LE(0) ?? 0xffffffff;
}
setInputTapKeySig(inputIndex, sig) {
this.setInput(inputIndex, psbtIn.TAP_KEY_SIG, b(), sig);
}
getInputTapKeySig(inputIndex) {
return this.getInputOptional(inputIndex, psbtIn.TAP_KEY_SIG, b());
}
setInputTapBip32Derivation(inputIndex, pubkey, hashes, masterFingerprint, path) {
if (pubkey.length != 32)
throw new Error("Invalid pubkey length: " + pubkey.length);
const buf = this.encodeTapBip32Derivation(hashes, masterFingerprint, path);
this.setInput(inputIndex, psbtIn.TAP_BIP32_DERIVATION, pubkey, buf);
}
getInputTapBip32Derivation(inputIndex, pubkey) {
const buf = this.getInput(inputIndex, psbtIn.TAP_BIP32_DERIVATION, pubkey);
return this.decodeTapBip32Derivation(buf);
}
getInputKeyDatas(inputIndex, keyType) {
return this.getKeyDatas(this.inputMaps[inputIndex], keyType);
}
setOutputRedeemScript(outputIndex, redeemScript) {
this.setOutput(outputIndex, psbtOut.REDEEM_SCRIPT, b(), redeemScript);
}
getOutputRedeemScript(outputIndex) {
return this.getOutput(outputIndex, psbtOut.REDEEM_SCRIPT, b());
}
setOutputBip32Derivation(outputIndex, pubkey, masterFingerprint, path) {
this.setOutput(outputIndex, psbtOut.BIP_32_DERIVATION, pubkey, this.encodeBip32Derivation(masterFingerprint, path));
}
getOutputBip32Derivation(outputIndex, pubkey) {
const buf = this.getOutput(outputIndex, psbtOut.BIP_32_DERIVATION, pubkey);
return this.decodeBip32Derivation(buf);
}
setOutputAmount(outputIndex, amount) {
this.setOutput(outputIndex, psbtOut.AMOUNT, b(), uint64LE(amount));
}
getOutputAmount(outputIndex) {
const buf = this.getOutput(outputIndex, psbtOut.AMOUNT, b());
return (0, buffertools_1.unsafeFrom64bitLE)(buf);
}
setOutputScript(outputIndex, scriptPubKey) {
this.setOutput(outputIndex, psbtOut.SCRIPT, b(), scriptPubKey);
}
getOutputScript(outputIndex) {
return this.getOutput(outputIndex, psbtOut.SCRIPT, b());
}
setOutputTapBip32Derivation(outputIndex, pubkey, hashes, fingerprint, path) {
const buf = this.encodeTapBip32Derivation(hashes, fingerprint, path);
this.setOutput(outputIndex, psbtOut.TAP_BIP32_DERIVATION, pubkey, buf);
}
getOutputTapBip32Derivation(outputIndex, pubkey) {
const buf = this.getOutput(outputIndex, psbtOut.TAP_BIP32_DERIVATION, pubkey);
return this.decodeTapBip32Derivation(buf);
}
deleteInputEntries(inputIndex, keyTypes) {
const map = this.inputMaps[inputIndex];
map.forEach((_v, k, m) => {
if (this.isKeyType(k, keyTypes)) {
m.delete(k);
}
});
}
copy(to) {
this.copyMap(this.globalMap, to.globalMap);
this.copyMaps(this.inputMaps, to.inputMaps);
this.copyMaps(this.outputMaps, to.outputMaps);
}
copyMaps(from, to) {
from.forEach((m, index) => {
const to_index = new Map();
this.copyMap(m, to_index);
to[index] = to_index;
});
}
copyMap(from, to) {
from.forEach((v, k) => to.set(k, Buffer.from(v)));
}
serialize() {
const buf = new buffertools_1.BufferWriter();
buf.writeSlice(Buffer.from([0x70, 0x73, 0x62, 0x74, 0xff]));
serializeMap(buf, this.globalMap);
this.inputMaps.forEach(map => {
serializeMap(buf, map);
});
this.outputMaps.forEach(map => {
serializeMap(buf, map);
});
return buf.buffer();
}
deserialize(psbt) {
const buf = new buffertools_1.BufferReader(psbt);
if (!buf.readSlice(5).equals(PSBT_MAGIC_BYTES)) {
throw new Error("Invalid magic bytes");
}
while (this.readKeyPair(this.globalMap, buf))
;
for (let i = 0; i < this.getGlobalInputCount(); i++) {
this.inputMaps[i] = new Map();
while (this.readKeyPair(this.inputMaps[i], buf))
;
}
for (let i = 0; i < this.getGlobalOutputCount(); i++) {
this.outputMaps[i] = new Map();
while (this.readKeyPair(this.outputMaps[i], buf))
;
}
}
readKeyPair(map, buf) {
const keyLen = buf.readVarInt();
if (keyLen == 0) {
return false;
}
const keyType = buf.readUInt8();
const keyData = buf.readSlice(keyLen - 1);
const value = buf.readVarSlice();
set(map, keyType, keyData, value);
return true;
}
getKeyDatas(map, keyType) {
const result = [];
map.forEach((_v, k) => {
if (this.isKeyType(k, [keyType])) {
result.push(Buffer.from(k.substring(2), "hex"));
}
});
return result;
}
isKeyType(hexKey, keyTypes) {
const keyType = Buffer.from(hexKey.substring(0, 2), "hex").readUInt8(0);
return keyTypes.some(k => k == keyType);
}
setGlobal(keyType, value) {
const key = new Key(keyType, Buffer.from([]));
this.globalMap.set(key.toString(), value);
}
getGlobal(keyType) {
return get(this.globalMap, keyType, b(), false);
}
getGlobalOptional(keyType) {
return get(this.globalMap, keyType, b(), true);
}
setInput(index, keyType, keyData, value) {
set(this.getMap(index, this.inputMaps), keyType, keyData, value);
}
getInput(index, keyType, keyData) {
return get(this.inputMaps[index], keyType, keyData, false);
}
getInputOptional(index, keyType, keyData) {
return get(this.inputMaps[index], keyType, keyData, true);
}
setOutput(index, keyType, keyData, value) {
set(this.getMap(index, this.outputMaps), keyType, keyData, value);
}
getOutput(index, keyType, keyData) {
return get(this.outputMaps[index], keyType, keyData, false);
}
getMap(index, maps) {
if (maps[index]) {
return maps[index];
}
return (maps[index] = new Map());
}
encodeBip32Derivation(masterFingerprint, path) {
const buf = new buffertools_1.BufferWriter();
this.writeBip32Derivation(buf, masterFingerprint, path);
return buf.buffer();
}
decodeBip32Derivation(buffer) {
const buf = new buffertools_1.BufferReader(buffer);
return this.readBip32Derivation(buf);
}
writeBip32Derivation(buf, masterFingerprint, path) {
buf.writeSlice(masterFingerprint);
path.forEach(element => {
buf.writeUInt32(element);
});
}
readBip32Derivation(buf) {
const masterFingerprint = buf.readSlice(4);
const path = [];
while (buf.offset < buf.buffer.length) {
path.push(buf.readUInt32());
}
return { masterFingerprint, path };
}
encodeTapBip32Derivation(hashes, masterFingerprint, path) {
const buf = new buffertools_1.BufferWriter();
buf.writeVarInt(hashes.length);
hashes.forEach(h => {
buf.writeSlice(h);
});
this.writeBip32Derivation(buf, masterFingerprint, path);
return buf.buffer();
}
decodeTapBip32Derivation(buffer) {
const buf = new buffertools_1.BufferReader(buffer);
const hashCount = buf.readVarInt();
const hashes = [];
for (let i = 0; i < hashCount; i++) {
hashes.push(buf.readSlice(32));
}
const deriv = this.readBip32Derivation(buf);
return { hashes, ...deriv };
}
}
exports.PsbtV2 = PsbtV2;
function get(map, keyType, keyData, acceptUndefined) {
if (!map)
throw Error("No such map");
const key = new Key(keyType, keyData);
const value = map.get(key.toString());
if (!value) {
if (acceptUndefined) {
return undefined;
}
throw new NoSuchEntry(key.toString());
}
// Make sure to return a copy, to protect the underlying data.
return Buffer.from(value);
}
class Key {
keyType;
keyData;
constructor(keyType, keyData) {
this.keyType = keyType;
this.keyData = keyData;
}
toString() {
const buf = new buffertools_1.BufferWriter();
this.toBuffer(buf);
return buf.buffer().toString("hex");
}
serialize(buf) {
buf.writeVarInt(1 + this.keyData.length);
this.toBuffer(buf);
}
toBuffer(buf) {
buf.writeUInt8(this.keyType);
buf.writeSlice(this.keyData);
}
}
class KeyPair {
key;
value;
constructor(key, value) {
this.key = key;
this.value = value;
}
serialize(buf) {
this.key.serialize(buf);
buf.writeVarSlice(this.value);
}
}
function createKey(buf) {
return new Key(buf.readUInt8(0), buf.slice(1));
}
function serializeMap(buf, map) {
for (const k of map.keys()) {
const value = map.get(k);
const keyPair = new KeyPair(createKey(Buffer.from(k, "hex")), value);
keyPair.serialize(buf);
}
buf.writeUInt8(0);
}
function b() {
return Buffer.from([]);
}
function set(map, keyType, keyData, value) {
const key = new Key(keyType, keyData);
map.set(key.toString(), value);
}
function uint32LE(n) {
const b = Buffer.alloc(4);
b.writeUInt32LE(n, 0);
return b;
}
function uint64LE(n) {
return (0, buffertools_1.unsafeTo64bitLE)(n);
}
function varint(n) {
const b = new buffertools_1.BufferWriter();
b.writeVarInt(n);
return b.buffer();
}
function fromVarint(buf) {
return new buffertools_1.BufferReader(buf).readVarInt();
}
//# sourceMappingURL=psbtv2.js.map