Skip to main content

cashu/
dhke.rs

1//! Diffie-Hellmann key exchange
2
3use std::ops::Deref;
4
5use bitcoin::hashes::sha256::Hash as Sha256Hash;
6use bitcoin::hashes::Hash;
7use bitcoin::secp256k1::{
8    Parity, PublicKey as NormalizedPublicKey, Scalar, Secp256k1, XOnlyPublicKey,
9};
10use thiserror::Error;
11
12use crate::nuts::nut01::{PublicKey, SecretKey};
13use crate::nuts::nut12::ProofDleq;
14use crate::nuts::{BlindSignature, Keys, Proof, Proofs};
15use crate::secret::Secret;
16use crate::util::hex;
17use crate::SECP256K1;
18
19const DOMAIN_SEPARATOR: &[u8; 28] = b"Secp256k1_HashToCurve_Cashu_";
20
21/// NUT00 Error
22#[derive(Debug, Error)]
23pub enum Error {
24    /// Token could not be validated
25    #[error("Token not verified")]
26    TokenNotVerified,
27    /// No valid point on curve
28    #[error("No valid point found")]
29    NoValidPoint,
30    /// Secp256k1 error
31    #[error(transparent)]
32    Secp256k1(#[from] bitcoin::secp256k1::Error),
33    // TODO: Remove use anyhow
34    /// Custom Error
35    #[error("`{0}`")]
36    Custom(String),
37}
38
39/// Deterministically maps a message to a public key point on the secp256k1
40/// curve, utilizing a domain separator to ensure uniqueness.
41///
42/// For definationn in NUT see [NUT-00](https://github.com/cashubtc/nuts/blob/main/00.md)
43pub fn hash_to_curve(message: &[u8]) -> Result<PublicKey, Error> {
44    let msg_to_hash: Vec<u8> = [DOMAIN_SEPARATOR, message].concat();
45
46    let msg_hash: [u8; 32] = Sha256Hash::hash(&msg_to_hash).to_byte_array();
47
48    let mut counter: u32 = 0;
49    while counter < 2_u32.pow(16) {
50        let mut bytes_to_hash: Vec<u8> = Vec::with_capacity(36);
51        bytes_to_hash.extend_from_slice(&msg_hash);
52        bytes_to_hash.extend_from_slice(&counter.to_le_bytes());
53        let hash: [u8; 32] = Sha256Hash::hash(&bytes_to_hash).to_byte_array();
54
55        // Try to parse public key
56        match XOnlyPublicKey::from_slice(&hash) {
57            Ok(pk) => {
58                return Ok(NormalizedPublicKey::from_x_only_public_key(pk, Parity::Even).into())
59            }
60            Err(_) => {
61                counter += 1;
62            }
63        }
64    }
65
66    Err(Error::NoValidPoint)
67}
68
69/// Convert iterator of [`PublicKey`] to byte array
70pub fn hash_e<I>(public_keys: I) -> [u8; 32]
71where
72    I: IntoIterator<Item = PublicKey>,
73{
74    let mut e: String = String::new();
75
76    for public_key in public_keys.into_iter() {
77        let uncompressed: [u8; 65] = public_key.to_uncompressed_bytes();
78        e.push_str(&hex::encode(uncompressed));
79    }
80
81    Sha256Hash::hash(e.as_bytes()).to_byte_array()
82}
83
84/// Blind Message
85///
86/// `B_ = Y + rG`
87pub fn blind_message(
88    secret: &[u8],
89    blinding_factor: Option<SecretKey>,
90) -> Result<(PublicKey, SecretKey), Error> {
91    let y: PublicKey = hash_to_curve(secret)?;
92    let r: SecretKey = blinding_factor.unwrap_or_else(SecretKey::generate);
93    Ok((y.combine(&r.public_key())?.into(), r))
94}
95
96/// Unblind Message
97///
98/// `C_ - rK`
99pub fn unblind_message(
100    // C_
101    blinded_key: &PublicKey,
102    r: &SecretKey,
103    // K
104    mint_pubkey: &PublicKey,
105) -> Result<PublicKey, Error> {
106    let r: Scalar = Scalar::from(r.deref().to_owned());
107
108    // a = r * K
109    let a: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &r)?.into();
110
111    // C_ - a
112    let a: PublicKey = a.negate(&SECP256K1).into();
113    Ok(blinded_key.combine(&a)?.into()) // C_ + (-a)
114}
115
116/// Construct Proof
117pub fn construct_proofs(
118    promises: Vec<BlindSignature>,
119    rs: Vec<SecretKey>,
120    secrets: Vec<Secret>,
121    keys: &Keys,
122) -> Result<Proofs, Error> {
123    if (promises.len() != rs.len()) || (promises.len() != secrets.len()) {
124        tracing::error!(
125            "Promises: {}, RS: {}, secrets:{}",
126            promises.len(),
127            rs.len(),
128            secrets.len()
129        );
130        return Err(Error::Custom(
131            "Lengths of promises, rs, and secrets must be equal".to_string(),
132        ));
133    }
134    let mut proofs = vec![];
135    for ((blinded_signature, r), secret) in promises.into_iter().zip(rs).zip(secrets) {
136        let blinded_c: PublicKey = blinded_signature.c;
137        let a: PublicKey = keys
138            .amount_key(blinded_signature.amount)
139            .ok_or(Error::Custom("Could not get proofs".to_string()))?;
140
141        let unblinded_signature: PublicKey = unblind_message(&blinded_c, &r, &a)?;
142
143        let dleq = blinded_signature.dleq.map(|d| ProofDleq::new(d.e, d.s, r));
144
145        let proof = Proof {
146            amount: blinded_signature.amount,
147            keyset_id: blinded_signature.keyset_id,
148            secret,
149            c: unblinded_signature,
150            witness: None,
151            dleq,
152            p2pk_e: None,
153        };
154
155        proofs.push(proof);
156    }
157
158    Ok(proofs)
159}
160
161/// Sign Blinded Message
162///
163/// `C_ = k * B_`, where:
164/// * `k` is the private key of mint (one for each amount)
165/// * `B_` is the blinded message
166#[inline]
167pub fn sign_message(k: &SecretKey, blinded_message: &PublicKey) -> Result<PublicKey, Error> {
168    let k: Scalar = Scalar::from(k.deref().to_owned());
169    Ok(blinded_message.mul_tweak(&SECP256K1, &k)?.into())
170}
171
172/// Verify Message
173pub fn verify_message(
174    a: &SecretKey,
175    unblinded_message: PublicKey,
176    msg: &[u8],
177) -> Result<(), Error> {
178    // Y
179    let y: PublicKey = hash_to_curve(msg)?;
180
181    // Compute the expected unblinded message
182    let expected_unblinded_message: PublicKey = y
183        .mul_tweak(&Secp256k1::new(), &Scalar::from(*a.deref()))?
184        .into();
185
186    // Compare the unblinded_message with the expected value
187    if unblinded_message == expected_unblinded_message {
188        return Ok(());
189    }
190
191    Err(Error::TokenNotVerified)
192}
193
194#[cfg(test)]
195mod tests {
196    use std::str::FromStr;
197
198    use super::*;
199
200    #[test]
201    fn test_hash_to_curve() {
202        let secret = "0000000000000000000000000000000000000000000000000000000000000000";
203        let sec_hex = hex::decode(secret).unwrap();
204
205        let y = hash_to_curve(&sec_hex).unwrap();
206        let expected_y = PublicKey::from_hex(
207            "024cce997d3b518f739663b757deaec95bcd9473c30a14ac2fd04023a739d1a725",
208        )
209        .unwrap();
210        assert_eq!(y, expected_y);
211
212        let secret = "0000000000000000000000000000000000000000000000000000000000000001";
213        let sec_hex = hex::decode(secret).unwrap();
214        let y = hash_to_curve(&sec_hex).unwrap();
215        let expected_y = PublicKey::from_hex(
216            "022e7158e11c9506f1aa4248bf531298daa7febd6194f003edcd9b93ade6253acf",
217        )
218        .unwrap();
219        assert_eq!(y, expected_y);
220        // Note that this message will take a few iterations of the loop before finding
221        // a valid point
222        let secret = "0000000000000000000000000000000000000000000000000000000000000002";
223        let sec_hex = hex::decode(secret).unwrap();
224        let y = hash_to_curve(&sec_hex).unwrap();
225        let expected_y = PublicKey::from_hex(
226            "026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f",
227        )
228        .unwrap();
229        assert_eq!(y, expected_y);
230    }
231
232    #[test]
233    fn test_hash_e() {
234        let c = PublicKey::from_str(
235            "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
236        )
237        .unwrap();
238
239        let k = PublicKey::from_str(
240            "020000000000000000000000000000000000000000000000000000000000000001",
241        )
242        .unwrap();
243
244        let r1 = PublicKey::from_str(
245            "020000000000000000000000000000000000000000000000000000000000000001",
246        )
247        .unwrap();
248
249        let r2 = PublicKey::from_str(
250            "020000000000000000000000000000000000000000000000000000000000000001",
251        )
252        .unwrap();
253
254        let e = hash_e(vec![r1, r2, k, c]);
255        let e_hex = hex::encode(e);
256
257        assert_eq!(
258            "a4dc034b74338c28c6bc3ea49731f2a24440fc7c4affc08b31a93fc9fbe6401e",
259            e_hex
260        )
261    }
262
263    #[test]
264    fn test_blind_message() {
265        let message =
266            hex::decode("d341ee4871f1f889041e63cf0d3823c713eea6aff01e80f1719f08f9e5be98f6")
267                .unwrap();
268        let sec: SecretKey =
269            SecretKey::from_hex("99fce58439fc37412ab3468b73db0569322588f62fb3a49182d67e23d877824a")
270                .unwrap();
271
272        let (b, r) = blind_message(&message, Some(sec.clone())).unwrap();
273
274        assert_eq!(sec, r);
275        assert_eq!(
276            b,
277            PublicKey::from_hex(
278                "033b1a9737a40cc3fd9b6af4b723632b76a67a36782596304612a6c2bfb5197e6d"
279            )
280            .unwrap()
281        );
282
283        let message =
284            hex::decode("f1aaf16c2239746f369572c0784d9dd3d032d952c2d992175873fb58fae31a60")
285                .unwrap();
286        let sec: SecretKey =
287            SecretKey::from_hex("f78476ea7cc9ade20f9e05e58a804cf19533f03ea805ece5fee88c8e2874ba50")
288                .unwrap();
289
290        let (b, r) = blind_message(&message, Some(sec.clone())).unwrap();
291
292        assert_eq!(sec, r);
293        assert_eq!(
294            b,
295            PublicKey::from_hex(
296                "029bdf2d716ee366eddf599ba252786c1033f47e230248a4612a5670ab931f1763"
297            )
298            .unwrap()
299        );
300    }
301
302    #[test]
303    fn test_unblind_message() {
304        let blinded_key = PublicKey::from_hex(
305            "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
306        )
307        .unwrap();
308
309        let r =
310            SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
311                .unwrap();
312        let a = PublicKey::from_hex(
313            "020000000000000000000000000000000000000000000000000000000000000001",
314        )
315        .unwrap();
316
317        let unblinded = unblind_message(&blinded_key, &r, &a).unwrap();
318
319        assert_eq!(
320            PublicKey::from_hex(
321                "03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd"
322            )
323            .unwrap(),
324            unblinded
325        );
326    }
327
328    #[test]
329    fn test_sign_message() {
330        use super::*;
331        let message = "test_message";
332        let sec =
333            SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
334                .unwrap();
335
336        let (blinded_message, _r) = blind_message(message.as_bytes(), Some(sec)).unwrap();
337        // A
338        let bob_sec =
339            SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
340                .unwrap();
341
342        // C_
343        let signed = sign_message(&bob_sec, &blinded_message).unwrap();
344
345        assert_eq!(
346            signed,
347            PublicKey::from_hex(
348                "025cc16fe33b953e2ace39653efb3e7a7049711ae1d8a2f7a9108753f1cdea742b"
349            )
350            .unwrap()
351        );
352
353        // A
354        let bob_sec =
355            SecretKey::from_hex("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f")
356                .unwrap();
357
358        // C_
359        let signed = sign_message(&bob_sec, &blinded_message).unwrap();
360
361        assert_eq!(
362            signed,
363            PublicKey::from_hex(
364                "027726f0e5757b4202a27198369a3477a17bc275b7529da518fc7cb4a1d927cc0d"
365            )
366            .unwrap()
367        );
368    }
369
370    #[test]
371    fn test_full_bhke() {
372        let message =
373            hex::decode("d341ee4871f1f889041e63cf0d3823c713eea6aff01e80f1719f08f9e5be98f6")
374                .unwrap();
375        let alice_sec: SecretKey =
376            SecretKey::from_hex("99fce58439fc37412ab3468b73db0569322588f62fb3a49182d67e23d877824a")
377                .unwrap();
378
379        let (b, r) = blind_message(&message, Some(alice_sec.clone())).unwrap();
380
381        let bob_sec =
382            SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
383                .unwrap();
384
385        // C_
386        let signed = sign_message(&bob_sec, &b).unwrap();
387
388        let unblinded = unblind_message(&signed, &r, &bob_sec.public_key()).unwrap();
389
390        assert!(verify_message(&bob_sec, unblinded, &message).is_ok());
391    }
392
393    /// Tests that `verify_message` correctly rejects verification when using an incorrect key.
394    ///
395    /// This test ensures that the verification process fails when attempting to verify
396    /// a signature with a different key than the one used to create it. This is critical
397    /// for security - if this check didn't exist, tokens could be forged by anyone.
398    ///
399    /// Mutant testing: Catches mutations that remove or weaken the key comparison logic
400    /// in `verify_message`, such as always returning Ok or ignoring the key parameter.
401    #[test]
402    fn test_verify_message_wrong_key() {
403        // Test that verify_message fails with wrong key
404        let message = b"test message";
405        let correct_key =
406            SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
407                .unwrap();
408        let wrong_key =
409            SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000002")
410                .unwrap();
411
412        let (blinded, r) = blind_message(message, None).unwrap();
413        let signed = sign_message(&correct_key, &blinded).unwrap();
414        let unblinded = unblind_message(&signed, &r, &correct_key.public_key()).unwrap();
415
416        // Should fail with wrong key
417        assert!(verify_message(&wrong_key, unblinded, message).is_err());
418    }
419
420    /// Tests that `verify_message` correctly rejects verification when the message doesn't match.
421    ///
422    /// This test ensures that attempting to verify a signature against a different message
423    /// than the one originally signed results in an error. This prevents message substitution
424    /// attacks where an attacker might try to claim a signature for one message is valid
425    /// for a different message.
426    ///
427    /// Mutant testing: Catches mutations that remove or weaken the message comparison logic,
428    /// such as skipping the hash_to_curve step or ignoring the message parameter entirely.
429    #[test]
430    fn test_verify_message_wrong_message() {
431        // Test that verify_message fails with wrong message
432        let message = b"test message";
433        let wrong_message = b"wrong message";
434        let key =
435            SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
436                .unwrap();
437
438        let (blinded, r) = blind_message(message, None).unwrap();
439        let signed = sign_message(&key, &blinded).unwrap();
440        let unblinded = unblind_message(&signed, &r, &key.public_key()).unwrap();
441
442        // Should fail with wrong message
443        assert!(verify_message(&key, unblinded, wrong_message).is_err());
444    }
445
446    /// Tests that `construct_proofs` returns an error when input vectors have mismatched lengths.
447    ///
448    /// This test verifies that the function properly validates that the `promises`, `rs`, and
449    /// `secrets` vectors all have the same length before processing. This is essential for
450    /// correctness - each proof requires exactly one promise, one blinding factor (r), and
451    /// one secret. Mismatched lengths would indicate a programming error or corrupted data.
452    ///
453    /// Mutant testing: Catches mutations that remove or weaken the length validation check
454    /// at the beginning of `construct_proofs`, such as changing `!=` to `==` or removing
455    /// the validation entirely, which could lead to panics or incorrect proof construction.
456    #[test]
457    fn test_construct_proofs_length_mismatch() {
458        use std::collections::BTreeMap;
459
460        use crate::nuts::nut02::Id;
461        use crate::Amount;
462
463        // Test that construct_proofs fails when lengths don't match
464        let mut keys_map = BTreeMap::new();
465        keys_map.insert(Amount::from(1), SecretKey::generate().public_key());
466        let keys = Keys::new(keys_map);
467
468        // Mismatched promises and rs lengths
469        let promise = BlindSignature {
470            amount: Amount::from(1),
471            c: SecretKey::generate().public_key(),
472            keyset_id: Id::from_str("00deadbeef123456").unwrap(),
473            dleq: None,
474        };
475        let promises = vec![promise];
476        let rs = vec![SecretKey::generate(), SecretKey::generate()]; // Different length
477        let secrets = vec![Secret::from_str("test").unwrap()];
478
479        let result = construct_proofs(promises, rs, secrets, &keys);
480        assert!(result.is_err());
481    }
482
483    /// Tests that `construct_proofs` returns the correct number of proof objects.
484    ///
485    /// This test verifies that when given N valid inputs (promises, blinding factors, secrets),
486    /// the function returns exactly N proofs, not zero or any other count. This ensures that
487    /// the loop in `construct_proofs` actually processes all inputs and accumulates results
488    /// correctly.
489    ///
490    /// Mutant testing: Specifically designed to catch mutations that replace the function body
491    /// with `Ok(Default::default())` or similar shortcuts that would return an empty vector
492    /// instead of processing the inputs. This is a common mutation that could pass tests that
493    /// only check for success without verifying the actual results.
494    #[test]
495    fn test_construct_proofs_returns_correct_count() {
496        use std::collections::BTreeMap;
497
498        use crate::nuts::nut02::Id;
499        use crate::Amount;
500
501        // Test that construct_proofs returns the correct number of proofs
502        let secret_key = SecretKey::generate();
503        let mut keys_map = BTreeMap::new();
504        keys_map.insert(Amount::from(1), secret_key.public_key());
505        let keys = Keys::new(keys_map);
506
507        let secret = Secret::from_str("test").unwrap();
508        let (blinded_message, r) = blind_message(secret.as_bytes(), None).unwrap();
509        let signature = sign_message(&secret_key, &blinded_message).unwrap();
510
511        let promise = BlindSignature {
512            amount: Amount::from(1),
513            c: signature,
514            keyset_id: Id::from_str("00deadbeef123456").unwrap(),
515            dleq: None,
516        };
517
518        let promises = vec![promise.clone(), promise.clone()];
519        let rs = vec![r.clone(), r];
520        let secrets = vec![secret.clone(), secret];
521
522        let proofs = construct_proofs(promises, rs, secrets, &keys).unwrap();
523
524        // Should return 2 proofs, not 0 (kills the Ok(Default::default()) mutant)
525        assert_eq!(proofs.len(), 2);
526    }
527
528    /// Tests that hash_to_curve properly increments the counter and terminates.
529    ///
530    /// The hash_to_curve function uses a counter that increments in a loop at line 61.
531    /// If the counter increment is mutated (e.g., to `counter *= 1`), the loop would
532    /// never progress and would run until the timeout.
533    ///
534    /// This test uses a message that requires multiple iterations to find a valid point,
535    /// ensuring the counter increment logic is working correctly.
536    ///
537    /// Mutant testing: Kills mutations that replace `counter += 1` with `counter *= 1`
538    /// or other operations that don't advance the counter.
539    #[test]
540    fn test_hash_to_curve_counter_increments() {
541        // This specific message is documented in test_hash_to_curve as taking
542        // "a few iterations of the loop before finding a valid point"
543        let secret = "0000000000000000000000000000000000000000000000000000000000000002";
544        let sec_hex = hex::decode(secret).unwrap();
545
546        let result = hash_to_curve(&sec_hex);
547        assert!(result.is_ok(), "hash_to_curve should find a valid point");
548
549        let y = result.unwrap();
550        let expected_y = PublicKey::from_hex(
551            "026cdbe15362df59cd1dd3c9c11de8aedac2106eca69236ecd9fbe117af897be4f",
552        )
553        .unwrap();
554        assert_eq!(y, expected_y);
555    }
556}