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 });
|
||||
const bip174_1 = require('bip174');
|
||||
const utils_1 = require('bip174/src/lib/utils');
|
||||
const classify = require('./classify');
|
||||
const payments = require('./payments');
|
||||
const bscript = require('./script');
|
||||
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) => (
|
||||
inputIndex,
|
||||
scriptPubKey,
|
||||
|
@ -25,7 +168,7 @@ const checkWitnessScript = scriptCheckerFactory(
|
|||
payments.p2wsh,
|
||||
'Witness script',
|
||||
);
|
||||
const isPayment = (script, payment) => {
|
||||
const isPaymentFactory = payment => script => {
|
||||
try {
|
||||
payment({ output: script });
|
||||
return true;
|
||||
|
@ -33,6 +176,17 @@ const isPayment = (script, payment) => {
|
|||
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) {
|
||||
let script;
|
||||
if (input.nonWitnessUtxo) {
|
||||
|
@ -60,134 +214,3 @@ function getScriptFromInput(inputIndex, input, _unsignedTx) {
|
|||
}
|
||||
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 { PsbtInput } from 'bip174/src/lib/interfaces';
|
||||
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 { Network } from './networks';
|
||||
import * as payments from './payments';
|
||||
import * as bscript from './script';
|
||||
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;
|
||||
|
||||
const scriptCheckerFactory = (
|
||||
|
@ -35,7 +196,11 @@ const checkWitnessScript = scriptCheckerFactory(
|
|||
'Witness script',
|
||||
);
|
||||
|
||||
const isPayment = (script: Buffer, payment: any): boolean => {
|
||||
type isPaymentFunction = (script: Buffer) => boolean;
|
||||
|
||||
const isPaymentFactory = (payment: any): isPaymentFunction => (
|
||||
script: Buffer,
|
||||
): boolean => {
|
||||
try {
|
||||
payment({ output: script });
|
||||
return true;
|
||||
|
@ -43,6 +208,18 @@ const isPayment = (script: Buffer, payment: any): boolean => {
|
|||
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(
|
||||
inputIndex: number,
|
||||
|
@ -73,140 +250,3 @@ function getScriptFromInput(
|
|||
}
|
||||
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