csv_adapter_bitcoin/
bip341.rs1use bitcoin::{
7 key::{TapTweak, XOnlyPublicKey},
8 secp256k1::Secp256k1,
9 taproot::TaprootSpendInfo,
10 Address, Network, ScriptBuf,
11};
12
13use csv_adapter_core::hash::Hash as CsvHash;
14
15#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct TapretCommitment {
18 pub protocol_id: [u8; 32],
19 pub commitment: CsvHash,
20}
21
22impl TapretCommitment {
23 pub fn new(protocol_id: [u8; 32], commitment: CsvHash) -> Self {
24 Self {
25 protocol_id,
26 commitment,
27 }
28 }
29
30 pub fn leaf_script(&self) -> ScriptBuf {
31 use bitcoin::{
32 opcodes::all::OP_RETURN,
33 script::{Builder, PushBytesBuf},
34 };
35 let mut payload = [0u8; 64];
36 payload[..32].copy_from_slice(&self.protocol_id);
37 payload[32..].copy_from_slice(self.commitment.as_bytes());
38 let push_bytes = PushBytesBuf::try_from(payload.to_vec()).unwrap();
39 Builder::new()
40 .push_opcode(OP_RETURN)
41 .push_slice(push_bytes)
42 .into_script()
43 }
44}
45
46pub fn derive_output_key(
50 internal_key: XOnlyPublicKey,
51 merkle_root: Option<bitcoin::taproot::TapNodeHash>,
52) -> Result<XOnlyPublicKey, Bip341Error> {
53 let secp = Secp256k1::new();
54 let tweaked = internal_key.tap_tweak(&secp, merkle_root);
55 Ok(tweaked.0.to_inner())
57}
58
59pub struct TaprootOutput {
61 pub output_key: XOnlyPublicKey,
62 pub merkle_root: Option<bitcoin::taproot::TapNodeHash>,
63 pub spend_info: Option<TaprootSpendInfo>,
64}
65
66impl TaprootOutput {
67 pub fn to_address(&self, network: Network) -> Address {
68 if let Some(ref spend_info) = self.spend_info {
69 Address::p2tr_tweaked(spend_info.output_key(), network)
70 } else {
71 let secp = Secp256k1::new();
73 let tweaked = self.output_key.tap_tweak(&secp, None);
74 Address::p2tr_tweaked(tweaked.0, network)
75 }
76 }
77}
78
79#[derive(Debug, thiserror::Error)]
81pub enum Bip341Error {
82 #[error("Invalid internal key")]
83 InvalidKey,
84}
85
86pub fn generate_test_keypair() -> (secp256k1::SecretKey, XOnlyPublicKey) {
88 use secp256k1::rand::thread_rng as secp_rng;
89 let secp = Secp256k1::new();
90 let mut rng = secp_rng();
91 let secret_key = secp256k1::SecretKey::new(&mut rng);
92 let public_key = secp256k1::PublicKey::from_secret_key(&secp, &secret_key);
93 let xonly = XOnlyPublicKey::from(public_key);
94 (secret_key, xonly)
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_derive_output_key_no_merkle_root() {
103 let (_, internal_key) = generate_test_keypair();
104 let output_key = derive_output_key(internal_key, None).unwrap();
105 assert_eq!(output_key.serialize().len(), 32);
106 }
107
108 #[test]
109 fn test_tapret_commitment_leaf_script() {
110 let tapret = TapretCommitment::new([1u8; 32], CsvHash::new([2u8; 32]));
111 let script = tapret.leaf_script();
112 assert!(script.is_op_return());
113 assert_eq!(script.len(), 66);
114 }
115
116 #[test]
117 fn test_output_key_deterministic() {
118 use secp256k1::SecretKey;
119
120 let secp = Secp256k1::new();
121 let secret = SecretKey::from_slice(&[0xAB; 32]).unwrap();
122 let public_key = secp256k1::PublicKey::from_secret_key(&secp, &secret);
123 let internal_key = XOnlyPublicKey::from(public_key);
124
125 let output1 = derive_output_key(internal_key, None).unwrap();
126 let output2 = derive_output_key(internal_key, None).unwrap();
127 assert_eq!(output1, output2);
128 }
129
130 #[test]
131 fn test_output_key_differs_by_merkle_root() {
132 use std::str::FromStr;
133 let (_, internal_key) = generate_test_keypair();
134 let output_no_root = derive_output_key(internal_key, None).unwrap();
135
136 let fake_root = bitcoin::taproot::TapNodeHash::from_str(
138 "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
139 )
140 .unwrap();
141 let output_with_root = derive_output_key(internal_key, Some(fake_root)).unwrap();
142 assert_ne!(output_no_root, output_with_root);
143 }
144}