1use sha2::{Digest, Sha256};
7
8pub fn tagged_hash(tag: &[u8], data: &[u8]) -> [u8; 32] {
12 use std::sync::OnceLock;
14 static TAP_SIGHASH_MID: OnceLock<[u8; 32]> = OnceLock::new();
15 static BIP322_MID: OnceLock<[u8; 32]> = OnceLock::new();
16 static BIP340_CHALLENGE_MID: OnceLock<[u8; 32]> = OnceLock::new();
17 static BIP340_AUX_MID: OnceLock<[u8; 32]> = OnceLock::new();
18
19 let tag_hash = match tag {
20 b"TapSighash" => *TAP_SIGHASH_MID.get_or_init(|| sha256(b"TapSighash")),
21 b"BIP0322-signed-message" => *BIP322_MID.get_or_init(|| sha256(b"BIP0322-signed-message")),
22 b"BIP0340/challenge" => *BIP340_CHALLENGE_MID.get_or_init(|| sha256(b"BIP0340/challenge")),
23 b"BIP0340/aux" => *BIP340_AUX_MID.get_or_init(|| sha256(b"BIP0340/aux")),
24 _ => sha256(tag),
25 };
26
27 let mut h = Sha256::new();
28 h.update(tag_hash);
29 h.update(tag_hash);
30 h.update(data);
31 let result = h.finalize();
32 let mut out = [0u8; 32];
33 out.copy_from_slice(&result);
34 out
35}
36
37pub fn double_sha256(data: &[u8]) -> [u8; 32] {
41 let h1 = Sha256::digest(data);
42 let h2 = Sha256::digest(h1);
43 let mut out = [0u8; 32];
44 out.copy_from_slice(&h2);
45 out
46}
47
48pub fn hash160(data: &[u8]) -> [u8; 20] {
52 use ripemd::{Digest as RipeDigest, Ripemd160};
53 let sha = Sha256::digest(data);
54 let ripe = Ripemd160::digest(sha);
55 let mut out = [0u8; 20];
56 out.copy_from_slice(&ripe);
57 out
58}
59
60pub fn sha256(data: &[u8]) -> [u8; 32] {
62 let h = Sha256::digest(data);
63 let mut out = [0u8; 32];
64 out.copy_from_slice(&h);
65 out
66}
67
68#[cfg(test)]
69#[allow(clippy::unwrap_used, clippy::expect_used)]
70mod tests {
71 use super::*;
72
73 #[test]
76 fn test_sha256_nist_empty() {
77 assert_eq!(
79 hex::encode(sha256(b"")),
80 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
81 );
82 }
83
84 #[test]
85 fn test_sha256_nist_abc() {
86 assert_eq!(
88 hex::encode(sha256(b"abc")),
89 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
90 );
91 }
92
93 #[test]
94 fn test_sha256_nist_448bit() {
95 assert_eq!(
97 hex::encode(sha256(
98 b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
99 )),
100 "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
101 );
102 }
103
104 #[test]
107 fn test_double_sha256_empty() {
108 assert_eq!(
110 hex::encode(double_sha256(b"")),
111 "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456"
112 );
113 }
114
115 #[test]
116 fn test_double_sha256_hello() {
117 let h = double_sha256(b"hello");
119 assert_eq!(h.len(), 32);
120 assert_ne!(h, sha256(b"hello")); }
122
123 #[test]
124 fn test_double_sha256_is_idempotent_on_input() {
125 let h1 = double_sha256(b"test");
126 let h2 = double_sha256(b"test");
127 assert_eq!(h1, h2);
128 }
129
130 #[test]
133 fn test_hash160_bitcoin_generator_point() {
134 let generator_pubkey =
138 hex::decode("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
139 .unwrap();
140 assert_eq!(
141 hex::encode(hash160(&generator_pubkey)),
142 "751e76e8199196d454941c45d1b3a323f1433bd6"
143 );
144 }
145
146 #[test]
147 fn test_hash160_empty() {
148 assert_eq!(
150 hex::encode(hash160(b"")),
151 "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"
152 );
153 }
154
155 #[test]
156 fn test_hash160_output_length() {
157 assert_eq!(hash160(b"any data").len(), 20);
158 assert_eq!(hash160(b"").len(), 20);
159 assert_eq!(hash160(&[0u8; 1000]).len(), 20);
160 }
161
162 #[test]
165 fn test_tagged_hash_deterministic() {
166 let h1 = tagged_hash(b"TapLeaf", b"data");
167 let h2 = tagged_hash(b"TapLeaf", b"data");
168 assert_eq!(h1, h2);
169 }
170
171 #[test]
172 fn test_tagged_hash_domain_separation() {
173 let h1 = tagged_hash(b"TapLeaf", b"data");
174 let h2 = tagged_hash(b"TapBranch", b"data");
175 let h3 = tagged_hash(b"BIP0340/challenge", b"data");
176 assert_ne!(h1, h2);
177 assert_ne!(h1, h3);
178 assert_ne!(h2, h3);
179 }
180
181 #[test]
182 fn test_tagged_hash_differs_from_plain_sha256() {
183 let plain = sha256(b"data");
185 let tagged = tagged_hash(b"BIP0340/aux", b"data");
186 assert_ne!(plain, tagged);
187 }
188
189 #[test]
190 fn test_tagged_hash_empty_tag_and_data() {
191 let h = tagged_hash(b"", b"");
192 assert_eq!(h.len(), 32);
193 assert_ne!(h, [0u8; 32]);
195 }
196
197 #[test]
198 fn test_bip322_message_hash_empty() {
199 assert_eq!(
201 hex::encode(tagged_hash(b"BIP0322-signed-message", b"")),
202 "c90c269c4f8fcbe6880f72a721ddfbf1914268a794cbb21cfafee13770ae19f1"
203 );
204 }
205
206 #[test]
207 fn test_bip322_message_hash_hello_world() {
208 assert_eq!(
210 hex::encode(tagged_hash(b"BIP0322-signed-message", b"Hello World")),
211 "f0eb03b1a75ac6d9847f55c624a99169b5dccba2a31f5b23bea77ba270de0a7a"
212 );
213 }
214
215 #[test]
216 fn test_tagged_hash_bip340_aux_vector() {
217 let h = tagged_hash(b"BIP0340/aux", &[0u8; 32]);
219 assert_eq!(h.len(), 32);
220 assert_ne!(h, [0u8; 32]);
221 }
222
223 #[test]
226 fn test_double_sha256_equals_sha256_of_sha256() {
227 let data = b"consistency check";
228 let single = sha256(data);
229 let double = sha256(&single);
230 assert_eq!(double_sha256(data), double);
231 }
232
233 #[test]
234 fn test_hash160_consistency() {
235 let data = b"hash160 consistency";
238 let sha_intermediate = sha256(data);
239 let h160_direct = hash160(data);
240 use ripemd::{Digest as _, Ripemd160};
242 let ripe = Ripemd160::digest(sha_intermediate);
243 assert_eq!(&h160_direct[..], &ripe[..]);
244 }
245}