nut12.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. //! NUT-12: Offline ecash signature validation
  2. //!
  3. //! <https://github.com/cashubtc/nuts/blob/main/12.md>
  4. use core::ops::Deref;
  5. use bitcoin::secp256k1::{self, Scalar};
  6. use serde::{Deserialize, Serialize};
  7. use thiserror::Error;
  8. use super::nut00::{BlindSignature, Proof};
  9. use super::nut01::{PublicKey, SecretKey};
  10. use super::nut02::Id;
  11. use crate::dhke::{hash_e, hash_to_curve};
  12. use crate::{Amount, SECP256K1};
  13. /// NUT12 Error
  14. #[derive(Debug, Error)]
  15. pub enum Error {
  16. /// Missing DLEQ Proof
  17. #[error("No DLEQ proof provided")]
  18. MissingDleqProof,
  19. /// Incomplete DLEQ Proof
  20. #[error("Incomplete DLEQ proof")]
  21. IncompleteDleqProof,
  22. /// Invalid DLEQ Proof
  23. #[error("Invalid DLEQ proof")]
  24. InvalidDleqProof,
  25. /// DHKE error
  26. #[error(transparent)]
  27. DHKE(#[from] crate::dhke::Error),
  28. /// NUT01 Error
  29. #[error(transparent)]
  30. NUT01(#[from] crate::nuts::nut01::Error),
  31. /// SECP256k1 Error
  32. #[error(transparent)]
  33. Secp256k1(#[from] secp256k1::Error),
  34. }
  35. /// Blinded Signature on Dleq
  36. ///
  37. /// Defined in [NUT12](https://github.com/cashubtc/nuts/blob/main/12.md)
  38. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  39. #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
  40. pub struct BlindSignatureDleq {
  41. /// e
  42. #[cfg_attr(feature = "swagger", schema(value_type = String))]
  43. pub e: SecretKey,
  44. /// s
  45. #[cfg_attr(feature = "swagger", schema(value_type = String))]
  46. pub s: SecretKey,
  47. }
  48. /// Proof Dleq
  49. ///
  50. /// Defined in [NUT12](https://github.com/cashubtc/nuts/blob/main/12.md)
  51. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  52. #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
  53. pub struct ProofDleq {
  54. /// e
  55. #[cfg_attr(feature = "swagger", schema(value_type = String))]
  56. pub e: SecretKey,
  57. /// s
  58. #[cfg_attr(feature = "swagger", schema(value_type = String))]
  59. pub s: SecretKey,
  60. /// Blinding factor
  61. #[cfg_attr(feature = "swagger", schema(value_type = String))]
  62. pub r: SecretKey,
  63. }
  64. impl ProofDleq {
  65. /// Create new [`ProofDleq`]
  66. pub fn new(e: SecretKey, s: SecretKey, r: SecretKey) -> Self {
  67. Self { e, s, r }
  68. }
  69. }
  70. /// Verify DLEQ
  71. fn verify_dleq(
  72. blinded_message: PublicKey, // B'
  73. blinded_signature: PublicKey, // C'
  74. e: &SecretKey,
  75. s: &SecretKey,
  76. mint_pubkey: PublicKey, // A
  77. ) -> Result<(), Error> {
  78. let e_bytes: [u8; 32] = e.to_secret_bytes();
  79. let e: Scalar = e.as_scalar();
  80. // a = e*A
  81. let a: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &e)?.into();
  82. // R1 = s*G - a
  83. let a: PublicKey = a.negate(&SECP256K1).into();
  84. let r1: PublicKey = s.public_key().combine(&a)?.into(); // s*G + (-a)
  85. // b = s*B'
  86. let s: Scalar = Scalar::from(s.deref().to_owned());
  87. let b: PublicKey = blinded_message.mul_tweak(&SECP256K1, &s)?.into();
  88. // c = e*C'
  89. let c: PublicKey = blinded_signature.mul_tweak(&SECP256K1, &e)?.into();
  90. // R2 = b - c
  91. let c: PublicKey = c.negate(&SECP256K1).into();
  92. let r2: PublicKey = b.combine(&c)?.into();
  93. // hash(R1,R2,A,C')
  94. let hash_e: [u8; 32] = hash_e([r1, r2, mint_pubkey, blinded_signature]);
  95. if e_bytes != hash_e {
  96. tracing::warn!("DLEQ on signature failed");
  97. tracing::debug!("e_bytes: {:?}, hash_e: {:?}", e_bytes, hash_e);
  98. return Err(Error::InvalidDleqProof);
  99. }
  100. Ok(())
  101. }
  102. fn calculate_dleq(
  103. blinded_signature: PublicKey, // C'
  104. blinded_message: &PublicKey, // B'
  105. mint_secret_key: &SecretKey, // a
  106. ) -> Result<BlindSignatureDleq, Error> {
  107. // Random nonce
  108. let r: SecretKey = SecretKey::generate();
  109. // R1 = r*G
  110. let r1 = r.public_key();
  111. // R2 = r*B'
  112. let r_scal: Scalar = r.as_scalar();
  113. let r2: PublicKey = blinded_message.mul_tweak(&SECP256K1, &r_scal)?.into();
  114. // e = hash(R1,R2,A,C')
  115. let e: [u8; 32] = hash_e([r1, r2, mint_secret_key.public_key(), blinded_signature]);
  116. let e_sk: SecretKey = SecretKey::from_slice(&e)?;
  117. // s1 = e*a
  118. let s1: SecretKey = e_sk.mul_tweak(&mint_secret_key.as_scalar())?.into();
  119. // s = r + s1
  120. let s: SecretKey = r.add_tweak(&s1.to_scalar())?.into();
  121. Ok(BlindSignatureDleq { e: e_sk, s })
  122. }
  123. impl Proof {
  124. /// Verify proof Dleq
  125. pub fn verify_dleq(&self, mint_pubkey: PublicKey) -> Result<(), Error> {
  126. match &self.dleq {
  127. Some(dleq) => {
  128. let y = hash_to_curve(self.secret.as_bytes())?;
  129. let r: Scalar = dleq.r.as_scalar();
  130. let bs1: PublicKey = mint_pubkey.mul_tweak(&SECP256K1, &r)?.into();
  131. let blinded_signature: PublicKey = self.c.combine(&bs1)?.into();
  132. let blinded_message: PublicKey = y.combine(&dleq.r.public_key())?.into();
  133. verify_dleq(
  134. blinded_message,
  135. blinded_signature,
  136. &dleq.e,
  137. &dleq.s,
  138. mint_pubkey,
  139. )
  140. }
  141. None => Err(Error::MissingDleqProof),
  142. }
  143. }
  144. }
  145. impl BlindSignature {
  146. /// New DLEQ
  147. #[inline]
  148. pub fn new(
  149. amount: Amount,
  150. blinded_signature: PublicKey,
  151. keyset_id: Id,
  152. blinded_message: &PublicKey,
  153. mint_secretkey: SecretKey,
  154. ) -> Result<Self, Error> {
  155. Ok(Self {
  156. amount,
  157. keyset_id,
  158. c: blinded_signature,
  159. dleq: Some(calculate_dleq(
  160. blinded_signature,
  161. blinded_message,
  162. &mint_secretkey,
  163. )?),
  164. })
  165. }
  166. /// Verify dleq on proof
  167. #[inline]
  168. pub fn verify_dleq(
  169. &self,
  170. mint_pubkey: PublicKey,
  171. blinded_message: PublicKey,
  172. ) -> Result<(), Error> {
  173. match &self.dleq {
  174. Some(dleq) => verify_dleq(blinded_message, self.c, &dleq.e, &dleq.s, mint_pubkey),
  175. None => Err(Error::MissingDleqProof),
  176. }
  177. }
  178. /// Add Dleq to proof
  179. /*
  180. r = random nonce
  181. R1 = r*G
  182. R2 = r*B'
  183. e = hash(R1,R2,A,C')
  184. s = r + e*a
  185. */
  186. pub fn add_dleq_proof(
  187. &mut self,
  188. blinded_message: &PublicKey,
  189. mint_secretkey: &SecretKey,
  190. ) -> Result<(), Error> {
  191. let dleq: BlindSignatureDleq = calculate_dleq(self.c, blinded_message, mint_secretkey)?;
  192. self.dleq = Some(dleq);
  193. Ok(())
  194. }
  195. }
  196. #[cfg(test)]
  197. mod tests {
  198. use std::str::FromStr;
  199. use super::*;
  200. #[test]
  201. fn test_blind_signature_dleq() {
  202. let blinded_sig = r#"{"amount":8,"id":"00882760bfa2eb41","C_":"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2","dleq":{"e":"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9","s":"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da"}}"#;
  203. let blinded: BlindSignature = serde_json::from_str(blinded_sig).unwrap();
  204. let secret_key =
  205. SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
  206. .unwrap();
  207. let mint_key = secret_key.public_key();
  208. let blinded_secret = PublicKey::from_str(
  209. "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
  210. )
  211. .unwrap();
  212. blinded.verify_dleq(mint_key, blinded_secret).unwrap()
  213. }
  214. #[test]
  215. fn test_proof_dleq() {
  216. let proof = r#"{"amount": 1,"id": "00882760bfa2eb41","secret": "daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9","C": "024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc","dleq": {"e": "b31e58ac6527f34975ffab13e70a48b6d2b0d35abc4b03f0151f09ee1a9763d4","s": "8fbae004c59e754d71df67e392b6ae4e29293113ddc2ec86592a0431d16306d8","r": "a6d13fcd7a18442e6076f5e1e7c887ad5de40a019824bdfa9fe740d302e8d861"}}"#;
  217. let proof: Proof = serde_json::from_str(proof).unwrap();
  218. // A
  219. let a: PublicKey = PublicKey::from_str(
  220. "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
  221. )
  222. .unwrap();
  223. assert!(proof.verify_dleq(a).is_ok());
  224. }
  225. /// Tests that verify_dleq correctly rejects verification with a wrong mint key.
  226. ///
  227. /// This test is critical for security - if the verification function doesn't properly
  228. /// check the mint key, an attacker could forge proofs using any key.
  229. ///
  230. /// Mutant testing: Catches mutations that replace verify_dleq with Ok(()) or remove
  231. /// the verification logic.
  232. #[test]
  233. fn test_proof_dleq_wrong_mint_key() {
  234. let proof = r#"{"amount": 1,"id": "00882760bfa2eb41","secret": "daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9","C": "024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc","dleq": {"e": "b31e58ac6527f34975ffab13e70a48b6d2b0d35abc4b03f0151f09ee1a9763d4","s": "8fbae004c59e754d71df67e392b6ae4e29293113ddc2ec86592a0431d16306d8","r": "a6d13fcd7a18442e6076f5e1e7c887ad5de40a019824bdfa9fe740d302e8d861"}}"#;
  235. let proof: Proof = serde_json::from_str(proof).unwrap();
  236. // Wrong mint key - different from the one used to create the proof
  237. let wrong_key: PublicKey = PublicKey::from_str(
  238. "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
  239. )
  240. .unwrap();
  241. // Verification should fail with wrong key
  242. assert!(proof.verify_dleq(wrong_key).is_err());
  243. }
  244. /// Tests that verify_dleq correctly rejects proofs with missing DLEQ data.
  245. ///
  246. /// This test ensures that proofs without DLEQ data are rejected when DLEQ
  247. /// verification is required.
  248. ///
  249. /// Mutant testing: Catches mutations that replace verify_dleq with Ok(()) or
  250. /// remove the None check.
  251. #[test]
  252. fn test_proof_dleq_missing() {
  253. let proof = r#"{"amount": 1,"id": "00882760bfa2eb41","secret": "daf4dd00a2b68a0858a80450f52c8a7d2ccf87d375e43e216e0c571f089f63e9","C": "024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc"}"#;
  254. let proof: Proof = serde_json::from_str(proof).unwrap();
  255. let a: PublicKey = PublicKey::from_str(
  256. "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
  257. )
  258. .unwrap();
  259. // Verification should fail when DLEQ is missing
  260. let result = proof.verify_dleq(a);
  261. assert!(result.is_err());
  262. assert!(matches!(result.unwrap_err(), Error::MissingDleqProof));
  263. }
  264. /// Tests that BlindSignature::verify_dleq correctly rejects verification with wrong mint key.
  265. ///
  266. /// This test ensures that blind signature DLEQ verification properly validates the mint key.
  267. ///
  268. /// Mutant testing: Catches mutations that replace BlindSignature::verify_dleq with Ok(())
  269. /// or remove the verification logic.
  270. #[test]
  271. fn test_blind_signature_dleq_wrong_key() {
  272. let blinded_sig = r#"{"amount":8,"id":"00882760bfa2eb41","C_":"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2","dleq":{"e":"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9","s":"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da"}}"#;
  273. let blinded: BlindSignature = serde_json::from_str(blinded_sig).unwrap();
  274. // Wrong secret key - different from the one used to create the signature
  275. let wrong_key =
  276. SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000002")
  277. .unwrap();
  278. let blinded_secret = PublicKey::from_str(
  279. "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
  280. )
  281. .unwrap();
  282. // Verification should fail with wrong key
  283. assert!(blinded
  284. .verify_dleq(wrong_key.public_key(), blinded_secret)
  285. .is_err());
  286. }
  287. /// Tests that BlindSignature::verify_dleq correctly rejects verification with tampered DLEQ data.
  288. ///
  289. /// This test ensures that tampering with the 'e' or 's' values in the DLEQ proof
  290. /// causes verification to fail.
  291. ///
  292. /// Mutant testing: Catches mutations that replace verify_dleq with Ok(()) or
  293. /// weaken the cryptographic checks.
  294. #[test]
  295. fn test_blind_signature_dleq_tampered() {
  296. // Tampered DLEQ data - 'e' and 's' values have been modified to wrong (but valid) values
  297. let tampered_sig = r#"{"amount":8,"id":"00882760bfa2eb41","C_":"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2","dleq":{"e":"0000000000000000000000000000000000000000000000000000000000000001","s":"0000000000000000000000000000000000000000000000000000000000000002"}}"#;
  298. let blinded: BlindSignature = serde_json::from_str(tampered_sig).unwrap();
  299. let secret_key =
  300. SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
  301. .unwrap();
  302. let blinded_secret = PublicKey::from_str(
  303. "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
  304. )
  305. .unwrap();
  306. // Verification should fail with tampered data
  307. assert!(blinded
  308. .verify_dleq(secret_key.public_key(), blinded_secret)
  309. .is_err());
  310. }
  311. /// Tests that BlindSignature::add_dleq_proof properly generates DLEQ data.
  312. ///
  313. /// This test ensures that add_dleq_proof actually adds the DLEQ proof and doesn't
  314. /// just return Ok(()) without doing anything.
  315. ///
  316. /// Mutant testing: Catches mutations that replace add_dleq_proof with Ok(())
  317. /// without actually adding the proof.
  318. #[test]
  319. fn test_add_dleq_proof() {
  320. use crate::nuts::nut02::Id;
  321. let secret_key =
  322. SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
  323. .unwrap();
  324. let blinded_message = PublicKey::from_str(
  325. "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
  326. )
  327. .unwrap();
  328. let blinded_signature = PublicKey::from_str(
  329. "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
  330. )
  331. .unwrap();
  332. let mut blind_sig = BlindSignature {
  333. amount: Amount::from(1),
  334. keyset_id: Id::from_str("00882760bfa2eb41").unwrap(),
  335. c: blinded_signature,
  336. dleq: None,
  337. };
  338. // Initially, DLEQ should be None
  339. assert!(blind_sig.dleq.is_none());
  340. // Add DLEQ proof
  341. blind_sig
  342. .add_dleq_proof(&blinded_message, &secret_key)
  343. .unwrap();
  344. // After adding, DLEQ should be Some
  345. assert!(blind_sig.dleq.is_some());
  346. // Verify the added DLEQ is valid
  347. assert!(blind_sig
  348. .verify_dleq(secret_key.public_key(), blinded_message)
  349. .is_ok());
  350. }
  351. }