1use {
2 base64::{engine::general_purpose, Engine},
3 bitcoin::hashes::{sha256, Hash},
4 bitcoin::{
5 absolute::LockTime,
6 address::AddressData,
7 blockdata::script,
8 consensus::Decodable,
9 consensus::Encodable,
10 key::{Keypair, TapTweak},
11 opcodes,
12 psbt::Psbt,
13 script::PushBytes,
14 secp256k1::{self, schnorr::Signature, Message, Secp256k1, XOnlyPublicKey},
15 sighash::{self, SighashCache, TapSighashType},
16 transaction::Version,
17 Address, Amount, EcdsaSighashType, OutPoint, PrivateKey, PublicKey, ScriptBuf, Sequence,
18 Transaction, TxIn, TxOut, Witness,
19 },
20 error::Error,
21 snafu::{ResultExt, Snafu},
22 std::str::FromStr,
23};
24
25mod error;
26mod sign;
27mod util;
28mod verify;
29
30pub use {sign::*, util::*, verify::*};
31pub use bitcoin;
32
33type Result<T = (), E = Error> = std::result::Result<T, E>;
34
35#[cfg(test)]
36mod tests {
37 use {super::*, bitcoin::consensus::deserialize, pretty_assertions::assert_eq, rand::RngCore};
38
39 const WIF_PRIVATE_KEY: &str = "L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k";
44 const SEGWIT_ADDRESS: &str = "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l";
45 const TAPROOT_ADDRESS: &str = "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3";
46 const LEGACY_ADDRESS: &str = "14vV3aCHBeStb5bkenkNHbe2YAFinYdXgc";
47
48 const NESTED_SEGWIT_WIF_PRIVATE_KEY: &str =
49 "KwTbAxmBXjoZM3bzbXixEr9nxLhyYSM4vp2swet58i19bw9sqk5z";
50 const NESTED_SEGWIT_ADDRESS: &str = "3HSVzEhCFuH9Z3wvoWTexy7BMVVp3PjS6f";
51
52 #[test]
53 fn message_hashes_are_correct() {
54 assert_eq!(
55 hex::encode(message_hash("".as_bytes())),
56 "c90c269c4f8fcbe6880f72a721ddfbf1914268a794cbb21cfafee13770ae19f1"
57 );
58
59 assert_eq!(
60 hex::encode(message_hash("Hello World".as_bytes())),
61 "f0eb03b1a75ac6d9847f55c624a99169b5dccba2a31f5b23bea77ba270de0a7a"
62 );
63 }
64
65 #[test]
66 fn to_spend_txids_correct() {
67 assert_eq!(
68 create_to_spend(
69 &Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(),
70 "".as_bytes()
71 )
72 .unwrap()
73 .compute_txid()
74 .to_string(),
75 "c5680aa69bb8d860bf82d4e9cd3504b55dde018de765a91bb566283c545a99a7"
76 );
77
78 assert_eq!(
79 create_to_spend(
80 &Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(),
81 "Hello World".as_bytes()
82 )
83 .unwrap()
84 .compute_txid()
85 .to_string(),
86 "b79d196740ad5217771c1098fc4a4b51e0535c32236c71f1ea4d61a2d603352b"
87 );
88 }
89
90 #[test]
91 fn to_sign_txids_correct() {
92 let to_spend = create_to_spend(
93 &Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(),
94 "".as_bytes(),
95 )
96 .unwrap();
97
98 let to_sign = create_to_sign(&to_spend, None).unwrap();
99
100 assert_eq!(
101 to_sign.unsigned_tx.compute_txid().to_string(),
102 "1e9654e951a5ba44c8604c4de6c67fd78a27e81dcadcfe1edf638ba3aaebaed6"
103 );
104
105 let to_spend = create_to_spend(
106 &Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(),
107 "Hello World".as_bytes(),
108 )
109 .unwrap();
110
111 let to_sign = create_to_sign(&to_spend, None).unwrap();
112
113 assert_eq!(
114 to_sign.unsigned_tx.compute_txid().to_string(),
115 "88737ae86f2077145f93cc4b153ae9a1cb8d56afa511988c149c5c8c9d93bddf"
116 );
117 }
118
119 #[test]
120 fn simple_verify_and_falsify_taproot() {
121 assert!(
122 verify::verify_simple_encoded(
123 TAPROOT_ADDRESS,
124 "Hello World",
125 "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
126 ).is_ok()
127 );
128
129 assert_eq!(
130 verify::verify_simple_encoded(
131 TAPROOT_ADDRESS,
132 "Hello World -- This should fail",
133 "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
134 ).unwrap_err().to_string(),
135 "Invalid signature"
136 );
137 }
138
139 #[test]
140 fn simple_sign_taproot() {
141 assert_eq!(
142 sign::sign_simple_encoded(TAPROOT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap(),
143 "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
144 );
145 }
146
147 #[test]
148 fn roundtrip_taproot_simple() {
149 assert!(verify::verify_simple_encoded(
150 TAPROOT_ADDRESS,
151 "Hello World",
152 &sign::sign_simple_encoded(TAPROOT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap()
153 )
154 .is_ok());
155 }
156
157 #[test]
158 fn roundtrip_taproot_full() {
159 assert!(verify::verify_full_encoded(
160 TAPROOT_ADDRESS,
161 "Hello World",
162 &sign::sign_full_encoded(TAPROOT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap()
163 )
164 .is_ok());
165 }
166
167 #[test]
168 fn invalid_address() {
169 assert_eq!(verify::verify_simple_encoded(
170 LEGACY_ADDRESS,
171 "",
172 "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI=").unwrap_err().to_string(),
173 format!("Unsuported address `{LEGACY_ADDRESS}`, only P2TR, P2WPKH and P2SH-P2WPKH allowed")
174 )
175 }
176
177 #[test]
178 fn signature_decode_error() {
179 assert_eq!(
180 verify::verify_simple_encoded(
181 TAPROOT_ADDRESS,
182 "Hello World",
183 "invalid signature not in base64 encoding"
184 )
185 .unwrap_err()
186 .to_string(),
187 "Decode error for signature `invalid signature not in base64 encoding`"
188 );
189
190 assert_eq!(
191 verify::verify_simple_encoded(
192 TAPROOT_ADDRESS,
193 "Hello World",
194 "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViH"
195 ).unwrap_err().to_string(),
196 "Decode error for signature `AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViH`"
197 )
198 }
199
200 #[test]
201 fn simple_verify_and_falsify_p2wpkh() {
202 assert!(
203 verify::verify_simple_encoded(
204 SEGWIT_ADDRESS,
205 "Hello World",
206 "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
207 ).is_ok()
208 );
209
210 assert!(
211 verify::verify_simple_encoded(
212 SEGWIT_ADDRESS,
213 "Hello World - this should fail",
214 "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
215 ).is_err()
216 );
217
218 assert!(
219 verify::verify_simple_encoded(
220 SEGWIT_ADDRESS,
221 "Hello World",
222 "AkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy"
223 ).is_ok()
224 );
225
226 assert!(
227 verify::verify_simple_encoded(
228 SEGWIT_ADDRESS,
229 "",
230 "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
231 ).is_ok()
232 );
233
234 assert!(
235 verify::verify_simple_encoded(
236 SEGWIT_ADDRESS,
237 "fail",
238 "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="
239 ).is_err()
240 );
241
242 assert!(
243 verify::verify_simple_encoded(
244 SEGWIT_ADDRESS,
245 "",
246 "AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy"
247 ).is_ok()
248 );
249 }
250
251 #[test]
252 fn simple_sign_p2wpkh() {
253 assert_eq!(
254 sign::sign_simple_encoded(SEGWIT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap(),
255 "AkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy"
256 );
257
258 assert_eq!(
259 sign::sign_simple_encoded(SEGWIT_ADDRESS, "", WIF_PRIVATE_KEY).unwrap(),
260 "AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy"
261 );
262 }
263
264 #[test]
265 fn roundtrip_p2wpkh_simple() {
266 assert!(verify::verify_simple_encoded(
267 SEGWIT_ADDRESS,
268 "Hello World",
269 &sign::sign_simple_encoded(SEGWIT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap()
270 )
271 .is_ok());
272 }
273
274 #[test]
275 fn roundtrip_p2wpkh_full() {
276 assert!(verify::verify_full_encoded(
277 SEGWIT_ADDRESS,
278 "Hello World",
279 &sign::sign_full_encoded(SEGWIT_ADDRESS, "Hello World", WIF_PRIVATE_KEY).unwrap()
280 )
281 .is_ok());
282 }
283
284 #[test]
285 fn simple_verify_and_falsify_p2sh_p2wpkh() {
286 assert!(verify::verify_simple_encoded(
287 NESTED_SEGWIT_ADDRESS,
288 "Hello World",
289 "AkgwRQIhAMd2wZSY3x0V9Kr/NClochoTXcgDaGl3OObOR17yx3QQAiBVWxqNSS+CKen7bmJTG6YfJjsggQ4Fa2RHKgBKrdQQ+gEhAxa5UDdQCHSQHfKQv14ybcYm1C9y6b12xAuukWzSnS+w"
290 ).is_ok()
291 );
292
293 assert!(verify::verify_simple_encoded(
294 NESTED_SEGWIT_ADDRESS,
295 "Hello World - this should fail",
296 "AkgwRQIhAMd2wZSY3x0V9Kr/NClochoTXcgDaGl3OObOR17yx3QQAiBVWxqNSS+CKen7bmJTG6YfJjsggQ4Fa2RHKgBKrdQQ+gEhAxa5UDdQCHSQHfKQv14ybcYm1C9y6b12xAuukWzSnS+w"
297 ).is_err()
298 );
299 }
300
301 #[test]
302 fn simple_sign_p2sh_p2wpkh() {
303 assert_eq!(
304 sign::sign_simple_encoded(NESTED_SEGWIT_ADDRESS, "Hello World", NESTED_SEGWIT_WIF_PRIVATE_KEY).unwrap(),
305 "AkgwRQIhAMd2wZSY3x0V9Kr/NClochoTXcgDaGl3OObOR17yx3QQAiBVWxqNSS+CKen7bmJTG6YfJjsggQ4Fa2RHKgBKrdQQ+gEhAxa5UDdQCHSQHfKQv14ybcYm1C9y6b12xAuukWzSnS+w"
306 );
307 }
308
309 #[test]
310 fn roundtrip_p2sh_p2wpkh_simple() {
311 assert!(verify::verify_simple_encoded(
312 NESTED_SEGWIT_ADDRESS,
313 "Hello World",
314 &sign::sign_simple_encoded(
315 NESTED_SEGWIT_ADDRESS,
316 "Hello World",
317 NESTED_SEGWIT_WIF_PRIVATE_KEY
318 )
319 .unwrap()
320 )
321 .is_ok());
322 }
323
324 #[test]
325 fn roundtrip_p2sh_p2wpkh_full() {
326 assert!(verify::verify_full_encoded(
327 NESTED_SEGWIT_ADDRESS,
328 "Hello World",
329 &sign::sign_full_encoded(
330 NESTED_SEGWIT_ADDRESS,
331 "Hello World",
332 NESTED_SEGWIT_WIF_PRIVATE_KEY
333 )
334 .unwrap()
335 )
336 .is_ok());
337 }
338
339 #[test]
340 fn adding_aux_randomness_roundtrips() {
341 let address = Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked();
342 let message = "Hello World with aux randomness".as_bytes();
343 let to_spend = create_to_spend(&address, message).unwrap();
344 let to_sign = create_to_sign(&to_spend, None).unwrap();
345 let private_key = PrivateKey::from_wif(WIF_PRIVATE_KEY).unwrap();
346
347 let mut aux_rand = [0u8; 32];
348 rand::thread_rng().fill_bytes(&mut aux_rand);
349
350 let witness =
351 create_message_signature_taproot(&to_spend, &to_sign, private_key, Some(aux_rand));
352
353 assert!(verify_simple(&address, message, witness).is_ok());
354 }
355
356 #[test]
357 fn test_verify_full_witness() {
358 let address =
359 Address::from_str("tb1p8e2qv0gjlssvgl5u2za7l6wzrgvukzw5qpvksqe4fy73qrtwgjsqcak3rw").unwrap();
360 let pubkey =
361 PublicKey::from_str("038ca9e365af7c923fde1a2fd56ec2f3b7863cd379d85aea846be501049f6c5ddd")
362 .unwrap();
363 let message =
364 "allstake.withdraw:7a715a51aaf7a516d1a680247cb5e71a1731c68c2ab29c20034b3fc7cfff4557:0"
365 .as_bytes();
366 let witness = deserialize::<Witness>(hex::decode("01418e922cb14a468bc0cc9eaff650272d7568369614e8bc610ba4cc7cad3ec9a84ad23e8ce657ecdb1601d1273897edc68287cc0a743d27434a7ec41775057143dd01").unwrap().as_slice()).unwrap();
367
368 assert!(verify_full_witness(&pubkey, &address.assume_checked(), message, witness).is_ok());
369 }
370
371 #[test]
372 fn test_verify_full_witness_wrong_address() {
373 let address =
374 Address::from_str("tb1p8e2qv0gjlssvgl5u2za7l6wzrgvukzw5qpvksqe4fy73qrtwgjsqcak3rw").unwrap();
375 let pubkey =
376 PublicKey::from_str("02999d8a64c41b29ba32790af1eb220adfb8cd038c758d0a2a59dcc3ec13bdac84")
377 .unwrap();
378 let message =
379 "allstake.withdraw:7a715a51aaf7a516d1a680247cb5e71a1731c68c2ab29c20034b3fc7cfff4557:0"
380 .as_bytes();
381 let witness = deserialize::<Witness>(hex::decode("01418e922cb14a468bc0cc9eaff650272d7568369614e8bc610ba4cc7cad3ec9a84ad23e8ce657ecdb1601d1273897edc68287cc0a743d27434a7ec41775057143dd01").unwrap().as_slice()).unwrap();
382
383 assert_eq!(
384 verify_full_witness(&pubkey, &address.assume_checked(), message, witness)
385 .unwrap_err()
386 .to_string(),
387 "Public key does not match"
388 );
389 }
390
391 #[test]
392 fn test_verify_full_witness_wrong_signature() {
393 let address =
394 Address::from_str("tb1p8e2qv0gjlssvgl5u2za7l6wzrgvukzw5qpvksqe4fy73qrtwgjsqcak3rw").unwrap();
395 let pubkey =
396 PublicKey::from_str("038ca9e365af7c923fde1a2fd56ec2f3b7863cd379d85aea846be501049f6c5ddd")
397 .unwrap();
398 let message =
399 "allstake.withdraw:7a715a51aaf7a516d1a680247cb5e71a1731c68c2ab29c20034b3fc7cfff4557:1"
400 .as_bytes();
401 let witness = deserialize::<Witness>(hex::decode("01418e922cb14a468bc0cc9eaff650272d7568369614e8bc610ba4cc7cad3ec9a84ad23e8ce657ecdb1601d1273897edc68287cc0a743d27434a7ec41775057143dd01").unwrap().as_slice()).unwrap();
402
403 assert_eq!(
404 verify_full_witness(&pubkey, &address.assume_checked(), message, witness)
405 .unwrap_err()
406 .to_string(),
407 "Invalid signature"
408 );
409 }
410
411 #[test]
412 fn roundtrip_p2wpkh_full_witness() {
413 let address = Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked();
414 let message = "Hello World".as_bytes();
415 let private_key = PrivateKey::from_wif(WIF_PRIVATE_KEY).unwrap();
416
417 let to_sign_txn = sign::sign_full(&address, message, private_key.clone()).unwrap();
418 let witness = to_sign_txn.input[0].witness.clone();
419 let secp = Secp256k1::new();
420
421 assert!(
422 verify_full_witness(&private_key.public_key(&secp), &address, message, witness).is_ok()
423 );
424 }
425
426 #[test]
427 fn roundtrip_p2tr_full_witness() {
428 let address = Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked();
429 let message = "Hello World".as_bytes();
430 let private_key = PrivateKey::from_wif(WIF_PRIVATE_KEY).unwrap();
431
432 let to_sign_txn = sign::sign_full(&address, message, private_key.clone()).unwrap();
433 let witness = to_sign_txn.input[0].witness.clone();
434 let secp = Secp256k1::new();
435
436 assert!(verify_full_witness(
437 &private_key.public_key(&secp),
438 &address,
439 message,
440 witness.clone(),
441 )
442 .is_ok());
443
444 assert!(verify_full_witness(
445 &private_key.public_key(&secp),
446 &address,
447 "Hello World - this should fail".as_bytes(),
448 witness,
449 )
450 .is_err());
451 }
452}