diff --git a/ark-secret-scalar/Cargo.toml b/ark-secret-scalar/Cargo.toml index 45e7a42..400cdbc 100644 --- a/ark-secret-scalar/Cargo.toml +++ b/ark-secret-scalar/Cargo.toml @@ -2,7 +2,7 @@ name = "ark-secret-scalar" description = "Secret scalars for non-constant-time fields and curves" authors = ["Jeff Burdges "] -version = "0.0.2" +version = "0.0.3" repository = "https://github.com/w3f/ring-vrf/tree/master/ark-secret-scalar" edition = "2021" license = "MIT/Apache-2.0" @@ -27,9 +27,7 @@ ark-transcript = { version = "0.0.2", default-features = false, path = "../ark-t [dev-dependencies] - -# TODO: Tests -# ark-bls12-377 = { version = "0.4", default-features = false, features = [ "curve" ] } +ark-bls12-377 = { version = "0.4", default-features = false, features = [ "curve" ] } [features] diff --git a/ark-secret-scalar/src/lib.rs b/ark-secret-scalar/src/lib.rs index 84183f2..80bf63e 100644 --- a/ark-secret-scalar/src/lib.rs +++ b/ark-secret-scalar/src/lib.rs @@ -3,10 +3,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![doc = include_str!("../README.md")] -use core::{ - cell::UnsafeCell, - ops::{Add,AddAssign,Mul} -}; +use core::ops::{Add, AddAssign, Index, IndexMut, Mul}; use ark_ff::{PrimeField}; // Field, Zero use ark_ec::{AffineRepr, Group}; // CurveGroup @@ -19,8 +16,6 @@ use zeroize::Zeroize; // TODO: Remove ark-transcript dependency once https://github.com/arkworks-rs/algebra/pull/643 lands use ark_transcript::xof_read_reduced; - - pub struct Rng2Xof(pub R); impl XofReader for Rng2Xof { fn read(&mut self, dest: &mut [u8]) { @@ -28,6 +23,45 @@ impl XofReader for Rng2Xof { } } +#[repr(transparent)] +#[derive(Zeroize, Clone)] +pub struct SecretScalar(F); + +impl SecretScalar { + /// Initialize and unbiased `SecretScalar` from a `XofReaader`. + pub fn from_xof(xof: &mut R) -> Self { + SecretScalar(xof_read_reduced(&mut *xof)) + } + + /// Multiply by a scalar. + pub fn mul_by_challenge(&self, rhs: &F) -> F { + let lhs = SecretSplit::from(self); + (lhs[0] * rhs) + (lhs[1] * rhs) + } +} + +impl From<&SecretSplit> for SecretScalar { + fn from(value: &SecretSplit) -> Self { + SecretScalar(value.0[0] + value.0[1]) + } +} + +impl From> for SecretScalar { + fn from(value: SecretSplit) -> Self { + SecretScalar::from(&value) + } +} + +impl Mul<&C> for &SecretScalar<::ScalarField> { + type Output = ::Group; + + /// Arkworks multiplies on the right since ark_ff is a dependency of ark_ec. + /// but ark_ec being our dependency requires left multiplication here. + fn mul(self, rhs: &C) -> Self::Output { + let lhs = SecretSplit::from(self); + rhs.mul(lhs[0]) + rhs.mul(lhs[1]) + } +} /// Secret scalar split into the sum of two scalars, which randomly /// mutate but retain the same sum. Incurs 2x penalty in scalar @@ -48,145 +82,180 @@ impl XofReader for Rng2Xof { // TODO: We split additively right now, but would a multiplicative splitting // help against rowhammer attacks on the secret key? #[repr(transparent)] -pub struct SecretScalar(UnsafeCell<[F; 2]>); +#[derive(Zeroize)] +pub struct SecretSplit([F; 2]); + +impl Index for SecretSplit { + type Output = F; -impl Clone for SecretScalar { - fn clone(&self) -> SecretScalar { - let n = self.operate(|ss| ss.clone()); - self.resplit(); - SecretScalar(UnsafeCell::new(n) ) + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] } } -impl PartialEq for SecretScalar { - fn eq(&self, rhs: &SecretScalar) -> bool { - let lhs = unsafe { &*self.0.get() }; - let rhs = unsafe { &*rhs.0.get() }; - ( (lhs[0] - rhs[0]) + (lhs[1] - rhs[1]) ).is_zero() +impl IndexMut for SecretSplit { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] } } -impl Eq for SecretScalar {} -impl Zeroize for SecretScalar { - fn zeroize(&mut self) { - self.0.get_mut().zeroize() +impl From> for SecretSplit { + fn from(value: SecretScalar) -> Self { + SecretSplit::from(&value) } } -impl Drop for SecretScalar { - fn drop(&mut self) { self.zeroize() } -} -impl SecretScalar { - /// Do computations with an immutable borrow of the two scalars. - /// - /// At the module level, we keep this method private, never pass - /// these references into user's code, and never accept user's - /// closures, so our being `!Sync` ensures memory safety. - /// All other method ensure only the sum of the scalars is visible - /// outside this module too. - fn operate(&self, f : O) -> R - where O: FnOnce(&[F; 2]) -> R - { - f(unsafe { &*self.0.get() }) - } - - /// Internal clone which skips replit. - fn risky_clone(&self) -> SecretScalar { - let n = self.operate(|ss| ss.clone()); - SecretScalar(UnsafeCell::new(n) ) - } - - /// Immutably borrow `&self` but add opposite random values to its two scalars. - /// - /// We encapsulate exposed interior mutability of `SecretScalar` here, but - /// our other methods should never reveal references into the scalars, - /// or even their individual valus. - pub fn resplit(&self) { +/// Randomply splits the secret in two components. +impl From<&SecretScalar> for SecretSplit { + fn from(value: &SecretScalar) -> Self { let mut xof = Rng2Xof(getrandom_or_panic()); - let x = xof_read_reduced(&mut xof); - let selfy = unsafe { &mut *self.0.get() }; - selfy[0] += &x; - selfy[1] -= &x; + let v1 = xof_read_reduced(&mut xof); + let v2 = value.0.sub(v1); + SecretSplit([v1, v2]) } +} - pub fn resplit_mut(&mut self) { +impl From for SecretSplit { + fn from(value: F) -> Self { + Self::from(&value) + } +} + +impl From<&F> for SecretSplit { + fn from(value: &F) -> Self { + SecretScalar(*value).into() + } +} + +/// Randomly resplits the cloned components. +impl Clone for SecretSplit { + fn clone(&self) -> SecretSplit { + let mut secret = SecretSplit(self.0.clone()); + secret.resplit(); + secret + } +} + +impl PartialEq for SecretSplit { + fn eq(&self, rhs: &SecretSplit) -> bool { + ((self[0] - rhs[0]) + (self[1] - rhs[1])).is_zero() + } +} + +impl Eq for SecretSplit {} + +impl Drop for SecretSplit { + fn drop(&mut self) { self.zeroize() } +} + +impl SecretSplit { + /// Randomply resplit the secret in two components. + pub fn resplit(&mut self) { let mut xof = Rng2Xof(getrandom_or_panic()); let x = xof_read_reduced(&mut xof); - let selfy = self.0.get_mut(); - selfy[0] += &x; - selfy[1] -= &x; + self[0] += &x; + self[1] -= &x; } /// Initialize and unbiased `SecretScalar` from a `XofReaader`. pub fn from_xof(xof: &mut R) -> Self { let mut xof = || xof_read_reduced(&mut *xof); - let mut ss = SecretScalar(UnsafeCell::new([xof(), xof()]) ); - ss.resplit_mut(); + let mut ss = SecretSplit([xof(), xof()]); + ss.resplit(); ss } /// Multiply by a scalar. pub fn mul_by_challenge(&self, rhs: &F) -> F { - let o = self.operate(|ss| (ss[0] * rhs) + (ss[1] * rhs) ); - self.resplit(); - o + (self[0] * rhs) + (self[1] * rhs) + } + + /// Get the secret scalar value by joining the two components. + pub fn scalar(&self) -> F { + self.0[0] + self.0[1] } /// Arkworks multiplies on the right since ark_ff is a dependency of ark_ec. /// but ark_ec being our dependency requires left multiplication here. fn mul_action>(&self, x: &mut G) { let mut y = x.clone(); - self.operate(|ss| { - *x *= ss[0]; - y *= ss[1]; - *x += y; - }); + *x *= self[0]; + y *= self[1]; + *x += y; } } -impl AddAssign<&SecretScalar> for SecretScalar { - fn add_assign(&mut self, rhs: &SecretScalar) { - let lhs = self.0.get_mut(); - rhs.operate(|rhs| { - lhs[0] += rhs[0]; - lhs[1] += rhs[1]; - }); +impl AddAssign<&SecretSplit> for SecretSplit { + fn add_assign(&mut self, rhs: &SecretSplit) { + self[0] += rhs[0]; + self[1] += rhs[1]; } } -impl Add<&SecretScalar> for &SecretScalar { - type Output = SecretScalar; - fn add(self, rhs: &SecretScalar) -> SecretScalar { - let mut lhs = self.risky_clone(); - lhs += rhs; - lhs.resplit_mut(); - lhs - } -} +impl Add<&SecretSplit> for SecretSplit { + type Output = SecretSplit; -/* -impl Mul<&G> for &SecretScalar<::ScalarField> { - type Output = G; - /// Arkworks multiplies on the right since ark_ff is a dependency of ark_ec. - /// but ark_ec being our dependency requires left multiplication here. - fn mul(self, rhs: &G) -> G { - let mut rhs = rhs.clone(); - self.mul_action(&mut rhs); - rhs + fn add(self, rhs: &SecretSplit) -> Self::Output { + let mut res = SecretSplit([self[0], self[1]]); + res += rhs; + res } } -*/ -impl Mul<&C> for &SecretScalar<::ScalarField> { +impl Mul<&C> for &SecretSplit<::ScalarField> { type Output = ::Group; + /// Arkworks multiplies on the right since ark_ff is a dependency of ark_ec. /// but ark_ec being our dependency requires left multiplication here. fn mul(self, rhs: &C) -> Self::Output { - let o = self.operate(|lhs| rhs.mul(lhs[0]) + rhs.mul(lhs[1])); + let o = rhs.mul(self[0]) + rhs.mul(self[1]); + use ark_ec::CurveGroup; debug_assert_eq!(o.into_affine(), { let mut t = rhs.into_group(); self.mul_action(&mut t); t }.into_affine() ); - self.resplit(); o } } +#[cfg(test)] +mod tests { + use super::*; + use ark_bls12_377::Fr; + use ark_ff::MontFp; + use ark_std::fmt::Debug; + + impl Debug for SecretSplit { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let selfy = &self.0; + f.debug_tuple("SecretScalar") + .field(&selfy[0]) + .field(&selfy[1]) + .finish() + } + } + + #[test] + fn from_single_scalar_works() { + let value: Fr = MontFp!("123456789"); + + let mut secret = SecretSplit::from(value); + assert_eq!(value, secret.scalar()); + + secret.resplit(); + assert_eq!(value, secret.scalar()); + + let secret2 = secret.clone(); + assert_ne!(secret.0[0], secret2.0[0]); + assert_ne!(secret.0[1], secret2.0[1]); + assert_eq!(secret, secret2); + } + + #[test] + fn mul_my_challenge_works() { + let value: Fr = MontFp!("123456789"); + let secret = SecretSplit::from(value); + + let factor = Fr::from(3); + let result = secret.mul_by_challenge(&factor); + assert_eq!(result, value * factor); + } +} diff --git a/bandersnatch_vrfs/Cargo.toml b/bandersnatch_vrfs/Cargo.toml index b604c48..29add3f 100644 --- a/bandersnatch_vrfs/Cargo.toml +++ b/bandersnatch_vrfs/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT/Apache-2.0" keywords = ["crypto", "cryptography", "vrf", "signature", "privacy"] [dependencies] -dleq_vrf = { version = "0.0.2", default-features = false, path = "../dleq_vrf", features = [ "scale" ] } +dleq_vrf = { default-features = false, path = "../dleq_vrf", features = [ "scale" ] } rand_core.workspace = true zeroize.workspace = true @@ -31,6 +31,9 @@ sha2 = { version = "0.10", default-features = false } rand_chacha = { version = "0.3.1", default-features = false } # Substrate curves are optional and gated by the 'substrate-curves' feature +# NOTE: do not enable any feature (e.g. under `std`) for these crates if you don't want +# to bloat your `Cargo.lock` with all the `sp-ark-*` dependencies. This happens even +# though you don't enable `substrate-curves` feature. sp-ark-ed-on-bls12-381-bandersnatch = { git = "https://github.com/paritytech/arkworks-substrate", default-features = false, optional = true } sp-ark-bls12-381 = { git = "https://github.com/paritytech/arkworks-substrate", default-features = false, optional = true } @@ -43,15 +46,19 @@ std = [ "ark-ff/std", "ark-ec/std", "ark-serialize/std", - "sp-ark-ed-on-bls12-381-bandersnatch?/std", - "sp-ark-bls12-381?/std", ] -getrandom = ["dleq_vrf/getrandom"] # "ring/getrandom"] +getrandom = ["dleq_vrf/getrandom"] print-trace = ["ark-std/print-trace"] +parallel = [ + "std", + "ring/parallel", + "ark-std/parallel", + "ark-ff/parallel", + "ark-ec/parallel", +] # Substrate curves allows to offload computationally heavy tasks to Substrate host functions. # Mostly useful in Substrate development context when targeting wasm32 architecture. substrate-curves = [ - "sp-ark-ed-on-bls12-381-bandersnatch", - "sp-ark-bls12-381", + "sp-ark-ed-on-bls12-381-bandersnatch", + "sp-ark-bls12-381", ] -# parallel = ["std", "ring/parallel"] # ["std", "rayon", "ark-std/parallel", "ark-ff/parallel", "ark-ec/parallel", "ark-poly/parallel"] diff --git a/dleq_vrf/Cargo.toml b/dleq_vrf/Cargo.toml index 1148e1d..cf37473 100644 --- a/dleq_vrf/Cargo.toml +++ b/dleq_vrf/Cargo.toml @@ -2,7 +2,7 @@ name = "dleq_vrf" description = "VRFs from Chaum-Pedersen DLEQ proofs, usable in Ring VRFs" authors = ["Sergey Vasilyev ", "Jeff Burdges ", "Syed Hosseini "] -version = "0.0.2" +version = "0.0.3" repository = "https://github.com/w3f/ring-vrf/tree/master/dleq_vrf" edition = "2021" license = "MIT/Apache-2.0" @@ -20,8 +20,8 @@ ark-ff.workspace = true ark-ec.workspace = true ark-serialize.workspace = true -ark-secret-scalar = { version = "0.0.2", default-features = false, path = "../ark-secret-scalar" } -ark-transcript = { version = "0.0.2", default-features = false, path = "../ark-transcript" } +ark-secret-scalar = { default-features = false, path = "../ark-secret-scalar" } +ark-transcript = { default-features = false, path = "../ark-transcript" } ark-scale = { workspace = true, optional = true } diff --git a/dleq_vrf/src/keys.rs b/dleq_vrf/src/keys.rs index 00e0642..d37aec8 100644 --- a/dleq_vrf/src/keys.rs +++ b/dleq_vrf/src/keys.rs @@ -14,13 +14,11 @@ use ark_serialize::{CanonicalSerialize,CanonicalDeserialize,SerializationError}; #[cfg(feature = "getrandom")] use ark_secret_scalar::{rand_core, RngCore}; - use ark_secret_scalar::SecretScalar; use crate::{ ThinVrf, transcript::digest::{Update,XofReader}, - }; @@ -86,7 +84,7 @@ pub struct SecretKey { pub(crate) thin: ThinVrf, /// Secret key represented as a scalar. - pub(crate) key: SecretScalar<::ScalarField>, + pub(crate) key: SecretScalar, /// Seed for deriving the nonces used in Schnorr proofs. /// @@ -146,8 +144,8 @@ impl ThinVrf { { let mut nonce_seed: [u8; 32] = [0u8; 32]; xof.read(&mut nonce_seed); - let mut key = SecretScalar::from_xof(&mut xof); - let public = self.make_public(&mut key); + let key = SecretScalar::from_xof(&mut xof); + let public = self.make_public(&key); SecretKey { thin: self, key, nonce_seed, public, #[cfg(debug_assertions)] test_vector_fake_rng: false, @@ -156,7 +154,7 @@ impl ThinVrf { /// Generate a `SecretKey` from a 32 byte seed. pub fn secretkey_from_seed(self, seed: &[u8; 32]) -> SecretKey { - use crate::transcript::digest::{ExtendableOutput}; + use crate::transcript::digest::ExtendableOutput; let mut xof = crate::transcript::Shake128::default(); xof.update(b"VrfSecretSeed"); xof.update(seed.as_ref()); diff --git a/dleq_vrf/src/thin.rs b/dleq_vrf/src/thin.rs index 69fa43d..f427176 100644 --- a/dleq_vrf/src/thin.rs +++ b/dleq_vrf/src/thin.rs @@ -7,6 +7,7 @@ use ark_std::{borrow::{Borrow,BorrowMut}, vec::Vec}; use ark_ec::{AffineRepr, CurveGroup}; +use ark_secret_scalar::SecretScalar; use crate::{ Transcript, IntoTranscript, @@ -109,7 +110,7 @@ impl Witness> { t.append(&r); let c: ::ScalarField = t.challenge(b"ThinVrfChallenge").read_reduce(); let s = k + secret.key.mul_by_challenge(&c); - k.zeroize(); + k.zeroize(); Batchable { compk: (), r, s } // TODO: Add some verify_final for additional rowhammer defenses? // We already hash the public key though, so no issues like Ed25519. @@ -135,15 +136,10 @@ impl Valid for Batchable> { */ impl ThinVrf { - pub(crate) fn make_public( - &self, - secret: &ark_secret_scalar::SecretScalar<::ScalarField> - ) -> PublicKey { - // #[cfg(feature = "getrandom")] - let p = secret * self.keying_base(); - // #[cfg(not(feature = "getrandom"))] - // let p = self.keying_base().mul(secret.0[0]) + self.keying_base().mul(secret.0[1]) ; - PublicKey(p.into_affine()) + pub(crate) fn make_public(&self, secret: &SecretScalar) -> PublicKey { + // let secret = SecretScalarSplit::from(secret); + let public = secret * self.keying_base(); + PublicKey(public.into_affine()) } /// Verify thin VRF signature diff --git a/nugget_bls/Cargo.toml b/nugget_bls/Cargo.toml index 6c1e479..f99fbed 100644 --- a/nugget_bls/Cargo.toml +++ b/nugget_bls/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["crypto", "cryptography", "bls", "signature"] [dependencies] -dleq_vrf = { version = "0.0.2", default-features = false, path = "../dleq_vrf" } +dleq_vrf = { default-features = false, path = "../dleq_vrf" } rand_core.workspace = true zeroize.workspace = true