|
|
@ -1,6 +1,7 @@ |
|
|
|
import { Psbt, Transaction } from 'bitcoinjs-lib'; |
|
|
|
import { p2wpkh } from 'bitcoinjs-lib/types/payments'; |
|
|
|
import { GlobalXpub, PsbtInput } from 'bip174/src/lib/interfaces'; |
|
|
|
import { Bip32Derivation, GlobalXpub, PsbtInput } from 'bip174/src/lib/interfaces'; |
|
|
|
import { Input } from 'bitcoinjs-lib/types/transaction'; |
|
|
|
|
|
|
|
type Nullable<T> = T | null; |
|
|
|
|
|
|
@ -28,12 +29,13 @@ export async function requestPayjoinWithCustomRemoteCall(psbt: Psbt, remoteCall: |
|
|
|
if (payjoinPsbt.data.globalMap.globalXpub && (payjoinPsbt.data.globalMap.globalXpub as GlobalXpub[]).length > 0) { |
|
|
|
throw new Error('GlobalXPubs should not be included in the receiver\'s PSBT'); |
|
|
|
} |
|
|
|
if (payjoinPsbt.data.outputs.filter(value => value.bip32Derivation && value.bip32Derivation.length>0).length > 0 || |
|
|
|
payjoinPsbt.data.inputs.filter(value => value.bip32Derivation && value.bip32Derivation.length>0).length > 0 ) |
|
|
|
{ |
|
|
|
if (hasKeypathInformationSet(payjoinPsbt.data.outputs) || hasKeypathInformationSet(payjoinPsbt.data.inputs)) { |
|
|
|
throw new Error(('Keypath information should not be included in the receiver\'s PSBT'); |
|
|
|
} |
|
|
|
|
|
|
|
// TODO: check sanity
|
|
|
|
|
|
|
|
|
|
|
|
// We make sure we don't sign what should not be signed
|
|
|
|
for (let index = 0; index < payjoinPsbt.inputCount; index++) { |
|
|
|
// check if input is Finalized
|
|
|
@ -79,6 +81,94 @@ export function requestPayjoin(psbt: Psbt, payjoinEndpoint: string) { |
|
|
|
return requestPayjoinWithCustomRemoteCall(psbt, psbt1 => doRequest(psbt1, payjoinEndpoint)); |
|
|
|
} |
|
|
|
|
|
|
|
function checkSanity(psbt: Psbt): boolean { |
|
|
|
return psbt.data.inputs.filter(value => !checkInputSanity(value)).length === 0; |
|
|
|
} |
|
|
|
|
|
|
|
function checkInputSanity(input: PsbtInput, txInput: Input): boolean { |
|
|
|
const errors: string[] = []; |
|
|
|
if (isFinalized(input)) { |
|
|
|
if (input.partialSig && input.partialSig.length > 0) { |
|
|
|
errors.push('Input finalized, but partial sigs are not empty'); |
|
|
|
} |
|
|
|
if (input.bip32Derivation && input.bip32Derivation.length > 0) { |
|
|
|
errors.push('Input finalized, but hd keypaths are not empty'); |
|
|
|
} |
|
|
|
if (input.sighashType) { |
|
|
|
errors.push('Input finalized, but sighash type is not null'); |
|
|
|
} |
|
|
|
if (input.redeemScript) { |
|
|
|
errors.push('Input finalized, but redeem script is not null'); |
|
|
|
} |
|
|
|
if (input.witnessScript) { |
|
|
|
errors.push('Input finalized, but witness script is not null'); |
|
|
|
} |
|
|
|
} |
|
|
|
if (input.witnessUtxo && input.nonWitnessUtxo) { |
|
|
|
errors.push('witness utxo and non witness utxo simultaneously present'); |
|
|
|
} |
|
|
|
|
|
|
|
if (input.witnessScript && !input.witnessUtxo) { |
|
|
|
errors.push('witness script present but no witness utxo'); |
|
|
|
} |
|
|
|
|
|
|
|
if (!input.finalScriptWitness && !input.witnessUtxo){ |
|
|
|
errors.push('final witness script present but no witness utxo'); |
|
|
|
} |
|
|
|
|
|
|
|
if(input.nonWitnessUtxo){ |
|
|
|
//TODO: get hash
|
|
|
|
const prevOutTxId = input.nonWitnessUtxo; |
|
|
|
let validOutpoint = true; |
|
|
|
|
|
|
|
if (txInput.hash != prevOutTxId) { |
|
|
|
errors.push('non_witness_utxo does not match the transaction id referenced by the global transaction sign'); |
|
|
|
validOutpoint = false; |
|
|
|
} |
|
|
|
if (txInput.index >= input.nonWitnessUtxo.Outputs.length) { |
|
|
|
errors.push('Global transaction referencing an out of bound output in non_witness_utxo'); |
|
|
|
validOutpoint = false; |
|
|
|
} |
|
|
|
if (input.redeemScript && validOutpoint) { |
|
|
|
if (input.redeemScript.Hash.ScriptPubKey != NonWitnessUtxo.Outputs[TxIn.PrevOut.N].ScriptPubKey) |
|
|
|
errors.Add(new PSBTError(Index, 'The redeem_script is not coherent with the scriptPubKey of the non_witness_utxo')); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (witness_utxo != null) { |
|
|
|
if (redeem_script != null) { |
|
|
|
if (redeem_script.Hash.ScriptPubKey != witness_utxo.ScriptPubKey) |
|
|
|
errors.Add(new PSBTError(Index, 'The redeem_script is not coherent with the scriptPubKey of the witness_utxo')); |
|
|
|
if (witness_script != null && |
|
|
|
redeem_script != null && |
|
|
|
PayToWitScriptHashTemplate.Instance.ExtractScriptPubKeyParameters(redeem_script) != witness_script.WitHash) |
|
|
|
errors.Add(new PSBTError(Index, 'witnessScript with witness UTXO does not match the redeemScript')); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (witness_utxo?.ScriptPubKey is; |
|
|
|
Script; |
|
|
|
s; |
|
|
|
) |
|
|
|
{ |
|
|
|
if (!s.IsScriptType(ScriptType.P2SH) && !s.IsScriptType(ScriptType.Witness)) |
|
|
|
errors.Add(new PSBTError(Index, 'A Witness UTXO is provided for a non-witness input')); |
|
|
|
if (s.IsScriptType(ScriptType.P2SH) && redeem_script is; |
|
|
|
Script; |
|
|
|
r && !r.IsScriptType(ScriptType.Witness); |
|
|
|
) |
|
|
|
errors.Add(new PSBTError(Index, 'A Witness UTXO is provided for a non-witness input')); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function hasKeypathInformationSet(items: { bip32Derivation?: Bip32Derivation[] }[]): boolean { |
|
|
|
return items.filter(value => value.bip32Derivation && value.bip32Derivation.length > 0).length > 0; |
|
|
|
} |
|
|
|
|
|
|
|
function isFinalized(input: PsbtInput) { |
|
|
|
return input.finalScriptSig !== undefined || |
|
|
|
input.finalScriptWitness !== undefined; |
|
|
|