Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BIP-174 test vectors #11

Merged
merged 1 commit into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/v0/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,52 @@ impl std::error::Error for ExtractTxError {
}
}

/// Errors encountered while doing the signer checks.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum SignerChecksError {
/// Witness input will produce a non-witness signature.
NonWitnessSig,
/// Non-witness input has a mismatch between the txid and prevout txid.
NonWitnessUtxoTxidMismatch,
/// Input has both witness and non-witness utxos.
WitnessAndNonWitnessUtxo,
/// Redeem script hash did not match the hash in the script_pubkey.
RedeemScriptMismatch,
/// Missing witness_utxo.
MissingTxOut,
/// Native segwit p2wsh script_pubkey did not match witness script hash.
WitnessScriptMismatchWsh,
/// Nested segwit p2wsh script_pubkey did not match redeem script hash.
WitnessScriptMismatchShWsh,
}

impl fmt::Display for SignerChecksError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use SignerChecksError::*;

match *self {
NonWitnessSig => write!(f, "witness input will produce a non-witness signature"),
NonWitnessUtxoTxidMismatch =>
write!(f, "non-witness input has a mismatch between the txid and prevout txid"),
WitnessAndNonWitnessUtxo => write!(f, "input has both witness and non-witness utxos"),
RedeemScriptMismatch =>
write!(f, "redeem script hash did not match the hash in the script_pubkey"),
MissingTxOut => write!(f, "missing witness_utxo"),
WitnessScriptMismatchWsh =>
write!(f, "native segwit p2wsh script_pubkey did not match witness script hash"),
WitnessScriptMismatchShWsh =>
write!(f, "nested segwit p2wsh script_pubkey did not match redeem script hash"),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for SignerChecksError {
// TODO: Match explicitly.
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}

/// Errors encountered while calculating the sighash message.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
Expand Down
79 changes: 77 additions & 2 deletions src/v0/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ use bitcoin::key::{PrivateKey, PublicKey};
use bitcoin::secp256k1::{Message, Secp256k1, Signing};
use bitcoin::sighash::{EcdsaSighashType, SighashCache};
use bitcoin::transaction::{Transaction, TxOut};
use bitcoin::{ecdsa, Amount};
use bitcoin::{ecdsa, Amount, ScriptBuf};

use crate::error::write_err;
use crate::prelude::*;
use crate::v0::error::{IndexOutOfBoundsError, SignError};
use crate::v0::error::{IndexOutOfBoundsError, SignError, SignerChecksError};
use crate::v0::map::{Global, Input, Map, Output};
use crate::Error;

Expand Down Expand Up @@ -178,6 +178,81 @@ impl Psbt {
Ok(())
}

/// Returns `Ok` if PSBT is
///
/// From BIP-174:
///
/// For a Signer to only produce valid signatures for what it expects to sign, it must check that the following conditions are true:
///
/// - If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
/// - If a witness UTXO is provided, no non-witness signature may be created
/// - If a redeemScript is provided, the scriptPubKey must be for that redeemScript
/// - If a witnessScript is provided, the scriptPubKey or the redeemScript must be for that witnessScript
/// - If a sighash type is provided, the signer must check that the sighash is acceptable. If unacceptable, they must fail.
/// - If a sighash type is not provided, the signer should sign using SIGHASH_ALL, but may use any sighash type they wish.
pub fn signer_checks(&self) -> Result<(), SignerChecksError> {
let unsigned_tx = &self.global.unsigned_tx;
for (i, input) in self.inputs.iter().enumerate() {
if input.witness_utxo.is_some() {
match self.output_type(i) {
Ok(OutputType::Bare) => return Err(SignerChecksError::NonWitnessSig),
Ok(_) => {}
Err(_) => {} // TODO: Is this correct?
}
}

if let Some(ref tx) = input.non_witness_utxo {
if tx.txid() != unsigned_tx.input[i].previous_output.txid {
return Err(SignerChecksError::NonWitnessUtxoTxidMismatch);
}
}

if let Some(ref redeem_script) = input.redeem_script {
match input.witness_utxo {
Some(ref tx_out) => {
let script_pubkey = ScriptBuf::new_p2sh(&redeem_script.script_hash());
if tx_out.script_pubkey != script_pubkey {
return Err(SignerChecksError::RedeemScriptMismatch);
}
}
None => return Err(SignerChecksError::MissingTxOut),
}
}

if let Some(ref witness_script) = input.witness_script {
match input.witness_utxo {
Some(ref utxo) => {
let script_pubkey = &utxo.script_pubkey;
if script_pubkey.is_p2wsh() {
if ScriptBuf::new_p2wsh(&witness_script.wscript_hash())
!= *script_pubkey
{
return Err(SignerChecksError::WitnessScriptMismatchWsh);
}
} else if script_pubkey.is_p2sh() {
if let Some(ref redeem_script) = input.redeem_script {
if ScriptBuf::new_p2wsh(&redeem_script.wscript_hash())
!= *script_pubkey
{
return Err(SignerChecksError::WitnessScriptMismatchShWsh);
}
}
} else {
// BIP does not specifically say there should not be a witness script here?
}
}
None => return Err(SignerChecksError::MissingTxOut),
}
}

if let Some(_sighash_type) = input.sighash_type {
// TODO: Check that sighash is accetable, what does that mean?
{}
}
}
Ok(())
}

/// Attempts to create _all_ the required signatures for this PSBT using `k`.
///
/// **NOTE**: Taproot inputs are, as yet, not supported by this function. We currently only
Expand Down
Loading