1use std::{fmt, sync::LazyLock};
9
10use ark_ff::{AdditiveGroup, Field};
11use num_bigint::BigUint;
12use zeroize::Zeroize;
13
14use crate::field::{Bn254Fr, Bn254FrError, Fr, fr_from_dec};
15
16pub type Point = (Fr, Fr);
18
19#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct BabyJubScalar(BigUint);
23
24pub struct BabyJubSecretScalar([u8; 32]);
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub struct BabyJubPoint {
35 x: Fr,
36 y: Fr,
37}
38
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub enum BabyJubError {
41 InvalidScalarDecimal,
42 NonCanonicalScalarDecimal,
43 ScalarOutOfRange,
44 ZeroSecretScalar,
45 InvalidCoordinate(Bn254FrError),
46 PointNotOnCurve,
47 PointNotInSubgroup,
48 IdentityPoint,
49}
50
51impl fmt::Display for BabyJubError {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 Self::InvalidScalarDecimal => f.write_str("invalid unsigned decimal BabyJubJub scalar"),
55 Self::NonCanonicalScalarDecimal => {
56 f.write_str("non-canonical decimal BabyJubJub scalar")
57 }
58 Self::ScalarOutOfRange => {
59 f.write_str("BabyJubJub scalar is greater than or equal to the subgroup order")
60 }
61 Self::ZeroSecretScalar => f.write_str("BabyJubJub secret scalar must be non-zero"),
62 Self::InvalidCoordinate(e) => write!(f, "invalid BabyJubJub coordinate: {e}"),
63 Self::PointNotOnCurve => f.write_str("point is not on BabyJubJub"),
64 Self::PointNotInSubgroup => {
65 f.write_str("point is not in the BabyJubJub prime-order subgroup")
66 }
67 Self::IdentityPoint => {
68 f.write_str("BabyJubJub identity is not a valid public or nonce point")
69 }
70 }
71 }
72}
73
74impl std::error::Error for BabyJubError {}
75
76impl From<Bn254FrError> for BabyJubError {
77 fn from(value: Bn254FrError) -> Self {
78 Self::InvalidCoordinate(value)
79 }
80}
81
82const COEFF_A_U64: u64 = 168700;
83const COEFF_D_U64: u64 = 168696;
84
85static COEFF_A: LazyLock<Fr> = LazyLock::new(|| Fr::from(COEFF_A_U64));
86static COEFF_D: LazyLock<Fr> = LazyLock::new(|| Fr::from(COEFF_D_U64));
87
88pub static BASE8: LazyLock<Point> = LazyLock::new(|| {
90 (
91 fr_from_dec("5299619240641551281634865583518297030282874472190772894086521144482721001553"),
92 fr_from_dec(
93 "16950150798460657717958625567821834550301663161624707787222815936182638968203",
94 ),
95 )
96});
97
98pub static SUB_ORDER: LazyLock<BigUint> = LazyLock::new(|| {
101 BigUint::parse_bytes(
102 b"2736030358979909402780800718157159386076813972158567259200215660948447373041",
103 10,
104 )
105 .unwrap()
106});
107
108fn parse_scalar_decimal(s: &str) -> Result<BigUint, BabyJubError> {
109 if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
110 return Err(BabyJubError::InvalidScalarDecimal);
111 }
112 if s.len() > 1 && s.starts_with('0') {
113 return Err(BabyJubError::NonCanonicalScalarDecimal);
114 }
115 BigUint::parse_bytes(s.as_bytes(), 10).ok_or(BabyJubError::InvalidScalarDecimal)
116}
117
118impl BabyJubScalar {
119 pub fn try_from_biguint(value: BigUint) -> Result<Self, BabyJubError> {
121 if value >= *SUB_ORDER {
122 return Err(BabyJubError::ScalarOutOfRange);
123 }
124 Ok(Self(value))
125 }
126
127 pub fn try_from_dec(s: &str) -> Result<Self, BabyJubError> {
128 Self::try_from_biguint(parse_scalar_decimal(s)?)
129 }
130
131 pub fn try_from_le_bytes(bytes: [u8; 32]) -> Result<Self, BabyJubError> {
132 Self::try_from_biguint(BigUint::from_bytes_le(&bytes))
133 }
134
135 #[inline]
136 pub fn as_biguint(&self) -> &BigUint {
137 &self.0
138 }
139
140 pub fn to_le_32(&self) -> [u8; 32] {
141 let bytes = self.0.to_bytes_le();
142 let mut out = [0u8; 32];
143 out[..bytes.len()].copy_from_slice(&bytes);
144 out
145 }
146
147 pub fn to_dec(&self) -> String {
148 self.0.to_str_radix(10)
149 }
150}
151
152impl BabyJubSecretScalar {
153 pub fn try_from_biguint(value: BigUint) -> Result<Self, BabyJubError> {
154 let scalar = BabyJubScalar::try_from_biguint(value)?;
155 if scalar.0 == BigUint::from(0u8) {
156 return Err(BabyJubError::ZeroSecretScalar);
157 }
158 Ok(Self(scalar.to_le_32()))
159 }
160
161 pub fn try_from_dec(s: &str) -> Result<Self, BabyJubError> {
162 Self::try_from_biguint(parse_scalar_decimal(s)?)
163 }
164
165 pub fn try_from_le_bytes(bytes: [u8; 32]) -> Result<Self, BabyJubError> {
166 Self::try_from_biguint(BigUint::from_bytes_le(&bytes))
167 }
168
169 #[inline]
170 pub fn to_le_32(&self) -> [u8; 32] {
171 self.0
172 }
173
174 #[inline]
175 pub(crate) fn to_biguint(&self) -> BigUint {
176 BigUint::from_bytes_le(&self.0)
177 }
178
179 pub fn to_dec(&self) -> String {
180 self.to_biguint().to_str_radix(10)
181 }
182}
183
184impl Drop for BabyJubSecretScalar {
185 fn drop(&mut self) {
186 self.0.zeroize();
187 }
188}
189
190impl BabyJubPoint {
191 pub fn try_from_xy(x: Fr, y: Fr) -> Result<Self, BabyJubError> {
194 let point = (x, y);
195 if !is_on_curve(point) {
196 return Err(BabyJubError::PointNotOnCurve);
197 }
198 if !is_in_subgroup(point) {
199 return Err(BabyJubError::PointNotInSubgroup);
200 }
201 Ok(Self { x, y })
202 }
203
204 pub fn try_from_xy_non_identity(x: Fr, y: Fr) -> Result<Self, BabyJubError> {
205 let point = Self::try_from_xy(x, y)?;
206 if point.is_identity() {
207 return Err(BabyJubError::IdentityPoint);
208 }
209 Ok(point)
210 }
211
212 pub fn try_from_dec(x: &str, y: &str) -> Result<Self, BabyJubError> {
215 let x = Bn254Fr::try_from_dec(x)?.into_inner();
216 let y = Bn254Fr::try_from_dec(y)?.into_inner();
217 Self::try_from_xy_non_identity(x, y)
218 }
219
220 #[inline]
221 pub(crate) fn from_subgroup_non_identity_unchecked(point: Point) -> Self {
222 debug_assert!(is_on_curve(point));
223 debug_assert!(is_in_subgroup(point));
224 debug_assert_ne!(point, identity());
225 Self {
226 x: point.0,
227 y: point.1,
228 }
229 }
230
231 #[inline]
232 pub fn x(&self) -> Fr {
233 self.x
234 }
235
236 #[inline]
237 pub fn y(&self) -> Fr {
238 self.y
239 }
240
241 #[inline]
242 pub fn as_tuple(&self) -> Point {
243 (self.x, self.y)
244 }
245
246 #[inline]
247 pub fn is_identity(&self) -> bool {
248 self.as_tuple() == identity()
249 }
250}
251
252#[inline]
254pub fn identity() -> Point {
255 (Fr::ZERO, Fr::ONE)
256}
257
258pub fn is_on_curve(point: Point) -> bool {
260 let (x, y) = point;
261 let x2 = x * x;
262 let y2 = y * y;
263 *COEFF_A * x2 + y2 == Fr::ONE + *COEFF_D * x2 * y2
264}
265
266pub fn is_in_subgroup(point: Point) -> bool {
268 is_on_curve(point) && mul_point_escalar(point, &SUB_ORDER) == identity()
269}
270
271pub fn add_point(p1: Point, p2: Point) -> Point {
280 let (x1, y1) = p1;
281 let (x2, y2) = p2;
282 let a = *COEFF_A;
283 let d = *COEFF_D;
284
285 let beta = x1 * y2;
286 let gamma = y1 * x2;
287 let delta = (y1 - a * x1) * (x2 + y2);
288 let dtau = d * (beta * gamma);
289
290 let x3 = (beta + gamma)
291 * (Fr::ONE + dtau)
292 .inverse()
293 .expect("babyjubjub: x denominator nonzero");
294 let y3 = (delta + a * beta - gamma)
295 * (Fr::ONE - dtau)
296 .inverse()
297 .expect("babyjubjub: y denominator nonzero");
298 (x3, y3)
299}
300
301pub fn mul_point_escalar(base: Point, e: &BigUint) -> Point {
305 let mut res = identity();
306 let mut exp = base;
307 for i in 0..e.bits() {
308 if e.bit(i) {
309 res = add_point(res, exp);
310 }
311 exp = add_point(exp, exp);
312 }
313 res
314}
315
316pub fn public_key_from_scalar(scalar: &BabyJubSecretScalar) -> BabyJubPoint {
318 BabyJubPoint::from_subgroup_non_identity_unchecked(mul_point_escalar(
319 *BASE8,
320 &scalar.to_biguint(),
321 ))
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use crate::field::FIELD_MODULUS_DEC;
328
329 #[test]
330 fn scalar_boundaries_are_strict() {
331 assert_eq!(
332 BabyJubSecretScalar::try_from_dec("0").err(),
333 Some(BabyJubError::ZeroSecretScalar)
334 );
335 assert_eq!(
336 BabyJubScalar::try_from_dec("00").unwrap_err(),
337 BabyJubError::NonCanonicalScalarDecimal
338 );
339 assert_eq!(
340 BabyJubScalar::try_from_dec(&SUB_ORDER.to_string()).unwrap_err(),
341 BabyJubError::ScalarOutOfRange
342 );
343 assert_eq!(
344 BabyJubSecretScalar::try_from_dec("1").unwrap().to_dec(),
345 "1"
346 );
347 }
348
349 #[test]
350 fn direct_public_key_is_checked() {
351 let one = BabyJubSecretScalar::try_from_dec("1").unwrap();
352 assert_eq!(public_key_from_scalar(&one).as_tuple(), *BASE8);
353 assert!(BabyJubPoint::try_from_xy(BASE8.0, BASE8.1).is_ok());
354 assert_eq!(
355 BabyJubPoint::try_from_xy_non_identity(Fr::ZERO, Fr::ONE),
356 Err(BabyJubError::IdentityPoint)
357 );
358 assert_eq!(
359 BabyJubPoint::try_from_xy(Fr::ZERO, Fr::ZERO),
360 Err(BabyJubError::PointNotOnCurve)
361 );
362 assert!(matches!(
363 BabyJubPoint::try_from_dec(FIELD_MODULUS_DEC, "1"),
364 Err(BabyJubError::InvalidCoordinate(_))
365 ));
366 }
367}