Fix validation error if SINGLE with missing corresponding output, remove check_index and check with get().ok_or(), more details in errors

This commit is contained in:
Riccardo Casatta 2021-07-16 16:12:29 +02:00
parent 2b3b22f559
commit 55ce3dd6ae
No known key found for this signature in database
GPG Key ID: FD986A969E450397
1 changed files with 69 additions and 19 deletions

View File

@ -124,8 +124,22 @@ pub enum Error {
/// Should never happen since we are always encoding, thus we are avoiding wrap the IO error /// Should never happen since we are always encoding, thus we are avoiding wrap the IO error
IoError, IoError,
/// Requested input index is greater than the number of inputs in the given transaction /// Requested index is greater or equal than the number of inputs in the transaction
IndexGreaterThanInputsSize, IndexOutOfInputsBounds {
/// Requested index
index: usize,
/// Number of transaction inputs
inputs_size: usize,
},
/// Using SIGHASH_SINGLE without a "corresponding output" (an output with the same index as the
/// input being verified) is a validation failure
SingleWithoutCorrespondingOutput {
/// Requested index
index: usize,
/// Number of transaction outputs
outputs_size: usize,
},
/// There are mismatches in the number of prevouts provided compared with the number of /// There are mismatches in the number of prevouts provided compared with the number of
/// inputs in the transaction /// inputs in the transaction
@ -146,7 +160,8 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Error::IoError => write!(f, "IoError"), Error::IoError => write!(f, "IoError"),
Error::IndexGreaterThanInputsSize => write!(f, "Requested input index is greater than the number of inputs in the given transaction"), Error::IndexOutOfInputsBounds { index, inputs_size } => write!(f, "Requested index ({}) is greater or equal than the number of transaction inputs ({})", index, inputs_size),
Error::SingleWithoutCorrespondingOutput { index, outputs_size } => write!(f, "SIGHASH_SINGLE for input ({}) haven't a corresponding output (#outputs:{})", index, outputs_size),
Error::PrevoutsSize => write!(f, "Number of supplied prevouts differs from the number of inputs in transaction"), Error::PrevoutsSize => write!(f, "Number of supplied prevouts differs from the number of inputs in transaction"),
Error::PrevoutIndex => write!(f, "The index requested is greater than available prevouts or different from the provided [Provided::Anyone] index"), Error::PrevoutIndex => write!(f, "The index requested is greater than available prevouts or different from the provided [Provided::Anyone] index"),
Error::PrevoutKind => write!(f, "A single prevout has been provided but all prevouts are needed without `ANYONECANPAY`"), Error::PrevoutKind => write!(f, "A single prevout has been provided but all prevouts are needed without `ANYONECANPAY`"),
@ -247,14 +262,6 @@ impl<R: Deref<Target = Transaction>> SigHashCache<R> {
} }
} }
fn check_index(&self, index: usize) -> Result<(), Error> {
if index >= self.tx.input.len() {
Err(Error::IndexGreaterThanInputsSize)
} else {
Ok(())
}
}
/// Encode the BIP341 signing data for any flag type into a given object implementing a /// Encode the BIP341 signing data for any flag type into a given object implementing a
/// std::io::Write trait. /// std::io::Write trait.
pub fn taproot_encode_signing_data_to<Write: io::Write>( pub fn taproot_encode_signing_data_to<Write: io::Write>(
@ -267,7 +274,6 @@ impl<R: Deref<Target = Transaction>> SigHashCache<R> {
sighash_type: SigHashType, sighash_type: SigHashType,
) -> Result<(), Error> { ) -> Result<(), Error> {
prevouts.check_all(&self.tx)?; prevouts.check_all(&self.tx)?;
self.check_index(input_index)?;
let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag(); let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag();
@ -327,7 +333,15 @@ impl<R: Deref<Target = Transaction>> SigHashCache<R> {
// scriptPubKey (35): scriptPubKey of the previous output spent by this input, serialized as script inside CTxOut. Its size is always 35 bytes. // scriptPubKey (35): scriptPubKey of the previous output spent by this input, serialized as script inside CTxOut. Its size is always 35 bytes.
// nSequence (4): nSequence of this input. // nSequence (4): nSequence of this input.
if anyone_can_pay { if anyone_can_pay {
let txin = &self.tx.input[input_index]; let txin =
&self
.tx
.input
.get(input_index)
.ok_or_else(|| Error::IndexOutOfInputsBounds {
index: input_index,
inputs_size: self.tx.input.len(),
})?;
let previous_output = prevouts.get(input_index)?; let previous_output = prevouts.get(input_index)?;
txin.previous_output.consensus_encode(&mut writer)?; txin.previous_output.consensus_encode(&mut writer)?;
previous_output.value.consensus_encode(&mut writer)?; previous_output.value.consensus_encode(&mut writer)?;
@ -354,7 +368,14 @@ impl<R: Deref<Target = Transaction>> SigHashCache<R> {
// sha_single_output (32): the SHA256 of the corresponding output in CTxOut format. // sha_single_output (32): the SHA256 of the corresponding output in CTxOut format.
if sighash == SigHashType::Single { if sighash == SigHashType::Single {
let mut enc = sha256::Hash::engine(); let mut enc = sha256::Hash::engine();
self.tx.output[input_index].consensus_encode(&mut enc)?; self.tx
.output
.get(input_index)
.ok_or_else(|| Error::SingleWithoutCorrespondingOutput {
index: input_index,
outputs_size: self.tx.output.len(),
})?
.consensus_encode(&mut enc)?;
let hash = sha256::Hash::from_engine(enc); let hash = sha256::Hash::from_engine(enc);
hash.consensus_encode(&mut writer)?; hash.consensus_encode(&mut writer)?;
} }
@ -413,8 +434,6 @@ impl<R: Deref<Target = Transaction>> SigHashCache<R> {
value: u64, value: u64,
sighash_type: LegacySigHashType, sighash_type: LegacySigHashType,
) -> Result<(), Error> { ) -> Result<(), Error> {
self.check_index(input_index)?;
let zero_hash = sha256d::Hash::default(); let zero_hash = sha256d::Hash::default();
let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag(); let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag();
@ -439,7 +458,15 @@ impl<R: Deref<Target = Transaction>> SigHashCache<R> {
} }
{ {
let txin = &self.tx.input[input_index]; let txin =
&self
.tx
.input
.get(input_index)
.ok_or_else(|| Error::IndexOutOfInputsBounds {
index: input_index,
inputs_size: self.tx.input.len(),
})?;
txin.previous_output.consensus_encode(&mut writer)?; txin.previous_output.consensus_encode(&mut writer)?;
script_code.consensus_encode(&mut writer)?; script_code.consensus_encode(&mut writer)?;
@ -490,7 +517,12 @@ impl<R: Deref<Target = Transaction>> SigHashCache<R> {
script_pubkey: &Script, script_pubkey: &Script,
sighash_type: U, sighash_type: U,
) -> Result<(), Error> { ) -> Result<(), Error> {
self.check_index(input_index)?; if input_index >= self.tx.input.len() {
return Err(Error::IndexOutOfInputsBounds {
index: input_index,
inputs_size: self.tx.input.len(),
});
}
self.tx self.tx
.encode_signing_data_to(&mut writer, input_index, script_pubkey, sighash_type.into()) .encode_signing_data_to(&mut writer, input_index, script_pubkey, sighash_type.into())
.expect("writers don't error"); .expect("writers don't error");
@ -785,7 +817,25 @@ mod tests {
); );
assert_eq!( assert_eq!(
c.taproot_signature_hash(10, &prevout, None, None, SigHashType::AllPlusAnyoneCanPay), c.taproot_signature_hash(10, &prevout, None, None, SigHashType::AllPlusAnyoneCanPay),
Err(Error::IndexGreaterThanInputsSize) Err(Error::IndexOutOfInputsBounds {
index: 10,
inputs_size: 1
})
);
let prevout = Prevouts::One(0, &tx_out);
assert_eq!(
c.taproot_signature_hash(0, &prevout, None, None, SigHashType::SinglePlusAnyoneCanPay),
Err(Error::SingleWithoutCorrespondingOutput {
index: 0,
outputs_size: 0
})
);
assert_eq!(
c.legacy_signature_hash(10, &Script::default(), 0u32),
Err(Error::IndexOutOfInputsBounds {
index: 10,
inputs_size: 1
})
); );
} }