UNPKG

@ledgerhq/hw-app-btc

Version:
464 lines • 17.4 kB
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { BufferReader, BufferWriter, unsafeFrom64bitLE, unsafeTo64bitLE } from "../buffertools"; export 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 || (psbtGlobal = {})); export 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 || (psbtIn = {})); export 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 || (psbtOut = {})); const PSBT_MAGIC_BYTES = Buffer.from([0x70, 0x73, 0x62, 0x74, 0xff]); export class NoSuchEntry extends Error { } /** * 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. */ export 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 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 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 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 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 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 BufferWriter(); this.writeBip32Derivation(buf, masterFingerprint, path); return buf.buffer(); } decodeBip32Derivation(buffer) { const buf = new 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 BufferWriter(); buf.writeVarInt(hashes.length); hashes.forEach(h => { buf.writeSlice(h); }); this.writeBip32Derivation(buf, masterFingerprint, path); return buf.buffer(); } decodeTapBip32Derivation(buffer) { const buf = new 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 }; } } 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 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 unsafeTo64bitLE(n); } function varint(n) { const b = new BufferWriter(); b.writeVarInt(n); return b.buffer(); } function fromVarint(buf) { return new BufferReader(buf).readVarInt(); } //# sourceMappingURL=psbtv2.js.map