add support for RISC Zero cryptographic accelerators

This commit is contained in:
Victor Graf 2024-05-29 17:42:25 -07:00
parent 5312a0311e
commit 385adda1fa
No known key found for this signature in database
14 changed files with 4430 additions and 39 deletions

View file

@ -10,3 +10,10 @@ resolver = "2"
[profile.dev] [profile.dev]
opt-level = 2 opt-level = 2
[patch.crates-io.crypto-bigint]
git = "https://github.com/risc0/RustCrypto-crypto-bigint"
tag = "v0.5.5-risczero.0"
[patch.crates-io.sha2]
git = "https://github.com/risc0/RustCrypto-hashes"
tag = "sha2-v0.10.8-risczero.0"

View file

@ -32,8 +32,6 @@ features = ["serde", "rand_core", "digest", "legacy_compatibility", "group-bits"
[dev-dependencies] [dev-dependencies]
sha2 = { version = "0.10", default-features = false } sha2 = { version = "0.10", default-features = false }
bincode = "1" bincode = "1"
criterion = { version = "0.5", features = ["html_reports"] }
hex = "0.4.2"
rand = "0.8" rand = "0.8"
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
@ -49,18 +47,26 @@ required-features = ["alloc", "rand_core"]
cfg-if = "1" cfg-if = "1"
ff = { version = "0.13", default-features = false, optional = true } ff = { version = "0.13", default-features = false, optional = true }
group = { version = "0.13", default-features = false, optional = true } group = { version = "0.13", default-features = false, optional = true }
hex = "0.4.2"
rand_core = { version = "0.6.4", default-features = false, optional = true } rand_core = { version = "0.6.4", default-features = false, optional = true }
digest = { version = "0.10", default-features = false, optional = true } digest = { version = "0.10", default-features = false, optional = true }
subtle = { version = "2.3.0", default-features = false } subtle = { version = "2.3.0", default-features = false }
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] } serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
zeroize = { version = "1", default-features = false, optional = true } zeroize = { version = "1", default-features = false, optional = true }
[target.'cfg(target_os = "zkvm")'.dependencies]
# Use crypto-bigint v0.5.5, which is overridden with a patch for RISC Zero acceleration.
crypto-bigint = { version = "=0.5.5", default-features = false, features = ["zeroize"] }
[target.'cfg(target_arch = "x86_64")'.dependencies] [target.'cfg(target_arch = "x86_64")'.dependencies]
cpufeatures = "0.2.6" cpufeatures = "0.2.6"
[target.'cfg(curve25519_dalek_backend = "fiat")'.dependencies] [target.'cfg(curve25519_dalek_backend = "fiat")'.dependencies]
fiat-crypto = { version = "0.2.1", default-features = false } fiat-crypto = { version = "0.2.1", default-features = false }
[target.'cfg(not(target_os = "zkvm"))'.dev-dependencies]
criterion = { version = "0.5.1", features = ["html_reports"] }
[features] [features]
default = ["alloc", "precomputed-tables", "zeroize"] default = ["alloc", "precomputed-tables", "zeroize"]
alloc = ["zeroize?/alloc"] alloc = ["zeroize?/alloc"]

View file

@ -383,13 +383,25 @@ impl ProjectivePoint {
let XX = self.X.square(); let XX = self.X.square();
let YY = self.Y.square(); let YY = self.Y.square();
let ZZ2 = self.Z.square2(); let ZZ2 = self.Z.square2();
let X_plus_Y = &self.X + &self.Y;
let X_plus_Y_sq = X_plus_Y.square();
let YY_plus_XX = &YY + &XX; let YY_plus_XX = &YY + &XX;
let YY_minus_XX = &YY - &XX; let YY_minus_XX = &YY - &XX;
cfg_if::cfg_if! {
if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
// According to https://en.wikipedia.org/wiki/Edwards_curve#Doubling,
// (x + y)^2 - x^2 - y^2 is used as an optimization for computing 2xy.
// However, multiplication is faster inside the zkvm so we compute
// 2xy directly instead.
let new_x = &(&FieldElement::TWO * &self.X) * &self.Y;
} else {
let X_plus_Y = &self.X + &self.Y;
let X_plus_Y_sq = X_plus_Y.square();
let new_x = &X_plus_Y_sq - &YY_plus_XX;
}
}
CompletedPoint { CompletedPoint {
X: &X_plus_Y_sq - &YY_plus_XX, X: new_x,
Y: YY_plus_XX, Y: YY_plus_XX,
Z: YY_minus_XX, Z: YY_minus_XX,
T: &ZZ2 - &YY_minus_XX, T: &ZZ2 - &YY_minus_XX,
@ -418,7 +430,14 @@ impl<'a, 'b> Add<&'b ProjectiveNielsPoint> for &'a EdwardsPoint {
let MM = &Y_minus_X * &other.Y_minus_X; let MM = &Y_minus_X * &other.Y_minus_X;
let TT2d = &self.T * &other.T2d; let TT2d = &self.T * &other.T2d;
let ZZ = &self.Z * &other.Z; let ZZ = &self.Z * &other.Z;
cfg_if::cfg_if! {
if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
let ZZ2 = &FieldElement::TWO * &ZZ;
} else {
let ZZ2 = &ZZ + &ZZ; let ZZ2 = &ZZ + &ZZ;
}
}
CompletedPoint { CompletedPoint {
X: &PP - &MM, X: &PP - &MM,
@ -440,7 +459,14 @@ impl<'a, 'b> Sub<&'b ProjectiveNielsPoint> for &'a EdwardsPoint {
let MP = &Y_minus_X * &other.Y_plus_X; let MP = &Y_minus_X * &other.Y_plus_X;
let TT2d = &self.T * &other.T2d; let TT2d = &self.T * &other.T2d;
let ZZ = &self.Z * &other.Z; let ZZ = &self.Z * &other.Z;
cfg_if::cfg_if! {
if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
let ZZ2 = &FieldElement::TWO * &ZZ;
} else {
let ZZ2 = &ZZ + &ZZ; let ZZ2 = &ZZ + &ZZ;
}
}
CompletedPoint { CompletedPoint {
X: &PM - &MP, X: &PM - &MP,
@ -461,7 +487,14 @@ impl<'a, 'b> Add<&'b AffineNielsPoint> for &'a EdwardsPoint {
let PP = &Y_plus_X * &other.y_plus_x; let PP = &Y_plus_X * &other.y_plus_x;
let MM = &Y_minus_X * &other.y_minus_x; let MM = &Y_minus_X * &other.y_minus_x;
let Txy2d = &self.T * &other.xy2d; let Txy2d = &self.T * &other.xy2d;
cfg_if::cfg_if! {
if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
let Z2 = &FieldElement::TWO * &self.Z;
} else {
let Z2 = &self.Z + &self.Z; let Z2 = &self.Z + &self.Z;
}
}
CompletedPoint { CompletedPoint {
X: &PP - &MM, X: &PP - &MM,
@ -482,7 +515,14 @@ impl<'a, 'b> Sub<&'b AffineNielsPoint> for &'a EdwardsPoint {
let PM = &Y_plus_X * &other.y_minus_x; let PM = &Y_plus_X * &other.y_minus_x;
let MP = &Y_minus_X * &other.y_plus_x; let MP = &Y_minus_X * &other.y_plus_x;
let Txy2d = &self.T * &other.xy2d; let Txy2d = &self.T * &other.xy2d;
cfg_if::cfg_if! {
if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
let Z2 = &FieldElement::TWO * &self.Z;
} else {
let Z2 = &self.Z + &self.Z; let Z2 = &self.Z + &self.Z;
}
}
CompletedPoint { CompletedPoint {
X: &PM - &MP, X: &PM - &MP,

View file

@ -31,6 +31,10 @@ cfg_if! {
#[doc(hidden)] #[doc(hidden)]
pub mod fiat_u64; pub mod fiat_u64;
} else if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
pub mod risc0;
} else { } else {
#[cfg(curve25519_dalek_bits = "32")] #[cfg(curve25519_dalek_bits = "32")]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,226 @@
//! Field arithmetic modulo \\(p = 2\^{255} - 19\\), using \\(32\\)-bit
//! limbs
use core::fmt::Debug;
use core::ops::Neg;
use core::ops::{Add, AddAssign};
use core::ops::{Mul, MulAssign};
use core::ops::{Sub, SubAssign};
use crypto_bigint::{risc0, Encoding, U256};
use subtle::{Choice, ConditionallySelectable, ConstantTimeLess};
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
/// A `FieldElementR0` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\). `FieldElementR0`
/// leverages RISC Zero's big integer accelerated zkvm circuit.
///
/// # Note
///
/// The `curve25519_dalek::field` module provides a type alias
/// `curve25519_dalek::field::FieldElement` to either `FieldElement51`,
/// `FieldElement2625` or `FieldElementR0`.
///
/// The backend-specific type `FieldElementR0` should not be used
/// outside of the `curve25519_dalek::field` module.
/// prime 2^255 - 19 which defines the field.
const P: U256 =
U256::from_be_hex("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED");
// Zero minus the modulus, using wrapping 256-bit arithmatic.
// Used for turning an single additive overflow into a reduction.
// Only two words of this value are non-zero.
const MODULUS_CORRECTION: U256 = U256::ZERO.wrapping_sub(&P);
#[derive(Copy, Clone)]
pub struct FieldElementR0(pub(crate) U256);
impl Debug for FieldElementR0 {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
write!(
f,
"FieldElementR0(U256::from_be_hex({:?}))",
hex::encode(&self.0.to_be_bytes())
)
}
}
#[cfg(feature = "zeroize")]
impl Zeroize for FieldElementR0 {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl<'b> AddAssign<&'b FieldElementR0> for FieldElementR0 {
fn add_assign(&mut self, rhs: &'b FieldElementR0) {
let self_limbs = self.0.as_limbs();
let rhs_limbs = rhs.0.as_limbs();
let correction_limbs = MODULUS_CORRECTION.as_limbs();
// Carrying addition of self and rhs, with the overflow correction added in.
// Correction is added to carries with wrapping_add since they cannot overflow.
let (a0, carry0) = self_limbs[0].adc(rhs_limbs[0], correction_limbs[0]);
let (a1, carry1) =
self_limbs[1].adc(rhs_limbs[1], carry0.wrapping_add(correction_limbs[1]));
let (a2, carry2) =
self_limbs[2].adc(rhs_limbs[2], carry1.wrapping_add(correction_limbs[2]));
let (a3, carry3) =
self_limbs[3].adc(rhs_limbs[3], carry2.wrapping_add(correction_limbs[3]));
let (a4, carry4) =
self_limbs[4].adc(rhs_limbs[4], carry3.wrapping_add(correction_limbs[4]));
let (a5, carry5) =
self_limbs[5].adc(rhs_limbs[5], carry4.wrapping_add(correction_limbs[5]));
let (a6, carry6) =
self_limbs[6].adc(rhs_limbs[6], carry5.wrapping_add(correction_limbs[6]));
let (a7, carry7) =
self_limbs[7].adc(rhs_limbs[7], carry6.wrapping_add(correction_limbs[7]));
self.0 = U256::from([a0, a1, a2, a3, a4, a5, a6, a7]);
// If the inputs are not in the range [0, p), then then carry7 may be greater than 1,
// indicating more than one overflow occurred. In this case, the code below will not
// correct the value. If the host is cooperative, this should never happen.
assert!(carry7.0 <= 1);
// If a carry occured, then the correction was already added and the result is correct.
// If a carry did not occur, the correction needs to be removed. Result will be in [0, p).
// Wrap and unwrap to prevent the compiler interpreting this as a boolean, potentially
// introducing non-constant time code.
let mask = 1 - Choice::from(carry7.0 as u8).unwrap_u8();
let c0 = MODULUS_CORRECTION.as_words()[0] * (mask as u32);
let c7 = MODULUS_CORRECTION.as_words()[7] * (mask as u32);
let correction = U256::from_words([c0, 0, 0, 0, 0, 0, 0, c7]);
// The correction value was either already added to a, or is 0, so this sub will not
// underflow.
self.0 = self.0.wrapping_sub(&correction);
}
}
impl<'a, 'b> Add<&'b FieldElementR0> for &'a FieldElementR0 {
type Output = FieldElementR0;
fn add(self, _rhs: &'b FieldElementR0) -> FieldElementR0 {
let mut output = *self;
output += _rhs;
output
}
}
impl<'b> SubAssign<&'b FieldElementR0> for FieldElementR0 {
fn sub_assign(&mut self, _rhs: &'b FieldElementR0) {
self.add_assign(&_rhs.neg());
}
}
impl<'a, 'b> Sub<&'b FieldElementR0> for &'a FieldElementR0 {
type Output = FieldElementR0;
fn sub(self, _rhs: &'b FieldElementR0) -> FieldElementR0 {
let mut output = *self;
output -= _rhs;
output
}
}
impl<'b> MulAssign<&'b FieldElementR0> for FieldElementR0 {
fn mul_assign(&mut self, _rhs: &'b FieldElementR0) {
let result = risc0::modmul_u256_denormalized(&self.0, &_rhs.0, &P);
self.0 = result;
}
}
impl<'a, 'b> Mul<&'b FieldElementR0> for &'a FieldElementR0 {
type Output = FieldElementR0;
fn mul(self, _rhs: &'b FieldElementR0) -> FieldElementR0 {
let mut output = *self;
output *= _rhs;
output
}
}
impl<'a> Neg for &'a FieldElementR0 {
type Output = FieldElementR0;
fn neg(self) -> FieldElementR0 {
let mut output = *self;
output.negate();
output
}
}
impl ConditionallySelectable for FieldElementR0 {
fn conditional_select(
a: &FieldElementR0,
b: &FieldElementR0,
choice: Choice,
) -> FieldElementR0 {
FieldElementR0(U256::conditional_select(&a.0, &b.0, choice))
}
}
impl FieldElementR0 {
/// The scalar \\( 0 \\).
pub const ZERO: FieldElementR0 = FieldElementR0(U256::ZERO);
/// The scalar \\( 1 \\).
pub const ONE: FieldElementR0 = FieldElementR0(U256::ONE);
/// The scalar \\( 2 \\).
pub const TWO: FieldElementR0 = FieldElementR0(U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000002",
));
/// The scalar \\( -1 \\). Set to P - 1
pub const MINUS_ONE: FieldElementR0 = FieldElementR0(U256::from_be_hex(
"7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC",
));
/// Invert the sign of this field element
pub fn negate(&mut self) {
let result = risc0::modmul_u256_denormalized(&self.0, &Self::MINUS_ONE.0, &P);
self.0 = result;
}
/// Given `k > 0`, return `self^(2^k)`.
pub fn pow2k(&self, k: u32) -> FieldElementR0 {
debug_assert!(k > 0);
let mut z = self.square();
for _ in 1..k {
z = z.square();
}
z
}
/// Load a `FieldElementR0` from the low 255 bits of a 256-bit
/// input.
pub fn from_bytes(data: &[u8; 32]) -> FieldElementR0 {
let mut val: U256 = U256::from_le_bytes(*data);
let val_words = val.as_words_mut();
val_words[7] = val_words[7] & 0x7FFFFFFF;
let val = U256::from_words(*val_words);
// Use a modular multiplication by one to reduce the value to [0, p).
let val = risc0::modmul_u256_denormalized(&val, &FieldElementR0::ONE.0, &P);
FieldElementR0(val)
}
/// Serialize this `FieldElementR0` to a 32-byte array. The
/// encoding is canonical.
#[allow(clippy::identity_op)]
pub fn as_bytes(&self) -> [u8; 32] {
// Check that the output is normalized. This will always be the case if the host is
// cooperative.
assert!(self.0.ct_lt(&P).unwrap_u8() == 1);
self.0.to_le_bytes()
}
/// Compute `self^2`.
pub fn square(&self) -> FieldElementR0 {
let result = risc0::modmul_u256_denormalized(&self.0, &self.0, &P);
FieldElementR0(result)
}
/// Compute `2*self^2`.
pub fn square2(&self) -> FieldElementR0 {
let squared = self.square();
let result = risc0::modmul_u256_denormalized(&Self::TWO.0, &squared.0, &P);
FieldElementR0(result)
}
}

View file

@ -0,0 +1,5 @@
pub mod field;
pub mod scalar;
pub mod constants;

View file

@ -0,0 +1,262 @@
//! Arithmetic mod 2^252 + 27742317777372353535851937790883648493
//! with RISC0 Acceleration
use core::fmt::Debug;
use crypto_bigint::{risc0, Encoding, U256};
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
use crate::constants;
/// Multiplicative Inverse of R mod L where R is the Montgomery modulus 2^261
const R_INVERSE: U256 =
U256::from_be_hex("064EDB637937F48C1B0A73AA1C7FD1B5FD934BE6D1D6D67AC7421B8F04C727E2");
/// 2^256 mod L
const TWO_POW_TWO_FIFTY_SIX: U256 =
U256::from_be_hex("0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC6EF5BF4737DCF70D6EC31748D98951D");
/// The `ScalarR0` struct represents an element in \\(\mathbb{Z} / \ell\mathbb{Z}\\)
#[derive(Copy, Clone)]
pub struct ScalarR0(pub U256);
impl Debug for ScalarR0 {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
write!(f, "ScalarR0: {:?}", &self.0)
}
}
#[cfg(feature = "zeroize")]
impl Zeroize for ScalarR0 {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl ScalarR0 {
/// The scalar \\( -1 mod L \\).
pub const MINUS_ONE: ScalarR0 = ScalarR0(U256::from_be_hex(
"1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3EC",
));
/// Unpack a 32 byte / 256 bit scalar.
pub fn from_bytes(bytes: &[u8; 32]) -> ScalarR0 {
ScalarR0(U256::from_le_bytes(*bytes))
}
/// Reduce a 64 byte / 512 bit scalar mod l.
pub fn from_bytes_wide(bytes: &[u8; 64]) -> ScalarR0 {
let lo: U256 = U256::from_le_bytes(
bytes[0..32]
.try_into()
.expect("unable to parse low 32 bytes"),
);
let hi: U256 = U256::from_le_bytes(
bytes[32..]
.try_into()
.expect("unable to parse high 32 bytes"),
);
let hi_shifted_left_256 = risc0::modmul_u256(&hi, &TWO_POW_TWO_FIFTY_SIX, &constants::L.0);
// add_mod assumes the lhs + rhs is less than 2p. To guarantee this, we need to mod
// lo and hi by L
let lo = risc0::modmul_u256(&lo, &U256::ONE, &constants::L.0);
let total = hi_shifted_left_256.add_mod(&lo, &constants::L.0);
ScalarR0(total)
}
/// Pack the limbs of this `ScalarR0` into 32 bytes.
#[allow(clippy::identity_op)]
pub fn as_bytes(&self) -> [u8; 32] {
let val = risc0::modmul_u256(&self.0, &U256::ONE, &constants::L.0);
val.to_le_bytes()
}
/// Compute `a + b` (mod l).
pub fn add(a: &ScalarR0, b: &ScalarR0) -> ScalarR0 {
let result = a.0.add_mod(&b.0, &constants::L.0);
ScalarR0(result)
}
/// Compute `a - b` (mod l).
pub fn sub(a: &ScalarR0, b: &ScalarR0) -> ScalarR0 {
let result = a.0.sub_mod(&b.0, &constants::L.0);
ScalarR0(result)
}
/// Compute `-1 * a` (mod l).
pub fn negate(a: &ScalarR0) -> ScalarR0 {
let result = risc0::modmul_u256(&a.0, &Self::MINUS_ONE.0, &constants::L.0);
ScalarR0(result)
}
/// Compute `a` (mod l).
pub fn reduce(a: &ScalarR0) -> ScalarR0 {
let result = risc0::modmul_u256(&a.0, &U256::ONE, &constants::L.0);
ScalarR0(result)
}
/// Compute `a * b` (mod l).
#[inline(never)]
pub fn mul(a: &ScalarR0, b: &ScalarR0) -> ScalarR0 {
let ab = risc0::modmul_u256(&a.0, &b.0, &constants::L.0);
ScalarR0(ab)
}
/// Compute `a^2` (mod l).
#[inline(never)]
#[allow(dead_code)] // XXX we don't expose square() via the Scalar API
pub fn square(&self) -> ScalarR0 {
let aa = risc0::modmul_u256(&self.0, &self.0, &constants::L.0);
ScalarR0(aa)
}
/// Compute `(a * b) / R` (mod l), where R is the Montgomery modulus 2^261
#[inline(never)]
pub fn montgomery_mul(a: &ScalarR0, b: &ScalarR0) -> ScalarR0 {
let ab = risc0::modmul_u256_denormalized(&a.0, &b.0, &constants::L.0);
let ab_r_inverse = risc0::modmul_u256(&ab, &R_INVERSE, &constants::L.0);
ScalarR0(ab_r_inverse)
}
/// Compute `(a^2) / R` (mod l) in Montgomery form, where R is the Montgomery modulus 2^261
#[inline(never)]
pub fn montgomery_square(&self) -> ScalarR0 {
let squared = risc0::modmul_u256_denormalized(&self.0, &self.0, &constants::L.0);
let squared_r_inverse = risc0::modmul_u256(&squared, &R_INVERSE, &constants::L.0);
ScalarR0(squared_r_inverse)
}
/// Puts a ScalarR0 in to Montgomery form, i.e. computes `a*R (mod l)`
#[inline(never)]
pub fn as_montgomery(&self) -> ScalarR0 {
let result = risc0::modmul_u256(&self.0, &constants::R.0, &constants::L.0);
ScalarR0(result)
}
/// Takes a ScalarR0 out of Montgomery form, i.e. computes `a/R (mod l)`
#[allow(clippy::wrong_self_convention)]
pub fn from_montgomery(&self) -> ScalarR0 {
let a_r_inverse = risc0::modmul_u256(&self.0, &R_INVERSE, &constants::L.0);
ScalarR0(a_r_inverse)
}
}
#[cfg(test)]
mod test {
use super::*;
/// Note: x is 2^253-1 which is slightly larger than the largest scalar produced by
/// this implementation (l-1), and should verify there are no overflows for valid scalars
///
/// x = 2^253-1 = 14474011154664524427946373126085988481658748083205070504932198000989141204991
/// x = 7237005577332262213973186563042994240801631723825162898930247062703686954002 mod l
/// x = 5147078182513738803124273553712992179887200054963030844803268920753008712037*R mod l in Montgomery form
pub static X: ScalarR0 = ScalarR0(U256::from_be_hex(
"0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB2106215D086329A7ED9CE5A30A2C12",
));
/// x^2 = 3078544782642840487852506753550082162405942681916160040940637093560259278169 mod l
pub static XX: ScalarR0 = ScalarR0(U256::from_be_hex(
"06CE65046DF0C268F73BB1CF485FD6F9F38A31531640FFD0EC01668020217559",
));
/// x^2 = 2912514428060642753613814151688322857484807845836623976981729207238463947987*R mod l in Montgomery form
pub static XX_MONT: ScalarR0 = ScalarR0(U256::from_be_hex(
"030EDB637937F48C1B0A73AA1C7FD1B5F92C4331DB769B6590AE3AA7752B4D2E",
));
/// y = 6145104759870991071742105800796537629880401874866217824609283457819451087098
pub static Y: ScalarR0 = ScalarR0(U256::from_be_hex(
"0D96018BB8255FFFCC11FAD13433D2BAF0672BBF9D75E1ECDACB75071E1458FA",
));
/// x*y = 36752150652102274958925982391442301741
pub static XY: ScalarR0 = ScalarR0(U256::from_be_hex(
"000000000000000000000000000000001BA634ED50D71D84E02EE6D76BA7632D",
));
/// x*y = 3783114862749659543382438697751927473898937741870308063443170013240655651591*R mod l in Montgomery form
pub static XY_MONT: ScalarR0 = ScalarR0(U256::from_be_hex(
"0BDC1CE001340933F1F248B4F7900DE0430C69094F0A867BD78C9C23277B51E1",
));
/// a = 2351415481556538453565687241199399922945659411799870114962672658845158063753
pub static A: ScalarR0 = ScalarR0(U256::from_be_hex(
"0532DA9FAB8C6B3F6E4FEC4C4A4AA782AAE3EE1BC3D2A67C0C45236C07B3BE89",
));
/// b = 4885590095775723760407499321843594317911456947580037491039278279440296187236
pub static B: ScalarR0 = ScalarR0(U256::from_be_hex(
"0ACD2560547394C091B013B3B5B5587D69FB0BC2DF24F65A4BCD3FAE55421564",
));
/// a+b = 0
/// a-b = 4702830963113076907131374482398799845891318823599740229925345317690316127506
pub static AB: ScalarR0 = ScalarR0(U256::from_be_hex(
"0A65B53F5718D67EDC9FD89894954F0555C7DC3787A54CF8188A46D80F677D12",
));
// c = (2^512 - 1) % l = 1627715501170711445284395025044413883736156588369414752970002579683115011840
pub static C: ScalarR0 = ScalarR0(U256::from_be_hex(
"0399411B7C309A3DCEEC73D217F5BE65D00E1BA768859347A40611E3449C0F00",
));
#[test]
fn mul_max() {
let res = ScalarR0::mul(&X, &X);
assert!(res.0 == XX.0);
}
#[test]
fn square_max() {
let res = X.square();
assert!(res.0 == XX.0);
}
#[test]
fn montgomery_mul_max() {
let res = ScalarR0::montgomery_mul(&X, &X);
assert!(res.0 == XX_MONT.0);
}
#[test]
fn montgomery_square_max() {
let res = X.montgomery_square();
assert!(res.0 == XX_MONT.0);
}
#[test]
fn mul() {
let res = ScalarR0::mul(&X, &Y);
assert!(res.0 == XY.0);
}
#[test]
fn montgomery_mul() {
let res = ScalarR0::montgomery_mul(&X, &Y);
assert!(res.0 == XY_MONT.0);
}
#[test]
fn add() {
let res = ScalarR0::add(&A, &B);
let zero = ScalarR0(U256::ZERO);
assert!(res.0 == zero.0);
}
#[test]
fn sub() {
let res = ScalarR0::sub(&A, &B);
assert!(res.0 == AB.0);
}
#[test]
fn from_bytes_wide() {
let bignum = [255u8; 64]; // 2^512 - 1
let reduced = ScalarR0::from_bytes_wide(&bignum);
assert!(reduced.0 == C.0);
}
}

View file

@ -28,6 +28,8 @@ cfg_if! {
pub use crate::backend::serial::fiat_u32::constants::*; pub use crate::backend::serial::fiat_u32::constants::*;
#[cfg(curve25519_dalek_bits = "64")] #[cfg(curve25519_dalek_bits = "64")]
pub use crate::backend::serial::fiat_u64::constants::*; pub use crate::backend::serial::fiat_u64::constants::*;
} else if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
pub use crate::backend::serial::risc0::constants::*;
} else { } else {
#[cfg(curve25519_dalek_bits = "32")] #[cfg(curve25519_dalek_bits = "32")]
pub use crate::backend::serial::u32::constants::*; pub use crate::backend::serial::u32::constants::*;
@ -144,7 +146,11 @@ mod test {
/// Test that d = -121665/121666 /// Test that d = -121665/121666
#[test] #[test]
#[cfg(all(curve25519_dalek_bits = "32", not(curve25519_dalek_backend = "fiat")))] #[cfg(all(
curve25519_dalek_bits = "32",
not(curve25519_dalek_backend = "fiat"),
not(target_os = "zkvm")
))]
fn test_d_vs_ratio() { fn test_d_vs_ratio() {
use crate::backend::serial::u32::field::FieldElement2625; use crate::backend::serial::u32::field::FieldElement2625;
let a = -&FieldElement2625([121665, 0, 0, 0, 0, 0, 0, 0, 0, 0]); let a = -&FieldElement2625([121665, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
@ -168,6 +174,25 @@ mod test {
assert_eq!(d2, constants::EDWARDS_D2); assert_eq!(d2, constants::EDWARDS_D2);
} }
/// Test that d = -121665/121666
#[test]
#[cfg(all(target_os = "zkvm", target_arch = "riscv32"))]
fn test_d_vs_ratio() {
use crate::backend::serial::risc0::field::FieldElementR0;
use crypto_bigint::U256;
let a = -&FieldElementR0(U256::from_be_hex(
"000000000000000000000000000000000000000000000000000000000001db41",
));
let b = FieldElementR0(U256::from_be_hex(
"000000000000000000000000000000000000000000000000000000000001db42",
));
let d = &a * &b.invert();
let d2 = &d + &d;
assert_eq!(d, constants::EDWARDS_D);
assert_eq!(d2, constants::EDWARDS_D2);
}
#[test] #[test]
fn test_sqrt_ad_minus_one() { fn test_sqrt_ad_minus_one() {
let a = FieldElement::MINUS_ONE; let a = FieldElement::MINUS_ONE;

View file

@ -63,6 +63,13 @@ cfg_if! {
/// The `FieldElement` type is an alias for one of the platform-specific /// The `FieldElement` type is an alias for one of the platform-specific
/// implementations. /// implementations.
pub(crate) type FieldElement = backend::serial::u64::field::FieldElement51; pub(crate) type FieldElement = backend::serial::u64::field::FieldElement51;
} else if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
/// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).
///
/// The `FieldElement` type is an alias for one of the platform-specific
/// implementations.
pub(crate) type FieldElement = backend::serial::risc0::field::FieldElementR0;
} else { } else {
/// A `FieldElement` represents an element of the field /// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\). /// \\( \mathbb Z / (2\^{255} - 19)\\).

View file

@ -147,6 +147,7 @@ use subtle::CtOption;
use zeroize::Zeroize; use zeroize::Zeroize;
use crate::backend; use crate::backend;
#[cfg(not(all(target_os = "zkvm", target_arch = "riscv32")))]
use crate::constants; use crate::constants;
cfg_if! { cfg_if! {
@ -179,6 +180,12 @@ cfg_if! {
/// module. /// module.
#[cfg_attr(docsrs, doc(cfg(curve25519_dalek_bits = "64")))] #[cfg_attr(docsrs, doc(cfg(curve25519_dalek_bits = "64")))]
type UnpackedScalar = backend::serial::u64::scalar::Scalar52; type UnpackedScalar = backend::serial::u64::scalar::Scalar52;
} else if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
/// An `UnpackedScalar` represents an element of the field GF(l), optimized for speed.
///
/// This is a type alias for one of the scalar types in the `backend`
/// module.
type UnpackedScalar = backend::serial::risc0::scalar::ScalarR0;
} else { } else {
/// An `UnpackedScalar` represents an element of the field GF(l), optimized for speed. /// An `UnpackedScalar` represents an element of the field GF(l), optimized for speed.
/// ///
@ -373,10 +380,16 @@ impl<'a> Neg for &'a Scalar {
type Output = Scalar; type Output = Scalar;
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn neg(self) -> Scalar { fn neg(self) -> Scalar {
cfg_if! {
if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
UnpackedScalar::negate(&self.unpack()).pack()
} else {
let self_R = UnpackedScalar::mul_internal(&self.unpack(), &constants::R); let self_R = UnpackedScalar::mul_internal(&self.unpack(), &constants::R);
let self_mod_l = UnpackedScalar::montgomery_reduce(&self_R); let self_mod_l = UnpackedScalar::montgomery_reduce(&self_R);
UnpackedScalar::sub(&UnpackedScalar::ZERO, &self_mod_l).pack() UnpackedScalar::sub(&UnpackedScalar::ZERO, &self_mod_l).pack()
} }
}
}
} }
impl Neg for Scalar { impl Neg for Scalar {
@ -1124,8 +1137,16 @@ impl Scalar {
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn reduce(&self) -> Scalar { fn reduce(&self) -> Scalar {
let x = self.unpack(); let x = self.unpack();
cfg_if! {
if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
let x_mod_l = UnpackedScalar::reduce(&x);
} else {
let xR = UnpackedScalar::mul_internal(&x, &constants::R); let xR = UnpackedScalar::mul_internal(&x, &constants::R);
let x_mod_l = UnpackedScalar::montgomery_reduce(&xR); let x_mod_l = UnpackedScalar::montgomery_reduce(&xR);
}
}
x_mod_l.pack() x_mod_l.pack()
} }
@ -1760,10 +1781,16 @@ pub(crate) mod test {
assert_eq!(reduced.bytes, expected.bytes); assert_eq!(reduced.bytes, expected.bytes);
// (x + 2^256x) * R // (x + 2^256x) * R
cfg_if! {
if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
let montgomery_reduced = UnpackedScalar::reduce(&UnpackedScalar::from_bytes_wide(&bignum));
} else {
let interim = let interim =
UnpackedScalar::mul_internal(&UnpackedScalar::from_bytes_wide(&bignum), &constants::R); UnpackedScalar::mul_internal(&UnpackedScalar::from_bytes_wide(&bignum), &constants::R);
// ((x + 2^256x) * R) / R (mod l) // ((x + 2^256x) * R) / R (mod l)
let montgomery_reduced = UnpackedScalar::montgomery_reduce(&interim); let montgomery_reduced = UnpackedScalar::montgomery_reduce(&interim);
}
}
// The Montgomery reduced scalar should match the reduced one, as well as the expected // The Montgomery reduced scalar should match the reduced one, as well as the expected
assert_eq!(montgomery_reduced.0, reduced.unpack().0); assert_eq!(montgomery_reduced.0, reduced.unpack().0);

View file

@ -46,13 +46,15 @@ sha3 = "0.10"
hex = "0.4" hex = "0.4"
bincode = "1.0" bincode = "1.0"
serde_json = "1.0" serde_json = "1.0"
criterion = { version = "0.5", features = ["html_reports"] }
hex-literal = "0.4" hex-literal = "0.4"
rand = "0.8" rand = "0.8"
rand_core = { version = "0.6.4", default-features = false } rand_core = { version = "0.6.4", default-features = false }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
toml = { version = "0.7" } toml = { version = "0.7" }
[target.'cfg(not(target_os = "zkvm"))'.dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]] [[bench]]
name = "ed25519_benchmarks" name = "ed25519_benchmarks"
harness = false harness = false

View file

@ -32,7 +32,6 @@ mod vectors {
use sha2::{digest::Digest, Sha512}; use sha2::{digest::Digest, Sha512};
use std::{ use std::{
fs::File,
io::{BufRead, BufReader}, io::{BufRead, BufReader},
ops::Neg, ops::Neg,
}; };
@ -46,16 +45,9 @@ mod vectors {
let mut line: String; let mut line: String;
let mut lineno: usize = 0; let mut lineno: usize = 0;
let f = File::open("TESTVECTORS"); // Include the test vectors file directly. Note that this makes the test binary large.
if f.is_err() { let testvectors_bytes = include_bytes!("../TESTVECTORS");
println!( let file = BufReader::new(testvectors_bytes.as_slice());
"This test is only available when the code has been cloned \
from the git repository, since the TESTVECTORS file is large \
and is therefore not included within the distributed crate."
);
panic!();
}
let file = BufReader::new(f.unwrap());
for l in file.lines() { for l in file.lines() {
lineno += 1; lineno += 1;

View file

@ -2,7 +2,7 @@ use ed25519::signature::Verifier;
use ed25519_dalek::{Signature, VerifyingKey}; use ed25519_dalek::{Signature, VerifyingKey};
use serde::{de::Error as SError, Deserialize, Deserializer}; use serde::{de::Error as SError, Deserialize, Deserializer};
use std::{collections::BTreeSet as Set, fs::File}; use std::collections::BTreeSet as Set;
/// The set of edge cases that [`VerifyingKey::verify()`] permits. /// The set of edge cases that [`VerifyingKey::verify()`] permits.
const VERIFY_ALLOWED_EDGECASES: &[Flag] = &[ const VERIFY_ALLOWED_EDGECASES: &[Flag] = &[
@ -115,13 +115,9 @@ where
} }
fn get_test_vectors() -> impl Iterator<Item = TestVector> { fn get_test_vectors() -> impl Iterator<Item = TestVector> {
let f = File::open("VALIDATIONVECTORS").expect( // Include the test vectors file directly. Note that this makes the test binary large.
"This test is only available when the code has been cloned from the git repository, since let validationvectors_bytes = include_bytes!("../VALIDATIONVECTORS");
the VALIDATIONVECTORS file is large and is therefore not included within the distributed \ serde_json::from_reader::<_, Vec<IntermediateTestVector>>(validationvectors_bytes.as_slice())
crate.",
);
serde_json::from_reader::<_, Vec<IntermediateTestVector>>(f)
.unwrap() .unwrap()
.into_iter() .into_iter()
.map(TestVector::from) .map(TestVector::from)