bip322_rs/
lib.rs

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  /// From https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#test-vectors
40  /// and https://github.com/ACken2/bip322-js/blob/main/test/Verifier.test.ts
41  /// and https://github.com/bitcoin/bitcoin/blob/29b28d07fa958b89e1c7916fda5d8654474cf495/src/test/util_tests.cpp#L2747
42
43  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}