Skip to main content

commonware_cryptography/
banderwagon.rs

1use crate::{
2    bls12381::primitives::group::{Scalar, ScalarReadCfg},
3    zk::circuit::{BoolVar, Context, Selector, Var},
4};
5#[cfg(not(feature = "std"))]
6use alloc::vec::Vec;
7use blst::blst_fr;
8use bytes::{Buf, BufMut};
9use commonware_codec::{Error as CodecError, FixedSize, Read, ReadExt, Write};
10use commonware_math::algebra::{
11    msm_naive, Additive, CryptoGroup, Field, HashToGroup, Multiplicative, Object, Random, Ring,
12    Space,
13};
14use commonware_parallel::Strategy;
15use core::{
16    array,
17    ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
18};
19
20/// A scalar exponent for the Banderwagon group [`G`]: an element of the scalar
21/// field `Z/r`, where `r` is the prime group order.
22///
23/// Stored as a canonical little-endian integer in `[0, r)`. The in-circuit
24/// secret is witnessed directly as its [`bits`](F::bits) (`F::BITS` of them,
25/// enough to cover all of `[0, r)`), with no recomposition or canonicity
26/// constraint (see `G::scalar_mul_bits`): the bits simply *are* the exponent.
27/// A witnessed bit pattern is not itself constrained to `[0, r)` and so may
28/// exceed `r`, but that is harmless: every use feeds the bits into a group
29/// scalar multiplication, which is well-defined modulo `r`, so any alias of the
30/// same residue yields the same result.
31#[derive(Clone, Debug, Default, Eq, PartialEq)]
32pub struct F {
33    limbs: [u64; 4],
34}
35
36impl F {
37    /// The bit-width of the modulus `r`; enough bits to represent any element of
38    /// `[0, r)`. See [`F`].
39    const BITS: usize = 253;
40
41    /// The field modulus `r`, the prime order of the Banderwagon group, as
42    /// little-endian 64-bit limbs. Every [`F`] value is kept canonical in
43    /// `[0, r)`, so `PartialEq` (derived limb equality) is correct.
44    const R: [u64; 4] = [
45        0x74fd_06b5_2876_e7e1,
46        0xff8f_8700_7419_0471,
47        0x0cce_7602_0268_7600,
48        0x1cfb_69d4_ca67_5f52,
49    ];
50
51    /// The little-endian bits of the integer, `F::BITS` of them (enough to
52    /// represent any element of `[0, r)`).
53    pub fn bits(&self) -> Vec<bool> {
54        (0..Self::BITS)
55            .map(|i| (self.limbs[i / 64] >> (i % 64)) & 1 == 1)
56            .collect()
57    }
58}
59
60impl Random for F {
61    fn random(mut rng: impl rand_core::CryptoRng) -> Self {
62        // Rejection-sample a uniform element of `[0, r)`. Each candidate is
63        // `F::BITS` random bits (the width of `r`), so it lands in `[0, 2^BITS)`
64        // and is accepted with probability `r / 2^BITS > 0.9`: ~1.1 draws on
65        // average.
66        loop {
67            let mut limbs: [u64; 4] = array::from_fn(|_| rng.next_u64());
68            limbs[3] &= (1u64 << (Self::BITS - 192)) - 1;
69            // Accept iff `limbs < r`, i.e. computing `limbs - r` borrows.
70            let mut borrow = false;
71            for (&x, &r) in limbs.iter().zip(Self::R.iter()) {
72                let (d, b) = x.overflowing_sub(r);
73                let (_, c) = d.overflowing_sub(borrow as u64);
74                borrow = b | c;
75            }
76            if borrow {
77                return Self { limbs };
78            }
79        }
80    }
81}
82
83impl<'a> AddAssign<&'a Self> for F {
84    fn add_assign(&mut self, rhs: &'a Self) {
85        // Add the two canonical `< r` integers limb by limb with carry. The sum
86        // is `< 2r < 2^254`, so it never overflows the 256-bit `limbs`.
87        let mut sum = [0u64; 4];
88        let mut carry = false;
89        for (s, (&x, &y)) in sum.iter_mut().zip(self.limbs.iter().zip(rhs.limbs.iter())) {
90            let (a, b) = x.overflowing_add(y);
91            let (a, c) = a.overflowing_add(carry as u64);
92            *s = a;
93            carry = b | c;
94        }
95        // Reduce: since `sum < 2r`, subtracting `r` at most once is enough.
96        // Compute `sum - r`; a final borrow means `sum < r`, so we keep `sum`.
97        let mut diff = [0u64; 4];
98        let mut borrow = false;
99        for (d, (&s, &r)) in diff.iter_mut().zip(sum.iter().zip(Self::R.iter())) {
100            let (x, b) = s.overflowing_sub(r);
101            let (x, c) = x.overflowing_sub(borrow as u64);
102            *d = x;
103            borrow = b | c;
104        }
105        self.limbs = if borrow { sum } else { diff };
106    }
107}
108
109impl<'a> Add<&'a Self> for F {
110    type Output = Self;
111
112    fn add(mut self, rhs: &'a Self) -> Self::Output {
113        self += rhs;
114        self
115    }
116}
117
118impl Neg for F {
119    type Output = Self;
120
121    fn neg(self) -> Self::Output {
122        // `-0 = 0`; otherwise `r - self`, which lies in `[1, r)` and is canonical.
123        if self.limbs == [0u64; 4] {
124            return self;
125        }
126        let mut diff = [0u64; 4];
127        let mut borrow = false;
128        for (d, (&r, &x)) in diff.iter_mut().zip(Self::R.iter().zip(self.limbs.iter())) {
129            let (v, b) = r.overflowing_sub(x);
130            let (v, c) = v.overflowing_sub(borrow as u64);
131            *d = v;
132            borrow = b | c;
133        }
134        Self { limbs: diff }
135    }
136}
137
138impl<'a> SubAssign<&'a Self> for F {
139    fn sub_assign(&mut self, rhs: &'a Self) {
140        let rhs = -rhs.clone();
141        *self += &rhs;
142    }
143}
144
145impl<'a> Sub<&'a Self> for F {
146    type Output = Self;
147
148    fn sub(mut self, rhs: &'a Self) -> Self::Output {
149        self -= rhs;
150        self
151    }
152}
153
154impl<'a> MulAssign<&'a Self> for F {
155    fn mul_assign(&mut self, rhs: &'a Self) {
156        // `a * b mod r` is `a` added to itself `b` times in the additive group,
157        // which `Additive::scale` computes by double-and-add. Each step reduces
158        // mod `r`, so the result is canonical.
159        //
160        // This is deliberately slow (~380 modular additions per multiply, ~100x
161        // a schoolbook mul) and not constant-time (the loop branches on the bits
162        // of `rhs`). That is fine for our use: the only production caller is the
163        // eVRF signing response `s = e * x + k`, whose cost is dwarfed by the
164        // accompanying `G` scalar multiplication, and whose surrounding native
165        // code is variable-time regardless. To improve when needed: replace with
166        // schoolbook 4x4-limb multiplication into a 512-bit product followed by a
167        // Montgomery (or Barrett) reduction, and make it constant-time if a
168        // caller ever multiplies secret material on a timing-sensitive path.
169        *self = self.scale(&rhs.limbs);
170    }
171}
172
173impl<'a> Mul<&'a Self> for F {
174    type Output = Self;
175
176    fn mul(mut self, rhs: &'a Self) -> Self::Output {
177        self *= rhs;
178        self
179    }
180}
181
182impl Object for F {}
183
184impl Additive for F {
185    fn zero() -> Self {
186        Self { limbs: [0u64; 4] }
187    }
188}
189
190impl Multiplicative for F {}
191
192impl Ring for F {
193    fn one() -> Self {
194        Self {
195            limbs: [1, 0, 0, 0],
196        }
197    }
198}
199
200impl Field for F {
201    fn inv(&self) -> Self {
202        // Fermat: for prime `r`, `a^(r-2) = a^-1` for nonzero `a`, and the
203        // square-and-multiply leaves `0^(r-2) = 0`, matching the trait contract.
204        // `r - 2` only borrows from the lowest limb (`R[0]` ends in `...e1`).
205        self.exp(&[Self::R[0] - 2, Self::R[1], Self::R[2], Self::R[3]])
206    }
207}
208
209impl FixedSize for F {
210    const SIZE: usize = 32;
211}
212
213impl Write for F {
214    fn write(&self, buf: &mut impl BufMut) {
215        // Little-endian canonical integer, one limb at a time.
216        for limb in self.limbs {
217            buf.put_u64_le(limb);
218        }
219    }
220}
221
222impl Read for F {
223    type Cfg = ();
224
225    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
226        let bytes = <[u8; 32]>::read(buf)?;
227        let limbs =
228            array::from_fn(|i| u64::from_le_bytes(bytes[i * 8..i * 8 + 8].try_into().unwrap()));
229        // Reject non-canonical encodings (`>= r`) so the `[0, r)` invariant that
230        // equality and arithmetic rely on holds for values from untrusted input.
231        // Canonical iff computing `limbs - r` borrows.
232        let mut borrow = false;
233        for (&x, &r) in limbs.iter().zip(Self::R.iter()) {
234            let (d, b) = x.overflowing_sub(r);
235            let (_, c) = d.overflowing_sub(borrow as u64);
236            borrow = b | c;
237        }
238        if !borrow {
239            return Err(CodecError::Invalid(
240                "banderwagon::F",
241                "scalar not canonical",
242            ));
243        }
244        Ok(Self { limbs })
245    }
246}
247
248#[cfg(any(test, feature = "arbitrary"))]
249impl arbitrary::Arbitrary<'_> for F {
250    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
251        // Reduce an arbitrary 256-bit integer mod `r` via the additive
252        // double-and-add (`1` scaled by the integer), keeping it canonical.
253        let limbs: [u64; 4] = [
254            u.arbitrary()?,
255            u.arbitrary()?,
256            u.arbitrary()?,
257            u.arbitrary()?,
258        ];
259        Ok(Self::one().scale(&limbs))
260    }
261}
262
263/// The Bandersnatch twisted Edwards `a` coefficient, `-5`, in Montgomery form.
264#[allow(dead_code)]
265const A: Scalar = Scalar(blst_fr {
266    l: [
267        0xffff_fff4_0000_000c,
268        0xece3_b023_ffec_4ff3,
269        0x66b6_2060_7396_203f,
270        0x6f23_d7e5_f361_df62,
271    ],
272});
273
274/// The Bandersnatch twisted Edwards `d` coefficient in Montgomery form.
275#[allow(dead_code)]
276const D: Scalar = Scalar(blst_fr {
277    l: [
278        0xa8dc_ed1b_47a2_c730,
279        0x381c_065a_ad3c_ccc7,
280        0x53ff_52e1_1883_51f8,
281        0x362e_8d63_990f_e940,
282    ],
283});
284
285// Banderwagon group structure:
286//
287// `G` holds a point on the Bandersnatch twisted Edwards curve
288// (`a*x^2 + y^2 = 1 + d*x^2*y^2`, with `a = -5`). The full curve group is not
289// prime order; it is isomorphic to `Z/2 x Z/2 x Z/r` (cofactor 4, `r` the large
290// prime). The cofactor part is the "2-torsion" subgroup `{O, (0,-1), S, S'}`,
291// where `(0,-1)` has order 2 and `S`, `S'` are the two other order-2 points.
292//
293// Banderwagon turns this into a clean prime-order group of size `r` in two steps:
294//
295//   1. Restrict to the subgroup of order `2r`, i.e. the prime-order subgroup
296//      together with its coset by `(0,-1)`. The order-2 points `S`, `S'` (and
297//      anything built from them) are *not* in this subgroup. Membership is
298//      enforced by a "subgroup check": `1 - a*x^2` must be a square in the base
299//      field (equivalently, in projective form, `z^2 - a*x^2`, avoiding an
300//      inversion). This is only needed when decoding an untrusted point; values
301//      produced internally (the generator plus the group law) never leave this
302//      subgroup, so we don't perform it here yet.
303//   2. Quotient that `2r` subgroup by the order-2 subgroup `{O, (0,-1)}`, which
304//      collapses it to size `r`. Concretely this identifies each point `P` with
305//      `P + (0,-1)`; since adding `(0,-1)` maps `(x, y)` to `(-x, -y)`, the two
306//      representatives of every group element are `(x, y)` and `(-x, -y)`.
307//
308// So a single group element has many in-memory representations (any projective
309// scaling of either of its two affine representatives), and equality (below)
310// must see through all of them.
311
312/// Represents a point in the Banderwagon group.
313///
314/// This group is defined over the BLS12-381 [`Scalar`] field.
315/// Because of that, we can efficiently use it in ZK proofs using BLS.
316#[derive(Clone, Debug)]
317pub struct G {
318    // We use a projective representation where xy = tz.
319    x: Scalar,
320    y: Scalar,
321    t: Scalar,
322    z: Scalar,
323}
324
325impl PartialEq for G {
326    fn eq(&self, other: &Self) -> bool {
327        // See the group-structure notes above `struct G`. Two representations are
328        // the same group element iff their affine `x:y` ratios match, i.e.
329        // `x1/z1 * y2/z2 == x2/z2 * y1/z1`. Clearing the `z` factors (they cancel)
330        // gives `x1 * y2 == x2 * y1`. This single check absorbs both sources of
331        // redundancy:
332        //
333        //   - projective scaling `(x,y,t,z)` vs `(λx,λy,λt,λz)`: the `λ`s cancel;
334        //   - the `(0,-1)` quotient `(x,y)` vs `(-x,-y)`: the signs cancel
335        //     (`x * -y == -x * y`).
336        //
337        // (Soundness relies on both points being valid subgroup elements; for
338        // points off the curve or outside the `2r` subgroup this is meaningless.)
339        self.x.clone() * &other.y == other.x.clone() * &self.y
340    }
341}
342
343impl Eq for G {}
344
345impl CryptoGroup for G {
346    type Scalar = F;
347
348    /// Returns the prime-order Bandersnatch generator in extended coordinates.
349    fn generator() -> Self {
350        Self {
351            // x = 0x29c132cc2c0b34c5743711777bbe42f32b79c022ad998465e1e71866a252ae18
352            x: Scalar(blst_fr {
353                l: [
354                    0xec26_27e1_e7ab_47f5,
355                    0x3e63_de48_4f01_aa9c,
356                    0xfe0f_5c3b_5394_6dc4,
357                    0x2d71_920b_aeb2_cfcd,
358                ],
359            }),
360            // y = 0x2a6c669eda123e0f157d8b50badcd586358cad81eee464605e3167b6cc974166
361            y: Scalar(blst_fr {
362                l: [
363                    0x4e30_593e_1895_bd34,
364                    0x156d_738f_32af_be4b,
365                    0x45ef_0b1c_cdeb_75f4,
366                    0x6a7c_ca00_37d2_e71f,
367                ],
368            }),
369            // t = x * y = 0x5e61c8a110562844571f0fdc470ac5ea53e51c121b538d00e2594f7a0d4781ab
370            t: Scalar(blst_fr {
371                l: [
372                    0x5a92_e8f6_97ad_b6b9,
373                    0xf138_8d46_06b1_4609,
374                    0x101c_7836_40a6_4516,
375                    0x1e9a_e707_3cc7_a9fc,
376                ],
377            }),
378            // z = 0x1
379            z: Scalar(blst_fr {
380                l: [
381                    0x0000_0001_ffff_fffe,
382                    0x5884_b7fa_0003_4802,
383                    0x998c_4fef_ecbc_4ff5,
384                    0x1824_b159_acc5_056f,
385                ],
386            }),
387        }
388    }
389}
390
391impl Object for G {}
392
393impl<'a> AddAssign<&'a Self> for G {
394    fn add_assign(&mut self, rhs: &'a Self) {
395        // A bit of a trick to take ownership of the fields.
396        let Self {
397            x: x1,
398            y: y1,
399            t: t1,
400            z: z1,
401        } = core::mem::replace(self, Self::zero());
402        // These are by reference.
403        let Self {
404            x: x2,
405            y: y2,
406            t: t2,
407            z: z2,
408        } = rhs;
409
410        let x1_y2 = x1.clone() * y2;
411        let y1_x2 = y1.clone() * x2;
412        let y1_y2 = y1 * y2;
413        let x1_x2 = x1 * x2;
414        let z1_z2 = z1 * z2;
415        let t1_t2 = t1 * t2;
416
417        let x1_y2_plus_y1_x2 = x1_y2 + &y1_x2;
418        let y1_y2_minus_a_x1_x2 = y1_y2 - &(x1_x2 * &A);
419        let d_t1_t2 = t1_t2 * &D;
420        let z_minus_d_t = z1_z2.clone() - &d_t1_t2;
421        let z_plus_d_t = z1_z2 + &d_t1_t2;
422
423        *self = Self {
424            x: x1_y2_plus_y1_x2.clone() * &z_minus_d_t,
425            y: y1_y2_minus_a_x1_x2.clone() * &z_plus_d_t,
426            t: x1_y2_plus_y1_x2 * &y1_y2_minus_a_x1_x2,
427            z: z_minus_d_t * &z_plus_d_t,
428        }
429    }
430}
431
432impl<'a> Add<&'a Self> for G {
433    type Output = Self;
434
435    fn add(mut self, rhs: &'a Self) -> Self::Output {
436        self += rhs;
437        self
438    }
439}
440
441impl<'a> SubAssign<&'a Self> for G {
442    fn sub_assign(&mut self, rhs: &'a Self) {
443        let rhs = -rhs.clone();
444        *self += &rhs;
445    }
446}
447
448impl<'a> Sub<&'a Self> for G {
449    type Output = Self;
450
451    fn sub(mut self, rhs: &'a Self) -> Self::Output {
452        self -= rhs;
453        self
454    }
455}
456
457impl Neg for G {
458    type Output = Self;
459
460    fn neg(self) -> Self::Output {
461        Self {
462            x: -self.x,
463            y: self.y,
464            t: -self.t,
465            z: self.z,
466        }
467    }
468}
469
470impl Additive for G {
471    fn zero() -> Self {
472        Self {
473            x: Scalar::zero(),
474            y: Scalar::one(),
475            t: Scalar::zero(),
476            z: Scalar::one(),
477        }
478    }
479
480    fn double(&mut self) {
481        let x_sq = {
482            let mut out = self.x.clone();
483            out.square();
484            out
485        };
486
487        let y_sq = {
488            let mut out = self.y.clone();
489            out.square();
490            out
491        };
492
493        let z_sq_twice = {
494            let mut out = self.z.clone();
495            out.square();
496            out.double();
497            out
498        };
499
500        let a_x_sq = x_sq.clone() * &A;
501
502        let x_plus_y_sq = {
503            let mut out = self.x.clone() + &self.y;
504            out.square();
505            out -= &x_sq;
506            out -= &y_sq;
507            out
508        };
509
510        let g = a_x_sq.clone() + &y_sq;
511        let f = g.clone() - &z_sq_twice;
512        let h = a_x_sq - &y_sq;
513
514        self.x = x_plus_y_sq.clone() * &f;
515        self.y = g.clone() * &h;
516        self.t = x_plus_y_sq * &h;
517        self.z = f * &g;
518    }
519}
520
521impl<'a> MulAssign<&'a F> for G {
522    fn mul_assign(&mut self, rhs: &'a F) {
523        *self = self.scale(&rhs.limbs);
524    }
525}
526
527impl<'a> Mul<&'a F> for G {
528    type Output = Self;
529
530    fn mul(mut self, rhs: &'a F) -> Self::Output {
531        self *= rhs;
532        self
533    }
534}
535
536impl Space<F> for G {
537    fn msm(points: &[Self], scalars: &[F], _strategy: &impl Strategy) -> Self {
538        msm_naive(points, scalars)
539    }
540}
541
542// Banderwagon (de)serialization. See the spec for full details:
543// <https://hackmd.io/@6iQDuIePQjyYBqDChYw_jg/BJBNcv9fq> and the implementation
544// notes <https://hackmd.io/wliPP_RMT4emsucVuCqfHA>.
545//
546// A group element is encoded as the single base-field element
547// `u = (X/Z) * Sign(Y/Z)`, written big-endian. Multiplying by `Sign(Y/Z)` makes
548// `P` and its quotient twin `P + (0,-1) = (-X,-Y)` (see notes above `struct G`)
549// serialize to the same bytes, since negating the point negates both `X` and
550// `Y`, leaving `u` unchanged. The sign, subgroup, and square-root helpers used
551// here live on [`Scalar`].
552
553impl Write for G {
554    fn write(&self, buf: &mut impl BufMut) {
555        // Convert to affine `(x, y) = (X/Z, Y/Z)`; `Z != 0` for all elements of
556        // the subgroup we represent.
557        let z_inv = self.z.inv();
558        let x = self.x.clone() * &z_inv;
559        let y = self.y.clone() * &z_inv;
560        // `u = x * Sign(y)`: keep `x` when `y` is the positive (largest)
561        // representative, otherwise negate it.
562        let u = if y.is_positive() { x } else { -x };
563        u.write(buf);
564    }
565}
566
567impl FixedSize for G {
568    const SIZE: usize = Scalar::SIZE;
569}
570
571impl G {
572    /// The affine coordinates `(x/z, y/z)` of this representative.
573    fn affine(&self) -> (Scalar, Scalar) {
574        let z_inv = self.z.inv();
575        (self.x.clone() * &z_inv, self.y.clone() * &z_inv)
576    }
577
578    /// Out-of-circuit squared affine x-coordinate of `[scalar] * self`.
579    ///
580    /// We expose the *squared* abscissa rather than the bare one because the
581    /// quotient identifies `(x, y)` with `(-x, -y)`: a plain x-coordinate flips
582    /// sign between the two representatives, whereas its square is a well-defined
583    /// function of the group element (no representative to agree on, no
584    /// `canonicalize` needed). The native counterpart of
585    /// the in-circuit [`scalar_mul_x_squared`](Self::scalar_mul_x_squared).
586    pub fn scalar_mul_x_squared_base(&self, scalar: &Scalar) -> Scalar {
587        let (mut x, _) = self.scale(&scalar_limbs(scalar)).affine();
588        x.square();
589        x
590    }
591
592    /// Out-of-circuit squared affine x-coordinate of `[x] * self` for an [`F`]
593    /// exponent — the native counterpart of
594    /// [`scalar_mul_x_squared_base`](Self::scalar_mul_x_squared_base), the
595    /// square is representative-independent.
596    pub fn scalar_mul_x_squared_f(&self, x: &F) -> Scalar {
597        let (mut a, _) = (self.clone() * x).affine();
598        a.square();
599        a
600    }
601
602    /// Return the canonical representative of this group element.
603    ///
604    /// A Banderwagon element has two affine representatives `(x, y)` and
605    /// `(-x, -y)`; this picks the one with positive `y` (the same choice
606    /// [`G::from_x`] makes when decoding), so that two in-memory values for the
607    /// same element become bit-identical. Used by [`GVar::constant`] so that a
608    /// point folded into a circuit yields representative-independent constants.
609    fn canonicalize(&self) -> Self {
610        let (x, y) = self.affine();
611        let u = if y.is_positive() { x } else { -x };
612        Self::from_x(u).expect("a valid subgroup element re-derives from its abscissa")
613    }
614
615    /// Recovers the group element whose serialization has abscissa `x`.
616    ///
617    /// Returns `None` if `x` is not the serialization of an element of the
618    /// subgroup we represent (i.e. it fails the subgroup check or lies off the
619    /// curve). This is the shared core of both [`Read`] and
620    /// [`hash_to_group`](HashToGroup::hash_to_group).
621    fn from_x(x: Scalar) -> Option<Self> {
622        let one = Scalar::one();
623        let x_sq = {
624            let mut out = x.clone();
625            out.square();
626            out
627        };
628
629        // `num = 1 - a*x^2`, `den = 1 - d*x^2`, and `y^2 = num / den`.
630        let num = one.clone() - &(x_sq.clone() * &A);
631        let den = one.clone() - &(x_sq * &D);
632
633        // Subgroup check: `1 - a*x^2` must be a square (see notes above `struct G`).
634        if !num.is_square() {
635            return None;
636        }
637
638        // Recover `y` and pick the positive (largest) representative. `sqrt`
639        // returning `None` means `x` is not a valid abscissa (point off-curve).
640        let ratio = num * &den.inv();
641        let mut y = ratio.sqrt()?;
642        if !y.is_positive() {
643            y = -y;
644        }
645
646        let t = x.clone() * &y;
647        Some(Self { x, y, t, z: one })
648    }
649}
650
651impl HashToGroup for G {
652    /// Hashes `(domain_separator, message)` to a Banderwagon point.
653    ///
654    /// Uses try-and-increment: for `counter = 0, 1, 2, ...` we derive a candidate
655    /// abscissa `x = H(domain_separator, message || counter)` and return the
656    /// first one that is a valid subgroup serialization. Because the
657    /// serialization is a bijection between group elements and valid abscissae,
658    /// and each candidate is an independent uniform field element, the result is
659    /// a uniformly random group element whose discrete log w.r.t. the generator
660    /// is unknown.
661    ///
662    /// We deliberately choose this over a constant-time map (e.g. Elligator-2 on
663    /// the Montgomery model of bandersnatch). Try-and-increment is *simpler* — it
664    /// reuses `from_x` and adds no new trusted constants or rational maps — at
665    /// the cost of *not being constant-time*: the number of attempts (~4 on
666    /// average, since roughly 1/4 of field elements are valid abscissae) depends
667    /// on the input.
668    ///
669    /// That tradeoff fits our use case. The intended caller is the Golden DKG
670    /// eVRF, where the hash inputs (public keys and messages) and outputs are
671    /// public and the secret scalar is only applied *afterwards* — so the
672    /// data-dependent timing reveals nothing secret. A future caller that hashes
673    /// secret material would instead need a constant-time map.
674    fn hash_to_group(domain_separator: &[u8], message: &[u8]) -> Self {
675        // `message || counter`, with an 8-byte big-endian counter we overwrite
676        // in place each attempt.
677        let mut data = Vec::with_capacity(message.len() + 8);
678        data.extend_from_slice(message);
679        data.extend_from_slice(&[0u8; 8]);
680        let counter_at = message.len();
681
682        // The loop is unbounded for totality, but is expected to terminate
683        // quickly: each attempt succeeds with probability ~1/4, so the number of
684        // iterations is geometric with mean ~4 and an exponentially small tail.
685        let mut counter: u64 = 0;
686        loop {
687            data[counter_at..].copy_from_slice(&counter.to_be_bytes());
688            // `Scalar::map` is RFC 9380 hash-to-field, giving a uniform abscissa.
689            if let Some(p) = Self::from_x(Scalar::map(domain_separator, &data)) {
690                return p;
691            }
692            counter += 1;
693        }
694    }
695}
696
697impl Read for G {
698    type Cfg = ();
699
700    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
701        let bytes = <[u8; 32]>::read(buf)?;
702        let mut bytes = bytes.as_ref();
703        let x = Scalar::read_cfg(&mut bytes, &ScalarReadCfg::AllowZero)
704            .map_err(|_| CodecError::Invalid("Banderwagon", "x not a canonical field element"))?;
705        Self::from_x(x).ok_or(CodecError::Invalid("Banderwagon", "point not in subgroup"))
706    }
707}
708
709/// An in circuit representation of a banderwagon point.
710#[derive(Clone)]
711pub struct GVar<'ctx> {
712    // We use an affine representation in circuit. Inversions are cheap in circuit,
713    // but expensive out of circuit. This makes the projecive representation less
714    // interesting for us.
715    x: Var<'ctx, Scalar>,
716    y: Var<'ctx, Scalar>,
717}
718
719impl<'ctx> AddAssign<&Self> for GVar<'ctx> {
720    fn add_assign(&mut self, rhs: &Self) {
721        // Complete affine addition law for the twisted Edwards curve
722        // `a*x^2 + y^2 = 1 + d*x^2*y^2` (the same formula as the projective
723        // `AddAssign` for `G`, specialized to `z = 1`):
724        //
725        //   x3 = (x1*y2 + y1*x2) / (1 + d*x1*x2*y1*y2)
726        //   y3 = (y1*y2 - a*x1*x2) / (1 - d*x1*x2*y1*y2)
727        //
728        // We use the standard circuit-optimal arrangement, which costs six
729        // multiplications (the rest are additions and scalings by the fixed
730        // constants `a`, `d`, all linear and free in an R1CS-style backend):
731        //
732        //   1. `A = x1*x2`
733        //   2. `B = y1*y2`
734        //   3. `U = (x1 + y1)*(x2 + y2)`, so `x1*y2 + y1*x2 = U - A - B`
735        //   4. `AB = A*B`, so the shared term `d*x1*x2*y1*y2 = d*AB`
736        //   5,6. the two `/` operations, each a single constraint `q*den == num`
737        //        (see [`Var`]'s `Div`).
738        //
739        // The curve is complete (`a` is a square and `d` is not), so both
740        // denominators are nonzero for every pair of subgroup points and the
741        // divisions never add an unsatisfiable or underconstrained quotient.
742        let Self { x: x1, y: y1 } = core::mem::replace(self, Self::identity());
743        let Self { x: x2, y: y2 } = rhs;
744
745        // Curve constants as native vars; they fold into the circuit as
746        // constants when combined with the (circuit-backed) coordinates.
747        let a = Var::native(A);
748        let d = Var::native(D);
749
750        let x1_x2 = x1.clone() * x2; // A
751        let y1_y2 = y1.clone() * y2; // B
752        let u = (x1 + &y1) * &(x2.clone() + y2); // U
753        let ab = x1_x2.clone() * &y1_y2; // A*B
754        let c = d * &ab; // d*x1*x2*y1*y2
755
756        let x_num = u - &x1_x2 - &y1_y2; // x1*y2 + y1*x2
757        let y_num = y1_y2 - &(a * &x1_x2); // y1*y2 - a*x1*x2
758        let one = Var::one();
759        let x_den = one.clone() + &c; // 1 + d*x1*x2*y1*y2
760        let y_den = one - &c; // 1 - d*x1*x2*y1*y2
761
762        *self = Self {
763            x: x_num / &x_den,
764            y: y_num / &y_den,
765        };
766    }
767}
768
769impl<'ctx> Add<&Self> for GVar<'ctx> {
770    type Output = Self;
771
772    fn add(mut self, rhs: &Self) -> Self::Output {
773        self += rhs;
774        self
775    }
776}
777
778/// Bit-length of the circuit field modulus `p` (`ceil(log2 p) = 255`).
779const SCALAR_BITS: usize = 255;
780
781impl<'ctx> GVar<'ctx> {
782    /// The circuit representation of the group identity, the twisted Edwards
783    /// neutral element `(0, 1)`. The complete addition law treats it as the
784    /// identity, so it is a valid starting accumulator.
785    fn identity() -> Self {
786        Self {
787            x: Var::zero(),
788            y: Var::one(),
789        }
790    }
791
792    /// The in-circuit representation of an out-of-circuit point, as native
793    /// constants (its canonical affine coordinates `(X/Z, Y/Z)`). Folding the
794    /// point in as a constant pins its representative, which is what makes
795    /// reading a coordinate of a derived point well-defined.
796    ///
797    /// The point is `canonicalize`d first, so the embedded
798    /// constants are a function of the *group element*, not of whichever of its
799    /// two affine representatives the caller happens to hold. Two parties folding
800    /// in the same element therefore build byte-identical circuits without having
801    /// to canonicalize their public inputs themselves.
802    pub fn constant(point: &G) -> Self {
803        let (x, y) = point.canonicalize().affine();
804        Self {
805            x: Var::native(x),
806            y: Var::native(y),
807        }
808    }
809
810    /// Select between two points based on `bit`: `on_true` when the bit is `1`,
811    /// `on_false` when it is `0`. Selecting each coordinate independently is
812    /// sound because `bit` is boolean, so the result is exactly one of the two
813    /// (already valid) input points.
814    fn select(bit: &BoolVar<'ctx, Scalar>, on_true: &Self, on_false: &Self) -> Self {
815        Self {
816            x: bit.select(&on_true.x, &on_false.x),
817            y: bit.select(&on_true.y, &on_false.y),
818        }
819    }
820
821    /// Assert (in circuit) that `self` and `other` are the same group element.
822    ///
823    /// Mirrors the out-of-circuit [`PartialEq`] for [`G`]: two representatives
824    /// denote the same quotient element iff `x1 * y2 == x2 * y1`. Constraining
825    /// the affine coordinates directly would be unsound here, because it would
826    /// reject the equally-valid twin representative `(-x, -y)`.
827    pub fn assert_eq(&self, other: &Self) {
828        (self.x.clone() * &other.y).assert_eq(&(other.x.clone() * &self.y));
829    }
830
831    /// Return the squared affine x-coordinate of this circuit point.
832    pub fn x_squared(&self) -> Var<'ctx, Scalar> {
833        self.x.clone() * &self.x
834    }
835
836    /// Multiply by a scalar given as its little-endian bits, via double-and-add.
837    ///
838    /// `cur` holds `[2^i] * self` and `acc` accumulates the conditionally-added
839    /// terms. The complete addition law makes the identity start and the
840    /// doublings (including over leading zero bits) need no special casing.
841    fn mul_bits(self, bits: &[BoolVar<'ctx, Scalar>]) -> Self {
842        let mut acc = Self::identity();
843        let mut cur = self;
844        for bit in bits {
845            let added = acc.clone() + &cur;
846            acc = Self::select(bit, &added, &acc);
847            cur = cur.clone() + &cur;
848        }
849        acc
850    }
851}
852
853/// The canonical little-endian 64-bit limbs of `x`'s integer value in `[0, r)`.
854///
855/// This is the integer [`Additive::scale`] multiplies a point by, and matches
856/// the canonical bit decomposition `to_canonical_bits_le` performs in circuit,
857/// so the out-of-circuit and in-circuit scalar muls agree.
858fn scalar_limbs(x: &Scalar) -> [u64; 4] {
859    let bytes = x.as_blst_scalar().b;
860    array::from_fn(|i| u64::from_le_bytes(bytes[i * 8..i * 8 + 8].try_into().unwrap()))
861}
862
863/// Decompose `x` into its canonical little-endian bits (`SCALAR_BITS` of them).
864///
865/// The bits are fresh witnesses bound to `x` by two constraints:
866///
867///   1. *recomposition*, `sum_i b_i * 2^i == x`, computed in the field;
868///   2. *canonicity*, the integer `sum_i b_i * 2^i < p`.
869///
870/// Both are required. Recomposition alone holds only modulo the field modulus
871/// `p`, so without the range check a prover could supply the non-canonical
872/// alias `x + p`; because the Banderwagon group order does not divide `p`, that
873/// alias scales a point to a *different* result. The canonicity check pins the
874/// decomposition to the unique integer in `[0, p)`.
875pub fn scalar_bits_le<'ctx>(
876    ctx: Context<'ctx, Scalar>,
877    x: &Var<'ctx, Scalar>,
878) -> Vec<BoolVar<'ctx, Scalar>> {
879    // `as_blst_scalar` yields the canonical integer in `[0, p)` as little-endian
880    // bytes, so bit `i` is `(bytes[i / 8] >> (i % 8)) & 1`.
881    let bits: Vec<BoolVar<'ctx, Scalar>> = (0..SCALAR_BITS)
882        .map(|i| {
883            let x = x.clone();
884            BoolVar::witness(ctx, move |v| {
885                (x.value(v).as_blst_scalar().b[i / 8] >> (i % 8)) & 1 == 1
886            })
887        })
888        .collect();
889
890    // Constraint 1: recomposition `sum_i b_i * 2^i == x`. Each `b_i * 2^i` is a
891    // scaling by a constant, so this whole sum is linear (free in the backend).
892    let mut acc = Var::zero();
893    let mut pow = Scalar::one();
894    for bit in &bits {
895        acc += &(bit.var().clone() * &Var::native(pow.clone()));
896        pow.double();
897    }
898    acc.assert_eq(x);
899
900    // Constraint 2: canonicity, `value <= p - 1` (equivalently `value < p`).
901    //
902    // We walk the bits from most to least significant alongside the bits of the
903    // largest canonical value `c = p - 1` (its little-endian bytes, just like
904    // the witness bits above), maintaining `run = "every 1-bit of c so far was
905    // matched by a 1 in value"`. At a 1-bit of `c` the value bit is free, but it
906    // is folded into `run`; at a 0-bit of `c`, if `run` still holds the value
907    // bit must be 0 (otherwise value would exceed `c`). This is the standard
908    // run-folding field-membership check.
909    let c = (-Scalar::one()).as_blst_scalar().b;
910    let mut run = BoolVar::constant(true);
911    for i in (0..SCALAR_BITS).rev() {
912        if (c[i / 8] >> (i % 8)) & 1 == 1 {
913            run = run & bits[i].clone();
914        } else {
915            (run.clone() & bits[i].clone()).assert_eq(&BoolVar::constant(false));
916        }
917    }
918
919    bits
920}
921
922impl G {
923    /// In-circuit scalar multiplication, returning the point `[scalar] * self`.
924    ///
925    /// `self` is an out-of-circuit (public) point. Taking it by value here, and
926    /// folding its canonical affine coordinates in as constants, pins its
927    /// representative; that is what makes a coordinate of the result well-defined
928    /// (a Banderwagon element has two affine representatives `(x, y)` and
929    /// `(-x, -y)`, so a coordinate of a *witnessed* point would be ambiguous).
930    ///
931    /// `scalar` may be a witness; it is canonically decomposed (see
932    /// `to_canonical_bits_le`) and the bits drive a double-and-add. Soundness
933    /// additionally assumes `self` is a valid prime-order subgroup element, as
934    /// produced by this module's constructors.
935    ///
936    /// Use [`GVar::assert_eq`] to bind the result against a known point (e.g. a
937    /// public key); use [`scalar_mul_x_squared`](Self::scalar_mul_x_squared) when
938    /// only a (representative-independent) abscissa is needed.
939    pub(crate) fn scalar_mul<'ctx>(
940        &self,
941        ctx: Context<'ctx, Scalar>,
942        scalar: &Var<'ctx, Scalar>,
943    ) -> GVar<'ctx> {
944        let bits = scalar_bits_le(ctx, scalar);
945        GVar::constant(self).mul_bits(&bits)
946    }
947
948    /// In-circuit fixed-base scalar multiplication for several public bases
949    /// sharing one scalar bit decomposition.
950    ///
951    /// `WINDOW` controls how many bits are selected at a time. Selector
952    /// monomials are built once per window and reused across every base.
953    pub fn scalar_mul_many_in_circuit<'ctx, const WINDOW: usize>(
954        bases: &[Self],
955        bits: &[BoolVar<'ctx, Scalar>],
956    ) -> Vec<GVar<'ctx>> {
957        const {
958            assert!(WINDOW >= 1 && WINDOW <= 8, "window size must be in 1..=8");
959        }
960
961        let mut accs = vec![GVar::identity(); bases.len()];
962        let mut shifted = bases.to_vec();
963        for window in bits.chunks(WINDOW) {
964            let selector = Selector::new(window);
965            for (acc, base) in accs.iter_mut().zip(&shifted) {
966                let (xs, ys): (Vec<_>, Vec<_>) = (0..(1usize << window.len()))
967                    .scan(Self::zero(), |p, _| {
968                        let (x, y) = p.canonicalize().affine();
969                        *p += base;
970                        Some((x, y))
971                    })
972                    .unzip();
973                *acc += &GVar {
974                    x: selector.select_constant(&xs),
975                    y: selector.select_constant(&ys),
976                }
977            }
978            for base in &mut shifted {
979                for _ in 0..window.len() {
980                    base.double();
981                }
982            }
983        }
984        accs
985    }
986
987    /// In-circuit squared affine x-coordinate of `[scalar] * self`.
988    ///
989    /// We expose the *squared* abscissa rather than the bare one because the
990    /// quotient identifies `(x, y)` with `(-x, -y)`: a plain x-coordinate flips
991    /// sign between the two representatives, whereas its square is a well-defined
992    /// function of the group element, so the read agrees regardless of which
993    /// representative a point happens to hold. The in-circuit counterpart of
994    /// [`scalar_mul_x_squared_base`](Self::scalar_mul_x_squared_base).
995    pub fn scalar_mul_x_squared<'ctx>(
996        &self,
997        ctx: Context<'ctx, Scalar>,
998        scalar: &Var<'ctx, Scalar>,
999    ) -> Var<'ctx, Scalar> {
1000        let x = self.scalar_mul(ctx, scalar).x;
1001        x.clone() * &x
1002    }
1003
1004    /// In-circuit `[scalar] * self` where the scalar is given *directly* as its
1005    /// little-endian bits, rather than as a [`Var`] to be decomposed.
1006    ///
1007    /// This is the path for an [`F`] exponent (e.g. the eVRF secret): the bits
1008    /// are the witness, so unlike `scalar_mul` there is no
1009    /// recomposition or canonicity constraint — see [`F`] for why that is sound
1010    /// at `F::BITS` width. The caller must allocate the bits once and reuse the
1011    /// same slice across operations, so a single exponent is bound everywhere.
1012    pub fn scalar_mul_bits<'ctx>(&self, bits: &[BoolVar<'ctx, Scalar>]) -> GVar<'ctx> {
1013        GVar::constant(self).mul_bits(bits)
1014    }
1015}
1016
1017#[cfg(test)]
1018mod tests {
1019    use super::*;
1020    use arbitrary::Unstructured;
1021    use commonware_codec::{DecodeExt, Encode, EncodeFixed};
1022    use commonware_invariants::minifuzz;
1023
1024    fn arbitrary_point(u: &mut Unstructured<'_>) -> arbitrary::Result<G> {
1025        Ok(G::generator()
1026            * &F {
1027                limbs: [u.arbitrary()?, 0, 0, 0],
1028            })
1029    }
1030
1031    #[test]
1032    fn test_field_laws() {
1033        // Exercise the full algebraic suite (additive, multiplicative, ring, and
1034        // field/inverse laws) on `F`.
1035        minifuzz::test(commonware_math::algebra::test_suites::fuzz_field::<F>);
1036    }
1037
1038    #[test]
1039    fn test_random_canonical_and_full_range() {
1040        // `random()` must stay canonical (`< r`) and, unlike the old 252-bit
1041        // cap, cover the whole field: values in `[2^252, r)` (top bit set) must
1042        // occur.
1043        let mut rng = commonware_utils::test_rng();
1044        let mut saw_top_bit = false;
1045        for _ in 0..1000 {
1046            let f = F::random(&mut rng);
1047            // Canonical iff `f - r` borrows.
1048            let mut borrow = false;
1049            for (&x, &r) in f.limbs.iter().zip(F::R.iter()) {
1050                let (d, b) = x.overflowing_sub(r);
1051                let (_, c) = d.overflowing_sub(borrow as u64);
1052                borrow = b | c;
1053            }
1054            assert!(borrow, "random() produced a non-canonical value: {f:?}");
1055            saw_top_bit |= f.bits()[F::BITS - 1];
1056        }
1057        assert!(
1058            saw_top_bit,
1059            "random() never set the top bit; range is capped"
1060        );
1061    }
1062
1063    #[test]
1064    fn test_f_codec_round_trip() {
1065        minifuzz::test(|u| {
1066            let f: F = u.arbitrary()?;
1067            assert_eq!(F::decode(f.encode()).unwrap(), f);
1068            Ok(())
1069        });
1070    }
1071
1072    #[test]
1073    fn test_f_codec_rejects_non_canonical() {
1074        // `r` itself (the modulus) is the smallest non-canonical encoding.
1075        let mut bytes = [0u8; 32];
1076        for (i, limb) in F::R.iter().enumerate() {
1077            bytes[i * 8..i * 8 + 8].copy_from_slice(&limb.to_le_bytes());
1078        }
1079        assert!(F::decode(&bytes[..]).is_err());
1080        assert!(F::decode(&[0xffu8; 32][..]).is_err());
1081    }
1082
1083    #[test]
1084    fn test_field_modulus_matches_group_order() {
1085        // The field modulus `r` must be the order of the group `G`: scaling the
1086        // generator by the integer product `a * b` must agree with scaling it by
1087        // the field product `a * b mod r`. This ties `F::R` to the independently
1088        // verified group law (see the codec test vectors), and would fail for any
1089        // other modulus.
1090        minifuzz::test(|u| {
1091            let a: F = u.arbitrary()?;
1092            let b: F = u.arbitrary()?;
1093            let g = G::generator();
1094            assert_eq!((g.clone() * &a) * &b, g * &(a * &b));
1095            Ok(())
1096        });
1097    }
1098
1099    #[test]
1100    fn test_eq_identity() {
1101        assert_eq!(G::zero(), G::zero());
1102    }
1103
1104    #[test]
1105    fn test_eq_reflexive() {
1106        minifuzz::test(|u| {
1107            let p = arbitrary_point(u)?;
1108            assert_eq!(p, p.clone());
1109            Ok(())
1110        });
1111    }
1112
1113    #[test]
1114    fn test_eq_invariant_under_projective_scaling() {
1115        // Scaling every coordinate by a common nonzero factor yields the same point.
1116        minifuzz::test(|u| {
1117            let p = arbitrary_point(u)?;
1118            let mut lambda: Scalar = u.arbitrary()?;
1119            if lambda == Scalar::zero() {
1120                lambda = Scalar::one();
1121            }
1122            let scaled = G {
1123                x: p.x.clone() * &lambda,
1124                y: p.y.clone() * &lambda,
1125                t: p.t.clone() * &lambda,
1126                z: p.z.clone() * &lambda,
1127            };
1128            assert_eq!(p, scaled);
1129            Ok(())
1130        });
1131    }
1132
1133    #[test]
1134    fn test_eq_invariant_under_two_torsion() {
1135        // Adding the order-2 point (0, -1) maps (x, y) to (-x, -y); `t = xy` is
1136        // unchanged. Banderwagon must treat this as the same group element.
1137        minifuzz::test(|u| {
1138            let p = arbitrary_point(u)?;
1139            let twin = G {
1140                x: -p.x.clone(),
1141                y: -p.y.clone(),
1142                t: p.t.clone(),
1143                z: p.z.clone(),
1144            };
1145            assert_eq!(p, twin);
1146            Ok(())
1147        });
1148    }
1149
1150    #[test]
1151    fn test_neq_distinct_points() {
1152        // `P` and `P + generator` differ by a prime-order element, so they are
1153        // always distinct group elements.
1154        minifuzz::test(|u| {
1155            let p = arbitrary_point(u)?;
1156            assert_ne!(p, p.clone() + &G::generator());
1157            Ok(())
1158        });
1159    }
1160
1161    #[test]
1162    fn test_codec_fixed_size() {
1163        assert_eq!(G::SIZE, 32);
1164    }
1165
1166    #[test]
1167    fn test_codec_round_trip_identity() {
1168        // The identity serializes to all-zero bytes and decodes back to itself.
1169        let encoded = G::zero().encode();
1170        assert_eq!(encoded.as_ref(), &[0u8; 32]);
1171        let decoded = G::decode(encoded).unwrap();
1172        assert_eq!(decoded, G::zero());
1173    }
1174
1175    #[test]
1176    fn test_codec_round_trip_generator() {
1177        let g = G::generator();
1178        let decoded = G::decode(g.encode()).unwrap();
1179        assert_eq!(decoded, g);
1180    }
1181
1182    const TEST_DST: &[u8] = b"COMMONWARE_BANDERWAGON_HASH_TO_CURVE_TEST";
1183
1184    #[test]
1185    fn test_hash_to_group_deterministic() {
1186        // Same inputs always produce the same point; it round-trips through the
1187        // codec (so it really is a valid subgroup element).
1188        let p = G::hash_to_group(TEST_DST, b"hello");
1189        let q = G::hash_to_group(TEST_DST, b"hello");
1190        assert_eq!(p, q);
1191        assert_eq!(G::decode(p.encode()).unwrap(), p);
1192    }
1193
1194    #[test]
1195    fn test_hash_to_group_distinct_messages() {
1196        // Different messages map to different points.
1197        let p = G::hash_to_group(TEST_DST, b"message-a");
1198        let q = G::hash_to_group(TEST_DST, b"message-b");
1199        assert_ne!(p, q);
1200    }
1201
1202    #[test]
1203    fn test_hash_to_group_in_subgroup() {
1204        // Every output must pass the subgroup check, i.e. re-encode/decode.
1205        minifuzz::test(|u| {
1206            let msg: Vec<u8> = u.arbitrary()?;
1207            let p = G::hash_to_group(TEST_DST, &msg);
1208            assert_eq!(G::decode(p.encode()).unwrap(), p);
1209            Ok(())
1210        });
1211    }
1212
1213    #[test]
1214    fn test_hash_to_group_laws() {
1215        minifuzz::test(commonware_math::algebra::test_suites::fuzz_hash_to_group::<G>);
1216    }
1217
1218    #[test]
1219    fn test_codec_fixed_vectors() {
1220        // Official Banderwagon test vectors: the serialization of `G, 2G, 4G,
1221        // 8G, ...` (successive doublings of the generator). Matching these
1222        // confirms interoperability with go-ipa / Ethereum Verkle.
1223        //
1224        // Source: crate-crypto/rust-verkle `banderwagon::element::fixed_test_vectors`.
1225        let expected = [
1226            "4a2c7486fd924882bf02c6908de395122843e3e05264d7991e18e7985dad51e9",
1227            "43aa74ef706605705989e8fd38df46873b7eae5921fbed115ac9d937399ce4d5",
1228            "5e5f550494159f38aa54d2ed7f11a7e93e4968617990445cc93ac8e59808c126",
1229            "0e7e3748db7c5c999a7bcd93d71d671f1f40090423792266f94cb27ca43fce5c",
1230            "14ddaa48820cb6523b9ae5fe9fe257cbbd1f3d598a28e670a40da5d1159d864a",
1231            "6989d1c82b2d05c74b62fb0fbdf8843adae62ff720d370e209a7b84e14548a7d",
1232            "26b8df6fa414bf348a3dc780ea53b70303ce49f3369212dec6fbe4b349b832bf",
1233            "37e46072db18f038f2cc7d3d5b5d1374c0eb86ca46f869d6a95fc2fb092c0d35",
1234            "2c1ce64f26e1c772282a6633fac7ca73067ae820637ce348bb2c8477d228dc7d",
1235            "297ab0f5a8336a7a4e2657ad7a33a66e360fb6e50812d4be3326fab73d6cee07",
1236            "5b285811efa7a965bd6ef5632151ebf399115fcc8f5b9b8083415ce533cc39ce",
1237            "1f939fa2fd457b3effb82b25d3fe8ab965f54015f108f8c09d67e696294ab626",
1238            "3088dcb4d3f4bacd706487648b239e0be3072ed2059d981fe04ce6525af6f1b8",
1239            "35fbc386a16d0227ff8673bc3760ad6b11009f749bb82d4facaea67f58fc60ed",
1240            "00f29b4f3255e318438f0a31e058e4c081085426adb0479f14c64985d0b956e0",
1241            "3fa4384b2fa0ecc3c0582223602921daaa893a97b64bdf94dcaa504e8b7b9e5f",
1242        ];
1243
1244        let mut point = G::generator();
1245        for (i, want) in expected.into_iter().enumerate() {
1246            let encoded = point.encode();
1247            assert_eq!(
1248                commonware_formatting::hex(encoded.as_ref()),
1249                want,
1250                "encoding mismatch at index {i}"
1251            );
1252            // The vectors must also decode back to the same point.
1253            let decoded = G::decode(encoded).unwrap();
1254            assert_eq!(decoded, point, "decode mismatch at index {i}");
1255            point.double();
1256        }
1257    }
1258
1259    #[test]
1260    fn test_codec_round_trip() {
1261        minifuzz::test(|u| {
1262            let p = arbitrary_point(u)?;
1263            let decoded = G::decode(p.encode()).unwrap();
1264            assert_eq!(decoded, p);
1265            Ok(())
1266        });
1267    }
1268
1269    #[test]
1270    fn test_codec_canonical_for_twin() {
1271        // `P` and its quotient twin `(-x, -y)` are the same group element, so
1272        // they must serialize to identical bytes.
1273        minifuzz::test(|u| {
1274            let p = arbitrary_point(u)?;
1275            let twin = G {
1276                x: -p.x.clone(),
1277                y: -p.y.clone(),
1278                t: p.t.clone(),
1279                z: p.z.clone(),
1280            };
1281            assert_eq!(p.encode(), twin.encode());
1282            Ok(())
1283        });
1284    }
1285
1286    #[test]
1287    fn test_codec_canonical_under_projective_scaling() {
1288        // Projective scaling does not change the group element, so the encoding
1289        // must be invariant under it.
1290        minifuzz::test(|u| {
1291            let p = arbitrary_point(u)?;
1292            let mut lambda: Scalar = u.arbitrary()?;
1293            if lambda == Scalar::zero() {
1294                lambda = Scalar::one();
1295            }
1296            let scaled = G {
1297                x: p.x.clone() * &lambda,
1298                y: p.y.clone() * &lambda,
1299                t: p.t.clone() * &lambda,
1300                z: p.z.clone() * &lambda,
1301            };
1302            assert_eq!(p.encode(), scaled.encode());
1303            Ok(())
1304        });
1305    }
1306
1307    #[test]
1308    fn test_decode_rejects_non_canonical_field_element() {
1309        // `r - 1` is the largest valid field element; `r` (and above) must be
1310        // rejected as non-canonical. These are the big-endian bytes of `r`.
1311        let r_bytes: [u8; 32] = [
1312            0x73, 0xed, 0xa7, 0x53, 0x29, 0x9d, 0x7d, 0x48, 0x33, 0x39, 0xd8, 0x08, 0x09, 0xa1,
1313            0xd8, 0x05, 0x53, 0xbd, 0xa4, 0x02, 0xff, 0xfe, 0x5b, 0xfe, 0xff, 0xff, 0xff, 0xff,
1314            0x00, 0x00, 0x00, 0x01,
1315        ];
1316        assert!(G::decode(&r_bytes[..]).is_err());
1317        assert!(G::decode(&[0xffu8; 32][..]).is_err());
1318    }
1319
1320    #[test]
1321    fn test_decode_rejects_off_subgroup() {
1322        // Search for an `x` whose `1 - a*x^2` is a non-square: decoding must fail
1323        // the subgroup check. We expect to find one quickly (~half of all `x`).
1324        minifuzz::test(|u| {
1325            let x: Scalar = u.arbitrary()?;
1326            let x_sq = {
1327                let mut out = x.clone();
1328                out.square();
1329                out
1330            };
1331            let num = Scalar::one() - &(x_sq * &A);
1332            if num != Scalar::zero() && !num.is_square() {
1333                let bytes = x.encode_fixed::<32>();
1334                assert!(G::decode(&bytes[..]).is_err());
1335            }
1336            Ok(())
1337        });
1338    }
1339
1340    /// Build the circuit `[scalar] * base` and assert (in circuit) that it equals
1341    /// `expected`, returning whether the circuit is satisfied. `witness` chooses
1342    /// whether the scalar is a circuit witness (exercising `to_canonical_bits_le`)
1343    /// or a public constant.
1344    ///
1345    /// The check uses the quotient-aware [`GVar::assert_eq`], so `base` and
1346    /// `expected` need not be canonical: either affine representative is accepted.
1347    fn run_scalar_mul(base: &G, scalar: &Scalar, expected: &G, witness: bool) -> bool {
1348        use crate::zk::circuit::build_with_values;
1349
1350        let base = base.clone();
1351        let expected = expected.clone();
1352        let scalar = scalar.clone();
1353        let (valued, _) = build_with_values(move |ctx| {
1354            let s = if witness {
1355                Var::witness(ctx, move |_| scalar)
1356            } else {
1357                Var::constant(ctx, scalar)
1358            };
1359            base.scalar_mul(ctx, &s)
1360                .assert_eq(&GVar::constant(&expected));
1361            Vec::new()
1362        });
1363        valued.is_satisfied()
1364    }
1365
1366    #[test]
1367    fn test_scalar_mul_small() {
1368        let base = G::generator();
1369        for k in [0u64, 1, 2, 3, 4, 7, 8, 15, 16, 1_000_000, u64::MAX] {
1370            let scalar = Scalar::from_u64(k);
1371            let expected = base.scale(&[k, 0, 0, 0]);
1372            assert!(
1373                run_scalar_mul(&base, &scalar, &expected, false),
1374                "constant scalar k={k}"
1375            );
1376            assert!(
1377                run_scalar_mul(&base, &scalar, &expected, true),
1378                "witness scalar k={k}"
1379            );
1380        }
1381    }
1382
1383    #[test]
1384    fn test_scalar_mul_rejects_wrong_result() {
1385        // Sanity: the in-circuit equality must actually constrain the result.
1386        let base = G::generator();
1387        let scalar = Scalar::from_u64(5);
1388        let correct = base.scale(&[5, 0, 0, 0]);
1389        let wrong = correct.clone() + &G::generator();
1390        assert!(run_scalar_mul(&base, &scalar, &correct, true));
1391        assert!(!run_scalar_mul(&base, &scalar, &wrong, true));
1392    }
1393
1394    #[test]
1395    fn test_scalar_mul_random() {
1396        // Random base point and full-width random scalar, exercising the witness
1397        // decomposition (recomposition + canonicity) over the whole bit range.
1398        minifuzz::test(|u| {
1399            let base = arbitrary_point(u)?;
1400            let scalar: Scalar = u.arbitrary()?;
1401            // Scale `base` by the scalar's canonical integer (little-endian
1402            // limbs), matching the integer `scalar_mul` decomposes in circuit.
1403            let bytes = scalar.as_blst_scalar().b;
1404            let limbs: [u64; 4] =
1405                array::from_fn(|i| u64::from_le_bytes(bytes[i * 8..i * 8 + 8].try_into().unwrap()));
1406            let expected = base.scale(&limbs);
1407            assert!(run_scalar_mul(&base, &scalar, &expected, true));
1408            Ok(())
1409        });
1410    }
1411}