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]
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]
sha2 = { version = "0.10", default-features = false }
bincode = "1"
criterion = { version = "0.5", features = ["html_reports"] }
hex = "0.4.2"
rand = "0.8"
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
@ -49,18 +47,26 @@ required-features = ["alloc", "rand_core"]
cfg-if = "1"
ff = { 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 }
digest = { version = "0.10", default-features = false, optional = true }
subtle = { version = "2.3.0", default-features = false }
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
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]
cpufeatures = "0.2.6"
[target.'cfg(curve25519_dalek_backend = "fiat")'.dependencies]
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]
default = ["alloc", "precomputed-tables", "zeroize"]
alloc = ["zeroize?/alloc"]

View file

@ -383,13 +383,25 @@ impl ProjectivePoint {
let XX = self.X.square();
let YY = self.Y.square();
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_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 {
X: &X_plus_Y_sq - &YY_plus_XX,
X: new_x,
Y: YY_plus_XX,
Z: 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 TT2d = &self.T * &other.T2d;
let ZZ = &self.Z * &other.Z;
let ZZ2 = &ZZ + &ZZ;
cfg_if::cfg_if! {
if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
let ZZ2 = &FieldElement::TWO * &ZZ;
} else {
let ZZ2 = &ZZ + &ZZ;
}
}
CompletedPoint {
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 TT2d = &self.T * &other.T2d;
let ZZ = &self.Z * &other.Z;
let ZZ2 = &ZZ + &ZZ;
cfg_if::cfg_if! {
if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
let ZZ2 = &FieldElement::TWO * &ZZ;
} else {
let ZZ2 = &ZZ + &ZZ;
}
}
CompletedPoint {
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 MM = &Y_minus_X * &other.y_minus_x;
let Txy2d = &self.T * &other.xy2d;
let Z2 = &self.Z + &self.Z;
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;
}
}
CompletedPoint {
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 MP = &Y_minus_X * &other.y_plus_x;
let Txy2d = &self.T * &other.xy2d;
let Z2 = &self.Z + &self.Z;
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;
}
}
CompletedPoint {
X: &PM - &MP,

View file

@ -31,6 +31,10 @@ cfg_if! {
#[doc(hidden)]
pub mod fiat_u64;
} else if #[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] {
pub mod risc0;
} else {
#[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::*;
#[cfg(curve25519_dalek_bits = "64")]
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 {
#[cfg(curve25519_dalek_bits = "32")]
pub use crate::backend::serial::u32::constants::*;
@ -144,7 +146,11 @@ mod test {
/// Test that d = -121665/121666
#[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() {
use crate::backend::serial::u32::field::FieldElement2625;
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);
}
/// 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]
fn test_sqrt_ad_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
/// implementations.
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 {
/// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).

View file

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

View file

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

View file

@ -32,7 +32,6 @@ mod vectors {
use sha2::{digest::Digest, Sha512};
use std::{
fs::File,
io::{BufRead, BufReader},
ops::Neg,
};
@ -46,16 +45,9 @@ mod vectors {
let mut line: String;
let mut lineno: usize = 0;
let f = File::open("TESTVECTORS");
if f.is_err() {
println!(
"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());
// Include the test vectors file directly. Note that this makes the test binary large.
let testvectors_bytes = include_bytes!("../TESTVECTORS");
let file = BufReader::new(testvectors_bytes.as_slice());
for l in file.lines() {
lineno += 1;

View file

@ -2,7 +2,7 @@ use ed25519::signature::Verifier;
use ed25519_dalek::{Signature, VerifyingKey};
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.
const VERIFY_ALLOWED_EDGECASES: &[Flag] = &[
@ -115,13 +115,9 @@ where
}
fn get_test_vectors() -> impl Iterator<Item = TestVector> {
let f = File::open("VALIDATIONVECTORS").expect(
"This test is only available when the code has been cloned from the git repository, since
the VALIDATIONVECTORS file is large and is therefore not included within the distributed \
crate.",
);
serde_json::from_reader::<_, Vec<IntermediateTestVector>>(f)
// Include the test vectors file directly. Note that this makes the test binary large.
let validationvectors_bytes = include_bytes!("../VALIDATIONVECTORS");
serde_json::from_reader::<_, Vec<IntermediateTestVector>>(validationvectors_bytes.as_slice())
.unwrap()
.into_iter()
.map(TestVector::from)