mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 12:49:23 +00:00
Update FROST signing to match the IETF draft
Modernizes dependencies
This commit is contained in:
1
sign/dalek-ff-group/.gitignore
vendored
1
sign/dalek-ff-group/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
Cargo.lock
|
||||
@@ -11,7 +11,7 @@ rand_core = "0.6"
|
||||
|
||||
subtle = "2.4"
|
||||
|
||||
ff = "0.10"
|
||||
group = "0.10"
|
||||
ff = "0.11"
|
||||
group = "0.11"
|
||||
|
||||
curve25519-dalek = "3.2"
|
||||
|
||||
@@ -6,7 +6,7 @@ use core::{
|
||||
|
||||
use rand_core::RngCore;
|
||||
|
||||
use subtle::{Choice, CtOption, ConditionallySelectable};
|
||||
use subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable};
|
||||
|
||||
pub use curve25519_dalek as dalek;
|
||||
|
||||
@@ -100,6 +100,10 @@ impl<'a> MulAssign<&'a Scalar> for Scalar {
|
||||
fn mul_assign(&mut self, other: &'a Scalar) { self.0 *= other.0 }
|
||||
}
|
||||
|
||||
impl ConstantTimeEq for Scalar {
|
||||
fn ct_eq(&self, _: &Self) -> Choice { unimplemented!() }
|
||||
}
|
||||
|
||||
impl ConditionallySelectable for Scalar {
|
||||
fn conditional_select(_: &Self, _: &Self, _: Choice) -> Self { unimplemented!() }
|
||||
}
|
||||
@@ -117,7 +121,7 @@ impl Field for Scalar {
|
||||
fn double(&self) -> Self { *self + self }
|
||||
fn invert(&self) -> CtOption<Self> { CtOption::new(Self(self.0.invert()), Choice::from(1 as u8)) }
|
||||
fn sqrt(&self) -> CtOption<Self> { unimplemented!() }
|
||||
fn is_zero(&self) -> bool { self.0 == DScalar::zero() }
|
||||
fn is_zero(&self) -> Choice { Choice::from(if self.0 == DScalar::zero() { 1 } else { 0 }) }
|
||||
fn cube(&self) -> Self { *self * self * self }
|
||||
fn pow_vartime<S: AsRef<[u64]>>(&self, _exp: S) -> Self { unimplemented!() }
|
||||
}
|
||||
@@ -130,11 +134,14 @@ impl PrimeField for Scalar {
|
||||
type Repr = [u8; 32];
|
||||
const NUM_BITS: u32 = 253;
|
||||
const CAPACITY: u32 = 252;
|
||||
fn from_repr(bytes: [u8; 32]) -> Option<Self> { DScalar::from_canonical_bytes(bytes).map(|x| Scalar(x)) }
|
||||
fn from_repr(bytes: [u8; 32]) -> CtOption<Self> {
|
||||
let scalar = DScalar::from_canonical_bytes(bytes).map(|x| Scalar(x));
|
||||
CtOption::new(scalar.unwrap_or(Scalar::zero()), Choice::from(if scalar.is_some() { 1 } else { 0 }))
|
||||
}
|
||||
fn to_repr(&self) -> [u8; 32] { self.0.to_bytes() }
|
||||
|
||||
const S: u32 = 0;
|
||||
fn is_odd(&self) -> bool { unimplemented!() }
|
||||
fn is_odd(&self) -> Choice { unimplemented!() }
|
||||
fn multiplicative_generator() -> Self { unimplemented!() }
|
||||
fn root_of_unity() -> Self { unimplemented!() }
|
||||
}
|
||||
|
||||
1
sign/frost/.gitignore
vendored
1
sign/frost/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
Cargo.lock
|
||||
@@ -7,17 +7,16 @@ authors = ["kayabaNerve (Luke Parker) <lukeparker5132@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
digest = "0.9"
|
||||
blake2 = "0.9"
|
||||
digest = "0.10"
|
||||
|
||||
rand_core = "0.6"
|
||||
|
||||
ff = "0.10"
|
||||
group = "0.10"
|
||||
ff = "0.11"
|
||||
group = "0.11"
|
||||
|
||||
thiserror = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4"
|
||||
rand = "0.8"
|
||||
jubjub = "0.7"
|
||||
sha2 = "0.10"
|
||||
k256 = { version = "0.10", features = ["arithmetic"] }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use core::{marker::PhantomData, fmt::Debug};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
use digest::Digest;
|
||||
|
||||
use group::Group;
|
||||
|
||||
use crate::{Curve, FrostError, sign};
|
||||
|
||||
pub trait Algorithm<C: Curve>: Clone + Debug {
|
||||
/// Algorithm to use FROST with
|
||||
pub trait Algorithm<C: Curve>: Clone {
|
||||
/// The resulting type of the signatures this algorithm will produce
|
||||
type Signature: Clone + Debug;
|
||||
|
||||
@@ -59,40 +59,24 @@ pub trait Algorithm<C: Curve>: Clone + Debug {
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
pub trait Hram: PartialEq + Eq + Copy + Clone + Debug {
|
||||
pub trait Hram<C: Curve>: Clone {
|
||||
/// HRAM function to generate a challenge
|
||||
/// H2 from the IETF draft despite having a different argument set (not pre-formatted)
|
||||
#[allow(non_snake_case)]
|
||||
fn hram<C: Curve>(R: &C::G, A: &C::G, m: &[u8]) -> C::F;
|
||||
fn hram(R: &C::G, A: &C::G, m: &[u8]) -> C::F;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Blake2bHram {}
|
||||
impl Hram for Blake2bHram {
|
||||
#[allow(non_snake_case)]
|
||||
fn hram<C: Curve>(R: &C::G, A: &C::G, m: &[u8]) -> C::F {
|
||||
C::F_from_bytes_wide(
|
||||
blake2::Blake2b::new()
|
||||
.chain(C::G_to_bytes(R))
|
||||
.chain(C::G_to_bytes(A))
|
||||
.chain(m)
|
||||
.finalize()
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.expect("couldn't convert a 64-byte hash to a 64-byte array")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Schnorr<C: Curve, H: Hram> {
|
||||
#[derive(Clone)]
|
||||
pub struct Schnorr<C: Curve, H: Hram<C>> {
|
||||
c: Option<C::F>,
|
||||
hram: PhantomData<H>,
|
||||
_hram: PhantomData<H>,
|
||||
}
|
||||
|
||||
impl<C: Curve, H: Hram> Schnorr<C, H> {
|
||||
impl<C: Curve, H: Hram<C>> Schnorr<C, H> {
|
||||
pub fn new() -> Schnorr<C, H> {
|
||||
Schnorr {
|
||||
c: None,
|
||||
hram: PhantomData
|
||||
_hram: PhantomData
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +88,8 @@ pub struct SchnorrSignature<C: Curve> {
|
||||
pub s: C::F,
|
||||
}
|
||||
|
||||
impl<C: Curve, H: Hram> Algorithm<C> for Schnorr<C, H> {
|
||||
/// Implementation of Schnorr signatures for use with FROST
|
||||
impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
||||
type Signature = SchnorrSignature<C>;
|
||||
|
||||
fn context(&self) -> Vec<u8> {
|
||||
@@ -141,7 +126,7 @@ impl<C: Curve, H: Hram> Algorithm<C> for Schnorr<C, H> {
|
||||
nonce: C::F,
|
||||
msg: &[u8],
|
||||
) -> C::F {
|
||||
let c = H::hram::<C>(&nonce_sum, ¶ms.group_key(), msg);
|
||||
let c = H::hram(&nonce_sum, ¶ms.group_key(), msg);
|
||||
self.c = Some(c);
|
||||
|
||||
nonce + (params.secret_share() * c)
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
use core::{convert::{TryFrom, TryInto}, cmp::min, fmt};
|
||||
use core::{convert::TryFrom, cmp::min, fmt};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
use blake2::{Digest, Blake2b};
|
||||
|
||||
use ff::{Field, PrimeField};
|
||||
use group::Group;
|
||||
|
||||
use crate::{Curve, MultisigParams, MultisigKeys, FrostError};
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn challenge<C: Curve>(l: usize, context: &str, R: &[u8], Am: &[u8]) -> C::F {
|
||||
let mut c = Vec::with_capacity(8 + context.len() + R.len() + Am.len());
|
||||
c.extend(&u64::try_from(l).unwrap().to_le_bytes());
|
||||
c.extend(context.as_bytes());
|
||||
c.extend(R); // R
|
||||
c.extend(Am); // A of the first commitment, which is what we're proving we have the private key
|
||||
// for
|
||||
// m of the rest of the commitments, authenticating them
|
||||
C::hash_to_F(&c)
|
||||
}
|
||||
|
||||
// Implements steps 1 through 3 of round 1 of FROST DKG. Returns the coefficients, commitments, and
|
||||
// the serialized commitments to be broadcasted over an authenticated channel to all parties
|
||||
// TODO: This potentially could return a much more robust serialized message, including a signature
|
||||
@@ -44,19 +55,7 @@ fn generate_key_r1<R: RngCore + CryptoRng, C: Curve>(
|
||||
let k = C::F::random(rng);
|
||||
#[allow(non_snake_case)]
|
||||
let R = C::generator_table() * k;
|
||||
let c = C::F_from_bytes_wide(
|
||||
Blake2b::new()
|
||||
.chain(&u64::try_from(params.i).unwrap().to_le_bytes())
|
||||
.chain(context.as_bytes())
|
||||
.chain(&C::G_to_bytes(&R)) // R
|
||||
.chain(&serialized) // A of the first commitment, which is what we're proving we have
|
||||
// the private key for
|
||||
// m of the rest of the commitments, authenticating them
|
||||
.finalize()
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.expect("couldn't convert a 64-byte hash to a 64-byte array")
|
||||
);
|
||||
let c = challenge::<C>(params.i, context, &C::G_to_bytes(&R), &serialized);
|
||||
let s = k + (coefficients[0] * c);
|
||||
|
||||
serialized.extend(&C::G_to_bytes(&R));
|
||||
@@ -155,17 +154,11 @@ fn verify_r1<R: RngCore + CryptoRng, C: Curve>(
|
||||
);
|
||||
points.push(C::generator());
|
||||
|
||||
let c = C::F_from_bytes_wide(
|
||||
Blake2b::new()
|
||||
// Bounded by n which is already checked to be within the u64 range
|
||||
.chain(&u64::try_from(l).unwrap().to_le_bytes())
|
||||
.chain(context.as_bytes())
|
||||
.chain(&serialized[l][commitments_len .. commitments_len + C::G_len()])
|
||||
.chain(&serialized[l][0 .. commitments_len])
|
||||
.finalize()
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.expect("couldn't convert a 64-byte hash to a 64-byte array")
|
||||
let c = challenge::<C>(
|
||||
l,
|
||||
context,
|
||||
&serialized[l][commitments_len .. commitments_len + C::G_len()],
|
||||
&serialized[l][0 .. commitments_len]
|
||||
);
|
||||
|
||||
if first {
|
||||
@@ -195,17 +188,11 @@ fn verify_r1<R: RngCore + CryptoRng, C: Curve>(
|
||||
&serialized[l][commitments_len + C::G_len() .. serialized[l].len()]
|
||||
).map_err(|_| FrostError::InvalidProofOfKnowledge(l))?;
|
||||
|
||||
let c = C::F_from_bytes_wide(
|
||||
Blake2b::new()
|
||||
// Bounded by n which is already checked to be within the u64 range
|
||||
.chain(&u64::try_from(l).unwrap().to_le_bytes())
|
||||
.chain(context.as_bytes())
|
||||
.chain(&serialized[l][commitments_len .. commitments_len + C::G_len()])
|
||||
.chain(&serialized[l][0 .. commitments_len])
|
||||
.finalize()
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.expect("couldn't convert a 64-byte hash to a 64-byte array")
|
||||
let c = challenge::<C>(
|
||||
l,
|
||||
context,
|
||||
&serialized[l][commitments_len .. commitments_len + C::G_len()],
|
||||
&serialized[l][0 .. commitments_len]
|
||||
);
|
||||
|
||||
if R != ((C::generator_table() * s) + (commitments[l][0] * (C::F::zero() - &c))) {
|
||||
@@ -389,6 +376,7 @@ impl fmt::Display for State {
|
||||
}
|
||||
|
||||
/// State machine which manages key generation
|
||||
#[allow(non_snake_case)]
|
||||
pub struct StateMachine<C: Curve> {
|
||||
params: MultisigParams,
|
||||
context: String,
|
||||
@@ -396,7 +384,7 @@ pub struct StateMachine<C: Curve> {
|
||||
coefficients: Option<Vec<C::F>>,
|
||||
our_commitments: Option<Vec<C::G>>,
|
||||
secret: Option<C::F>,
|
||||
commitments: Option<Vec<Vec<C::G>>>,
|
||||
commitments: Option<Vec<Vec<C::G>>>
|
||||
}
|
||||
|
||||
impl<C: Curve> StateMachine<C> {
|
||||
@@ -410,7 +398,7 @@ impl<C: Curve> StateMachine<C> {
|
||||
coefficients: None,
|
||||
our_commitments: None,
|
||||
secret: None,
|
||||
commitments: None,
|
||||
commitments: None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,10 @@ pub mod sign;
|
||||
pub enum CurveError {
|
||||
#[error("invalid length for data (expected {0}, got {0})")]
|
||||
InvalidLength(usize, usize),
|
||||
// Push towards hex encoding in error messages
|
||||
#[error("invalid scalar ({0})")]
|
||||
InvalidScalar(String),
|
||||
#[error("invalid point ({0})")]
|
||||
InvalidPoint(String),
|
||||
#[error("invalid scalar")]
|
||||
InvalidScalar,
|
||||
#[error("invalid point")]
|
||||
InvalidPoint,
|
||||
}
|
||||
|
||||
/// Unified trait to manage a field/group
|
||||
@@ -58,6 +57,16 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug {
|
||||
// This could also be written as -> Option<C::G> with None for not implemented
|
||||
fn multiexp_vartime(scalars: &[Self::F], points: &[Self::G]) -> Self::G;
|
||||
|
||||
/// Hash the message as needed to calculate the binding factor
|
||||
/// H3 from the IETF draft
|
||||
fn hash_msg(msg: &[u8]) -> Vec<u8>;
|
||||
|
||||
/// Field element from hash, used in key generation and to calculate the binding factor
|
||||
/// H1 from the IETF draft
|
||||
/// Key generation uses it as if it's H2 to generate a challenge for a Proof of Knowledge
|
||||
#[allow(non_snake_case)]
|
||||
fn hash_to_F(data: &[u8]) -> Self::F;
|
||||
|
||||
// The following methods would optimally be F:: and G:: yet developers can't control F/G
|
||||
// They can control a trait they pass into this library
|
||||
|
||||
@@ -82,10 +91,6 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug {
|
||||
#[allow(non_snake_case)]
|
||||
fn F_from_le_slice(slice: &[u8]) -> Result<Self::F, CurveError>;
|
||||
|
||||
/// Field element from slice. Must support reducing the input into a valid field element
|
||||
#[allow(non_snake_case)]
|
||||
fn F_from_le_slice_unreduced(slice: &[u8]) -> Self::F;
|
||||
|
||||
/// Group element from slice. Should be canonical
|
||||
#[allow(non_snake_case)]
|
||||
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError>;
|
||||
@@ -97,10 +102,6 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug {
|
||||
/// Obtain a vector of the byte encoding of G
|
||||
#[allow(non_snake_case)]
|
||||
fn G_to_bytes(g: &Self::G) -> Vec<u8>;
|
||||
|
||||
/// Takes 64-bytes and returns a scalar reduced mod n
|
||||
#[allow(non_snake_case)]
|
||||
fn F_from_bytes_wide(bytes: [u8; 64]) -> Self::F;
|
||||
}
|
||||
|
||||
/// Parameters for a multisig
|
||||
|
||||
@@ -2,19 +2,12 @@ use core::{convert::{TryFrom, TryInto}, cmp::min, fmt};
|
||||
use std::rc::Rc;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
use blake2::{Digest, Blake2b};
|
||||
|
||||
use ff::{Field, PrimeField};
|
||||
use group::Group;
|
||||
|
||||
use crate::{Curve, MultisigParams, MultisigKeys, FrostError, algorithm::Algorithm};
|
||||
|
||||
// Matches ZCash's FROST Jubjub implementation
|
||||
const BINDING_DST: &'static [u8; 9] = b"FROST_rho";
|
||||
// Doesn't match ZCash except for their desire for messages to be hashed in advance before used
|
||||
// here and domain separated
|
||||
const BINDING_MESSAGE_DST: &'static [u8; 17] = b"FROST_rho_message";
|
||||
|
||||
/// Calculate the lagrange coefficient
|
||||
pub fn lagrange<F: PrimeField>(
|
||||
i: usize,
|
||||
@@ -198,7 +191,18 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
||||
#[allow(non_snake_case)]
|
||||
let mut B = Vec::with_capacity(multisig_params.n + 1);
|
||||
B.push(None);
|
||||
let mut b: Vec<u8> = vec![];
|
||||
|
||||
// Commitments + a presumed 32-byte hash of the message
|
||||
let mut b: Vec<u8> = Vec::with_capacity((multisig_params.n * 2 * C::G_len()) + 32);
|
||||
|
||||
// If the offset functionality provided by this library is in use, include it in the binding
|
||||
// factor
|
||||
if params.keys.offset.is_some() {
|
||||
b.extend(&C::F_to_le_bytes(¶ms.keys.offset.unwrap()));
|
||||
}
|
||||
// Also include any context the algorithm may want to specify
|
||||
b.extend(¶ms.algorithm.context());
|
||||
|
||||
for l in 1 ..= multisig_params.n {
|
||||
if l == multisig_params.i {
|
||||
if commitments[l].is_some() {
|
||||
@@ -206,8 +210,7 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
||||
}
|
||||
|
||||
B.push(Some(our_preprocess.commitments));
|
||||
// Slightly more robust
|
||||
b.extend(&u64::try_from(l).unwrap().to_le_bytes());
|
||||
b.extend(&u16::try_from(l).unwrap().to_le_bytes());
|
||||
b.extend(&our_preprocess.serialized[0 .. commit_len]);
|
||||
continue;
|
||||
}
|
||||
@@ -237,46 +240,26 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
||||
let E = C::G_from_slice(&commitments[C::G_len() .. commitments_len])
|
||||
.map_err(|_| FrostError::InvalidCommitment(l))?;
|
||||
B.push(Some([D, E]));
|
||||
b.extend(&u64::try_from(l).unwrap().to_le_bytes());
|
||||
b.extend(&u16::try_from(l).unwrap().to_le_bytes());
|
||||
b.extend(&commitments[0 .. commit_len]);
|
||||
}
|
||||
|
||||
let offset = if params.keys.offset.is_some() {
|
||||
C::F_to_le_bytes(¶ms.keys.offset.unwrap())
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let context = params.algorithm.context();
|
||||
let mut p = Vec::with_capacity(multisig_params.t);
|
||||
let mut pi = C::F::zero();
|
||||
for l in ¶ms.view.included {
|
||||
p.push(
|
||||
C::F_from_bytes_wide(
|
||||
Blake2b::new()
|
||||
.chain(BINDING_DST)
|
||||
.chain(u64::try_from(*l).unwrap().to_le_bytes())
|
||||
.chain(Blake2b::new().chain(BINDING_MESSAGE_DST).chain(msg).finalize())
|
||||
.chain(&offset)
|
||||
.chain(&context)
|
||||
.chain(&b)
|
||||
.finalize()
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.expect("couldn't convert a 64-byte hash to a 64-byte array")
|
||||
)
|
||||
);
|
||||
b.extend(&C::hash_msg(&msg));
|
||||
let b = C::hash_to_F(&b);
|
||||
|
||||
let view = ¶ms.view;
|
||||
let view = ¶ms.view;
|
||||
for l in ¶ms.view.included {
|
||||
params.algorithm.process_addendum(
|
||||
view,
|
||||
*l,
|
||||
B[*l].as_ref().unwrap(),
|
||||
&p[p.len() - 1],
|
||||
&b,
|
||||
if *l == multisig_params.i {
|
||||
pi = p[p.len() - 1];
|
||||
&our_preprocess.serialized[commitments_len .. our_preprocess.serialized.len()]
|
||||
} else {
|
||||
&commitments[*l].as_ref().unwrap()[commitments_len .. commitments[*l].as_ref().unwrap().len()]
|
||||
&commitments[*l].as_ref().unwrap()[
|
||||
commitments_len .. commitments[*l].as_ref().unwrap().len()
|
||||
]
|
||||
}
|
||||
)?;
|
||||
}
|
||||
@@ -288,7 +271,7 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
||||
for i in 0 .. params.view.included.len() {
|
||||
let commitments = B[params.view.included[i]].unwrap();
|
||||
#[allow(non_snake_case)]
|
||||
let this_R = commitments[0] + (commitments[1] * p[i]);
|
||||
let this_R = commitments[0] + (commitments[1] * b);
|
||||
Ris.push(this_R);
|
||||
R += this_R;
|
||||
}
|
||||
@@ -297,7 +280,7 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
|
||||
let share = params.algorithm.sign_share(
|
||||
view,
|
||||
R,
|
||||
our_preprocess.nonces[0] + (our_preprocess.nonces[1] * pi),
|
||||
our_preprocess.nonces[0] + (our_preprocess.nonces[1] * b),
|
||||
msg
|
||||
);
|
||||
Ok((Package { Ris, R, share }, C::F_to_le_bytes(&share)))
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
use core::convert::TryInto;
|
||||
|
||||
use group::{Group, GroupEncoding};
|
||||
use digest::Digest;
|
||||
use ff::PrimeField;
|
||||
use group::GroupEncoding;
|
||||
|
||||
use jubjub::{Fr, SubgroupPoint};
|
||||
use frost::{CurveError, Curve, multiexp_vartime};
|
||||
use sha2::{Sha256, Sha512};
|
||||
|
||||
use k256::{
|
||||
elliptic_curve::{generic_array::GenericArray, bigint::{ArrayEncoding, U512}, ops::Reduce},
|
||||
Scalar,
|
||||
ProjectivePoint
|
||||
};
|
||||
|
||||
use frost::{CurveError, Curve, multiexp_vartime, algorithm::Hram};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Jubjub;
|
||||
impl Curve for Jubjub {
|
||||
type F = Fr;
|
||||
type G = SubgroupPoint;
|
||||
type T = SubgroupPoint;
|
||||
pub struct Secp256k1;
|
||||
impl Curve for Secp256k1 {
|
||||
type F = Scalar;
|
||||
type G = ProjectivePoint;
|
||||
type T = ProjectivePoint;
|
||||
|
||||
fn id() -> String {
|
||||
"Jubjub".to_string()
|
||||
"secp256k1".to_string()
|
||||
}
|
||||
|
||||
fn id_len() -> u8 {
|
||||
@@ -21,15 +30,28 @@ impl Curve for Jubjub {
|
||||
}
|
||||
|
||||
fn generator() -> Self::G {
|
||||
Self::G::generator()
|
||||
Self::G::GENERATOR
|
||||
}
|
||||
|
||||
fn generator_table() -> Self::T {
|
||||
Self::G::generator()
|
||||
Self::G::GENERATOR
|
||||
}
|
||||
|
||||
fn multiexp_vartime(scalars: &[Self::F], points: &[Self::G]) -> Self::G {
|
||||
multiexp_vartime::<Jubjub>(scalars, points)
|
||||
multiexp_vartime::<Secp256k1>(scalars, points)
|
||||
}
|
||||
|
||||
// The IETF draft doesn't specify a secp256k1 ciphersuite
|
||||
// This test just uses the simplest ciphersuite which would still be viable to deploy
|
||||
fn hash_msg(msg: &[u8]) -> Vec<u8> {
|
||||
(&Sha256::digest(msg)).to_vec()
|
||||
}
|
||||
|
||||
// Use wide reduction for security
|
||||
fn hash_to_F(data: &[u8]) -> Self::F {
|
||||
Scalar::from_uint_reduced(
|
||||
U512::from_be_byte_array(Sha512::new().chain_update("rho").chain_update(data).finalize())
|
||||
)
|
||||
}
|
||||
|
||||
fn F_len() -> usize {
|
||||
@@ -37,46 +59,54 @@ impl Curve for Jubjub {
|
||||
}
|
||||
|
||||
fn G_len() -> usize {
|
||||
32
|
||||
33
|
||||
}
|
||||
|
||||
fn F_from_le_slice(slice: &[u8]) -> Result<Self::F, CurveError> {
|
||||
let scalar = Self::F::from_bytes(
|
||||
&slice.try_into().map_err(|_| CurveError::InvalidLength(32, slice.len()))?
|
||||
);
|
||||
if scalar.is_some().into() {
|
||||
Ok(scalar.unwrap())
|
||||
} else {
|
||||
Err(CurveError::InvalidScalar(hex::encode(slice)))
|
||||
let mut bytes: [u8; 32] = slice.try_into().map_err(
|
||||
|_| CurveError::InvalidLength(32, slice.len())
|
||||
)?;
|
||||
bytes.reverse();
|
||||
let scalar = Scalar::from_repr(bytes.into());
|
||||
if scalar.is_none().unwrap_u8() == 1 {
|
||||
Err(CurveError::InvalidScalar)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn F_from_le_slice_unreduced(slice: &[u8]) -> Self::F {
|
||||
let mut wide: [u8; 64] = [0; 64];
|
||||
wide[..slice.len()].copy_from_slice(slice);
|
||||
Self::F::from_bytes_wide(&wide)
|
||||
Ok(scalar.unwrap())
|
||||
}
|
||||
|
||||
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError> {
|
||||
let point = Self::G::from_bytes(
|
||||
&slice.try_into().map_err(|_| CurveError::InvalidLength(32, slice.len()))?
|
||||
);
|
||||
if point.is_some().into() {
|
||||
Ok(point.unwrap())
|
||||
} else {
|
||||
Err(CurveError::InvalidPoint(hex::encode(slice)))?
|
||||
let point = ProjectivePoint::from_bytes(GenericArray::from_slice(slice));
|
||||
if point.is_none().unwrap_u8() == 1 {
|
||||
Err(CurveError::InvalidScalar)?;
|
||||
}
|
||||
Ok(point.unwrap())
|
||||
}
|
||||
|
||||
fn F_to_le_bytes(f: &Self::F) -> Vec<u8> {
|
||||
f.to_bytes().to_vec()
|
||||
let mut res: [u8; 32] = f.to_bytes().into();
|
||||
res.reverse();
|
||||
res.to_vec()
|
||||
}
|
||||
|
||||
fn G_to_bytes(g: &Self::G) -> Vec<u8> {
|
||||
g.to_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn F_from_bytes_wide(bytes: [u8; 64]) -> Self::F {
|
||||
Self::F::from_bytes_wide(&bytes)
|
||||
(&g.to_bytes()).to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone)]
|
||||
pub struct TestHram {}
|
||||
impl Hram<Secp256k1> for TestHram {
|
||||
#[allow(non_snake_case)]
|
||||
fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar {
|
||||
Scalar::from_uint_reduced(
|
||||
U512::from_be_byte_array(
|
||||
Sha512::new()
|
||||
.chain_update(Secp256k1::G_to_bytes(R))
|
||||
.chain_update(Secp256k1::G_to_bytes(A))
|
||||
.chain_update(m)
|
||||
.finalize()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,19 @@ use std::rc::Rc;
|
||||
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
|
||||
use digest::Digest;
|
||||
use sha2::Sha256;
|
||||
|
||||
use frost::{
|
||||
Curve,
|
||||
MultisigParams, MultisigKeys,
|
||||
key_gen,
|
||||
algorithm::{Algorithm, Schnorr, Blake2bHram, SchnorrSignature},
|
||||
algorithm::{Algorithm, Schnorr, SchnorrSignature},
|
||||
sign
|
||||
};
|
||||
|
||||
mod common;
|
||||
use common::Jubjub;
|
||||
use common::{Secp256k1, TestHram};
|
||||
|
||||
const PARTICIPANTS: usize = 8;
|
||||
|
||||
@@ -81,7 +84,7 @@ fn key_gen_and_sign() {
|
||||
).unwrap()
|
||||
);
|
||||
machines.push(
|
||||
key_gen::StateMachine::<Jubjub>::new(
|
||||
key_gen::StateMachine::<Secp256k1>::new(
|
||||
params[i - 1],
|
||||
"FF/Group Rust key_gen test".to_string()
|
||||
)
|
||||
@@ -114,7 +117,7 @@ fn key_gen_and_sign() {
|
||||
|
||||
let these_keys = machines[i - 1].complete(our_secret_shares).unwrap();
|
||||
assert_eq!(
|
||||
MultisigKeys::<Jubjub>::deserialize(&these_keys.serialize()).unwrap(),
|
||||
MultisigKeys::<Secp256k1>::deserialize(&these_keys.serialize()).unwrap(),
|
||||
these_keys
|
||||
);
|
||||
keys.push(Rc::new(these_keys.clone()));
|
||||
@@ -130,14 +133,14 @@ fn key_gen_and_sign() {
|
||||
assert_eq!(group_key.unwrap(), these_keys.group_key());
|
||||
}
|
||||
|
||||
sign(Schnorr::<Jubjub, Blake2bHram>::new(), keys.clone());
|
||||
sign(Schnorr::<Secp256k1, TestHram>::new(), keys.clone());
|
||||
|
||||
let mut randomization = [0; 64];
|
||||
(&mut OsRng).fill_bytes(&mut randomization);
|
||||
sign(
|
||||
Schnorr::<Jubjub, Blake2bHram>::new(),
|
||||
Schnorr::<Secp256k1, TestHram>::new(),
|
||||
keys.iter().map(
|
||||
|keys| Rc::new(keys.offset(Jubjub::F_from_bytes_wide(randomization)))
|
||||
|keys| Rc::new(keys.offset(Secp256k1::hash_to_F(&Sha256::digest(&randomization))))
|
||||
).collect()
|
||||
);
|
||||
}
|
||||
|
||||
4
sign/monero/.gitignore
vendored
4
sign/monero/.gitignore
vendored
@@ -1,3 +1 @@
|
||||
Cargo.lock
|
||||
|
||||
.build
|
||||
c/.build
|
||||
|
||||
@@ -12,16 +12,13 @@ thiserror = "1"
|
||||
|
||||
rand_core = "0.6"
|
||||
|
||||
hex = "0.4"
|
||||
|
||||
digest = "0.9"
|
||||
tiny-keccak = { version = "2.0", features = ["keccak"] }
|
||||
blake2 = "0.9"
|
||||
blake2 = "0.10"
|
||||
|
||||
curve25519-dalek = { version = "3.2", features = ["std", "simd_backend"] }
|
||||
|
||||
ff = { version = "0.10", optional = true }
|
||||
group = { version = "0.10", optional = true }
|
||||
ff = { version = "0.11", optional = true }
|
||||
group = { version = "0.11", optional = true }
|
||||
dalek-ff-group = { path = "../dalek-ff-group", optional = true }
|
||||
frost = { path = "../frost", optional = true }
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use digest::Digest;
|
||||
use blake2::Blake2b;
|
||||
use blake2::{Digest, Blake2b512};
|
||||
|
||||
use curve25519_dalek::{
|
||||
constants::ED25519_BASEPOINT_TABLE,
|
||||
@@ -82,10 +81,10 @@ pub(crate) fn sign_core(
|
||||
let z;
|
||||
|
||||
let mut next_rand = rand_source;
|
||||
next_rand = Blake2b::digest(&next_rand).as_slice().try_into().unwrap();
|
||||
next_rand = Blake2b512::digest(&next_rand).as_slice().try_into().unwrap();
|
||||
{
|
||||
let a = Scalar::from_bytes_mod_order_wide(&next_rand);
|
||||
next_rand = Blake2b::digest(&next_rand).as_slice().try_into().unwrap();
|
||||
next_rand = Blake2b512::digest(&next_rand).as_slice().try_into().unwrap();
|
||||
C_out = commitment(&a, ssr.amount);
|
||||
|
||||
for member in &ssr.ring {
|
||||
@@ -149,7 +148,7 @@ pub(crate) fn sign_core(
|
||||
s.resize(n, Scalar::zero());
|
||||
while j != i {
|
||||
s[j] = Scalar::from_bytes_mod_order_wide(&next_rand);
|
||||
next_rand = Blake2b::digest(&next_rand).as_slice().try_into().unwrap();
|
||||
next_rand = Blake2b512::digest(&next_rand).as_slice().try_into().unwrap();
|
||||
let c_p = mu_P * c;
|
||||
let c_c = mu_C * c;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use digest::Digest;
|
||||
use blake2::Blake2b;
|
||||
use blake2::{Digest, Blake2b512};
|
||||
|
||||
use curve25519_dalek::{
|
||||
constants::ED25519_BASEPOINT_TABLE,
|
||||
@@ -11,7 +10,7 @@ use curve25519_dalek::{
|
||||
|
||||
use dalek_ff_group as dfg;
|
||||
use group::Group;
|
||||
use frost::{Curve, FrostError, algorithm::Algorithm};
|
||||
use frost::{Curve, FrostError, algorithm::Algorithm, sign::ParamsView};
|
||||
|
||||
use monero::util::ringct::{Key, Clsag};
|
||||
|
||||
@@ -94,11 +93,11 @@ impl Algorithm<Ed25519> for Multisig {
|
||||
|
||||
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
group_key: &dfg::EdwardsPoint,
|
||||
view: &ParamsView<Ed25519>,
|
||||
nonces: &[dfg::Scalar; 2]
|
||||
) -> Vec<u8> {
|
||||
#[allow(non_snake_case)]
|
||||
let H = hash_to_point(&group_key.0);
|
||||
let H = hash_to_point(&view.group_key().0);
|
||||
let h0 = nonces[0].0 * H;
|
||||
let h1 = nonces[1].0 * H;
|
||||
// 32 + 32 + 64 + 64
|
||||
@@ -112,6 +111,7 @@ impl Algorithm<Ed25519> for Multisig {
|
||||
|
||||
fn process_addendum(
|
||||
&mut self,
|
||||
_: &ParamsView<Ed25519>,
|
||||
l: usize,
|
||||
commitments: &[dfg::EdwardsPoint; 2],
|
||||
p: &dfg::Scalar,
|
||||
@@ -147,19 +147,32 @@ impl Algorithm<Ed25519> for Multisig {
|
||||
|
||||
fn sign_share(
|
||||
&mut self,
|
||||
_: dfg::EdwardsPoint,
|
||||
secret: dfg::Scalar,
|
||||
nonce: dfg::Scalar,
|
||||
view: &ParamsView<Ed25519>,
|
||||
nonce_sum: dfg::EdwardsPoint,
|
||||
_: &[u8],
|
||||
nonce: dfg::Scalar,
|
||||
_: &[u8]
|
||||
) -> dfg::Scalar {
|
||||
// Use everyone's commitments to derive a random source all signers can agree upon
|
||||
// Cannot be manipulated to effect and all signers must, and will, know this
|
||||
let rand_source = Blake2b::new().chain("Clsag_randomness").chain(&self.b).finalize().as_slice().try_into().unwrap();
|
||||
#[allow(non_snake_case)]
|
||||
let (clsag, c, mu_C, z, mu_P, C_out) = sign_core(rand_source, self.image, &self.msg, &self.ssr, nonce_sum.0, self.AH.0);
|
||||
let rand_source = Keccak::v512()
|
||||
.chain("Clsag_randomness")
|
||||
.chain(&self.b)
|
||||
.finalize()
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let share = dfg::Scalar(nonce.0 - (c * (mu_P * secret.0)));
|
||||
#[allow(non_snake_case)]
|
||||
let (clsag, c, mu_C, z, mu_P, C_out) = sign_core(
|
||||
rand_source,
|
||||
self.image,
|
||||
&self.msg,
|
||||
&self.ssr,
|
||||
nonce_sum.0,
|
||||
self.AH.0
|
||||
);
|
||||
|
||||
let share = dfg::Scalar(nonce.0 - (c * (mu_P * view.secret_share().0)));
|
||||
|
||||
self.interim = Some(ClsagSignInterim { c, mu_C, z, mu_P, clsag, C_out });
|
||||
share
|
||||
|
||||
@@ -2,8 +2,7 @@ use core::convert::TryInto;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use digest::Digest;
|
||||
use blake2::Blake2b;
|
||||
use blake2::{Digest, Blake2b512};
|
||||
|
||||
use curve25519_dalek::{
|
||||
constants::ED25519_BASEPOINT_TABLE as DTable,
|
||||
@@ -49,6 +48,14 @@ impl Curve for Ed25519 {
|
||||
EdwardsPoint(DPoint::vartime_multiscalar_mul(scalars, points))
|
||||
}
|
||||
|
||||
fn hash_msg(msg: &[u8]) -> Vec<u8> {
|
||||
Blake2b512::digest(msg)
|
||||
}
|
||||
|
||||
fn hash_to_F(data: &[u8]) -> Self::F {
|
||||
dfg::Scalar::from_hash(Blake2b512::new().chain(data))
|
||||
}
|
||||
|
||||
fn F_len() -> usize {
|
||||
32
|
||||
}
|
||||
@@ -61,19 +68,13 @@ impl Curve for Ed25519 {
|
||||
let scalar = Self::F::from_repr(
|
||||
slice.try_into().map_err(|_| CurveError::InvalidLength(32, slice.len()))?
|
||||
);
|
||||
if scalar.is_some() {
|
||||
if scalar.is_some().unwrap_u8() == 1 {
|
||||
Ok(scalar.unwrap())
|
||||
} else {
|
||||
Err(CurveError::InvalidScalar(hex::encode(slice)))
|
||||
Err(CurveError::InvalidScalar)
|
||||
}
|
||||
}
|
||||
|
||||
fn F_from_le_slice_unreduced(slice: &[u8]) -> Self::F {
|
||||
let mut wide: [u8; 64] = [0; 64];
|
||||
wide[..slice.len()].copy_from_slice(slice);
|
||||
dfg::Scalar::from_bytes_mod_order_wide(&wide)
|
||||
}
|
||||
|
||||
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError> {
|
||||
let point = dfg::CompressedEdwardsY::new(
|
||||
slice.try_into().map_err(|_| CurveError::InvalidLength(32, slice.len()))?
|
||||
@@ -83,11 +84,11 @@ impl Curve for Ed25519 {
|
||||
let point = point.unwrap();
|
||||
// Ban torsioned points
|
||||
if !point.is_torsion_free() {
|
||||
Err(CurveError::InvalidPoint(hex::encode(slice)))?
|
||||
Err(CurveError::InvalidPoint)?
|
||||
}
|
||||
Ok(point)
|
||||
} else {
|
||||
Err(CurveError::InvalidPoint(hex::encode(slice)))?
|
||||
Err(CurveError::InvalidPoint)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,10 +99,6 @@ impl Curve for Ed25519 {
|
||||
fn G_to_bytes(g: &Self::G) -> Vec<u8> {
|
||||
g.compress().to_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn F_from_bytes_wide(bytes: [u8; 64]) -> Self::F {
|
||||
dfg::Scalar::from_bytes_mod_order_wide(&bytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Used to prove legitimacy in several locations
|
||||
@@ -124,7 +121,7 @@ impl DLEqProof {
|
||||
let R2 = r * H;
|
||||
|
||||
let c = DScalar::from_hash(
|
||||
Blake2b::new()
|
||||
Blake2b512::new()
|
||||
.chain(R1.compress().to_bytes())
|
||||
.chain(R2.compress().to_bytes())
|
||||
.chain((secret * &DTable).compress().to_bytes())
|
||||
@@ -148,7 +145,7 @@ impl DLEqProof {
|
||||
let R2 = (s * H) - (c * alt);
|
||||
|
||||
let expected_c = DScalar::from_hash(
|
||||
Blake2b::new()
|
||||
Blake2b512::new()
|
||||
.chain(R1.compress().to_bytes())
|
||||
.chain(R2.compress().to_bytes())
|
||||
.chain(primary.compress().to_bytes())
|
||||
|
||||
Reference in New Issue
Block a user