sylow 0.1.1

Implementation of the BLS signature scheme using the alt-bn128 curve.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
//! This module contains the implementations of the š”¾ā‚ group for BN254 elliptic curve.
//!
//! This module defines š”¾ā‚ as the r-torsion subgroup of E(š”½ā‚š), where E is the BN254 elliptic curve
//! over the base field š”½ā‚š. For BN254, the entire curve E(š”½ā‚š) is the r-torsion subgroup, simplifying
//! the implementation as no additional subgroup checks are needed beyond ensuring points are on the curve.
//!
//! Key features:
//! - Affine and projective coordinate representations
//! - Point operations (addition, scalar multiplication, etc.)
//! - Hashing to curve points
//! - Serialization and deserialization of curve points
//!
//! The curve equation is y² = x³ + 3 over š”½ā‚š.
//! The curve has a generator point (1, 2), which is used as the base for scalar multiplication
//! and other operations.

// TODO(Notably missing here is the representation as š”¾ā‚(š”½ā‚šĀ²))
// rather than as projective or affine coordinates

use crate::fields::fp::{FieldExtensionTrait, Fp};
use crate::groups::group::{GroupAffine, GroupError, GroupProjective, GroupTrait};
use crate::hasher::Expander;
use crate::svdw::{MapError, SvdW, SvdWTrait};
use crypto_bigint::rand_core::CryptoRngCore;
use num_traits::{One, Zero};
use std::sync::OnceLock;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};

/// Affine representation of a point in the š”¾ā‚ group
pub type G1Affine = GroupAffine<1, 1, Fp>;

/// Projective representation of a point in the š”¾ā‚ group
pub type G1Projective = GroupProjective<1, 1, Fp>;

/// Static instance of the Shallue-van de Woestijne map for š”¾ā‚ on the BN254 curve
static BN254_SVDW: OnceLock<Result<SvdW, MapError>> = OnceLock::new();

/// Returns the Shallue-van de Woestijne map for š”¾ā‚ on the BN254 curve
///
/// This function initializes the SvdW map if it hasn't been initialized yet,
/// and returns a reference to it.
///
/// # Returns
///
/// A result containing either a reference to the SvdW map or a reference to a [`MapError`]
pub fn get_bn254_svdw() -> Result<&'static SvdW, &'static MapError> {
    BN254_SVDW
        .get_or_init(|| SvdW::precompute_constants(Fp::ZERO, Fp::THREE))
        .as_ref()
}

impl GroupTrait<1, 1, Fp> for G1Affine {
    /// Returns the generator point (1, 2) for the š”¾ā‚ group
    fn generator() -> Self {
        Self {
            x: Fp::ONE,
            y: Fp::TWO,
            infinity: Choice::from(0u8),
        }
    }

    /// Returns the generator point for š”¾ā‚
    ///
    /// Note: The endomorphism is not used for š”¾ā‚, so this just returns the generator
    fn endomorphism(&self) -> Self {
        Self::generator()
    }

    /// Generates a random point in the š”¾ā‚ group
    fn rand<R: CryptoRngCore>(rng: &mut R) -> Self {
        Self::from(G1Projective::rand(rng))
    }

    /// Hashes a message to a point in the š”¾ā‚ group
    fn hash_to_curve<E: Expander>(exp: &E, msg: &[u8]) -> Result<Self, GroupError> {
        match G1Projective::hash_to_curve(exp, msg) {
            Ok(d) => Ok(Self::from(d)),
            Err(e) => Err(e),
        }
    }

    /// Signs a message using a private key and returns a point in the š”¾ā‚ group
    fn sign_message<E: Expander>(exp: &E, msg: &[u8], private_key: Fp) -> Result<Self, GroupError> {
        match G1Projective::sign_message(exp, msg, private_key) {
            Ok(d) => Ok(Self::from(d)),
            Err(e) => Err(e),
        }
    }
}

impl G1Affine {
    /// Instantiates a new element in affine coordinates in š”¾ā‚.
    ///
    /// The input values must pass the curve equation check y² = x³ + 3.
    /// No additional subgroup check is required for š”¾ā‚ in BN254 as the entire curve E(š”½ā‚š) is the r-torsion subgroup.
    ///
    /// # Arguments
    ///
    /// * `v` - An array of two field elements representing the x and y coordinates of the point
    ///
    /// # Returns
    ///
    /// * `Result<Self, GroupError>` - A new point if the coordinates are on the curve, or an error if they're not
    ///
    /// # Examples
    ///
    /// ```
    /// use sylow::*;
    /// let generator = G1Affine::new([Fp::ONE, Fp::TWO]);
    /// ```
    pub fn new(v: [Fp; 2]) -> Result<Self, GroupError> {
        let is_on_curve = {
            let y2 = v[1].square();
            let x2 = v[0].square();
            let lhs = y2 - (x2 * v[0]);
            let rhs = <Fp as FieldExtensionTrait<1, 1>>::curve_constant();
            tracing::trace!(?y2, ?x2, ?lhs, ?rhs, "G1Affine::new");
            lhs.ct_eq(&rhs)
        };

        // every point in G1 on the curve is in the r-torsion of BN254,
        // so we don't need to check for subgroup membership
        tracing::trace!(?is_on_curve, "G1Affine::new");
        match bool::from(is_on_curve) {
            true => Ok(Self {
                x: v[0],
                y: v[1],
                infinity: Choice::from(0u8),
            }),
            false => Err(GroupError::NotOnCurve),
        }
    }

    /// Serializes an element of š”¾ā‚ into uncompressed big-endian form.
    ///
    /// The most significant bit is set if the point is the point at infinity.
    /// Elements in š”¾ā‚ are two elements of š”½ā‚š, so the total byte size of a š”¾ā‚ element is 32 + 32 = 64 bytes.
    ///
    /// # Returns
    ///
    /// * `[u8; 64]` - A 64-byte array representing the point
    ///
    /// # Examples
    ///
    /// ```
    /// use sylow::*;
    ///
    /// let point = G1Affine::generator();
    /// let point_bytes = point.to_be_bytes();
    /// ```
    pub fn to_be_bytes(self) -> [u8; 64] {
        let mut res = [0u8; 64];
        res[0..32].copy_from_slice(
            &Fp::conditional_select(&self.x, &Fp::ZERO, self.infinity).to_be_bytes()[..],
        );
        res[32..64].copy_from_slice(
            &Fp::conditional_select(&self.y, &Fp::ONE, self.infinity).to_be_bytes()[..],
        );
        // we need to set the most significant bit if it's the point at infinity
        // the seven below is to set the most significant bit at index 8 - 1 = 7
        res[0] |= u8::conditional_select(&0u8, &(1u8 << 7), self.infinity);

        res
    }
    /// This returns the big-endian uncompressed bytes of the point, but scrubs the bytes if the
    /// point is at infinity in order to be compatible with the expectation / convention used by
    /// Geth / Reth, despite this not being mathematically rigorously representative of a point
    /// at infinity.
    ///
    /// # Returns
    ///
    /// * `[u8; 64]` - A 64-byte array representing the point
    ///
    /// # Examples
    ///
    /// ```
    /// use sylow::*;
    ///
    /// let point = G1Affine::generator();
    /// let point_bytes = point.to_be_bytes();
    /// ```
    pub fn to_be_bytes_scrubbed(self) -> [u8; 64] {
        let normal_bytes = self.to_be_bytes();
        let zero_bytes = [0u8; 64];

        // Use conditional_select to choose between normal_bytes and zero_bytes
        let mut result = [0u8; 64];
        for i in 0..64 {
            result[i] = u8::conditional_select(&normal_bytes[i], &zero_bytes[i], self.infinity);
        }
        result
    }
    /// Deserializes an element of š”¾ā‚ from an uncompressed big-endian form.
    ///
    /// The most significant bit indicates if the point is at infinity.
    ///
    /// # Arguments
    ///
    /// * `bytes` - A 64-byte array representing the point
    ///
    /// # Returns
    ///
    /// * `CtOption<G1Projective>` - A point on the curve or the point at infinity, if the evaluation is valid
    ///
    /// Note: This returns a G1Projective, as it's the representation used for arithmetic operations.
    ///       We define this method though on the affine representation
    ///       which requires 32 fewer bytes to instantiate for the same point.
    ///
    /// # Examples
    ///
    /// ```
    /// use sylow::*;
    /// let p = G1Affine::generator();
    /// let bytes = p.to_be_bytes();
    /// let p2 = G1Affine::from_be_bytes(&bytes).unwrap();
    /// assert_eq!(p, p2.into(), "Deserialization failed");
    /// ```
    ///
    /// # Notes
    ///
    /// This function deserializes a point from an uncompressed big endian form. The most
    /// significant bit is set if the point is the point at infinity, and therefore must be
    /// explicitly checked to correctly evaluate the bytes.
    pub fn from_be_bytes(bytes: &[u8; 64]) -> CtOption<G1Projective> {
        Self::from_be_bytes_unchecked(bytes).and_then(|p| {
            let infinity_flag = bool::from(p.infinity);
            if infinity_flag {
                CtOption::new(G1Projective::zero(), Choice::from(1u8))
            } else {
                match G1Projective::new([p.x, p.y, Fp::ONE]) {
                    Ok(p) => CtOption::new(p, Choice::from(1u8)),
                    Err(_) => CtOption::new(G1Projective::zero(), Choice::from(0u8)),
                }
            }
        })
    }

    /// This is a helper function to `Self::from_be_bytes` that does the extraction of the
    /// relevant information from the bytes themselves. This function can be thought of as
    /// handling the programmatic aspects of the byte array (correct length, correct evaluation
    /// in terms of field components, etc.), but the other functional requirements on these
    /// bytes, like curve and subgroup membership, are enforced by `Self::from_be_bytes`,
    /// which is why this function is not exposed publicly.
    fn from_be_bytes_unchecked(bytes: &[u8; 64]) -> CtOption<Self> {
        let infinity_flag = Choice::from((bytes[0] >> 7) & 1);

        //try to get the x coord
        let x = {
            let mut tmp = [0u8; 32];
            tmp.copy_from_slice(&bytes[0..32]);

            tmp[0] &= 0b0111_1111; // mask away the flag bit
            Fp::from_be_bytes(&tmp)
        };

        //try to get the y coord
        let y = {
            let mut tmp = [0u8; 32];
            tmp.copy_from_slice(&bytes[32..64]);

            Fp::from_be_bytes(&tmp)
        };
        x.and_then(|x| {
            y.and_then(|y| {
                let p = Self::conditional_select(
                    &G1Affine {
                        x,
                        y,
                        infinity: infinity_flag,
                    },
                    &G1Affine::zero(),
                    infinity_flag,
                );

                let is_some = (!infinity_flag)
                    | (infinity_flag & Choice::from((x.is_zero() & y.is_one()) as u8));
                CtOption::new(p, is_some)
            })
        })
    }
}
impl GroupTrait<1, 1, Fp> for G1Projective {
    /// Returns the generator point for š”¾ā‚ in projective coordinates
    fn generator() -> Self {
        Self::from(G1Affine::generator())
    }

    // Returns the generator point for š”¾ā‚
    ///
    /// Note: The endomorphism is not used for š”¾ā‚, so this just returns the generator
    fn endomorphism(&self) -> Self {
        Self::generator()
    }

    /// Generates a random point in the š”¾ā‚ group
    fn rand<R: CryptoRngCore>(rng: &mut R) -> Self {
        Self::generator() * <Fp as FieldExtensionTrait<1, 1>>::rand(rng)
    }

    /// Hashes a message to a point on the š”¾ā‚ group
    ///
    /// This process involves two steps:
    /// 1. Hash the message to two field elements using the `expand_message` function
    /// 2. Map these field elements to curve points and combine them
    ///
    /// See `hasher.rs` and `svdw.rs` for more details on the underlying algorithms.
    fn hash_to_curve<E: Expander>(exp: &E, msg: &[u8]) -> Result<Self, GroupError> {
        const COUNT: usize = 2;
        const L: usize = 48;
        let scalars = exp
            .hash_to_field(msg, COUNT, L)
            .expect("Hashing to base field failed");
        tracing::trace!(?scalars, "GroupTrait::hash_to_curve");
        match get_bn254_svdw() {
            Ok(bn254_g1_svdw) => {
                let a = G1Projective::from(
                    bn254_g1_svdw
                        .unchecked_map_to_point(scalars[0])
                        .expect("Failed to hash"),
                );
                let b = G1Projective::from(
                    bn254_g1_svdw
                        .unchecked_map_to_point(scalars[1])
                        .expect("Failed to hash"),
                );
                tracing::trace!(?a, ?b, "GroupTrait::hash_to_curve");
                Ok(a + b)
            }
            _ => Err(GroupError::CannotHashToGroup),
        }
    }

    /// Signs a message using a private key in the base field [`Fp`], returning a point on the š”¾ā‚ group
    ///
    /// # Examples
    ///
    /// ```
    /// use sylow::*;
    /// use crypto_bigint::rand_core::OsRng;
    /// use sha3::Keccak256;
    ///
    /// const DST: &[u8; 30] = b"WARLOCK-CHAOS-V01-CS01-SHA-256";
    /// const MSG: &[u8; 4] = &20_i32.to_be_bytes();
    /// const K: u64 = 128;
    ///
    /// let expander = XMDExpander::<Keccak256>::new(DST, K);
    /// let rando = <Fp as FieldExtensionTrait<1, 1>>::rand(&mut OsRng);
    ///
    /// if let Ok(d) = G1Projective::sign_message(&expander, MSG, rando) {
    ///     println!("DST: {:?}", String::from_utf8_lossy(DST));
    ///     println!("Message: {:?}", String::from_utf8_lossy(MSG));
    ///     println!("private key: {:?}", rando.value());
    /// }
    /// ```
    fn sign_message<E: Expander>(exp: &E, msg: &[u8], private_key: Fp) -> Result<Self, GroupError> {
        if let Ok(d) = Self::hash_to_curve(exp, msg) {
            return Ok(d * private_key);
        }
        Err(GroupError::CannotHashToGroup)
    }
}
impl G1Projective {
    /// Instantiates a new element in projective coordinates in š”¾ā‚.
    ///
    /// The input values must pass the curve equation checks in projective form:
    /// Y²Z = X³ + 3Z³
    ///
    /// # Arguments
    ///
    /// * `v` - An array of three field elements representing the X, Y, and Z coordinates of the point
    ///
    /// # Returns
    ///
    /// * `Result<Self, GroupError>` - A new point if the coordinates satisfy the curve equation, or an error if they don't
    ///
    /// # Examples
    ///
    /// ```
    /// use sylow::*;
    /// let generator = G1Projective::new([Fp::ONE, Fp::TWO, Fp::ONE]);
    /// ```
    #[allow(dead_code)]
    pub fn new(v: [Fp; 3]) -> Result<Self, GroupError> {
        let is_on_curve = {
            let y2 = v[1].square();
            let x2 = v[0].square();
            let z2 = v[2].square();
            let lhs = y2 * v[2];
            let rhs = x2 * v[0] + z2 * v[2] * <Fp as FieldExtensionTrait<1, 1>>::curve_constant();
            tracing::trace!(?y2, ?x2, ?z2, ?lhs, ?rhs, "G1Projective::new");
            lhs.ct_eq(&rhs) | Choice::from(v[2].is_zero() as u8)
        };
        tracing::trace!(?is_on_curve, "G1Projective::new");
        match bool::from(is_on_curve) {
            true => Ok(Self {
                x: v[0],
                y: v[1],
                z: v[2],
            }),
            false => Err(GroupError::NotOnCurve),
        }
    }
}

impl<'a> From<&'a [Fp; 2]> for G1Projective {
    /// Converts an array of two field elements (representing affine coordinates) to a projective point.
    ///
    /// # Arguments
    ///
    /// * `value` - A reference to an array of two field elements [x, y]
    ///
    /// # Returns
    ///
    /// * `G1Projective` - The corresponding point in projective coordinates
    ///
    /// # Panics
    ///
    /// If the affine coordinates do not represent a valid point on the curve.
    fn from(value: &'a [Fp; 2]) -> Self {
        G1Affine::new(*value)
            .expect("Conversion to affine failed")
            .into()
    }
}

impl From<[Fp; 2]> for G1Projective {
    /// Converts an array of two field elements (representing affine coordinates) to a projective point.
    ///
    /// This is a convenience wrapper around the implementation of `From<&[Fp; 2]>`.
    ///
    /// # Arguments
    ///
    /// * `value` - An array of two field elements [x, y]
    ///
    /// # Returns
    ///
    /// * `G1Projective` - The corresponding point in projective coordinates
    ///
    /// # Panics
    ///
    /// If the affine coordinates do not represent a valid point on the curve.
    fn from(value: [Fp; 2]) -> Self {
        G1Projective::from(&value)
    }
}