use super::Error;
use super::tx::{TransparentTx, TxOut};
use super::zip244::{
AMOUNTS, OUTPUTS, PREVOUTS, SCRIPTS, SEQUENCE, ShieldedDigests, TRANSPARENT, TXIN, blake2b256, blake2b256_parts,
header_digest, outputs_digest, prevouts_digest, sequence_digest, txid_personalization,
};
use crate::encoding::write_var_bytes;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum SighashType {
All = 0x01,
None = 0x02,
Single = 0x03,
AllAnyoneCanPay = 0x81,
NoneAnyoneCanPay = 0x82,
SingleAnyoneCanPay = 0x83,
}
impl SighashType {
pub const ANYONECANPAY: u8 = 0x80;
pub const fn from_u8(byte: u8) -> Result<Self, Error> {
match byte {
0x01 => Ok(Self::All),
0x02 => Ok(Self::None),
0x03 => Ok(Self::Single),
0x81 => Ok(Self::AllAnyoneCanPay),
0x82 => Ok(Self::NoneAnyoneCanPay),
0x83 => Ok(Self::SingleAnyoneCanPay),
other => Err(Error::InvalidSighashType(other)),
}
}
pub const fn to_u8(self) -> u8 {
self as u8
}
pub const fn anyone_can_pay(self) -> bool {
(self as u8) & Self::ANYONECANPAY != 0
}
const fn base(self) -> u8 {
(self as u8) & 0x1f
}
}
impl TryFrom<u8> for SighashType {
type Error = Error;
fn try_from(byte: u8) -> Result<Self, Error> {
Self::from_u8(byte)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrevOut {
pub value: i64,
pub script_pubkey: Vec<u8>,
}
pub fn transparent_sighash(
tx: &TransparentTx,
index: usize,
prevouts: &[PrevOut],
hash_type: SighashType,
shielded: &ShieldedDigests,
) -> Result<[u8; 32], Error> {
if prevouts.len() != tx.inputs.len() {
return Err(Error::PrevoutCountMismatch);
}
let input = tx.inputs.get(index).ok_or(Error::NoSuchInput(index))?;
let prevout = prevouts.get(index).ok_or(Error::NoSuchInput(index))?;
let anyone_can_pay = hash_type.anyone_can_pay();
let base = hash_type.base();
let (prevouts_sig, amounts_sig, scripts_sig, sequence_sig) = if anyone_can_pay {
(blake2b256(PREVOUTS, &[]), blake2b256(AMOUNTS, &[]), blake2b256(SCRIPTS, &[]), blake2b256(SEQUENCE, &[]))
} else {
let mut amounts = Vec::with_capacity(8 * prevouts.len());
let mut scripts = Vec::with_capacity(26 * prevouts.len());
for p in prevouts {
amounts.extend_from_slice(&p.value.to_le_bytes());
write_var_bytes(&mut scripts, &p.script_pubkey);
}
(prevouts_digest(tx), blake2b256(AMOUNTS, &amounts), blake2b256(SCRIPTS, &scripts), sequence_digest(tx))
};
let outputs_sig = match (base, tx.outputs.get(index)) {
(0x02, _) => blake2b256(OUTPUTS, &[]),
(0x03, Some(output)) => outputs_digest(core::slice::from_ref::<TxOut>(output)),
(0x03, None) => blake2b256(OUTPUTS, &[]),
_ => outputs_digest(&tx.outputs),
};
let mut txin = Vec::with_capacity(36 + 8 + 26 + 4);
txin.extend_from_slice(&input.prevout.to_bytes());
txin.extend_from_slice(&prevout.value.to_le_bytes());
write_var_bytes(&mut txin, &prevout.script_pubkey);
txin.extend_from_slice(&input.sequence.to_le_bytes());
let txin_sig = blake2b256(TXIN, &txin);
let transparent_sig = blake2b256_parts(
TRANSPARENT,
&[&[hash_type.to_u8()], &prevouts_sig, &amounts_sig, &scripts_sig, &sequence_sig, &outputs_sig, &txin_sig],
);
Ok(blake2b256_parts(
&txid_personalization(tx.branch_id),
&[&header_digest(tx), &transparent_sig, &shielded.sapling, &shielded.orchard],
))
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use super::*;
#[test]
fn only_the_six_zip244_types_parse() {
let valid: Vec<u8> = (0..=255u8).filter(|b| SighashType::from_u8(*b).is_ok()).collect();
assert_eq!(valid, vec![0x01, 0x02, 0x03, 0x81, 0x82, 0x83]);
for b in valid {
assert_eq!(SighashType::from_u8(b).unwrap().to_u8(), b);
}
}
#[test]
fn checks_the_input_index_and_prevouts() {
let tx = TransparentTx { branch_id: 1, lock_time: 0, expiry_height: 0, inputs: vec![], outputs: vec![] };
let empty = ShieldedDigests::empty();
assert_eq!(transparent_sighash(&tx, 0, &[], SighashType::All, &empty), Err(Error::NoSuchInput(0)));
let p = PrevOut { value: 1, script_pubkey: vec![] };
assert_eq!(transparent_sighash(&tx, 0, &[p], SighashType::All, &empty), Err(Error::PrevoutCountMismatch));
}
}