1use core::ops::Deref;
6
7use bitcoin::secp256k1::{self, Scalar};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11use super::nut00::{BlindSignature, Proof};
12use super::nut01::{PublicKey, SecretKey};
13use super::nut02::Id;
14use crate::dhke::{hash_e, hash_to_curve};
15use crate::{Amount, SECP256K1};
16
17#[derive(Debug, Error)]
19pub enum Error {
20 #[error("No DLEQ proof provided")]
22 MissingDleqProof,
23 #[error("Incomplete DLEQ proof")]
25 IncompleteDleqProof,
26 #[error("Invalid DLEQ proof")]
28 InvalidDleqProof,
29 #[error(transparent)]
31 DHKE(#[from] crate::dhke::Error),
32 #[error(transparent)]
34 NUT01(#[from] crate::nuts::nut01::Error),
35 #[error(transparent)]
37 Secp256k1(#[from] secp256k1::Error),
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
45pub struct BlindSignatureDleq {
46 #[cfg_attr(feature = "swagger", schema(value_type = String))]
48 pub e: SecretKey,
49 #[cfg_attr(feature = "swagger", schema(value_type = String))]
51 pub s: SecretKey,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
59pub struct ProofDleq {
60 #[cfg_attr(feature = "swagger", schema(value_type = String))]
62 pub e: SecretKey,
63 #[cfg_attr(feature = "swagger", schema(value_type = String))]
65 pub s: SecretKey,
66 #[cfg_attr(feature = "swagger", schema(value_type = String))]
68 pub r: SecretKey,
69}
70
71impl ProofDleq {
72 pub fn new(e: SecretKey, s: SecretKey, r: SecretKey) -> Self {
74 Self { e, s, r }
75 }
76}
77
78fn verify_dleq(
80 blinded_message: PublicKey, blinded_signature: PublicKey, e: &SecretKey,
83 s: &SecretKey,
84 mint_pubkey: PublicKey, ) -> Result<(), Error> {
86 let e_bytes: [u8; 32] = e.to_secret_bytes();
87 let e: Scalar = e.as_scalar();
88
89 let a: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &e)?.into();
91
92 let a: PublicKey = a.negate(&SECP256K1).into();
94 let r1: PublicKey = s.public_key().combine(&a)?.into(); let s: Scalar = Scalar::from(s.deref().to_owned());
98 let b: PublicKey = blinded_message.mul_tweak(&SECP256K1, &s)?.into();
99
100 let c: PublicKey = blinded_signature.mul_tweak(&SECP256K1, &e)?.into();
102
103 let c: PublicKey = c.negate(&SECP256K1).into();
105 let r2: PublicKey = b.combine(&c)?.into();
106
107 let hash_e: [u8; 32] = hash_e([r1, r2, mint_pubkey, blinded_signature]);
109
110 if e_bytes != hash_e {
111 tracing::warn!("DLEQ on signature failed");
112 tracing::debug!("e_bytes: {:?}, hash_e: {:?}", e_bytes, hash_e);
113 return Err(Error::InvalidDleqProof);
114 }
115
116 Ok(())
117}
118
119fn calculate_dleq(
120 blinded_signature: PublicKey, blinded_message: &PublicKey, mint_secret_key: &SecretKey, ) -> Result<BlindSignatureDleq, Error> {
124 let r: SecretKey = SecretKey::generate();
126
127 let r1 = r.public_key();
129
130 let r_scal: Scalar = r.as_scalar();
132 let r2: PublicKey = blinded_message.mul_tweak(&SECP256K1, &r_scal)?.into();
133
134 let e: [u8; 32] = hash_e([r1, r2, mint_secret_key.public_key(), blinded_signature]);
136 let e_sk: SecretKey = SecretKey::from_slice(&e)?;
137
138 let s1: SecretKey = e_sk.mul_tweak(&mint_secret_key.as_scalar())?.into();
140
141 let s: SecretKey = r.add_tweak(&s1.to_scalar())?.into();
143
144 Ok(BlindSignatureDleq { e: e_sk, s })
145}
146
147impl Proof {
148 pub fn verify_dleq(&self, mint_pubkey: PublicKey) -> Result<(), Error> {
150 match &self.dleq {
151 Some(dleq) => {
152 let y = hash_to_curve(self.secret.as_bytes())?;
153
154 let r: Scalar = dleq.r.as_scalar();
155 let bs1: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &r)?.into();
156
157 let blinded_signature: PublicKey = self.c.combine(&bs1)?.into();
158 let blinded_message: PublicKey = y.combine(&dleq.r.public_key())?.into();
159
160 verify_dleq(
161 blinded_message,
162 blinded_signature,
163 &dleq.e,
164 &dleq.s,
165 mint_pubkey,
166 )
167 }
168 None => Err(Error::MissingDleqProof),
169 }
170 }
171}
172
173impl BlindSignature {
174 #[inline]
176 pub fn new(
177 amount: Amount,
178 blinded_signature: PublicKey,
179 keyset_id: Id,
180 blinded_message: &PublicKey,
181 mint_secretkey: SecretKey,
182 ) -> Result<Self, Error> {
183 Ok(Self {
184 amount,
185 keyset_id,
186 c: blinded_signature,
187 dleq: Some(calculate_dleq(
188 blinded_signature,
189 blinded_message,
190 &mint_secretkey,
191 )?),
192 })
193 }
194
195 #[inline]
197 pub fn verify_dleq(
198 &self,
199 mint_pubkey: PublicKey,
200 blinded_message: PublicKey,
201 ) -> Result<(), Error> {
202 match &self.dleq {
203 Some(dleq) => verify_dleq(blinded_message, self.c, &dleq.e, &dleq.s, mint_pubkey),
204 None => Err(Error::MissingDleqProof),
205 }
206 }
207
208 pub fn add_dleq_proof(
217 &mut self,
218 blinded_message: &PublicKey,
219 mint_secretkey: &SecretKey,
220 ) -> Result<(), Error> {
221 let dleq: BlindSignatureDleq = calculate_dleq(self.c, blinded_message, mint_secretkey)?;
222 self.dleq = Some(dleq);
223 Ok(())
224 }
225}
226
227#[cfg(test)]
228mod tests {
229
230 use std::str::FromStr;
231
232 use super::*;
233
234 #[test]
235 fn test_blind_signature_dleq() {
236 let blinded_sig = r#"{"amount":8,"id":"00882760bfa2eb41","C_":"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2","dleq":{"e":"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9","s":"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da"}}"#;
237
238 let blinded: BlindSignature = serde_json::from_str(blinded_sig).unwrap();
239
240 let secret_key =
241 SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
242 .unwrap();
243
244 let mint_key = secret_key.public_key();
245
246 let blinded_secret = PublicKey::from_str(
247 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
248 )
249 .unwrap();
250
251 blinded.verify_dleq(mint_key, blinded_secret).unwrap()
252 }
253
254 #[test]
255 fn test_proof_dleq() {
256 let proof = r#"{"amount": 1,"id": "00882760bfa2eb41","secret": "daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9","C": "024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc","dleq": {"e": "b31e58ac6527f34975ffab13e70a48b6d2b0d35abc4b03f0151f09ee1a9763d4","s": "8fbae004c59e754d71df67e392b6ae4e29293113ddc2ec86592a0431d16306d8","r": "a6d13fcd7a18442e6076f5e1e7c887ad5de40a019824bdfa9fe740d302e8d861"}}"#;
257
258 let proof: Proof = serde_json::from_str(proof).unwrap();
259
260 let a: PublicKey = PublicKey::from_str(
262 "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
263 )
264 .unwrap();
265
266 assert!(proof.verify_dleq(a).is_ok());
267 }
268
269 #[test]
277 fn test_proof_dleq_wrong_mint_key() {
278 let proof = r#"{"amount": 1,"id": "00882760bfa2eb41","secret": "daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9","C": "024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc","dleq": {"e": "b31e58ac6527f34975ffab13e70a48b6d2b0d35abc4b03f0151f09ee1a9763d4","s": "8fbae004c59e754d71df67e392b6ae4e29293113ddc2ec86592a0431d16306d8","r": "a6d13fcd7a18442e6076f5e1e7c887ad5de40a019824bdfa9fe740d302e8d861"}}"#;
279
280 let proof: Proof = serde_json::from_str(proof).unwrap();
281
282 let wrong_key: PublicKey = PublicKey::from_str(
284 "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
285 )
286 .unwrap();
287
288 assert!(proof.verify_dleq(wrong_key).is_err());
290 }
291
292 #[test]
300 fn test_proof_dleq_missing() {
301 let proof = r#"{"amount": 1,"id": "00882760bfa2eb41","secret": "daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9","C": "024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc"}"#;
302
303 let proof: Proof = serde_json::from_str(proof).unwrap();
304
305 let a: PublicKey = PublicKey::from_str(
306 "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
307 )
308 .unwrap();
309
310 let result = proof.verify_dleq(a);
312 assert!(result.is_err());
313 assert!(matches!(result.unwrap_err(), Error::MissingDleqProof));
314 }
315
316 #[test]
323 fn test_blind_signature_dleq_wrong_key() {
324 let blinded_sig = r#"{"amount":8,"id":"00882760bfa2eb41","C_":"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2","dleq":{"e":"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9","s":"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da"}}"#;
325
326 let blinded: BlindSignature = serde_json::from_str(blinded_sig).unwrap();
327
328 let wrong_key =
330 SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000002")
331 .unwrap();
332
333 let blinded_secret = PublicKey::from_str(
334 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
335 )
336 .unwrap();
337
338 assert!(blinded
340 .verify_dleq(wrong_key.public_key(), blinded_secret)
341 .is_err());
342 }
343
344 #[test]
352 fn test_blind_signature_dleq_tampered() {
353 let tampered_sig = r#"{"amount":8,"id":"00882760bfa2eb41","C_":"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2","dleq":{"e":"0000000000000000000000000000000000000000000000000000000000000001","s":"0000000000000000000000000000000000000000000000000000000000000002"}}"#;
355
356 let blinded: BlindSignature = serde_json::from_str(tampered_sig).unwrap();
357
358 let secret_key =
359 SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
360 .unwrap();
361
362 let blinded_secret = PublicKey::from_str(
363 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
364 )
365 .unwrap();
366
367 assert!(blinded
369 .verify_dleq(secret_key.public_key(), blinded_secret)
370 .is_err());
371 }
372
373 #[test]
381 fn test_add_dleq_proof() {
382 use crate::nuts::nut02::Id;
383
384 let secret_key =
385 SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
386 .unwrap();
387
388 let blinded_message = PublicKey::from_str(
389 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
390 )
391 .unwrap();
392
393 let blinded_signature = PublicKey::from_str(
394 "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
395 )
396 .unwrap();
397
398 let mut blind_sig = BlindSignature {
399 amount: Amount::from(1),
400 keyset_id: Id::from_str("00882760bfa2eb41").unwrap(),
401 c: blinded_signature,
402 dleq: None,
403 };
404
405 assert!(blind_sig.dleq.is_none());
407
408 blind_sig
410 .add_dleq_proof(&blinded_message, &secret_key)
411 .unwrap();
412
413 assert!(blind_sig.dleq.is_some());
415
416 assert!(blind_sig
418 .verify_dleq(secret_key.public_key(), blinded_message)
419 .is_ok());
420 }
421}