Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…into mike/dkg-tests
  • Loading branch information
merolish committed Aug 20, 2024
2 parents 54dbfcb + cb50b5a commit c0e15c0
Show file tree
Hide file tree
Showing 19 changed files with 503 additions and 241 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ name = "dkg"
path = "examples/dkg.rs"

[dependencies]
hex-literal = "0.4.1"
num-traits = "0.2.19"
subtle = "2.6.1"
crypto-bigint = "0.6.0-rc.2"
sha3 = "0.11.0-pre.4"
lazy_static = "1.5.0"


[dev-dependencies]
hex-literal = "0.4.1"
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.120"
sha2 = "0.11.0-pre.4"
Expand Down
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# sylow
![Logo](./sylow.png)

[![License](https://img.shields.io/crates/l/sylow)](https://choosealicense.com/licenses/mit/)
[![Crates.io](https://img.shields.io/crates/v/sylow)](https://crates.io/crates/sylow)
[![Docs](https://img.shields.io/crates/v/sylow?color=blue&label=docs)](https://docs.rs/sylow/)
![CI](https://github.com/warlock-labs/sylow/actions/workflows/CI.yml/badge.svg)

sylow is a Rust library implementing the BLS (Boneh-Lynn-Shacham) signature scheme using the alt-bn128 (BN254) elliptic curve. It provides threshold signing capabilities and associated utilities, initially developed for use in the Warlock Chaos product.
Sylow (*ˈsyːlɔv*) is a Rust library providing functionality for signature generation and verification using
the alt-bn128 (BN 254) elliptic curve, initially developed for use in the Warlock Chaos product. It provides a
general finite field implementation, and extends it for usage into groups on the relevant elliptic curves of alt-bn128.

## Features

- Implementation of BLS signatures on the alt-bn128 (BN254) curve
- Support for threshold signatures
- Efficient pairing operations leveraging the alt-bn128 curve's properties
- Utilities for key generation, signing, and verification
- Compatibility with Ethereum's precompiled contracts for alt-bn128 operations
Expand Down Expand Up @@ -45,9 +45,10 @@ For more examples and usage details, see the [API documentation](https://docs.rs

## Core Concepts

- **BLS Signatures**: A signature scheme allowing for signature aggregation and threshold signing.
- **Finite fields**: These serve as the backbone of modern cryptography, allowing for secure signature schemes.
- **alt-bn128 (BN254) Curve**: An elliptic curve with efficient pairing operations, widely used in zkSNARKs and supported by Ethereum precompiles.
- **Threshold Signatures**: A cryptographic primitive allowing a subset of parties to collaboratively sign messages.
- **Optimal ate pairing**: A cryptographic primitive allowing for efficient computation to verify the validity of a
cryptographic signature.

## Performance

Expand All @@ -57,11 +58,9 @@ The alt-bn128 curve is chosen for its efficiency and widespread support, particu

The following features and improvements are planned for future releases:

- [ ] Basic BLS signature implementation
- [ ] Key generation utilities
- [ ] Signature aggregation
- [ ] Threshold signature scheme
- [ ] Optimizations for common operations
- [x] Basic signature implementation
- [x] Key generation utilities
- [x] Optimizations for common operations
- [ ] Extended test suite and benchmarks
- [ ] Support for serialization formats used in blockchain contexts

Expand All @@ -75,6 +74,10 @@ This project is licensed under the [MIT License](https://choosealicense.com/lice

## Contact

This project is maintained by:
- Tristan Britt [[email protected]](mailto:[email protected])
- 0xAlcibiades [[email protected]](mailto:[email protected])

Warlock Labs - [https://github.com/warlock-labs](https://github.com/warlock-labs)

Project Link: [https://github.com/warlock-labs/sylow](https://github.com/warlock-labs/sylow)
28 changes: 28 additions & 0 deletions examples/epoch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! This example shows how to leverage the batch computation of the Miller loops, or otherwise
//! reuse the same G2 element in the pairing in repeated verifications.
use crypto_bigint::rand_core::OsRng;
use subtle::ConstantTimeEq;
use sylow::{
pairing, FieldExtensionTrait, Fp, Fr, G1Affine, G1Projective, G2Projective, GroupTrait,
};

fn main() {
// First, let's generate a shared secret ...
let private_key = Fp::new(Fr::rand(&mut OsRng).value());
// ... and a public key from it, at which we evaluate the coefficients of the Miller loops
let pubkey = (G2Projective::generator() * private_key).precompute();
// Now, imagine we have 10 signatures we wish to verify.
let hashed_msgs: Vec<G1Affine> = (0..10).map(|_| G1Affine::rand(&mut OsRng)).collect();

let signatures: Vec<G1Projective> = hashed_msgs
.iter()
.map(|x| G1Projective::from(x) * private_key)
.collect();
// We can evaluate each of them individually using the precomputed coefficients ...
for (sig, msg) in signatures.iter().zip(hashed_msgs.iter()) {
let lhs = pairing(sig, &G2Projective::generator());
let rhs = pubkey.miller_loop(msg).final_exponentiation();
assert!(bool::from(lhs.ct_eq(&rhs)));
}
println!("All signatures are valid!");
}
4 changes: 4 additions & 0 deletions src/fields/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@ impl<const D: usize, const N: usize, F: FieldExtensionTrait<D, N>> FieldExtensio
/// This is a const constructor that takes a slice of field elements and returns a field extension
/// The usage of the generics means that it is possible to instantiate any representation of
/// an extension need.
/// # Arguments
/// * `c` - a slice of field elements
pub(crate) const fn new(c: &[F; N]) -> Self {
Self(*c)
}
/// There is eventually a need to be able to perform multiplication across different field
/// extensions, and more or less this corresponds to a basic scaling, see
/// <https://eprint.iacr.org/2010/354.pdf>
/// # Arguments
/// * `factor` - a field element that is used to scale the extension element
pub(crate) fn scale(&self, factor: F) -> Self {
let mut i = 0;
let mut retval = [F::zero(); N];
Expand Down
76 changes: 59 additions & 17 deletions src/fields/fp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,15 @@ pub trait FieldExtensionTrait<const D: usize, const N: usize>:
+ Inv<Output = Self>
+ From<u64>
{
// multiplication in a field extension is dictated
// heavily such a value below
/// multiplication in a field extension is dictated heavily such a value below,
/// so we define the quadratic non-residue here
fn quadratic_non_residue() -> Self;

/// generate a random value in the field extension based on the random number generator from
/// `crypto_bigint`
fn rand<R: CryptoRngCore>(rng: &mut R) -> Self;

/// because each extension is directly used in a j-invariant 0 curve, we define the constant
/// of that curve over the extension field. Namely, it is the value `b` in the equation
/// `y^2=x^3+b`.
fn curve_constant() -> Self;
}
/// Visibility settings in rust on macro exports make this seem as not use, even though its
Expand All @@ -107,6 +110,19 @@ where
/// This means that we roll our implementation into a proc macro that
/// provides all the needed functionality.

/// This macro defines a finite prime field and implements various traits and methods for it.
///
/// # Parameters
///
/// * `$wrapper_name`: The name of the wrapper struct for the field.
/// * `$mod_struct`: The name of the modulus struct.
/// * `$output`: The name of the output type for Montgomery form.
/// * `$uint_type`: The underlying unsigned integer type used for the field elements.
/// * `$limbs`: The number of limbs in the underlying unsigned integer type.
/// * `$modulus`: The modulus of the field as a string.
/// * `$degree`: The degree of the field extension.
/// * `$nreps`: The number of elements required for a unique representation of an element in the
/// extension.
#[allow(unused_macros)]
macro_rules! define_finite_prime_field {
($wrapper_name:ident, $mod_struct:ident, $output:ident, $uint_type:ty, $limbs:expr,
Expand All @@ -118,15 +134,24 @@ macro_rules! define_finite_prime_field {
//special struct for const-time arithmetic on montgomery form integers mod p
type $output = crypto_bigint::modular::ConstMontyForm<$mod_struct, { $mod_struct::LIMBS }>;
#[derive(Clone, Debug, Copy)] //to be used in const contexts
/// This is the actual struct that serves as our finite field implementation, containing
/// the modulus of the field, as well as the output type that contains the internal
/// Montgomery arithmetic logic
pub struct $wrapper_name($mod_struct, $output);

impl FinitePrimeField<$limbs, $uint_type, $degree, $nreps> for $wrapper_name {}

impl $wrapper_name {
// builder structure to create elements in the base field of a given value
/// builder structure to create elements in the base field
/// # Arguments
/// * `value` - $uint_type - the value to create the element from
pub const fn new(value: $uint_type) -> Self {
Self($mod_struct, $output::new(&value))
}
/// builder structure to create elements in the base field from a string
/// representation of the value in base 10
/// # Arguments
/// * `value` - &str - the string representation of the value to create the element from
pub fn new_from_str(value: &str) -> Option<Self> {
let ints: Vec<_> = {
let mut acc = Self::zero();
Expand All @@ -150,18 +175,25 @@ macro_rules! define_finite_prime_field {
}
Some(res)
}
// take the element and convert it to "normal" form from montgomery form
/// take the element and convert it to "normal" form from montgomery form
pub const fn value(&self) -> $uint_type {
self.1.retrieve()
}
/// returns the value of the finite field modulus as a $uint_type
pub fn characteristic() -> $uint_type {
<$uint_type>::from($mod_struct::MODULUS.as_nz_ref().get())
}
/// the constant zero in the field
pub const ZERO: Self = Self::new(<$uint_type>::from_words([0x0; 4]));
/// the constant one in the field
pub const ONE: Self = Self::new(<$uint_type>::from_words([0x1, 0x0, 0x0, 0x0]));
/// the constant two in the field
pub const TWO: Self = Self::new(<$uint_type>::from_words([0x2, 0x0, 0x0, 0x0]));
/// the constant three in the field
pub const THREE: Self = Self::new(<$uint_type>::from_words([0x3, 0x0, 0x0, 0x0]));
/// the constant four in the field
pub const FOUR: Self = Self::new(<$uint_type>::from_words([0x4, 0x0, 0x0, 0x0]));
/// the constant nine in the field
pub const NINE: Self = Self::new(<$uint_type>::from_words([0x9, 0x0, 0x0, 0x0]));
}
// we make the base field an extension of the
Expand All @@ -173,6 +205,9 @@ macro_rules! define_finite_prime_field {
// = -1
Self::new((-Self::ONE).1.retrieve())
}
/// Generate a random value in the field
/// # Arguments
/// * `rng` - R: CryptoRngCore - the random number generator to use
fn rand<R: CryptoRngCore>(rng: &mut R) -> Self {
Self::new(<$uint_type>::random_mod(
rng,
Expand All @@ -187,6 +222,8 @@ macro_rules! define_finite_prime_field {
}
}
impl From<u64> for $wrapper_name {
// many often there is a need to create a simple value like `3` in the base field,
// which is what this accomplishes
fn from(value: u64) -> Self {
Self($mod_struct, $output::new(&<$uint_type>::from_u64(value)))
}
Expand Down Expand Up @@ -401,37 +438,42 @@ impl From<Fr> for Fp {
}
}
impl Fp {
/// This determines the frobenius mapping of the element in the base field, aka x^p. This
/// function is inherently expensive, and we never call it on the base field, but if
/// we did, it's only defined for p=1. Specialized versions exist for all extensions which
/// will require the frobenius transformation.
pub fn frobenius(&self, exponent: usize) -> Self {
// this function is inherently expensive, and we never call it on the base field, but if
// we did, it's only defined for p=1. Specialized versions exist for all extensions which
// will require the frobenius transformation
match exponent {
1 => self.pow(BN254_FP_MODULUS.value()),
_ => *self,
}
}
/// This is an instantiation of Shank's algorithm, which solves congruences of
/// the form $r^2\equiv n \mod p$, namely the sqrt of n. It does not work for
/// composite moduli (aka non-prime p), since that is the integer factorization
/// problem. The full algorithm is not necessary here, and has the additional
/// simplification that we can exploit in our case. Namely, the BN254 curve has a
/// prime that is congruent to 3 mod 4. In this case, the sqrt only has the
/// possible solution of $\pm pow(n, \frac{p+1}{4})$, which is where this magic
/// number below comes from ;)
pub fn sqrt(&self) -> CtOption<Self> {
// This is an instantiation of Shank's algorithm, which solves congruences of
// the form $r^2\equiv n \mod p$, namely the sqrt of n. It does not work for
// composite moduli (aka nonprime p), since that is the integer factorization
// problem. The full algorithm is not necessary here, and has the additional
// simpication that we can exploit in our case. Namely, the BN254 curve has a
// prime that is congruent to 3 mod 4. In this case, the sqrt only has the
// possible solution of $\pm pow(n, \frac{p+1}{4})$, which is where this magic
// number below comes from ;)
let arg = ((Self::new(Self::characteristic()) + Self::one()) / Self::from(4)).value();
let sqrt = self.pow(arg);
CtOption::new(sqrt, sqrt.square().ct_eq(self))
}
/// Returns the square of the element in the base field
pub fn square(&self) -> Self {
(*self) * (*self)
}
/// Determines if the element in the base field is a square of another element
pub fn is_square(&self) -> Choice {
let p_minus_1_div_2 =
((Self::new(Self::characteristic()) - Self::from(1)) / Self::from(2)).value();
let retval = self.pow(p_minus_1_div_2);
Choice::from((retval == Self::zero() || retval == Self::one()) as u8)
}
/// Determines the 'sign' of a value in the base field,
/// see <https://datatracker.ietf.org/doc/html/rfc9380#section-4.1> for more details
pub fn sgn0(&self) -> Choice {
let a = *self % Self::from(2u64);
if a.is_zero() {
Expand Down
16 changes: 13 additions & 3 deletions src/fields/fp12.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! we likewise define the specifics of the dodectic extension of
//! We likewise define the specifics of the dodectic extension of
//! bn254 here, defined by the tower F_{p^{12}} = F_{p^6}(w) / (w^2 - v)
//! Now, there is some flexibility in how we define this. Why?
//! Well, we can either represent an element of F_{p^{12}} as 2 elements
Expand Down Expand Up @@ -189,15 +189,16 @@ impl FieldExtensionTrait<12, 2> for Fp12 {
])
}
fn curve_constant() -> Self {
unimplemented!()
Self::from(3)
}
}

impl<'a, 'b> Mul<&'b Fp12> for &'a Fp12 {
type Output = Fp12;
fn mul(self, other: &'b Fp12) -> Self::Output {
// this is again simple Karatsuba multiplication
// see comments in Fp2 impl of `Mul` trait
// see comments in Fp2 impl of `Mul` trait, or otherwise see Alg 20 of
// <https://eprint.iacr.org/2010/354.pdf>
let t0 = self.0[0] * other.0[0];
let t1 = self.0[1] * other.0[1];

Expand All @@ -221,6 +222,7 @@ impl MulAssign for Fp12 {
impl Inv for Fp12 {
type Output = Self;
fn inv(self) -> Self::Output {
// Implements Alg 23 of <https://eprint.iacr.org/2010/354.pdf>
let tmp = (self.0[0].square() - (self.0[1].square().residue_mul())).inv();
Self([self.0[0] * tmp, -(self.0[1] * tmp)])
}
Expand Down Expand Up @@ -287,6 +289,14 @@ impl Fp12 {
/// The function below is called by `zcash`, `bn`, and `arkworks` as `mul_by_024`, referring to
/// the indices of the non-zero elements in the 6x Fp2 representation above for the
/// multiplication.
///
/// # Arguments
/// * `ell_0` - Fp2, the first entry of the sparse element
/// * `ell_vw` - Fp2, the second entry of the sparse element
/// * `ell_vv` - Fp2, the third entry of the sparse element
///
/// # Returns
/// * A dense Fp12 element
pub(crate) fn sparse_mul(&self, ell_0: Fp2, ell_vw: Fp2, ell_vv: Fp2) -> Fp12 {
let z0 = self.0[0].0[0];
let z1 = self.0[0].0[1];
Expand Down
10 changes: 10 additions & 0 deletions src/fields/fp2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ pub type Fp2 = FieldExtension<2, 2, Fp>;
// helper functions for us on this specific extension, but
// don't generalize to any extension.
impl Fp2 {
/// A simple square and multipy algorithm for exponentiation
/// # Arguments
/// * `by` - Fp, the exponent to raise the element to
///
/// Note that the argument is required to be an element of the base field, and the expansion
/// of this element via `to_words()` always returns &[u64; 4], which lets this run constant time
/// for any field element.
pub fn pow(&self, by: &Fp) -> Self {
let bits = by.value().to_words();
let mut res = Self::one();
Expand All @@ -68,6 +75,9 @@ impl Fp2 {
pub(crate) fn residue_mul(&self) -> Self {
self * &FP2_QUADRATIC_NON_RESIDUE
}
/// Frobenius mapping of a quadratic extension element to a given power
/// # Arguments
/// * `exponent` - usize, the power to raise the element to
pub fn frobenius(&self, exponent: usize) -> Self {
let frobenius_coeff_fp2: &[Fp; 2] = &[
// Fp::quadratic_non_residue()**(((p^0) - 1) / 2)
Expand Down
1 change: 1 addition & 0 deletions src/fields/fp6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ impl MulAssign for Fp6 {
impl Inv for Fp6 {
type Output = Self;
fn inv(self) -> Self::Output {
// Implements a low-overhead version of Alg 17 of <https://eprint.iacr.org/2010/354.pdf>
let t0 = self.0[0].square() - self.0[1] * self.0[2].residue_mul();
let t1 = self.0[2].square().residue_mul() - self.0[0] * self.0[1];
let t2 = self.0[1].square() - self.0[0] * self.0[2];
Expand Down
11 changes: 11 additions & 0 deletions src/fields/utils.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
use crypto_bigint::{Encoding, U256, U512};

/// This function is used to convert a smaller byte array to a larger one
/// It's mainly useful for upcasting arithmetic. For example, in order to compute p^2 in
/// non-modular arithmetic, having p as a U256 will cause overflow in p^2, so we up-cast it toa
/// U512, and then do the squaring to contain the result. The below simply does this conversion
/// for a given input and output dimension.
/// # Generics
/// * `N` - the size of the input slice
/// * `M` - the size of the output slice
/// # Arguments
/// * `smaller_bytes` - a slice of bytes that is to be converted to a larger slice
pub(crate) fn to_larger_uint<const N: usize, const M: usize>(smaller_bytes: &[u8; N]) -> [u8; M] {
assert!(M > N, "Target size must be larger than source size");
let mut larger_bytes = [0u8; M];
larger_bytes[M - N..].copy_from_slice(smaller_bytes);
larger_bytes
}

/// A specific instantiation of casting from U256 to U512, used in the hashing operations
// Specific conversion functions
pub(crate) fn u256_to_u512(u256: &U256) -> U512 {
U512::from_be_bytes(to_larger_uint::<32, 64>(&u256.to_be_bytes()))
Expand Down
Loading

0 comments on commit c0e15c0

Please sign in to comment.