rustywallet_taproot/
tweak.rs

1//! Key tweaking for Taproot
2//!
3//! Implements key tweaking as specified in BIP341.
4
5use crate::error::TaprootError;
6use crate::tagged_hash::{TapNodeHash, TapTweakHash};
7use crate::xonly::{Parity, XOnlyPublicKey};
8use secp256k1::{Secp256k1, SecretKey};
9
10/// Tweak a public key for Taproot
11///
12/// Computes: P' = P + t*G where t = tagged_hash("TapTweak", P || merkle_root)
13pub fn tweak_public_key(
14    internal_key: &XOnlyPublicKey,
15    merkle_root: Option<&TapNodeHash>,
16) -> Result<(XOnlyPublicKey, Parity), TaprootError> {
17    let secp = Secp256k1::new();
18    
19    // Compute tweak
20    let tweak_hash = TapTweakHash::from_key_and_merkle_root(
21        &internal_key.serialize(),
22        merkle_root,
23    );
24    
25    // Apply tweak
26    internal_key.tweak_add(&secp, tweak_hash.as_bytes())
27}
28
29/// Tweak a private key for Taproot
30///
31/// If the public key has odd y-coordinate, the private key is negated first.
32pub fn tweak_private_key(
33    secret_key: &[u8; 32],
34    internal_pubkey: &XOnlyPublicKey,
35    merkle_root: Option<&TapNodeHash>,
36) -> Result<[u8; 32], TaprootError> {
37    let secp = Secp256k1::new();
38    
39    // Get the secret key
40    let mut sk = SecretKey::from_slice(secret_key)
41        .map_err(|e| TaprootError::Secp256k1Error(e.to_string()))?;
42    
43    // Check if we need to negate (if y-coordinate is odd)
44    let pubkey = sk.public_key(&secp);
45    let (_, parity) = pubkey.x_only_public_key();
46    
47    if parity == secp256k1::Parity::Odd {
48        sk = sk.negate();
49    }
50    
51    // Compute tweak
52    let tweak_hash = TapTweakHash::from_key_and_merkle_root(
53        &internal_pubkey.serialize(),
54        merkle_root,
55    );
56    
57    // Apply tweak
58    let scalar = secp256k1::Scalar::from_be_bytes(*tweak_hash.as_bytes())
59        .map_err(|e| TaprootError::InvalidTweak(e.to_string()))?;
60    
61    let tweaked = sk.add_tweak(&scalar)
62        .map_err(|e| TaprootError::InvalidTweak(e.to_string()))?;
63    
64    Ok(tweaked.secret_bytes())
65}
66
67/// Compute the output key from internal key and merkle root
68pub fn compute_output_key(
69    internal_key: &XOnlyPublicKey,
70    merkle_root: Option<&TapNodeHash>,
71) -> Result<(XOnlyPublicKey, Parity), TaprootError> {
72    tweak_public_key(internal_key, merkle_root)
73}
74
75/// Compute the tweak value
76pub fn compute_tweak(
77    internal_key: &XOnlyPublicKey,
78    merkle_root: Option<&TapNodeHash>,
79) -> [u8; 32] {
80    TapTweakHash::from_key_and_merkle_root(
81        &internal_key.serialize(),
82        merkle_root,
83    ).to_bytes()
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    fn get_test_keypair() -> ([u8; 32], XOnlyPublicKey) {
91        let secp = Secp256k1::new();
92        let secret = [
93            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
94            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
95            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
96            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
97        ];
98        let sk = SecretKey::from_slice(&secret).unwrap();
99        let pk = sk.public_key(&secp);
100        let (xonly, _) = pk.x_only_public_key();
101        (secret, XOnlyPublicKey::from_inner(xonly))
102    }
103
104    #[test]
105    fn test_tweak_public_key_no_merkle() {
106        let (_, internal_key) = get_test_keypair();
107        
108        let (output_key, parity) = tweak_public_key(&internal_key, None).unwrap();
109        
110        // Output key should be different from internal key
111        assert_ne!(output_key.serialize(), internal_key.serialize());
112        
113        // Should be deterministic
114        let (output_key2, parity2) = tweak_public_key(&internal_key, None).unwrap();
115        assert_eq!(output_key, output_key2);
116        assert_eq!(parity, parity2);
117    }
118
119    #[test]
120    fn test_tweak_public_key_with_merkle() {
121        let (_, internal_key) = get_test_keypair();
122        let merkle_root = TapNodeHash::from_bytes([0x42; 32]);
123        
124        let (output_key1, _) = tweak_public_key(&internal_key, None).unwrap();
125        let (output_key2, _) = tweak_public_key(&internal_key, Some(&merkle_root)).unwrap();
126        
127        // Different merkle roots should produce different output keys
128        assert_ne!(output_key1, output_key2);
129    }
130
131    #[test]
132    fn test_tweak_private_key() {
133        let secp = Secp256k1::new();
134        let (secret, internal_key) = get_test_keypair();
135        
136        // Tweak private key
137        let tweaked_secret = tweak_private_key(&secret, &internal_key, None).unwrap();
138        
139        // Derive public key from tweaked private key
140        let tweaked_sk = SecretKey::from_slice(&tweaked_secret).unwrap();
141        let tweaked_pk = tweaked_sk.public_key(&secp);
142        let (tweaked_xonly, _) = tweaked_pk.x_only_public_key();
143        
144        // Tweak public key directly
145        let (output_key, _) = tweak_public_key(&internal_key, None).unwrap();
146        
147        // They should match
148        assert_eq!(tweaked_xonly.serialize(), output_key.serialize());
149    }
150
151    #[test]
152    fn test_compute_tweak() {
153        let (_, internal_key) = get_test_keypair();
154        
155        let tweak1 = compute_tweak(&internal_key, None);
156        let tweak2 = compute_tweak(&internal_key, None);
157        
158        // Should be deterministic
159        assert_eq!(tweak1, tweak2);
160        
161        // With merkle root should be different
162        let merkle_root = TapNodeHash::from_bytes([0x42; 32]);
163        let tweak3 = compute_tweak(&internal_key, Some(&merkle_root));
164        assert_ne!(tweak1, tweak3);
165    }
166}