1use std::{fmt, sync::LazyLock};
14
15use hmac::{Hmac, KeyInit, Mac};
16use num_bigint::BigUint;
17use sha2::Sha512;
18
19use crate::babyjubjub::{
20 BASE8, BabyJubError, BabyJubPoint, BabyJubScalar, BabyJubSecretScalar, Point, SUB_ORDER,
21 add_point, mul_point_escalar, public_key_from_scalar,
22};
23use crate::blake512::blake512;
24use crate::encoding::{biguint_to_le_bytes, from_hex, le_bytes_to_biguint};
25use crate::field::{Bn254Fr, fr_from_biguint, fr_to_biguint};
26use crate::poseidon::poseidon;
27
28const SCALAR_NONCE_LABEL: &[u8] = b"CURVY_BABYJUB_SCALAR_NONCE_V1";
29
30static NONCE_REJECTION_LIMIT: LazyLock<BigUint> = LazyLock::new(|| {
31 let two_512 = BigUint::from(1u8) << 512usize;
32 &two_512 - (&two_512 % &*SUB_ORDER)
33});
34
35type HmacSha512 = Hmac<Sha512>;
36
37#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct Signature {
40 pub r8: Point,
41 pub s: BigUint,
42}
43
44#[derive(Clone, Debug, PartialEq, Eq)]
47pub struct ScalarSignature {
48 pub r8: BabyJubPoint,
49 pub s: BabyJubScalar,
50}
51
52impl ScalarSignature {
53 pub fn to_signature(&self) -> Signature {
55 Signature {
56 r8: self.r8.as_tuple(),
57 s: self.s.as_biguint().clone(),
58 }
59 }
60}
61
62pub struct ScalarSigningKey {
65 secret: BabyJubSecretScalar,
66 public: BabyJubPoint,
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
70pub enum ScalarSignatureError {
71 InvalidKey(BabyJubError),
72 NonceCounterExhausted,
73 InternalVerificationFailed,
74}
75
76impl fmt::Display for ScalarSignatureError {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 match self {
79 Self::InvalidKey(e) => write!(f, "invalid scalar signing key: {e}"),
80 Self::NonceCounterExhausted => f.write_str("deterministic nonce counter exhausted"),
81 Self::InternalVerificationFailed => {
82 f.write_str("scalar signature failed internal verification")
83 }
84 }
85 }
86}
87
88impl std::error::Error for ScalarSignatureError {}
89
90impl From<BabyJubError> for ScalarSignatureError {
91 fn from(value: BabyJubError) -> Self {
92 Self::InvalidKey(value)
93 }
94}
95
96impl ScalarSigningKey {
97 pub fn from_secret(secret: BabyJubSecretScalar) -> Self {
98 let public = public_key_from_scalar(&secret);
99 Self { secret, public }
100 }
101
102 pub fn from_decimal(value: &str) -> Result<Self, ScalarSignatureError> {
103 Ok(Self::from_secret(BabyJubSecretScalar::try_from_dec(value)?))
104 }
105
106 pub fn from_le_bytes(bytes: [u8; 32]) -> Result<Self, ScalarSignatureError> {
107 Ok(Self::from_secret(BabyJubSecretScalar::try_from_le_bytes(
108 bytes,
109 )?))
110 }
111
112 #[inline]
113 pub fn verifying_key(&self) -> &BabyJubPoint {
114 &self.public
115 }
116
117 pub fn sign_curvy_v1(&self, message: Bn254Fr) -> Result<ScalarSignature, ScalarSignatureError> {
118 sign_scalar_compat(message, &self.secret, &self.public)
119 }
120}
121
122fn prune_buffer(mut b: [u8; 32]) -> [u8; 32] {
125 b[0] &= 0xf8;
126 b[31] &= 0x7f;
127 b[31] |= 0x40;
128 b
129}
130
131fn pruned_scalar_buffer(private_key: &[u8]) -> [u8; 32] {
133 let hash = blake512(private_key);
134 let mut h32 = [0u8; 32];
135 h32.copy_from_slice(&hash[0..32]);
136 prune_buffer(h32)
137}
138
139pub fn derive_secret_scalar(private_key: &[u8]) -> BigUint {
141 let pruned = pruned_scalar_buffer(private_key);
142 (le_bytes_to_biguint(&pruned) >> 3u32) % &*SUB_ORDER
143}
144
145pub fn derive_public_key(private_key: &[u8]) -> Point {
147 mul_point_escalar(*BASE8, &derive_secret_scalar(private_key))
148}
149
150pub fn pub_from_private_key_hex(hex: &str) -> Point {
153 derive_public_key(&from_hex(hex))
154}
155
156pub fn ephemeral_pub_key(scalar: &BigUint) -> Point {
158 mul_point_escalar(*BASE8, scalar)
159}
160
161pub fn sign(message: &BigUint, private_key: &[u8]) -> Signature {
169 let hash = blake512(private_key);
170
171 let mut h32 = [0u8; 32];
172 h32.copy_from_slice(&hash[0..32]);
173 let s = le_bytes_to_biguint(&prune_buffer(h32)); let a = mul_point_escalar(*BASE8, &(&s >> 3u32));
175
176 let msg_buff = biguint_to_le_bytes(message, 32);
178 let mut compose = Vec::with_capacity(64);
179 compose.extend_from_slice(&hash[32..64]);
180 compose.extend_from_slice(&msg_buff);
181 let r = le_bytes_to_biguint(&blake512(&compose)) % &*SUB_ORDER;
182
183 let r8 = mul_point_escalar(*BASE8, &r);
184 let hm = poseidon(&[r8.0, r8.1, a.0, a.1, fr_from_biguint(message)]);
185
186 let s_sig = (r + fr_to_biguint(&hm) * s) % &*SUB_ORDER;
188
189 Signature { r8, s: s_sig }
190}
191
192pub fn sign_hex(message: &BigUint, hex: &str) -> Signature {
194 sign(message, &from_hex(hex))
195}
196
197fn deterministic_scalar_nonce(
198 secret: &BabyJubSecretScalar,
199 public: &BabyJubPoint,
200 message: Bn254Fr,
201) -> Result<BabyJubSecretScalar, ScalarSignatureError> {
202 let key = secret.to_le_32();
203 let ax = Bn254Fr::from_fr(public.x()).to_le_32();
204 let ay = Bn254Fr::from_fr(public.y()).to_le_32();
205 let msg = message.to_le_32();
206
207 for counter in 0..=u32::MAX {
208 let mut mac = HmacSha512::new_from_slice(&key).expect("HMAC accepts a 32-byte key");
209 mac.update(SCALAR_NONCE_LABEL);
210 mac.update(&ax);
211 mac.update(&ay);
212 mac.update(&msg);
213 mac.update(&counter.to_be_bytes());
214 let digest = mac.finalize().into_bytes();
215 let candidate = BigUint::from_bytes_le(&digest);
216 if candidate >= *NONCE_REJECTION_LIMIT {
217 continue;
218 }
219 let reduced = candidate % &*SUB_ORDER;
220 if reduced != BigUint::from(0u8) {
221 return Ok(BabyJubSecretScalar::try_from_biguint(reduced)
222 .expect("nonce is canonical and non-zero"));
223 }
224 }
225 Err(ScalarSignatureError::NonceCounterExhausted)
226}
227
228pub fn sign_scalar_compat(
233 message: Bn254Fr,
234 secret: &BabyJubSecretScalar,
235 public: &BabyJubPoint,
236) -> Result<ScalarSignature, ScalarSignatureError> {
237 let expected_public = public_key_from_scalar(secret);
238 if &expected_public != public {
239 return Err(ScalarSignatureError::InternalVerificationFailed);
240 }
241
242 let nonce = deterministic_scalar_nonce(secret, public, message)?;
243 let r = nonce.to_biguint();
244 let r8 = public_key_from_scalar(&nonce);
245 let h = poseidon(&[r8.x(), r8.y(), public.x(), public.y(), message.into_inner()]);
246 let e = (BigUint::from(8u8) * fr_to_biguint(&h)) % &*SUB_ORDER;
247 let response = (r + e * secret.to_biguint()) % &*SUB_ORDER;
248 let signature = ScalarSignature {
249 r8,
250 s: BabyJubScalar::try_from_biguint(response)
251 .expect("response was reduced modulo subgroup order"),
252 };
253 if !verify_scalar_compat(message, public, &signature) {
254 return Err(ScalarSignatureError::InternalVerificationFailed);
255 }
256 Ok(signature)
257}
258
259pub fn verify_scalar_compat(
262 message: Bn254Fr,
263 public: &BabyJubPoint,
264 signature: &ScalarSignature,
265) -> bool {
266 if public.is_identity() || signature.r8.is_identity() {
267 return false;
268 }
269 let h = poseidon(&[
270 signature.r8.x(),
271 signature.r8.y(),
272 public.x(),
273 public.y(),
274 message.into_inner(),
275 ]);
276 let e = (BigUint::from(8u8) * fr_to_biguint(&h)) % &*SUB_ORDER;
277 let left = mul_point_escalar(*BASE8, signature.s.as_biguint());
278 let right = add_point(
279 signature.r8.as_tuple(),
280 mul_point_escalar(public.as_tuple(), &e),
281 );
282 left == right
283}