use crate::tagged_hash::{tagged_hash, TapLeafHash, TAG_TAP_SIGHASH};
use sha2::{Digest, Sha256};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Default)]
pub enum TaprootSighashType {
#[default]
Default,
All,
None,
Single,
AllAnyoneCanPay,
NoneAnyoneCanPay,
SingleAnyoneCanPay,
}
impl TaprootSighashType {
pub fn to_u8(self) -> u8 {
match self {
Self::Default => 0x00,
Self::All => 0x01,
Self::None => 0x02,
Self::Single => 0x03,
Self::AllAnyoneCanPay => 0x81,
Self::NoneAnyoneCanPay => 0x82,
Self::SingleAnyoneCanPay => 0x83,
}
}
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0x00 => Some(Self::Default),
0x01 => Some(Self::All),
0x02 => Some(Self::None),
0x03 => Some(Self::Single),
0x81 => Some(Self::AllAnyoneCanPay),
0x82 => Some(Self::NoneAnyoneCanPay),
0x83 => Some(Self::SingleAnyoneCanPay),
_ => None,
}
}
pub fn is_anyone_can_pay(self) -> bool {
matches!(
self,
Self::AllAnyoneCanPay | Self::NoneAnyoneCanPay | Self::SingleAnyoneCanPay
)
}
pub fn is_none(self) -> bool {
matches!(self, Self::None | Self::NoneAnyoneCanPay)
}
pub fn is_single(self) -> bool {
matches!(self, Self::Single | Self::SingleAnyoneCanPay)
}
}
#[derive(Clone, Debug)]
pub struct TxOut {
pub value: u64,
pub script_pubkey: Vec<u8>,
}
#[allow(clippy::too_many_arguments)]
pub fn taproot_key_path_sighash(
tx_version: i32,
tx_locktime: u32,
prevouts: &[TxOut],
input_index: usize,
sequences: &[u32],
outputs: &[TxOut],
sighash_type: TaprootSighashType,
annex: Option<&[u8]>,
) -> [u8; 32] {
compute_sighash(
tx_version,
tx_locktime,
prevouts,
input_index,
sequences,
outputs,
sighash_type,
annex,
None, 0, 0, )
}
#[allow(clippy::too_many_arguments)]
pub fn taproot_script_path_sighash(
tx_version: i32,
tx_locktime: u32,
prevouts: &[TxOut],
input_index: usize,
sequences: &[u32],
outputs: &[TxOut],
sighash_type: TaprootSighashType,
annex: Option<&[u8]>,
leaf_hash: &TapLeafHash,
key_version: u8,
codesep_pos: u32,
) -> [u8; 32] {
compute_sighash(
tx_version,
tx_locktime,
prevouts,
input_index,
sequences,
outputs,
sighash_type,
annex,
Some(leaf_hash),
key_version,
codesep_pos,
)
}
#[allow(clippy::too_many_arguments)]
fn compute_sighash(
tx_version: i32,
tx_locktime: u32,
prevouts: &[TxOut],
input_index: usize,
sequences: &[u32],
outputs: &[TxOut],
sighash_type: TaprootSighashType,
annex: Option<&[u8]>,
leaf_hash: Option<&TapLeafHash>,
key_version: u8,
codesep_pos: u32,
) -> [u8; 32] {
let mut data = Vec::new();
data.push(0x00);
data.push(sighash_type.to_u8());
data.extend_from_slice(&tx_version.to_le_bytes());
data.extend_from_slice(&tx_locktime.to_le_bytes());
if !sighash_type.is_anyone_can_pay() {
data.extend_from_slice(&hash_prevouts(prevouts, input_index));
data.extend_from_slice(&hash_amounts(prevouts));
data.extend_from_slice(&hash_scriptpubkeys(prevouts));
data.extend_from_slice(&hash_sequences(sequences));
}
if !sighash_type.is_none() && !sighash_type.is_single() {
data.extend_from_slice(&hash_outputs(outputs));
}
let mut spend_type = 0u8;
if annex.is_some() {
spend_type |= 1;
}
if leaf_hash.is_some() {
spend_type |= 2;
}
data.push(spend_type);
if sighash_type.is_anyone_can_pay() {
data.extend_from_slice(&[0u8; 36]);
data.extend_from_slice(&prevouts[input_index].value.to_le_bytes());
let spk = &prevouts[input_index].script_pubkey;
write_compact_size(&mut data, spk.len());
data.extend_from_slice(spk);
data.extend_from_slice(&sequences[input_index].to_le_bytes());
} else {
data.extend_from_slice(&(input_index as u32).to_le_bytes());
}
if let Some(annex_data) = annex {
let annex_hash = sha256(annex_data);
data.extend_from_slice(&annex_hash);
}
if sighash_type.is_single() && input_index < outputs.len() {
data.extend_from_slice(&hash_single_output(&outputs[input_index]));
}
if let Some(leaf) = leaf_hash {
data.extend_from_slice(leaf.as_bytes());
data.push(key_version);
data.extend_from_slice(&codesep_pos.to_le_bytes());
}
tagged_hash(TAG_TAP_SIGHASH, &data)
}
fn hash_prevouts(_prevouts: &[TxOut], _input_index: usize) -> [u8; 32] {
[0u8; 32]
}
fn hash_amounts(prevouts: &[TxOut]) -> [u8; 32] {
let mut data = Vec::new();
for prevout in prevouts {
data.extend_from_slice(&prevout.value.to_le_bytes());
}
sha256(&data)
}
fn hash_scriptpubkeys(prevouts: &[TxOut]) -> [u8; 32] {
let mut data = Vec::new();
for prevout in prevouts {
write_compact_size(&mut data, prevout.script_pubkey.len());
data.extend_from_slice(&prevout.script_pubkey);
}
sha256(&data)
}
fn hash_sequences(sequences: &[u32]) -> [u8; 32] {
let mut data = Vec::new();
for seq in sequences {
data.extend_from_slice(&seq.to_le_bytes());
}
sha256(&data)
}
fn hash_outputs(outputs: &[TxOut]) -> [u8; 32] {
let mut data = Vec::new();
for output in outputs {
data.extend_from_slice(&output.value.to_le_bytes());
write_compact_size(&mut data, output.script_pubkey.len());
data.extend_from_slice(&output.script_pubkey);
}
sha256(&data)
}
fn hash_single_output(output: &TxOut) -> [u8; 32] {
let mut data = Vec::new();
data.extend_from_slice(&output.value.to_le_bytes());
write_compact_size(&mut data, output.script_pubkey.len());
data.extend_from_slice(&output.script_pubkey);
sha256(&data)
}
fn sha256(data: &[u8]) -> [u8; 32] {
let hash = Sha256::digest(data);
let mut result = [0u8; 32];
result.copy_from_slice(&hash);
result
}
fn write_compact_size(buf: &mut Vec<u8>, value: usize) {
if value < 0xfd {
buf.push(value as u8);
} else if value <= 0xffff {
buf.push(0xfd);
buf.extend_from_slice(&(value as u16).to_le_bytes());
} else if value <= 0xffffffff {
buf.push(0xfe);
buf.extend_from_slice(&(value as u32).to_le_bytes());
} else {
buf.push(0xff);
buf.extend_from_slice(&(value as u64).to_le_bytes());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sighash_type_roundtrip() {
let types = [
TaprootSighashType::Default,
TaprootSighashType::All,
TaprootSighashType::None,
TaprootSighashType::Single,
TaprootSighashType::AllAnyoneCanPay,
TaprootSighashType::NoneAnyoneCanPay,
TaprootSighashType::SingleAnyoneCanPay,
];
for sighash in types {
let byte = sighash.to_u8();
let parsed = TaprootSighashType::from_u8(byte).unwrap();
assert_eq!(sighash, parsed);
}
}
#[test]
fn test_sighash_flags() {
assert!(!TaprootSighashType::Default.is_anyone_can_pay());
assert!(TaprootSighashType::AllAnyoneCanPay.is_anyone_can_pay());
assert!(!TaprootSighashType::All.is_none());
assert!(TaprootSighashType::None.is_none());
assert!(!TaprootSighashType::All.is_single());
assert!(TaprootSighashType::Single.is_single());
}
#[test]
fn test_key_path_sighash() {
let mut script = vec![0x51, 0x20];
script.extend_from_slice(&[0x00; 32]);
let prevouts = vec![TxOut {
value: 100_000,
script_pubkey: script.clone(),
}];
let sequences = vec![0xffffffff];
let outputs = vec![TxOut {
value: 90_000,
script_pubkey: script,
}];
let hash = taproot_key_path_sighash(
2,
0,
&prevouts,
0,
&sequences,
&outputs,
TaprootSighashType::Default,
None,
);
let hash2 = taproot_key_path_sighash(
2,
0,
&prevouts,
0,
&sequences,
&outputs,
TaprootSighashType::Default,
None,
);
assert_eq!(hash, hash2);
}
}