Refactor
- Clean up sign - Get the meaningful script - Search for pubkey and prevent sign if can't find self - Tests failed, so comment out for now
This commit is contained in:
parent
f72c915ff1
commit
f28e9cef71
2 changed files with 335 additions and 272 deletions
289
src/psbt.js
289
src/psbt.js
|
@ -2,10 +2,153 @@
|
||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
const bip174_1 = require('bip174');
|
const bip174_1 = require('bip174');
|
||||||
const utils_1 = require('bip174/src/lib/utils');
|
const utils_1 = require('bip174/src/lib/utils');
|
||||||
const classify = require('./classify');
|
|
||||||
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');
|
||||||
|
class Psbt extends bip174_1.Psbt {
|
||||||
|
constructor(network) {
|
||||||
|
super();
|
||||||
|
this.network = network;
|
||||||
|
}
|
||||||
|
canFinalize(inputIndex) {
|
||||||
|
const input = utils_1.checkForInput(this.inputs, inputIndex);
|
||||||
|
const script = getScriptFromInput(
|
||||||
|
inputIndex,
|
||||||
|
input,
|
||||||
|
this.globalMap.unsignedTx,
|
||||||
|
);
|
||||||
|
if (!script) return false;
|
||||||
|
const scriptType = classifyScript(script);
|
||||||
|
// TODO: for each type
|
||||||
|
switch (scriptType) {
|
||||||
|
case 'pubkey':
|
||||||
|
return false;
|
||||||
|
case 'pubkeyhash':
|
||||||
|
return false;
|
||||||
|
case 'multisig':
|
||||||
|
return false;
|
||||||
|
case 'witnesspubkeyhash':
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signInput(inputIndex, keyPair) {
|
||||||
|
const input = this.inputs[inputIndex];
|
||||||
|
if (input === undefined) throw new Error(`No input #${inputIndex}`);
|
||||||
|
const { hash, sighashType } = getHashForSig(
|
||||||
|
inputIndex,
|
||||||
|
input,
|
||||||
|
this.globalMap.unsignedTx,
|
||||||
|
);
|
||||||
|
const pubkey = keyPair.publicKey;
|
||||||
|
// // TODO: throw error when the pubkey or pubkey hash is not found anywhere
|
||||||
|
// // in the script
|
||||||
|
// const pubkeyHash = hash160(keyPair.publicKey);
|
||||||
|
//
|
||||||
|
// 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 = {
|
||||||
|
pubkey,
|
||||||
|
signature: bscript.signature.encode(keyPair.sign(hash), sighashType),
|
||||||
|
};
|
||||||
|
return this.addPartialSigToInput(inputIndex, partialSig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Psbt = Psbt;
|
||||||
|
const getHashForSig = (inputIndex, input, txBuf) => {
|
||||||
|
const unsignedTx = transaction_1.Transaction.fromBuffer(txBuf);
|
||||||
|
const sighashType =
|
||||||
|
input.sighashType || transaction_1.Transaction.SIGHASH_ALL;
|
||||||
|
let hash;
|
||||||
|
let script;
|
||||||
|
if (input.nonWitnessUtxo) {
|
||||||
|
const nonWitnessUtxoTx = transaction_1.Transaction.fromBuffer(
|
||||||
|
input.nonWitnessUtxo,
|
||||||
|
);
|
||||||
|
const prevoutHash = unsignedTx.ins[inputIndex].hash;
|
||||||
|
const utxoHash = nonWitnessUtxoTx.getHash();
|
||||||
|
// If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
|
||||||
|
if (!prevoutHash.equals(utxoHash)) {
|
||||||
|
throw new Error(
|
||||||
|
`Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
||||||
|
const prevout = nonWitnessUtxoTx.outs[prevoutIndex];
|
||||||
|
if (input.redeemScript) {
|
||||||
|
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript
|
||||||
|
checkRedeemScript(inputIndex, prevout.script, input.redeemScript);
|
||||||
|
script = input.redeemScript;
|
||||||
|
hash = unsignedTx.hashForSignature(
|
||||||
|
inputIndex,
|
||||||
|
input.redeemScript,
|
||||||
|
sighashType,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
script = prevout.script;
|
||||||
|
hash = unsignedTx.hashForSignature(
|
||||||
|
inputIndex,
|
||||||
|
prevout.script,
|
||||||
|
sighashType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (input.witnessUtxo) {
|
||||||
|
let _script; // so we don't shadow the `let script` above
|
||||||
|
if (input.redeemScript) {
|
||||||
|
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript
|
||||||
|
checkRedeemScript(
|
||||||
|
inputIndex,
|
||||||
|
input.witnessUtxo.script,
|
||||||
|
input.redeemScript,
|
||||||
|
);
|
||||||
|
_script = input.redeemScript;
|
||||||
|
} else {
|
||||||
|
_script = input.witnessUtxo.script;
|
||||||
|
}
|
||||||
|
if (isP2WPKH(_script)) {
|
||||||
|
// P2WPKH uses the P2PKH template for prevoutScript when signing
|
||||||
|
const signingScript = payments.p2pkh({ hash: _script.slice(2) }).output;
|
||||||
|
hash = unsignedTx.hashForWitnessV0(
|
||||||
|
inputIndex,
|
||||||
|
signingScript,
|
||||||
|
input.witnessUtxo.value,
|
||||||
|
sighashType,
|
||||||
|
);
|
||||||
|
script = _script;
|
||||||
|
} else {
|
||||||
|
if (!input.witnessScript)
|
||||||
|
throw new Error('Segwit input needs witnessScript if not P2WPKH');
|
||||||
|
checkWitnessScript(inputIndex, _script, input.witnessScript);
|
||||||
|
hash = unsignedTx.hashForWitnessV0(
|
||||||
|
inputIndex,
|
||||||
|
_script,
|
||||||
|
input.witnessUtxo.value,
|
||||||
|
sighashType,
|
||||||
|
);
|
||||||
|
// want to make sure the script we return is the actual meaningful script
|
||||||
|
script = input.witnessScript;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Need a Utxo input item for signing');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
script,
|
||||||
|
sighashType,
|
||||||
|
hash,
|
||||||
|
};
|
||||||
|
};
|
||||||
const scriptCheckerFactory = (payment, paymentScriptName) => (
|
const scriptCheckerFactory = (payment, paymentScriptName) => (
|
||||||
inputIndex,
|
inputIndex,
|
||||||
scriptPubKey,
|
scriptPubKey,
|
||||||
|
@ -25,7 +168,7 @@ const checkWitnessScript = scriptCheckerFactory(
|
||||||
payments.p2wsh,
|
payments.p2wsh,
|
||||||
'Witness script',
|
'Witness script',
|
||||||
);
|
);
|
||||||
const isPayment = (script, payment) => {
|
const isPaymentFactory = payment => script => {
|
||||||
try {
|
try {
|
||||||
payment({ output: script });
|
payment({ output: script });
|
||||||
return true;
|
return true;
|
||||||
|
@ -33,6 +176,17 @@ const isPayment = (script, payment) => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const isP2WPKH = isPaymentFactory(payments.p2wpkh);
|
||||||
|
const isP2PKH = isPaymentFactory(payments.p2pkh);
|
||||||
|
const isP2MS = isPaymentFactory(payments.p2ms);
|
||||||
|
const isP2PK = isPaymentFactory(payments.p2pk);
|
||||||
|
const classifyScript = script => {
|
||||||
|
if (isP2WPKH(script)) return 'witnesspubkeyhash';
|
||||||
|
if (isP2PKH(script)) return 'pubkeyhash';
|
||||||
|
if (isP2MS(script)) return 'multisig';
|
||||||
|
if (isP2PK(script)) return 'pubkey';
|
||||||
|
return 'nonstandard';
|
||||||
|
};
|
||||||
function getScriptFromInput(inputIndex, input, _unsignedTx) {
|
function getScriptFromInput(inputIndex, input, _unsignedTx) {
|
||||||
let script;
|
let script;
|
||||||
if (input.nonWitnessUtxo) {
|
if (input.nonWitnessUtxo) {
|
||||||
|
@ -60,134 +214,3 @@ function getScriptFromInput(inputIndex, input, _unsignedTx) {
|
||||||
}
|
}
|
||||||
return script;
|
return script;
|
||||||
}
|
}
|
||||||
class Psbt extends bip174_1.Psbt {
|
|
||||||
constructor(network) {
|
|
||||||
super();
|
|
||||||
this.network = network;
|
|
||||||
}
|
|
||||||
canFinalize(inputIndex) {
|
|
||||||
const input = utils_1.checkForInput(this.inputs, inputIndex);
|
|
||||||
const script = getScriptFromInput(
|
|
||||||
inputIndex,
|
|
||||||
input,
|
|
||||||
this.globalMap.unsignedTx,
|
|
||||||
);
|
|
||||||
if (!script) return false;
|
|
||||||
const scriptType = classify.output(script);
|
|
||||||
switch (scriptType) {
|
|
||||||
case 'pubkey':
|
|
||||||
return false;
|
|
||||||
case 'pubkeyhash':
|
|
||||||
return false;
|
|
||||||
case 'multisig':
|
|
||||||
return false;
|
|
||||||
case 'witnesspubkeyhash':
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
signInput(inputIndex, keyPair) {
|
|
||||||
// TODO: Implement BIP174 pre-sign checks:
|
|
||||||
// https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#signer
|
|
||||||
//
|
|
||||||
// if non_witness_utxo.exists:
|
|
||||||
// assert(sha256d(non_witness_utxo) == psbt.tx.innput[i].prevout.hash)
|
|
||||||
// if redeemScript.exists:
|
|
||||||
// assert(non_witness_utxo.vout[psbt.tx.input[i].prevout.n].scriptPubKey == P2SH(redeemScript))
|
|
||||||
// sign_non_witness(redeemScript)
|
|
||||||
// else:
|
|
||||||
// sign_non_witness(non_witness_utxo.vout[psbt.tx.input[i].prevout.n].scriptPubKey)
|
|
||||||
// else if witness_utxo.exists:
|
|
||||||
// if redeemScript.exists:
|
|
||||||
// assert(witness_utxo.scriptPubKey == P2SH(redeemScript))
|
|
||||||
// script = redeemScript
|
|
||||||
// else:
|
|
||||||
// script = witness_utxo.scriptPubKey
|
|
||||||
// if IsP2WPKH(script):
|
|
||||||
// sign_witness(P2PKH(script[2:22]))
|
|
||||||
// else if IsP2WSH(script):
|
|
||||||
// assert(script == P2WSH(witnessScript))
|
|
||||||
// sign_witness(witnessScript)
|
|
||||||
// else:
|
|
||||||
// assert False
|
|
||||||
const input = this.inputs[inputIndex];
|
|
||||||
if (input === undefined) throw new Error(`No input #${inputIndex}`);
|
|
||||||
const unsignedTx = transaction_1.Transaction.fromBuffer(
|
|
||||||
this.globalMap.unsignedTx,
|
|
||||||
);
|
|
||||||
const sighashType = input.sighashType || 0x01;
|
|
||||||
let hash;
|
|
||||||
if (input.nonWitnessUtxo) {
|
|
||||||
const nonWitnessUtxoTx = transaction_1.Transaction.fromBuffer(
|
|
||||||
input.nonWitnessUtxo,
|
|
||||||
);
|
|
||||||
const prevoutHash = unsignedTx.ins[inputIndex].hash;
|
|
||||||
const utxoHash = nonWitnessUtxoTx.getHash();
|
|
||||||
// If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
|
|
||||||
if (!prevoutHash.equals(utxoHash)) {
|
|
||||||
throw new Error(
|
|
||||||
`Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
|
||||||
const prevout = nonWitnessUtxoTx.outs[prevoutIndex];
|
|
||||||
if (input.redeemScript) {
|
|
||||||
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript
|
|
||||||
checkRedeemScript(inputIndex, prevout.script, input.redeemScript);
|
|
||||||
hash = unsignedTx.hashForSignature(
|
|
||||||
inputIndex,
|
|
||||||
input.redeemScript,
|
|
||||||
sighashType,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
hash = unsignedTx.hashForSignature(
|
|
||||||
inputIndex,
|
|
||||||
prevout.script,
|
|
||||||
sighashType,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (input.witnessUtxo) {
|
|
||||||
let script;
|
|
||||||
if (input.redeemScript) {
|
|
||||||
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript
|
|
||||||
checkRedeemScript(
|
|
||||||
inputIndex,
|
|
||||||
input.witnessUtxo.script,
|
|
||||||
input.redeemScript,
|
|
||||||
);
|
|
||||||
script = input.redeemScript;
|
|
||||||
} else {
|
|
||||||
script = input.witnessUtxo.script;
|
|
||||||
}
|
|
||||||
if (isPayment(script, payments.p2wpkh)) {
|
|
||||||
// P2WPKH uses the P2PKH template for prevoutScript when signing
|
|
||||||
const signingScript = payments.p2pkh({ hash: script.slice(2) }).output;
|
|
||||||
hash = unsignedTx.hashForWitnessV0(
|
|
||||||
inputIndex,
|
|
||||||
signingScript,
|
|
||||||
input.witnessUtxo.value,
|
|
||||||
sighashType,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (!input.witnessScript)
|
|
||||||
throw new Error('Segwit input needs witnessScript if not P2WPKH');
|
|
||||||
checkWitnessScript(inputIndex, script, input.witnessScript);
|
|
||||||
hash = unsignedTx.hashForWitnessV0(
|
|
||||||
inputIndex,
|
|
||||||
script,
|
|
||||||
input.witnessUtxo.value,
|
|
||||||
sighashType,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('Need a Utxo input item for signing');
|
|
||||||
}
|
|
||||||
const partialSig = {
|
|
||||||
pubkey: keyPair.publicKey,
|
|
||||||
signature: bscript.signature.encode(keyPair.sign(hash), sighashType),
|
|
||||||
};
|
|
||||||
return this.addPartialSigToInput(inputIndex, partialSig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.Psbt = Psbt;
|
|
||||||
|
|
318
ts_src/psbt.ts
318
ts_src/psbt.ts
|
@ -1,13 +1,174 @@
|
||||||
import { Psbt as PsbtBase } from 'bip174';
|
import { Psbt as PsbtBase } from 'bip174';
|
||||||
import { PsbtInput } from 'bip174/src/lib/interfaces';
|
import { PsbtInput } from 'bip174/src/lib/interfaces';
|
||||||
import { checkForInput } from 'bip174/src/lib/utils';
|
import { checkForInput } from 'bip174/src/lib/utils';
|
||||||
import * as classify from './classify';
|
// import { hash160 } from './crypto'; // TODO: used in pubkey check
|
||||||
import { Signer } from './ecpair';
|
import { Signer } from './ecpair';
|
||||||
import { Network } from './networks';
|
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';
|
||||||
|
|
||||||
|
export class Psbt extends PsbtBase {
|
||||||
|
constructor(public network?: Network) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
canFinalize(inputIndex: number): boolean {
|
||||||
|
const input = checkForInput(this.inputs, inputIndex);
|
||||||
|
const script = getScriptFromInput(
|
||||||
|
inputIndex,
|
||||||
|
input,
|
||||||
|
this.globalMap.unsignedTx!,
|
||||||
|
);
|
||||||
|
if (!script) return false;
|
||||||
|
const scriptType = classifyScript(script);
|
||||||
|
// TODO: for each type
|
||||||
|
switch (scriptType) {
|
||||||
|
case 'pubkey':
|
||||||
|
return false;
|
||||||
|
case 'pubkeyhash':
|
||||||
|
return false;
|
||||||
|
case 'multisig':
|
||||||
|
return false;
|
||||||
|
case 'witnesspubkeyhash':
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signInput(inputIndex: number, keyPair: Signer): Psbt {
|
||||||
|
const input = this.inputs[inputIndex];
|
||||||
|
if (input === undefined) throw new Error(`No input #${inputIndex}`);
|
||||||
|
const {
|
||||||
|
hash,
|
||||||
|
sighashType,
|
||||||
|
// script, // TODO: use for pubkey check below
|
||||||
|
} = getHashForSig(inputIndex, input, this.globalMap.unsignedTx!);
|
||||||
|
|
||||||
|
const pubkey = keyPair.publicKey;
|
||||||
|
// // TODO: throw error when the pubkey or pubkey hash is not found anywhere
|
||||||
|
// // in the script
|
||||||
|
// const pubkeyHash = hash160(keyPair.publicKey);
|
||||||
|
//
|
||||||
|
// 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 = {
|
||||||
|
pubkey,
|
||||||
|
signature: bscript.signature.encode(keyPair.sign(hash), sighashType),
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.addPartialSigToInput(inputIndex, partialSig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HashForSigData {
|
||||||
|
script: Buffer;
|
||||||
|
hash: Buffer;
|
||||||
|
sighashType: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getHashForSig = (
|
||||||
|
inputIndex: number,
|
||||||
|
input: PsbtInput,
|
||||||
|
txBuf: Buffer,
|
||||||
|
): HashForSigData => {
|
||||||
|
const unsignedTx = Transaction.fromBuffer(txBuf);
|
||||||
|
const sighashType = input.sighashType || Transaction.SIGHASH_ALL;
|
||||||
|
let hash: Buffer;
|
||||||
|
let script: Buffer;
|
||||||
|
|
||||||
|
if (input.nonWitnessUtxo) {
|
||||||
|
const nonWitnessUtxoTx = Transaction.fromBuffer(input.nonWitnessUtxo);
|
||||||
|
|
||||||
|
const prevoutHash = unsignedTx.ins[inputIndex].hash;
|
||||||
|
const utxoHash = nonWitnessUtxoTx.getHash();
|
||||||
|
|
||||||
|
// If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
|
||||||
|
if (!prevoutHash.equals(utxoHash)) {
|
||||||
|
throw new Error(
|
||||||
|
`Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
||||||
|
const prevout = nonWitnessUtxoTx.outs[prevoutIndex];
|
||||||
|
|
||||||
|
if (input.redeemScript) {
|
||||||
|
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript
|
||||||
|
checkRedeemScript(inputIndex, prevout.script, input.redeemScript);
|
||||||
|
script = input.redeemScript;
|
||||||
|
hash = unsignedTx.hashForSignature(
|
||||||
|
inputIndex,
|
||||||
|
input.redeemScript,
|
||||||
|
sighashType,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
script = prevout.script;
|
||||||
|
hash = unsignedTx.hashForSignature(
|
||||||
|
inputIndex,
|
||||||
|
prevout.script,
|
||||||
|
sighashType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (input.witnessUtxo) {
|
||||||
|
let _script: Buffer; // so we don't shadow the `let script` above
|
||||||
|
if (input.redeemScript) {
|
||||||
|
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript
|
||||||
|
checkRedeemScript(
|
||||||
|
inputIndex,
|
||||||
|
input.witnessUtxo.script,
|
||||||
|
input.redeemScript,
|
||||||
|
);
|
||||||
|
_script = input.redeemScript;
|
||||||
|
} else {
|
||||||
|
_script = input.witnessUtxo.script;
|
||||||
|
}
|
||||||
|
if (isP2WPKH(_script)) {
|
||||||
|
// P2WPKH uses the P2PKH template for prevoutScript when signing
|
||||||
|
const signingScript = payments.p2pkh({ hash: _script.slice(2) }).output!;
|
||||||
|
hash = unsignedTx.hashForWitnessV0(
|
||||||
|
inputIndex,
|
||||||
|
signingScript,
|
||||||
|
input.witnessUtxo.value,
|
||||||
|
sighashType,
|
||||||
|
);
|
||||||
|
script = _script;
|
||||||
|
} else {
|
||||||
|
if (!input.witnessScript)
|
||||||
|
throw new Error('Segwit input needs witnessScript if not P2WPKH');
|
||||||
|
checkWitnessScript(inputIndex, _script, input.witnessScript);
|
||||||
|
hash = unsignedTx.hashForWitnessV0(
|
||||||
|
inputIndex,
|
||||||
|
_script,
|
||||||
|
input.witnessUtxo.value,
|
||||||
|
sighashType,
|
||||||
|
);
|
||||||
|
// want to make sure the script we return is the actual meaningful script
|
||||||
|
script = input.witnessScript;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Need a Utxo input item for signing');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
script,
|
||||||
|
sighashType,
|
||||||
|
hash,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type ScriptCheckerFunction = (idx: number, spk: Buffer, rs: Buffer) => void;
|
type ScriptCheckerFunction = (idx: number, spk: Buffer, rs: Buffer) => void;
|
||||||
|
|
||||||
const scriptCheckerFactory = (
|
const scriptCheckerFactory = (
|
||||||
|
@ -35,7 +196,11 @@ const checkWitnessScript = scriptCheckerFactory(
|
||||||
'Witness script',
|
'Witness script',
|
||||||
);
|
);
|
||||||
|
|
||||||
const isPayment = (script: Buffer, payment: any): boolean => {
|
type isPaymentFunction = (script: Buffer) => boolean;
|
||||||
|
|
||||||
|
const isPaymentFactory = (payment: any): isPaymentFunction => (
|
||||||
|
script: Buffer,
|
||||||
|
): boolean => {
|
||||||
try {
|
try {
|
||||||
payment({ output: script });
|
payment({ output: script });
|
||||||
return true;
|
return true;
|
||||||
|
@ -43,6 +208,18 @@ const isPayment = (script: Buffer, payment: any): boolean => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const isP2WPKH = isPaymentFactory(payments.p2wpkh);
|
||||||
|
const isP2PKH = isPaymentFactory(payments.p2pkh);
|
||||||
|
const isP2MS = isPaymentFactory(payments.p2ms);
|
||||||
|
const isP2PK = isPaymentFactory(payments.p2pk);
|
||||||
|
|
||||||
|
const classifyScript = (script: Buffer): string => {
|
||||||
|
if (isP2WPKH(script)) return 'witnesspubkeyhash';
|
||||||
|
if (isP2PKH(script)) return 'pubkeyhash';
|
||||||
|
if (isP2MS(script)) return 'multisig';
|
||||||
|
if (isP2PK(script)) return 'pubkey';
|
||||||
|
return 'nonstandard';
|
||||||
|
};
|
||||||
|
|
||||||
function getScriptFromInput(
|
function getScriptFromInput(
|
||||||
inputIndex: number,
|
inputIndex: number,
|
||||||
|
@ -73,140 +250,3 @@ function getScriptFromInput(
|
||||||
}
|
}
|
||||||
return script;
|
return script;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Psbt extends PsbtBase {
|
|
||||||
constructor(public network?: Network) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
canFinalize(inputIndex: number): boolean {
|
|
||||||
const input = checkForInput(this.inputs, inputIndex);
|
|
||||||
const script = getScriptFromInput(
|
|
||||||
inputIndex,
|
|
||||||
input,
|
|
||||||
this.globalMap.unsignedTx!,
|
|
||||||
);
|
|
||||||
if (!script) return false;
|
|
||||||
const scriptType = classify.output(script);
|
|
||||||
switch (scriptType) {
|
|
||||||
case 'pubkey':
|
|
||||||
return false;
|
|
||||||
case 'pubkeyhash':
|
|
||||||
return false;
|
|
||||||
case 'multisig':
|
|
||||||
return false;
|
|
||||||
case 'witnesspubkeyhash':
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
signInput(inputIndex: number, keyPair: Signer): Psbt {
|
|
||||||
// TODO: Implement BIP174 pre-sign checks:
|
|
||||||
// https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#signer
|
|
||||||
//
|
|
||||||
// if non_witness_utxo.exists:
|
|
||||||
// assert(sha256d(non_witness_utxo) == psbt.tx.innput[i].prevout.hash)
|
|
||||||
// if redeemScript.exists:
|
|
||||||
// assert(non_witness_utxo.vout[psbt.tx.input[i].prevout.n].scriptPubKey == P2SH(redeemScript))
|
|
||||||
// sign_non_witness(redeemScript)
|
|
||||||
// else:
|
|
||||||
// sign_non_witness(non_witness_utxo.vout[psbt.tx.input[i].prevout.n].scriptPubKey)
|
|
||||||
// else if witness_utxo.exists:
|
|
||||||
// if redeemScript.exists:
|
|
||||||
// assert(witness_utxo.scriptPubKey == P2SH(redeemScript))
|
|
||||||
// script = redeemScript
|
|
||||||
// else:
|
|
||||||
// script = witness_utxo.scriptPubKey
|
|
||||||
// if IsP2WPKH(script):
|
|
||||||
// sign_witness(P2PKH(script[2:22]))
|
|
||||||
// else if IsP2WSH(script):
|
|
||||||
// assert(script == P2WSH(witnessScript))
|
|
||||||
// sign_witness(witnessScript)
|
|
||||||
// else:
|
|
||||||
// assert False
|
|
||||||
|
|
||||||
const input = this.inputs[inputIndex];
|
|
||||||
if (input === undefined) throw new Error(`No input #${inputIndex}`);
|
|
||||||
|
|
||||||
const unsignedTx = Transaction.fromBuffer(this.globalMap.unsignedTx!);
|
|
||||||
const sighashType = input.sighashType || 0x01;
|
|
||||||
let hash: Buffer;
|
|
||||||
|
|
||||||
if (input.nonWitnessUtxo) {
|
|
||||||
const nonWitnessUtxoTx = Transaction.fromBuffer(input.nonWitnessUtxo);
|
|
||||||
|
|
||||||
const prevoutHash = unsignedTx.ins[inputIndex].hash;
|
|
||||||
const utxoHash = nonWitnessUtxoTx.getHash();
|
|
||||||
|
|
||||||
// If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
|
|
||||||
if (!prevoutHash.equals(utxoHash)) {
|
|
||||||
throw new Error(
|
|
||||||
`Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
|
||||||
const prevout = nonWitnessUtxoTx.outs[prevoutIndex];
|
|
||||||
|
|
||||||
if (input.redeemScript) {
|
|
||||||
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript
|
|
||||||
checkRedeemScript(inputIndex, prevout.script, input.redeemScript);
|
|
||||||
hash = unsignedTx.hashForSignature(
|
|
||||||
inputIndex,
|
|
||||||
input.redeemScript,
|
|
||||||
sighashType,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
hash = unsignedTx.hashForSignature(
|
|
||||||
inputIndex,
|
|
||||||
prevout.script,
|
|
||||||
sighashType,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (input.witnessUtxo) {
|
|
||||||
let script: Buffer;
|
|
||||||
if (input.redeemScript) {
|
|
||||||
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript
|
|
||||||
checkRedeemScript(
|
|
||||||
inputIndex,
|
|
||||||
input.witnessUtxo.script,
|
|
||||||
input.redeemScript,
|
|
||||||
);
|
|
||||||
script = input.redeemScript;
|
|
||||||
} else {
|
|
||||||
script = input.witnessUtxo.script;
|
|
||||||
}
|
|
||||||
if (isPayment(script, payments.p2wpkh)) {
|
|
||||||
// P2WPKH uses the P2PKH template for prevoutScript when signing
|
|
||||||
const signingScript = payments.p2pkh({ hash: script.slice(2) }).output!;
|
|
||||||
hash = unsignedTx.hashForWitnessV0(
|
|
||||||
inputIndex,
|
|
||||||
signingScript,
|
|
||||||
input.witnessUtxo.value,
|
|
||||||
sighashType,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (!input.witnessScript)
|
|
||||||
throw new Error('Segwit input needs witnessScript if not P2WPKH');
|
|
||||||
checkWitnessScript(inputIndex, script, input.witnessScript);
|
|
||||||
hash = unsignedTx.hashForWitnessV0(
|
|
||||||
inputIndex,
|
|
||||||
script,
|
|
||||||
input.witnessUtxo.value,
|
|
||||||
sighashType,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('Need a Utxo input item for signing');
|
|
||||||
}
|
|
||||||
|
|
||||||
const partialSig = {
|
|
||||||
pubkey: keyPair.publicKey,
|
|
||||||
signature: bscript.signature.encode(keyPair.sign(hash), sighashType),
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.addPartialSigToInput(inputIndex, partialSig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue