use std::{fmt, sync::LazyLock};
use ark_ff::{AdditiveGroup, Field};
use num_bigint::BigUint;
use zeroize::Zeroize;
use crate::field::{Bn254Fr, Bn254FrError, Fr, fr_from_dec};
pub type Point = (Fr, Fr);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BabyJubScalar(BigUint);
pub struct BabyJubSecretScalar([u8; 32]);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BabyJubPoint {
x: Fr,
y: Fr,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BabyJubError {
InvalidScalarDecimal,
NonCanonicalScalarDecimal,
ScalarOutOfRange,
ZeroSecretScalar,
InvalidCoordinate(Bn254FrError),
PointNotOnCurve,
PointNotInSubgroup,
IdentityPoint,
}
impl fmt::Display for BabyJubError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidScalarDecimal => f.write_str("invalid unsigned decimal BabyJubJub scalar"),
Self::NonCanonicalScalarDecimal => {
f.write_str("non-canonical decimal BabyJubJub scalar")
}
Self::ScalarOutOfRange => {
f.write_str("BabyJubJub scalar is greater than or equal to the subgroup order")
}
Self::ZeroSecretScalar => f.write_str("BabyJubJub secret scalar must be non-zero"),
Self::InvalidCoordinate(e) => write!(f, "invalid BabyJubJub coordinate: {e}"),
Self::PointNotOnCurve => f.write_str("point is not on BabyJubJub"),
Self::PointNotInSubgroup => {
f.write_str("point is not in the BabyJubJub prime-order subgroup")
}
Self::IdentityPoint => {
f.write_str("BabyJubJub identity is not a valid public or nonce point")
}
}
}
}
impl std::error::Error for BabyJubError {}
impl From<Bn254FrError> for BabyJubError {
fn from(value: Bn254FrError) -> Self {
Self::InvalidCoordinate(value)
}
}
const COEFF_A_U64: u64 = 168700;
const COEFF_D_U64: u64 = 168696;
static COEFF_A: LazyLock<Fr> = LazyLock::new(|| Fr::from(COEFF_A_U64));
static COEFF_D: LazyLock<Fr> = LazyLock::new(|| Fr::from(COEFF_D_U64));
pub static BASE8: LazyLock<Point> = LazyLock::new(|| {
(
fr_from_dec("5299619240641551281634865583518297030282874472190772894086521144482721001553"),
fr_from_dec(
"16950150798460657717958625567821834550301663161624707787222815936182638968203",
),
)
});
pub static SUB_ORDER: LazyLock<BigUint> = LazyLock::new(|| {
BigUint::parse_bytes(
b"2736030358979909402780800718157159386076813972158567259200215660948447373041",
10,
)
.unwrap()
});
fn parse_scalar_decimal(s: &str) -> Result<BigUint, BabyJubError> {
if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
return Err(BabyJubError::InvalidScalarDecimal);
}
if s.len() > 1 && s.starts_with('0') {
return Err(BabyJubError::NonCanonicalScalarDecimal);
}
BigUint::parse_bytes(s.as_bytes(), 10).ok_or(BabyJubError::InvalidScalarDecimal)
}
impl BabyJubScalar {
pub fn try_from_biguint(value: BigUint) -> Result<Self, BabyJubError> {
if value >= *SUB_ORDER {
return Err(BabyJubError::ScalarOutOfRange);
}
Ok(Self(value))
}
pub fn try_from_dec(s: &str) -> Result<Self, BabyJubError> {
Self::try_from_biguint(parse_scalar_decimal(s)?)
}
pub fn try_from_le_bytes(bytes: [u8; 32]) -> Result<Self, BabyJubError> {
Self::try_from_biguint(BigUint::from_bytes_le(&bytes))
}
#[inline]
pub fn as_biguint(&self) -> &BigUint {
&self.0
}
pub fn to_le_32(&self) -> [u8; 32] {
let bytes = self.0.to_bytes_le();
let mut out = [0u8; 32];
out[..bytes.len()].copy_from_slice(&bytes);
out
}
pub fn to_dec(&self) -> String {
self.0.to_str_radix(10)
}
}
impl BabyJubSecretScalar {
pub fn try_from_biguint(value: BigUint) -> Result<Self, BabyJubError> {
let scalar = BabyJubScalar::try_from_biguint(value)?;
if scalar.0 == BigUint::from(0u8) {
return Err(BabyJubError::ZeroSecretScalar);
}
Ok(Self(scalar.to_le_32()))
}
pub fn try_from_dec(s: &str) -> Result<Self, BabyJubError> {
Self::try_from_biguint(parse_scalar_decimal(s)?)
}
pub fn try_from_le_bytes(bytes: [u8; 32]) -> Result<Self, BabyJubError> {
Self::try_from_biguint(BigUint::from_bytes_le(&bytes))
}
#[inline]
pub fn to_le_32(&self) -> [u8; 32] {
self.0
}
#[inline]
pub(crate) fn to_biguint(&self) -> BigUint {
BigUint::from_bytes_le(&self.0)
}
pub fn to_dec(&self) -> String {
self.to_biguint().to_str_radix(10)
}
}
impl Drop for BabyJubSecretScalar {
fn drop(&mut self) {
self.0.zeroize();
}
}
impl BabyJubPoint {
pub fn try_from_xy(x: Fr, y: Fr) -> Result<Self, BabyJubError> {
let point = (x, y);
if !is_on_curve(point) {
return Err(BabyJubError::PointNotOnCurve);
}
if !is_in_subgroup(point) {
return Err(BabyJubError::PointNotInSubgroup);
}
Ok(Self { x, y })
}
pub fn try_from_xy_non_identity(x: Fr, y: Fr) -> Result<Self, BabyJubError> {
let point = Self::try_from_xy(x, y)?;
if point.is_identity() {
return Err(BabyJubError::IdentityPoint);
}
Ok(point)
}
pub fn try_from_dec(x: &str, y: &str) -> Result<Self, BabyJubError> {
let x = Bn254Fr::try_from_dec(x)?.into_inner();
let y = Bn254Fr::try_from_dec(y)?.into_inner();
Self::try_from_xy_non_identity(x, y)
}
#[inline]
pub(crate) fn from_subgroup_non_identity_unchecked(point: Point) -> Self {
debug_assert!(is_on_curve(point));
debug_assert!(is_in_subgroup(point));
debug_assert_ne!(point, identity());
Self {
x: point.0,
y: point.1,
}
}
#[inline]
pub fn x(&self) -> Fr {
self.x
}
#[inline]
pub fn y(&self) -> Fr {
self.y
}
#[inline]
pub fn as_tuple(&self) -> Point {
(self.x, self.y)
}
#[inline]
pub fn is_identity(&self) -> bool {
self.as_tuple() == identity()
}
}
#[inline]
pub fn identity() -> Point {
(Fr::ZERO, Fr::ONE)
}
pub fn is_on_curve(point: Point) -> bool {
let (x, y) = point;
let x2 = x * x;
let y2 = y * y;
*COEFF_A * x2 + y2 == Fr::ONE + *COEFF_D * x2 * y2
}
pub fn is_in_subgroup(point: Point) -> bool {
is_on_curve(point) && mul_point_escalar(point, &SUB_ORDER) == identity()
}
pub fn add_point(p1: Point, p2: Point) -> Point {
let (x1, y1) = p1;
let (x2, y2) = p2;
let a = *COEFF_A;
let d = *COEFF_D;
let beta = x1 * y2;
let gamma = y1 * x2;
let delta = (y1 - a * x1) * (x2 + y2);
let dtau = d * (beta * gamma);
let x3 = (beta + gamma)
* (Fr::ONE + dtau)
.inverse()
.expect("babyjubjub: x denominator nonzero");
let y3 = (delta + a * beta - gamma)
* (Fr::ONE - dtau)
.inverse()
.expect("babyjubjub: y denominator nonzero");
(x3, y3)
}
pub fn mul_point_escalar(base: Point, e: &BigUint) -> Point {
let mut res = identity();
let mut exp = base;
for i in 0..e.bits() {
if e.bit(i) {
res = add_point(res, exp);
}
exp = add_point(exp, exp);
}
res
}
pub fn public_key_from_scalar(scalar: &BabyJubSecretScalar) -> BabyJubPoint {
BabyJubPoint::from_subgroup_non_identity_unchecked(mul_point_escalar(
*BASE8,
&scalar.to_biguint(),
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::field::FIELD_MODULUS_DEC;
#[test]
fn scalar_boundaries_are_strict() {
assert_eq!(
BabyJubSecretScalar::try_from_dec("0").err(),
Some(BabyJubError::ZeroSecretScalar)
);
assert_eq!(
BabyJubScalar::try_from_dec("00").unwrap_err(),
BabyJubError::NonCanonicalScalarDecimal
);
assert_eq!(
BabyJubScalar::try_from_dec(&SUB_ORDER.to_string()).unwrap_err(),
BabyJubError::ScalarOutOfRange
);
assert_eq!(
BabyJubSecretScalar::try_from_dec("1").unwrap().to_dec(),
"1"
);
}
#[test]
fn direct_public_key_is_checked() {
let one = BabyJubSecretScalar::try_from_dec("1").unwrap();
assert_eq!(public_key_from_scalar(&one).as_tuple(), *BASE8);
assert!(BabyJubPoint::try_from_xy(BASE8.0, BASE8.1).is_ok());
assert_eq!(
BabyJubPoint::try_from_xy_non_identity(Fr::ZERO, Fr::ONE),
Err(BabyJubError::IdentityPoint)
);
assert_eq!(
BabyJubPoint::try_from_xy(Fr::ZERO, Fr::ZERO),
Err(BabyJubError::PointNotOnCurve)
);
assert!(matches!(
BabyJubPoint::try_from_dec(FIELD_MODULUS_DEC, "1"),
Err(BabyJubError::InvalidCoordinate(_))
));
}
}