Finalize and extract done
This commit is contained in:
parent
343297a359
commit
813b84f91f
3 changed files with 400 additions and 93 deletions
217
src/psbt.js
217
src/psbt.js
|
@ -6,6 +6,7 @@ const crypto_1 = require('./crypto');
|
||||||
const payments = require('./payments');
|
const payments = require('./payments');
|
||||||
const bscript = require('./script');
|
const bscript = require('./script');
|
||||||
const transaction_1 = require('./transaction');
|
const transaction_1 = require('./transaction');
|
||||||
|
const varuint = require('varuint-bitcoin');
|
||||||
class Psbt extends bip174_1.Psbt {
|
class Psbt extends bip174_1.Psbt {
|
||||||
// protected __TX: Transaction;
|
// protected __TX: Transaction;
|
||||||
constructor(network) {
|
constructor(network) {
|
||||||
|
@ -23,56 +24,75 @@ class Psbt extends bip174_1.Psbt {
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
canFinalize(inputIndex) {
|
extractTransaction() {
|
||||||
|
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
|
||||||
|
const tx = transaction_1.Transaction.fromBuffer(this.globalMap.unsignedTx);
|
||||||
|
this.inputs.forEach((input, idx) => {
|
||||||
|
if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
|
||||||
|
if (input.finalScriptWitness) {
|
||||||
|
const decompiled = bscript.decompile(input.finalScriptWitness);
|
||||||
|
if (decompiled) tx.ins[idx].witness = bscript.toStack(decompiled);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
finalizeAllInputs() {
|
||||||
|
const inputResults = range(this.inputs.length).map(idx =>
|
||||||
|
this.finalizeInput(idx),
|
||||||
|
);
|
||||||
|
const result = inputResults.every(val => val === true);
|
||||||
|
return {
|
||||||
|
result,
|
||||||
|
inputResults,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
finalizeInput(inputIndex) {
|
||||||
const input = utils_1.checkForInput(this.inputs, inputIndex);
|
const input = utils_1.checkForInput(this.inputs, inputIndex);
|
||||||
const script = getScriptFromInput(
|
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
|
||||||
inputIndex,
|
inputIndex,
|
||||||
input,
|
input,
|
||||||
this.globalMap.unsignedTx,
|
this.globalMap.unsignedTx,
|
||||||
);
|
);
|
||||||
if (!script) return false;
|
if (!script) return false;
|
||||||
const scriptType = classifyScript(script);
|
const scriptType = classifyScript(script);
|
||||||
const hasSigs = (neededSigs, partialSig) => {
|
if (!canFinalize(input, script, scriptType)) return false;
|
||||||
if (!partialSig) return false;
|
let finalScriptSig;
|
||||||
if (partialSig.length > neededSigs)
|
let finalScriptWitness;
|
||||||
throw new Error('Too many signatures');
|
// Wow, the payments API is very handy
|
||||||
return partialSig.length === neededSigs;
|
const payment = getPayment(script, scriptType, input.partialSig);
|
||||||
};
|
const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment });
|
||||||
switch (scriptType) {
|
const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment });
|
||||||
case 'pubkey':
|
if (isSegwit) {
|
||||||
return hasSigs(1, input.partialSig);
|
if (p2wsh) {
|
||||||
case 'pubkeyhash':
|
finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness);
|
||||||
return hasSigs(1, input.partialSig);
|
} else {
|
||||||
case 'multisig':
|
finalScriptWitness = witnessStackToScriptWitness(payment.witness);
|
||||||
const p2ms = payments.p2ms({ output: script });
|
}
|
||||||
return hasSigs(p2ms.m, input.partialSig);
|
if (p2sh) {
|
||||||
case 'witnesspubkeyhash':
|
finalScriptSig = bscript.compile([p2sh.redeem.output]);
|
||||||
return hasSigs(1, input.partialSig);
|
}
|
||||||
default:
|
} else {
|
||||||
return false;
|
finalScriptSig = payment.input;
|
||||||
}
|
}
|
||||||
|
if (finalScriptSig)
|
||||||
|
this.addFinalScriptSigToInput(inputIndex, finalScriptSig);
|
||||||
|
if (finalScriptWitness)
|
||||||
|
this.addFinalScriptWitnessToInput(inputIndex, finalScriptWitness);
|
||||||
|
if (!finalScriptSig && !finalScriptWitness) return false;
|
||||||
|
this.clearFinalizedInput(inputIndex);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
signInput(inputIndex, keyPair) {
|
signInput(inputIndex, keyPair) {
|
||||||
const input = this.inputs[inputIndex];
|
const input = utils_1.checkForInput(this.inputs, inputIndex);
|
||||||
if (input === undefined) throw new Error(`No input #${inputIndex}`);
|
if (!keyPair || !keyPair.publicKey)
|
||||||
|
throw new Error('Need Signer to sign input');
|
||||||
const { hash, sighashType, script } = getHashForSig(
|
const { hash, sighashType, script } = getHashForSig(
|
||||||
inputIndex,
|
inputIndex,
|
||||||
input,
|
input,
|
||||||
this.globalMap.unsignedTx,
|
this.globalMap.unsignedTx,
|
||||||
);
|
);
|
||||||
const pubkey = keyPair.publicKey;
|
const pubkey = keyPair.publicKey;
|
||||||
const pubkeyHash = crypto_1.hash160(keyPair.publicKey);
|
checkScriptForPubkey(pubkey, script);
|
||||||
const decompiled = bscript.decompile(script);
|
|
||||||
if (decompiled === null) throw new Error('Unknown script error');
|
|
||||||
const hasKey = decompiled.some(element => {
|
|
||||||
if (typeof element === 'number') return false;
|
|
||||||
return element.equals(pubkey) || element.equals(pubkeyHash);
|
|
||||||
});
|
|
||||||
if (!hasKey) {
|
|
||||||
throw new Error(
|
|
||||||
`Can not sign for this input with the key ${pubkey.toString('hex')}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const partialSig = {
|
const partialSig = {
|
||||||
pubkey,
|
pubkey,
|
||||||
signature: bscript.signature.encode(keyPair.sign(hash), sighashType),
|
signature: bscript.signature.encode(keyPair.sign(hash), sighashType),
|
||||||
|
@ -81,6 +101,77 @@ class Psbt extends bip174_1.Psbt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.Psbt = Psbt;
|
exports.Psbt = Psbt;
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Helper functions
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
function isFinalized(input) {
|
||||||
|
return !!input.finalScriptSig || !!input.finalScriptWitness;
|
||||||
|
}
|
||||||
|
function getPayment(script, scriptType, partialSig) {
|
||||||
|
let payment;
|
||||||
|
switch (scriptType) {
|
||||||
|
case 'multisig':
|
||||||
|
payment = payments.p2ms({
|
||||||
|
output: script,
|
||||||
|
signatures: partialSig.map(ps => ps.signature),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'pubkey':
|
||||||
|
payment = payments.p2pk({
|
||||||
|
output: script,
|
||||||
|
signature: partialSig[0].signature,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'pubkeyhash':
|
||||||
|
payment = payments.p2pkh({
|
||||||
|
output: script,
|
||||||
|
pubkey: partialSig[0].pubkey,
|
||||||
|
signature: partialSig[0].signature,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'witnesspubkeyhash':
|
||||||
|
payment = payments.p2wpkh({
|
||||||
|
output: script,
|
||||||
|
pubkey: partialSig[0].pubkey,
|
||||||
|
signature: partialSig[0].signature,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return payment;
|
||||||
|
}
|
||||||
|
function canFinalize(input, script, scriptType) {
|
||||||
|
switch (scriptType) {
|
||||||
|
case 'pubkey':
|
||||||
|
case 'pubkeyhash':
|
||||||
|
case 'witnesspubkeyhash':
|
||||||
|
return hasSigs(1, input.partialSig);
|
||||||
|
case 'multisig':
|
||||||
|
const p2ms = payments.p2ms({ output: script });
|
||||||
|
return hasSigs(p2ms.m, input.partialSig);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function checkScriptForPubkey(pubkey, script) {
|
||||||
|
const pubkeyHash = crypto_1.hash160(pubkey);
|
||||||
|
const decompiled = bscript.decompile(script);
|
||||||
|
if (decompiled === null) throw new Error('Unknown script error');
|
||||||
|
const hasKey = decompiled.some(element => {
|
||||||
|
if (typeof element === 'number') return false;
|
||||||
|
return element.equals(pubkey) || element.equals(pubkeyHash);
|
||||||
|
});
|
||||||
|
if (!hasKey) {
|
||||||
|
throw new Error(
|
||||||
|
`Can not sign for this input with the key ${pubkey.toString('hex')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
const getHashForSig = (inputIndex, input, txBuf) => {
|
const getHashForSig = (inputIndex, input, txBuf) => {
|
||||||
const unsignedTx = transaction_1.Transaction.fromBuffer(txBuf);
|
const unsignedTx = transaction_1.Transaction.fromBuffer(txBuf);
|
||||||
const sighashType =
|
const sighashType =
|
||||||
|
@ -202,29 +293,67 @@ const classifyScript = script => {
|
||||||
return 'nonstandard';
|
return 'nonstandard';
|
||||||
};
|
};
|
||||||
function getScriptFromInput(inputIndex, input, _unsignedTx) {
|
function getScriptFromInput(inputIndex, input, _unsignedTx) {
|
||||||
let script;
|
const res = {
|
||||||
|
script: null,
|
||||||
|
isSegwit: false,
|
||||||
|
isP2SH: false,
|
||||||
|
isP2WSH: false,
|
||||||
|
};
|
||||||
if (input.nonWitnessUtxo) {
|
if (input.nonWitnessUtxo) {
|
||||||
if (input.redeemScript) {
|
if (input.redeemScript) {
|
||||||
script = input.redeemScript;
|
res.isP2SH = true;
|
||||||
|
res.script = input.redeemScript;
|
||||||
} else {
|
} else {
|
||||||
const unsignedTx = transaction_1.Transaction.fromBuffer(_unsignedTx);
|
const unsignedTx = transaction_1.Transaction.fromBuffer(_unsignedTx);
|
||||||
const nonWitnessUtxoTx = transaction_1.Transaction.fromBuffer(
|
const nonWitnessUtxoTx = transaction_1.Transaction.fromBuffer(
|
||||||
input.nonWitnessUtxo,
|
input.nonWitnessUtxo,
|
||||||
);
|
);
|
||||||
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
||||||
script = nonWitnessUtxoTx.outs[prevoutIndex].script;
|
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
|
||||||
}
|
}
|
||||||
} else if (input.witnessUtxo) {
|
} else if (input.witnessUtxo) {
|
||||||
|
res.isSegwit = true;
|
||||||
|
res.isP2SH = !!input.redeemScript;
|
||||||
|
res.isP2WSH = !!input.witnessScript;
|
||||||
if (input.witnessScript) {
|
if (input.witnessScript) {
|
||||||
script = input.witnessScript;
|
res.script = input.witnessScript;
|
||||||
} else if (input.redeemScript) {
|
} else if (input.redeemScript) {
|
||||||
script = payments.p2pkh({ hash: input.redeemScript.slice(2) }).output;
|
res.script = payments.p2pkh({
|
||||||
|
hash: input.redeemScript.slice(2),
|
||||||
|
}).output;
|
||||||
} else {
|
} else {
|
||||||
script = payments.p2pkh({ hash: input.witnessUtxo.script.slice(2) })
|
res.script = payments.p2pkh({
|
||||||
.output;
|
hash: input.witnessUtxo.script.slice(2),
|
||||||
|
}).output;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
return script;
|
return res;
|
||||||
}
|
}
|
||||||
|
const hasSigs = (neededSigs, partialSig) => {
|
||||||
|
if (!partialSig) return false;
|
||||||
|
if (partialSig.length > neededSigs) throw new Error('Too many signatures');
|
||||||
|
return partialSig.length === neededSigs;
|
||||||
|
};
|
||||||
|
function witnessStackToScriptWitness(witness) {
|
||||||
|
let buffer = Buffer.allocUnsafe(0);
|
||||||
|
function writeSlice(slice) {
|
||||||
|
buffer = Buffer.concat([buffer, Buffer.from(slice)]);
|
||||||
|
}
|
||||||
|
function writeVarInt(i) {
|
||||||
|
const currentLen = buffer.length;
|
||||||
|
const varintLen = varuint.encodingLength(i);
|
||||||
|
buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
|
||||||
|
varuint.encode(i, buffer, currentLen);
|
||||||
|
}
|
||||||
|
function writeVarSlice(slice) {
|
||||||
|
writeVarInt(slice.length);
|
||||||
|
writeSlice(slice);
|
||||||
|
}
|
||||||
|
function writeVector(vector) {
|
||||||
|
writeVarInt(vector.length);
|
||||||
|
vector.forEach(writeVarSlice);
|
||||||
|
}
|
||||||
|
writeVector(witness);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
const range = n => [...Array(n).keys()];
|
||||||
|
|
268
ts_src/psbt.ts
268
ts_src/psbt.ts
|
@ -1,5 +1,5 @@
|
||||||
import { Psbt as PsbtBase } from 'bip174';
|
import { Psbt as PsbtBase } from 'bip174';
|
||||||
import { PsbtInput } from 'bip174/src/lib/interfaces';
|
import { PartialSig, PsbtInput } from 'bip174/src/lib/interfaces';
|
||||||
import { checkForInput } from 'bip174/src/lib/utils';
|
import { checkForInput } from 'bip174/src/lib/utils';
|
||||||
import { hash160 } from './crypto';
|
import { hash160 } from './crypto';
|
||||||
import { Signer } from './ecpair';
|
import { Signer } from './ecpair';
|
||||||
|
@ -7,6 +7,7 @@ import { Network } from './networks';
|
||||||
import * as payments from './payments';
|
import * as payments from './payments';
|
||||||
import * as bscript from './script';
|
import * as bscript from './script';
|
||||||
import { Transaction } from './transaction';
|
import { Transaction } from './transaction';
|
||||||
|
const varuint = require('varuint-bitcoin');
|
||||||
|
|
||||||
export class Psbt extends PsbtBase {
|
export class Psbt extends PsbtBase {
|
||||||
// protected __TX: Transaction;
|
// protected __TX: Transaction;
|
||||||
|
@ -25,41 +26,84 @@ export class Psbt extends PsbtBase {
|
||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
canFinalize(inputIndex: number): boolean {
|
extractTransaction(): Transaction {
|
||||||
|
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
|
||||||
|
const tx = Transaction.fromBuffer(this.globalMap.unsignedTx!);
|
||||||
|
this.inputs.forEach((input, idx) => {
|
||||||
|
if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
|
||||||
|
if (input.finalScriptWitness) {
|
||||||
|
const decompiled = bscript.decompile(input.finalScriptWitness);
|
||||||
|
if (decompiled) tx.ins[idx].witness = bscript.toStack(decompiled);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
finalizeAllInputs(): {
|
||||||
|
result: boolean;
|
||||||
|
inputResults: boolean[];
|
||||||
|
} {
|
||||||
|
const inputResults = range(this.inputs.length).map(idx =>
|
||||||
|
this.finalizeInput(idx),
|
||||||
|
);
|
||||||
|
const result = inputResults.every(val => val === true);
|
||||||
|
return {
|
||||||
|
result,
|
||||||
|
inputResults,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
finalizeInput(inputIndex: number): boolean {
|
||||||
const input = checkForInput(this.inputs, inputIndex);
|
const input = checkForInput(this.inputs, inputIndex);
|
||||||
const script = getScriptFromInput(
|
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
|
||||||
inputIndex,
|
inputIndex,
|
||||||
input,
|
input,
|
||||||
this.globalMap.unsignedTx!,
|
this.globalMap.unsignedTx!,
|
||||||
);
|
);
|
||||||
if (!script) return false;
|
if (!script) return false;
|
||||||
|
|
||||||
const scriptType = classifyScript(script);
|
const scriptType = classifyScript(script);
|
||||||
|
if (!canFinalize(input, script, scriptType)) return false;
|
||||||
|
|
||||||
const hasSigs = (neededSigs: number, partialSig?: any[]): boolean => {
|
let finalScriptSig: Buffer | undefined;
|
||||||
if (!partialSig) return false;
|
let finalScriptWitness: Buffer | undefined;
|
||||||
if (partialSig.length > neededSigs)
|
|
||||||
throw new Error('Too many signatures');
|
|
||||||
return partialSig.length === neededSigs;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (scriptType) {
|
// Wow, the payments API is very handy
|
||||||
case 'pubkey':
|
const payment: payments.Payment = getPayment(
|
||||||
return hasSigs(1, input.partialSig);
|
script,
|
||||||
case 'pubkeyhash':
|
scriptType,
|
||||||
return hasSigs(1, input.partialSig);
|
input.partialSig!,
|
||||||
case 'multisig':
|
);
|
||||||
const p2ms = payments.p2ms({ output: script });
|
const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment });
|
||||||
return hasSigs(p2ms.m!, input.partialSig);
|
const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment });
|
||||||
case 'witnesspubkeyhash':
|
|
||||||
return hasSigs(1, input.partialSig);
|
if (isSegwit) {
|
||||||
default:
|
if (p2wsh) {
|
||||||
return false;
|
finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness!);
|
||||||
|
} else {
|
||||||
|
finalScriptWitness = witnessStackToScriptWitness(payment.witness!);
|
||||||
|
}
|
||||||
|
if (p2sh) {
|
||||||
|
finalScriptSig = bscript.compile([p2sh.redeem!.output!]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finalScriptSig = payment.input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (finalScriptSig)
|
||||||
|
this.addFinalScriptSigToInput(inputIndex, finalScriptSig);
|
||||||
|
if (finalScriptWitness)
|
||||||
|
this.addFinalScriptWitnessToInput(inputIndex, finalScriptWitness);
|
||||||
|
if (!finalScriptSig && !finalScriptWitness) return false;
|
||||||
|
|
||||||
|
this.clearFinalizedInput(inputIndex);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
signInput(inputIndex: number, keyPair: Signer): Psbt {
|
signInput(inputIndex: number, keyPair: Signer): Psbt {
|
||||||
const input = this.inputs[inputIndex];
|
const input = checkForInput(this.inputs, inputIndex);
|
||||||
if (input === undefined) throw new Error(`No input #${inputIndex}`);
|
if (!keyPair || !keyPair.publicKey)
|
||||||
|
throw new Error('Need Signer to sign input');
|
||||||
const { hash, sighashType, script } = getHashForSig(
|
const { hash, sighashType, script } = getHashForSig(
|
||||||
inputIndex,
|
inputIndex,
|
||||||
input,
|
input,
|
||||||
|
@ -67,21 +111,8 @@ export class Psbt extends PsbtBase {
|
||||||
);
|
);
|
||||||
|
|
||||||
const pubkey = keyPair.publicKey;
|
const pubkey = keyPair.publicKey;
|
||||||
const pubkeyHash = hash160(keyPair.publicKey);
|
|
||||||
|
|
||||||
const decompiled = bscript.decompile(script);
|
checkScriptForPubkey(pubkey, script);
|
||||||
if (decompiled === null) throw new Error('Unknown script error');
|
|
||||||
|
|
||||||
const hasKey = decompiled.some(element => {
|
|
||||||
if (typeof element === 'number') return false;
|
|
||||||
return element.equals(pubkey) || element.equals(pubkeyHash);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!hasKey) {
|
|
||||||
throw new Error(
|
|
||||||
`Can not sign for this input with the key ${pubkey.toString('hex')}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const partialSig = {
|
const partialSig = {
|
||||||
pubkey,
|
pubkey,
|
||||||
|
@ -92,6 +123,93 @@ export class Psbt extends PsbtBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Helper functions
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
function isFinalized(input: PsbtInput): boolean {
|
||||||
|
return !!input.finalScriptSig || !!input.finalScriptWitness;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPayment(
|
||||||
|
script: Buffer,
|
||||||
|
scriptType: string,
|
||||||
|
partialSig: PartialSig[],
|
||||||
|
): payments.Payment {
|
||||||
|
let payment: payments.Payment;
|
||||||
|
switch (scriptType) {
|
||||||
|
case 'multisig':
|
||||||
|
payment = payments.p2ms({
|
||||||
|
output: script,
|
||||||
|
signatures: partialSig.map(ps => ps.signature),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'pubkey':
|
||||||
|
payment = payments.p2pk({
|
||||||
|
output: script,
|
||||||
|
signature: partialSig[0].signature,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'pubkeyhash':
|
||||||
|
payment = payments.p2pkh({
|
||||||
|
output: script,
|
||||||
|
pubkey: partialSig[0].pubkey,
|
||||||
|
signature: partialSig[0].signature,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'witnesspubkeyhash':
|
||||||
|
payment = payments.p2wpkh({
|
||||||
|
output: script,
|
||||||
|
pubkey: partialSig[0].pubkey,
|
||||||
|
signature: partialSig[0].signature,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return payment!;
|
||||||
|
}
|
||||||
|
|
||||||
|
function canFinalize(
|
||||||
|
input: PsbtInput,
|
||||||
|
script: Buffer,
|
||||||
|
scriptType: string,
|
||||||
|
): boolean {
|
||||||
|
switch (scriptType) {
|
||||||
|
case 'pubkey':
|
||||||
|
case 'pubkeyhash':
|
||||||
|
case 'witnesspubkeyhash':
|
||||||
|
return hasSigs(1, input.partialSig);
|
||||||
|
case 'multisig':
|
||||||
|
const p2ms = payments.p2ms({ output: script });
|
||||||
|
return hasSigs(p2ms.m!, input.partialSig);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkScriptForPubkey(pubkey: Buffer, script: Buffer): void {
|
||||||
|
const pubkeyHash = hash160(pubkey);
|
||||||
|
|
||||||
|
const decompiled = bscript.decompile(script);
|
||||||
|
if (decompiled === null) throw new Error('Unknown script error');
|
||||||
|
|
||||||
|
const hasKey = decompiled.some(element => {
|
||||||
|
if (typeof element === 'number') return false;
|
||||||
|
return element.equals(pubkey) || element.equals(pubkeyHash);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasKey) {
|
||||||
|
throw new Error(
|
||||||
|
`Can not sign for this input with the key ${pubkey.toString('hex')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface HashForSigData {
|
interface HashForSigData {
|
||||||
script: Buffer;
|
script: Buffer;
|
||||||
hash: Buffer;
|
hash: Buffer;
|
||||||
|
@ -239,32 +357,86 @@ const classifyScript = (script: Buffer): string => {
|
||||||
return 'nonstandard';
|
return 'nonstandard';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface GetScriptReturn {
|
||||||
|
script: Buffer | null;
|
||||||
|
isSegwit: boolean;
|
||||||
|
isP2SH: boolean;
|
||||||
|
isP2WSH: boolean;
|
||||||
|
}
|
||||||
function getScriptFromInput(
|
function getScriptFromInput(
|
||||||
inputIndex: number,
|
inputIndex: number,
|
||||||
input: PsbtInput,
|
input: PsbtInput,
|
||||||
_unsignedTx: Buffer,
|
_unsignedTx: Buffer,
|
||||||
): Buffer | undefined {
|
): GetScriptReturn {
|
||||||
let script: Buffer;
|
const res: GetScriptReturn = {
|
||||||
|
script: null,
|
||||||
|
isSegwit: false,
|
||||||
|
isP2SH: false,
|
||||||
|
isP2WSH: false,
|
||||||
|
};
|
||||||
if (input.nonWitnessUtxo) {
|
if (input.nonWitnessUtxo) {
|
||||||
if (input.redeemScript) {
|
if (input.redeemScript) {
|
||||||
script = input.redeemScript;
|
res.isP2SH = true;
|
||||||
|
res.script = input.redeemScript;
|
||||||
} else {
|
} else {
|
||||||
const unsignedTx = Transaction.fromBuffer(_unsignedTx);
|
const unsignedTx = Transaction.fromBuffer(_unsignedTx);
|
||||||
const nonWitnessUtxoTx = Transaction.fromBuffer(input.nonWitnessUtxo);
|
const nonWitnessUtxoTx = Transaction.fromBuffer(input.nonWitnessUtxo);
|
||||||
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
||||||
script = nonWitnessUtxoTx.outs[prevoutIndex].script;
|
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
|
||||||
}
|
}
|
||||||
} else if (input.witnessUtxo) {
|
} else if (input.witnessUtxo) {
|
||||||
|
res.isSegwit = true;
|
||||||
|
res.isP2SH = !!input.redeemScript;
|
||||||
|
res.isP2WSH = !!input.witnessScript;
|
||||||
if (input.witnessScript) {
|
if (input.witnessScript) {
|
||||||
script = input.witnessScript;
|
res.script = input.witnessScript;
|
||||||
} else if (input.redeemScript) {
|
} else if (input.redeemScript) {
|
||||||
script = payments.p2pkh({ hash: input.redeemScript.slice(2) }).output!;
|
res.script = payments.p2pkh({
|
||||||
|
hash: input.redeemScript.slice(2),
|
||||||
|
}).output!;
|
||||||
} else {
|
} else {
|
||||||
script = payments.p2pkh({ hash: input.witnessUtxo.script.slice(2) })
|
res.script = payments.p2pkh({
|
||||||
.output!;
|
hash: input.witnessUtxo.script.slice(2),
|
||||||
|
}).output!;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
return script;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasSigs = (neededSigs: number, partialSig?: any[]): boolean => {
|
||||||
|
if (!partialSig) return false;
|
||||||
|
if (partialSig.length > neededSigs) throw new Error('Too many signatures');
|
||||||
|
return partialSig.length === neededSigs;
|
||||||
|
};
|
||||||
|
|
||||||
|
function witnessStackToScriptWitness(witness: Buffer[]): Buffer {
|
||||||
|
let buffer = Buffer.allocUnsafe(0);
|
||||||
|
|
||||||
|
function writeSlice(slice: Buffer): void {
|
||||||
|
buffer = Buffer.concat([buffer, Buffer.from(slice)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeVarInt(i: number): void {
|
||||||
|
const currentLen = buffer.length;
|
||||||
|
const varintLen = varuint.encodingLength(i);
|
||||||
|
|
||||||
|
buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
|
||||||
|
varuint.encode(i, buffer, currentLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeVarSlice(slice: Buffer): void {
|
||||||
|
writeVarInt(slice.length);
|
||||||
|
writeSlice(slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeVector(vector: Buffer[]): void {
|
||||||
|
writeVarInt(vector.length);
|
||||||
|
vector.forEach(writeVarSlice);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeVector(witness);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = (n: number): number[] => [...Array(n).keys()];
|
||||||
|
|
8
types/psbt.d.ts
vendored
8
types/psbt.d.ts
vendored
|
@ -1,9 +1,15 @@
|
||||||
import { Psbt as PsbtBase } from 'bip174';
|
import { Psbt as PsbtBase } from 'bip174';
|
||||||
import { Signer } from './ecpair';
|
import { Signer } from './ecpair';
|
||||||
import { Network } from './networks';
|
import { Network } from './networks';
|
||||||
|
import { Transaction } from './transaction';
|
||||||
export declare class Psbt extends PsbtBase {
|
export declare class Psbt extends PsbtBase {
|
||||||
network?: Network | undefined;
|
network?: Network | undefined;
|
||||||
constructor(network?: Network | undefined);
|
constructor(network?: Network | undefined);
|
||||||
canFinalize(inputIndex: number): boolean;
|
extractTransaction(): Transaction;
|
||||||
|
finalizeAllInputs(): {
|
||||||
|
result: boolean;
|
||||||
|
inputResults: boolean[];
|
||||||
|
};
|
||||||
|
finalizeInput(inputIndex: number): boolean;
|
||||||
signInput(inputIndex: number, keyPair: Signer): Psbt;
|
signInput(inputIndex: number, keyPair: Signer): Psbt;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue