Update FROST signing to match the IETF draft

Modernizes dependencies
This commit is contained in:
Luke Parker 2022-04-23 03:49:30 -04:00
parent 76a6ff46be
commit e22dcb1441
No known key found for this signature in database
GPG key ID: F9F1386DB1E119B6
18 changed files with 226 additions and 724 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
target
Cargo.lock

497
Cargo.lock generated
View file

@ -1,497 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "base58-monero"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "935c90240f9b7749c80746bf88ad9cb346f34b01ee30ad4d566dfdecd6e3cc6a"
dependencies = [
"thiserror",
]
[[package]]
name = "bitvec"
version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "blake2"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174"
dependencies = [
"crypto-mac",
"digest",
"opaque-debug",
]
[[package]]
name = "bls12_381"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397"
dependencies = [
"ff",
"rand_core 0.6.3",
"subtle",
]
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-mac"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
dependencies = [
"generic-array",
"subtle",
]
[[package]]
name = "curve25519-dalek"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
dependencies = [
"byteorder",
"digest",
"packed_simd_2",
"rand_core 0.5.1",
"subtle",
"zeroize",
]
[[package]]
name = "dalek-ff-group"
version = "0.1.0"
dependencies = [
"curve25519-dalek",
"ff",
"group",
"rand_core 0.6.3",
"subtle",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]]
name = "ff"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
dependencies = [
"bitvec",
"rand_core 0.6.3",
"subtle",
]
[[package]]
name = "fixed-hash"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c"
dependencies = [
"byteorder",
"rand",
"rustc-hex",
"static_assertions",
]
[[package]]
name = "frost"
version = "0.1.0"
dependencies = [
"blake2",
"digest",
"ff",
"group",
"hex",
"jubjub",
"rand",
"rand_core 0.6.3",
"thiserror",
]
[[package]]
name = "funty"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e"
[[package]]
name = "generic-array"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
dependencies = [
"cfg-if",
"libc",
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "group"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
dependencies = [
"byteorder",
"ff",
"rand_core 0.6.3",
"subtle",
]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-literal"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
[[package]]
name = "jubjub"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "593fc4726ca80edb47ee18ab4d826719e25c2096991a79308b44fb915c6014ef"
dependencies = [
"bitvec",
"bls12_381",
"ff",
"group",
"rand_core 0.6.3",
"subtle",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
[[package]]
name = "libm"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
[[package]]
name = "monero"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3732061cea7e75dc68ef986e0d5a393b3606c258c996abb4a81b759613ea1a0"
dependencies = [
"base58-monero",
"curve25519-dalek",
"fixed-hash",
"hex",
"hex-literal",
"sealed",
"thiserror",
"tiny-keccak",
]
[[package]]
name = "monero-sign"
version = "0.1.0"
dependencies = [
"blake2",
"curve25519-dalek",
"dalek-ff-group",
"digest",
"ff",
"frost",
"group",
"hex",
"lazy_static",
"monero",
"rand",
"rand_core 0.6.3",
"thiserror",
"tiny-keccak",
]
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "packed_simd_2"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "defdcfef86dcc44ad208f71d9ff4ce28df6537a4e0d6b0e8e845cb8ca10059a6"
dependencies = [
"cfg-if",
"libm",
]
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro2"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core 0.6.3",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.3",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom 0.2.6",
]
[[package]]
name = "rustc-hex"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
[[package]]
name = "sealed"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "636b9882a0f4cc2039488df89a10eb4b7976d4b6c1917fc0518f3f0f5e2c72ca"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "typenum"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wyz"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188"
dependencies = [
"tap",
]
[[package]]
name = "zeroize"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"

View file

@ -1 +0,0 @@
Cargo.lock

View file

@ -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"

View file

@ -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!() }
}

View file

@ -1 +0,0 @@
Cargo.lock

View file

@ -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"] }

View file

@ -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, &params.group_key(), msg);
let c = H::hram(&nonce_sum, &params.group_key(), msg);
self.c = Some(c);
nonce + (params.secret_share() * c)

View file

@ -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
}
}

View file

@ -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

View file

@ -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(&params.keys.offset.unwrap()));
}
// Also include any context the algorithm may want to specify
b.extend(&params.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(&params.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 &params.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 = &params.view;
let view = &params.view;
for l in &params.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)))

View file

@ -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()
)
)
}
}

View file

@ -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()
);
}

View file

@ -1,3 +1 @@
Cargo.lock
.build
c/.build

View file

@ -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 }

View file

@ -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;

View file

@ -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

View file

@ -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())