mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Replace Ciphersuite::hash_to_F
The prior-present `Ciphersuite::hash_to_F` was a sin. Implementations took a DST, yet were not require to securely handle it. It was also biased towards the requirements of `modular-frost` as `ciphersuite` was originally written all those years ago, when `modular-frost` had needs exceeding what `ff`, `group` satisfied. Now, the hash is bound to produce an output which can be converted to a scalar with `ff::FromUniformBytes`. A new `hash_to_F`, which accepts a single argument of the value to hash (removing the potential to insecurely handle the DST by removing the DST entirely). Due to `digest` yielding a `GenericArray`, yet `FromUniformBytes` taking a `const usize`, the `ciphersuite` crate now defines a `FromUniformBytes` trait taking an array (then implemented for all satisfiers of `ff::FromUniformBytes`). In order to get the array type from the `GenericArray`, the output of the hash, `digest` is updated to the `0.11` release candidate which moves to `flexible-array` which solves that problem. The existing, specific `hash_to_F` functions have been moved to `modular-frost` as necessary. `flexible-array` itself is patched to a fork due to https://github.com/RustCrypto/hybrid-array/issues/131.
This commit is contained in:
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/ciphersuite
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["ciphersuite", "ff", "group"]
|
||||
edition = "2021"
|
||||
rust-version = "1.73"
|
||||
rust-version = "1.85"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -24,7 +24,7 @@ rand_core = { version = "0.6", default-features = false }
|
||||
zeroize = { version = "^1.5", default-features = false, features = ["derive"] }
|
||||
subtle = { version = "^2.4", default-features = false }
|
||||
|
||||
digest = { version = "0.10", default-features = false, features = ["core-api"] }
|
||||
digest = { version = "0.11.0-rc.0", default-features = false, features = ["block-api"] }
|
||||
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false }
|
||||
|
||||
ff = { version = "0.13", default-features = false, features = ["bits"] }
|
||||
@@ -38,7 +38,7 @@ rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
||||
ff-group-tests = { version = "0.13", path = "../ff-group-tests" }
|
||||
|
||||
[features]
|
||||
alloc = ["std-shims"]
|
||||
alloc = ["std-shims", "ff/alloc"]
|
||||
std = [
|
||||
"alloc",
|
||||
|
||||
@@ -49,7 +49,6 @@ std = [
|
||||
"zeroize/std",
|
||||
"subtle/std",
|
||||
|
||||
"digest/std",
|
||||
"transcript/std",
|
||||
|
||||
"ff/std",
|
||||
|
||||
@@ -17,10 +17,6 @@ Secp256k1 and P-256 are offered via [k256](https://crates.io/crates/k256) and
|
||||
[p256](https://crates.io/crates/p256), two libraries maintained by
|
||||
[RustCrypto](https://github.com/RustCrypto).
|
||||
|
||||
Their `hash_to_F` is the
|
||||
[IETF's hash to curve](https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html),
|
||||
yet applied to their scalar field.
|
||||
|
||||
Please see the [`ciphersuite-kp256`](https://docs.rs/ciphersuite-kp256) crate for more info.
|
||||
|
||||
### Ed25519/Ristretto
|
||||
@@ -29,12 +25,6 @@ Ed25519/Ristretto are offered via
|
||||
[dalek-ff-group](https://crates.io/crates/dalek-ff-group), an ff/group wrapper
|
||||
around [curve25519-dalek](https://crates.io/crates/curve25519-dalek).
|
||||
|
||||
Their `hash_to_F` is the wide reduction of SHA2-512, as used in
|
||||
[RFC-8032](https://www.rfc-editor.org/rfc/rfc8032). This is also compliant with
|
||||
the draft
|
||||
[RFC-RISTRETTO](https://www.ietf.org/archive/id/draft-irtf-cfrg-ristretto255-decaf448-05.html).
|
||||
The domain-separation tag is naively prefixed to the message.
|
||||
|
||||
Please see the [`dalek-ff-group`](https://docs.rs/dalek-ff-group) crate for more info.
|
||||
|
||||
### Ed448
|
||||
@@ -43,8 +33,4 @@ Ed448 is offered via [minimal-ed448](https://crates.io/crates/minimal-ed448), an
|
||||
explicitly not recommended, unaudited, incomplete Ed448 implementation, limited
|
||||
to its prime-order subgroup.
|
||||
|
||||
Its `hash_to_F` is the wide reduction of SHAKE256, with a 114-byte output, as
|
||||
used in [RFC-8032](https://www.rfc-editor.org/rfc/rfc8032). The
|
||||
domain-separation tag is naively prefixed to the message.
|
||||
|
||||
Please see the [`minimal-ed448`](https://docs.rs/minimal-ed448) crate for more info.
|
||||
|
||||
@@ -21,9 +21,8 @@ rand_core = { version = "0.6", default-features = false }
|
||||
|
||||
zeroize = { version = "^1.5", default-features = false, features = ["derive"] }
|
||||
|
||||
sha2 = { version = "0.10", default-features = false }
|
||||
sha2 = { version = "0.11.0-rc.0", default-features = false }
|
||||
|
||||
elliptic-curve = { version = "0.13", default-features = false, features = ["hash2curve"] }
|
||||
p256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits", "hash2curve"] }
|
||||
k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits", "hash2curve"] }
|
||||
|
||||
@@ -43,9 +42,6 @@ std = [
|
||||
|
||||
"zeroize/std",
|
||||
|
||||
"sha2/std",
|
||||
|
||||
"elliptic-curve/std",
|
||||
"p256/std",
|
||||
"k256/std",
|
||||
|
||||
|
||||
@@ -3,15 +3,9 @@
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use sha2::Sha256;
|
||||
use sha2::Sha512;
|
||||
|
||||
use elliptic_curve::{
|
||||
generic_array::GenericArray,
|
||||
bigint::{NonZero, CheckedAdd, Encoding, U384},
|
||||
hash2curve::{Expander, ExpandMsg, ExpandMsgXmd},
|
||||
};
|
||||
|
||||
use ciphersuite::{group::ff::PrimeField, Ciphersuite};
|
||||
use ciphersuite::Ciphersuite;
|
||||
|
||||
pub use k256;
|
||||
pub use p256;
|
||||
@@ -27,148 +21,31 @@ macro_rules! kp_curve {
|
||||
impl Ciphersuite for $Ciphersuite {
|
||||
type F = $lib::Scalar;
|
||||
type G = $lib::ProjectivePoint;
|
||||
type H = Sha256;
|
||||
type H = Sha512;
|
||||
|
||||
const ID: &'static [u8] = $ID;
|
||||
|
||||
fn generator() -> Self::G {
|
||||
$lib::ProjectivePoint::GENERATOR
|
||||
}
|
||||
|
||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
||||
// While one of these two libraries does support directly hashing to the Scalar field, the
|
||||
// other doesn't. While that's probably an oversight, this is a universally working method
|
||||
|
||||
// This method is from
|
||||
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html
|
||||
// Specifically, Section 5
|
||||
|
||||
// While that draft, overall, is intended for hashing to curves, that necessitates
|
||||
// detailing how to hash to a finite field. The draft comments that its mechanism for
|
||||
// doing so, which it uses to derive field elements, is also applicable to the scalar field
|
||||
|
||||
// The hash_to_field function is intended to provide unbiased values
|
||||
// In order to do so, a wide reduction from an extra k bits is applied, minimizing bias to
|
||||
// 2^-k
|
||||
// k is intended to be the bits of security of the suite, which is 128 for secp256k1 and
|
||||
// P-256
|
||||
const K: usize = 128;
|
||||
|
||||
// L is the amount of bytes of material which should be used in the wide reduction
|
||||
// The 256 is for the bit-length of the primes, rounded up to the nearest byte threshold
|
||||
// This is a simplification of the formula from the end of section 5
|
||||
const L: usize = (256 + K) / 8; // 48
|
||||
|
||||
// In order to perform this reduction, we need to use 48-byte numbers
|
||||
// First, convert the modulus to a 48-byte number
|
||||
// This is done by getting -1 as bytes, parsing it into a U384, and then adding back one
|
||||
let mut modulus = [0; L];
|
||||
// The byte repr of scalars will be 32 big-endian bytes
|
||||
// Set the lower 32 bytes of our 48-byte array accordingly
|
||||
modulus[16 ..].copy_from_slice(&(Self::F::ZERO - Self::F::ONE).to_bytes());
|
||||
// Use a checked_add + unwrap since this addition cannot fail (being a 32-byte value with
|
||||
// 48-bytes of space)
|
||||
// While a non-panicking saturating_add/wrapping_add could be used, they'd likely be less
|
||||
// performant
|
||||
let modulus = U384::from_be_slice(&modulus).checked_add(&U384::ONE).unwrap();
|
||||
|
||||
// The defined P-256 and secp256k1 ciphersuites both use expand_message_xmd
|
||||
let mut wide = U384::from_be_bytes({
|
||||
let mut bytes = [0; 48];
|
||||
ExpandMsgXmd::<Sha256>::expand_message(&[msg], &[dst], 48)
|
||||
.unwrap()
|
||||
.fill_bytes(&mut bytes);
|
||||
bytes
|
||||
})
|
||||
.rem(&NonZero::new(modulus).unwrap())
|
||||
.to_be_bytes();
|
||||
|
||||
// Now that this has been reduced back to a 32-byte value, grab the lower 32-bytes
|
||||
let mut array = *GenericArray::from_slice(&wide[16 ..]);
|
||||
let res = $lib::Scalar::from_repr(array).unwrap();
|
||||
|
||||
// Zeroize the temp values we can due to the possibility hash_to_F is being used for nonces
|
||||
wide.zeroize();
|
||||
array.zeroize();
|
||||
res
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_oversize_dst<C: Ciphersuite>() {
|
||||
use sha2::Digest;
|
||||
|
||||
// The draft specifies DSTs >255 bytes should be hashed into a 32-byte DST
|
||||
let oversize_dst = [0x00; 256];
|
||||
let actual_dst = Sha256::digest([b"H2C-OVERSIZE-DST-".as_ref(), &oversize_dst].concat());
|
||||
// Test the hash_to_F function handles this
|
||||
// If it didn't, these would return different values
|
||||
assert_eq!(C::hash_to_F(&oversize_dst, &[]), C::hash_to_F(&actual_dst, &[]));
|
||||
}
|
||||
|
||||
/// Ciphersuite for Secp256k1.
|
||||
///
|
||||
/// hash_to_F is implemented via the IETF draft for hash to curve's hash_to_field (v16).
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct Secp256k1;
|
||||
kp_curve!("secp256k1", k256, Secp256k1, b"secp256k1");
|
||||
#[test]
|
||||
fn test_secp256k1() {
|
||||
ff_group_tests::group::test_prime_group_bits::<_, k256::ProjectivePoint>(&mut rand_core::OsRng);
|
||||
|
||||
// Ideally, a test vector from hash_to_field (not FROST) would be here
|
||||
// Unfortunately, the IETF draft only provides vectors for field elements, not scalars
|
||||
// Vectors have been requested in
|
||||
// https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/issues/343
|
||||
|
||||
assert_eq!(
|
||||
Secp256k1::hash_to_F(
|
||||
b"FROST-secp256k1-SHA256-v11nonce",
|
||||
&hex::decode(
|
||||
"\
|
||||
80cbea5e405d169999d8c4b30b755fedb26ab07ec8198cda4873ed8ce5e16773\
|
||||
08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c"
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
.to_repr()
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<Vec<_>>(),
|
||||
hex::decode("acc83278035223c1ba464e2d11bfacfc872b2b23e1041cf5f6130da21e4d8068").unwrap()
|
||||
);
|
||||
|
||||
test_oversize_dst::<Secp256k1>();
|
||||
}
|
||||
|
||||
/// Ciphersuite for P-256.
|
||||
///
|
||||
/// hash_to_F is implemented via the IETF draft for hash to curve's hash_to_field (v16).
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct P256;
|
||||
kp_curve!("p256", p256, P256, b"P-256");
|
||||
#[test]
|
||||
fn test_p256() {
|
||||
ff_group_tests::group::test_prime_group_bits::<_, p256::ProjectivePoint>(&mut rand_core::OsRng);
|
||||
|
||||
assert_eq!(
|
||||
P256::hash_to_F(
|
||||
b"FROST-P256-SHA256-v11nonce",
|
||||
&hex::decode(
|
||||
"\
|
||||
f4e8cf80aec3f888d997900ac7e3e349944b5a6b47649fc32186d2f1238103c6\
|
||||
0c9c1a0fe806c184add50bbdcac913dda73e482daf95dcb9f35dbb0d8a9f7731"
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
.to_repr()
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<Vec<_>>(),
|
||||
hex::decode("f871dfcf6bcd199342651adc361b92c941cb6a0d8c8c1a3b91d79e2c1bf3722d").unwrap()
|
||||
);
|
||||
|
||||
test_oversize_dst::<P256>();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ use rand_core::{RngCore, CryptoRng};
|
||||
use zeroize::Zeroize;
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
use digest::{core_api::BlockSizeUser, Digest, HashMarker};
|
||||
pub use digest;
|
||||
use digest::{array::ArraySize, block_api::BlockSizeUser, OutputSizeUser, Digest, HashMarker};
|
||||
use transcript::SecureDigest;
|
||||
|
||||
pub use group;
|
||||
@@ -26,13 +27,25 @@ use group::{
|
||||
#[cfg(feature = "alloc")]
|
||||
use group::GroupEncoding;
|
||||
|
||||
pub trait FromUniformBytes<T> {
|
||||
fn from_uniform_bytes(bytes: &T) -> Self;
|
||||
}
|
||||
impl<const N: usize, F: group::ff::FromUniformBytes<N>> FromUniformBytes<[u8; N]> for F {
|
||||
fn from_uniform_bytes(bytes: &[u8; N]) -> Self {
|
||||
F::from_uniform_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Unified trait defining a ciphersuite around an elliptic curve.
|
||||
pub trait Ciphersuite:
|
||||
'static + Send + Sync + Clone + Copy + PartialEq + Eq + Debug + Zeroize
|
||||
{
|
||||
/// Scalar field element type.
|
||||
// This is available via G::Scalar yet `C::G::Scalar` is ambiguous, forcing horrific accesses
|
||||
type F: PrimeField + PrimeFieldBits + Zeroize;
|
||||
type F: PrimeField
|
||||
+ PrimeFieldBits
|
||||
+ Zeroize
|
||||
+ FromUniformBytes<<<Self::H as OutputSizeUser>::OutputSize as ArraySize>::ArrayType<u8>>;
|
||||
/// Group element type.
|
||||
type G: Group<Scalar = Self::F> + GroupOps + PrimeGroup + Zeroize + ConstantTimeEq;
|
||||
/// Hash algorithm used with this curve.
|
||||
@@ -46,16 +59,10 @@ pub trait Ciphersuite:
|
||||
// While group does provide this in its API, privacy coins may want to use a custom basepoint
|
||||
fn generator() -> Self::G;
|
||||
|
||||
/// Hash the provided domain-separation tag and message to a scalar. Ciphersuites MAY naively
|
||||
/// prefix the tag to the message, enabling transpotion between the two. Accordingly, this
|
||||
/// function should NOT be used in any scheme where one tag is a valid substring of another
|
||||
/// UNLESS the specific Ciphersuite is verified to handle the DST securely.
|
||||
///
|
||||
/// Verifying specific ciphersuites have secure tag handling is not recommended, due to it
|
||||
/// breaking the intended modularity of ciphersuites. Instead, component-specific tags with
|
||||
/// further purpose tags are recommended ("Schnorr-nonce", "Schnorr-chal").
|
||||
#[allow(non_snake_case)]
|
||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F;
|
||||
fn hash_to_F(data: &[u8]) -> Self::F {
|
||||
Self::F::from_uniform_bytes(&Self::H::digest(data).into())
|
||||
}
|
||||
|
||||
/// Generate a random non-zero scalar.
|
||||
#[allow(non_snake_case)]
|
||||
|
||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dalek-ff-gr
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["curve25519", "ed25519", "ristretto", "dalek", "group"]
|
||||
edition = "2021"
|
||||
rust-version = "1.73"
|
||||
rust-version = "1.85"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -25,7 +25,7 @@ subtle = { version = "^2.4", default-features = false }
|
||||
rand_core = { version = "0.6", default-features = false }
|
||||
|
||||
digest = { version = "0.10", default-features = false }
|
||||
sha2 = { version = "0.10", default-features = false }
|
||||
sha2 = { version = "0.11.0-rc.0", default-features = false }
|
||||
|
||||
ff = { version = "0.13", default-features = false, features = ["bits"] }
|
||||
group = { version = "0.13", default-features = false }
|
||||
@@ -42,5 +42,5 @@ ff-group-tests = { path = "../ff-group-tests" }
|
||||
|
||||
[features]
|
||||
alloc = ["zeroize/alloc", "ciphersuite/alloc", "curve25519-dalek/alloc"]
|
||||
std = ["alloc", "zeroize/std", "subtle/std", "rand_core/std", "digest/std", "sha2/std", "ciphersuite/std"]
|
||||
std = ["alloc", "zeroize/std", "subtle/std", "rand_core/std", "digest/std", "ciphersuite/std"]
|
||||
default = ["std"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use sha2::{Digest, Sha512};
|
||||
use sha2::Sha512;
|
||||
|
||||
use group::Group;
|
||||
use crate::Scalar;
|
||||
@@ -27,71 +27,24 @@ macro_rules! dalek_curve {
|
||||
fn generator() -> Self::G {
|
||||
$Point::generator()
|
||||
}
|
||||
|
||||
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
||||
let mut digest = Sha512::new();
|
||||
digest.update(dst);
|
||||
digest.update(data);
|
||||
Scalar::from_hash(digest)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Ciphersuite for Ristretto.
|
||||
///
|
||||
/// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition
|
||||
/// between the two. This means `dst: b"abc", data: b"def"`, will produce the same scalar as
|
||||
/// `dst: "abcdef", data: b""`. Please use carefully, not letting dsts be substrings of each other.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct Ristretto;
|
||||
dalek_curve!("ristretto", Ristretto, RistrettoPoint, b"ristretto");
|
||||
#[test]
|
||||
fn test_ristretto() {
|
||||
ff_group_tests::group::test_prime_group_bits::<_, RistrettoPoint>(&mut rand_core::OsRng);
|
||||
|
||||
assert_eq!(
|
||||
Ristretto::hash_to_F(
|
||||
b"FROST-RISTRETTO255-SHA512-v11nonce",
|
||||
&hex::decode(
|
||||
"\
|
||||
81800157bb554f299fe0b6bd658e4c4591d74168b5177bf55e8dceed59dc80c7\
|
||||
5c3430d391552f6e60ecdc093ff9f6f4488756aa6cebdbad75a768010b8f830e"
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
.to_bytes()
|
||||
.as_ref(),
|
||||
&hex::decode("40f58e8df202b21c94f826e76e4647efdb0ea3ca7ae7e3689bc0cbe2e2f6660c").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
/// Ciphersuite for Ed25519, inspired by RFC-8032.
|
||||
///
|
||||
/// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition
|
||||
/// between the two. This means `dst: b"abc", data: b"def"`, will produce the same scalar as
|
||||
/// `dst: "abcdef", data: b""`. Please use carefully, not letting dsts be substrings of each other.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct Ed25519;
|
||||
dalek_curve!("ed25519", Ed25519, EdwardsPoint, b"edwards25519");
|
||||
#[test]
|
||||
fn test_ed25519() {
|
||||
ff_group_tests::group::test_prime_group_bits::<_, EdwardsPoint>(&mut rand_core::OsRng);
|
||||
|
||||
// Ideally, a test vector from RFC-8032 (not FROST) would be here
|
||||
// Unfortunately, the IETF draft doesn't provide any vectors for the derived challenges
|
||||
assert_eq!(
|
||||
Ed25519::hash_to_F(
|
||||
b"FROST-ED25519-SHA512-v11nonce",
|
||||
&hex::decode(
|
||||
"\
|
||||
9d06a6381c7a4493929761a73692776772b274236fb5cfcc7d1b48ac3a9c249f\
|
||||
929dcc590407aae7d388761cddb0c0db6f5627aea8e217f4a033f2ec83d93509"
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
.to_bytes()
|
||||
.as_ref(),
|
||||
&hex::decode("70652da3e8d7533a0e4b9e9104f01b48c396b5b553717784ed8d05c6a36b9609").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dkg"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["dkg", "multisig", "threshold", "ff", "group"]
|
||||
edition = "2021"
|
||||
rust-version = "1.73"
|
||||
rust-version = "1.85"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dkg/dealer"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["dkg", "multisig", "threshold", "ff", "group"]
|
||||
edition = "2021"
|
||||
rust-version = "1.73"
|
||||
rust-version = "1.85"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dkg/musig"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["dkg", "multisig", "threshold", "ff", "group"]
|
||||
edition = "2021"
|
||||
rust-version = "1.79"
|
||||
rust-version = "1.85"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -11,7 +11,7 @@ use std_shims::{
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use ciphersuite::{group::GroupEncoding, Ciphersuite};
|
||||
use ciphersuite::{digest::Digest, group::GroupEncoding, FromUniformBytes, Ciphersuite};
|
||||
|
||||
pub use dkg::*;
|
||||
|
||||
@@ -80,7 +80,7 @@ fn binding_factor_transcript<C: Ciphersuite>(
|
||||
|
||||
fn binding_factor<C: Ciphersuite>(mut transcript: Vec<u8>, i: u16) -> C::F {
|
||||
transcript.extend(i.to_le_bytes());
|
||||
C::hash_to_F(b"dkg-musig", &transcript)
|
||||
C::F::from_uniform_bytes(&C::H::digest(&transcript).into())
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
|
||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dkg/recover
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["dkg", "multisig", "threshold", "ff", "group"]
|
||||
edition = "2021"
|
||||
rust-version = "1.73"
|
||||
rust-version = "1.85"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -19,7 +19,7 @@ workspace = true
|
||||
[dependencies]
|
||||
zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] }
|
||||
|
||||
sha3 = { version = "0.10", default-features = false }
|
||||
sha3 = { version = "0.11.0-rc.0", default-features = false }
|
||||
|
||||
prime-field = { path = "../prime-field", default-features = false }
|
||||
ciphersuite = { path = "../ciphersuite", default-features = false }
|
||||
@@ -33,5 +33,5 @@ ff-group-tests = { path = "../ff-group-tests" }
|
||||
|
||||
[features]
|
||||
alloc = ["zeroize/alloc", "prime-field/alloc", "ciphersuite/alloc"]
|
||||
std = ["alloc", "zeroize/std", "sha3/std", "prime-field/std", "ciphersuite/std"]
|
||||
std = ["alloc", "zeroize/std", "prime-field/std", "ciphersuite/std"]
|
||||
default = ["std"]
|
||||
|
||||
@@ -3,15 +3,12 @@ use zeroize::Zeroize;
|
||||
use sha3::{
|
||||
digest::{
|
||||
typenum::U114, core_api::BlockSizeUser, Update, Output, OutputSizeUser, FixedOutput,
|
||||
ExtendableOutput, XofReader, HashMarker, Digest,
|
||||
ExtendableOutput, XofReader, HashMarker,
|
||||
},
|
||||
Shake256,
|
||||
};
|
||||
|
||||
use ciphersuite::{
|
||||
group::{ff::FromUniformBytes, Group},
|
||||
Ciphersuite,
|
||||
};
|
||||
use ciphersuite::{group::Group, Ciphersuite};
|
||||
|
||||
use crate::{Scalar, Point};
|
||||
|
||||
@@ -52,11 +49,6 @@ impl FixedOutput for Shake256_114 {
|
||||
}
|
||||
impl HashMarker for Shake256_114 {}
|
||||
|
||||
/// Ciphersuite for Ed448, inspired by RFC-8032. This is not recommended for usage.
|
||||
///
|
||||
/// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition
|
||||
/// between the two. This means `dst: b"abc", data: b"def"`, will produce the same scalar as
|
||||
/// `dst: "abcdef", data: b""`. Please use carefully, not letting dsts be substrings of each other.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct Ed448;
|
||||
impl Ciphersuite for Ed448 {
|
||||
@@ -69,48 +61,9 @@ impl Ciphersuite for Ed448 {
|
||||
fn generator() -> Self::G {
|
||||
Point::generator()
|
||||
}
|
||||
|
||||
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
||||
let mut digest = Self::H::new();
|
||||
Update::update(&mut digest, dst);
|
||||
Update::update(&mut digest, data);
|
||||
let digest = digest.finalize();
|
||||
|
||||
let mut wide_scalar = [0; 114];
|
||||
wide_scalar.copy_from_slice(digest.as_ref());
|
||||
Scalar::from_uniform_bytes(&wide_scalar)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ed448() {
|
||||
use ciphersuite::group::ff::PrimeField;
|
||||
|
||||
ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng);
|
||||
|
||||
// Ideally, a test vector from RFC-8032 (not FROST) would be here
|
||||
// Unfortunately, the IETF draft doesn't provide any vectors for the derived challenges
|
||||
assert_eq!(
|
||||
Ed448::hash_to_F(
|
||||
b"FROST-ED448-SHAKE256-v11nonce",
|
||||
&hex::decode(
|
||||
"\
|
||||
89bf16040081ff2990336b200613787937ebe1f024b8cdff90eb6f1c741d91c1\
|
||||
4a2b2f5858a932ad3d3b18bd16e76ced3070d72fd79ae4402df201f5\
|
||||
25e754716a1bc1b87a502297f2a99d89ea054e0018eb55d39562fd01\
|
||||
00"
|
||||
)
|
||||
.unwrap()
|
||||
)
|
||||
.to_repr()
|
||||
.as_ref(),
|
||||
hex::decode(
|
||||
"\
|
||||
67a6f023e77361707c6e894c625e809e80f33fdb310810053ae29e28\
|
||||
e7011f3193b9020e73c183a98cc3a519160ed759376dd92c94831622\
|
||||
00"
|
||||
)
|
||||
.unwrap()
|
||||
.as_slice()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ short-weierstrass = { path = "../short-weierstrass", default-features = false }
|
||||
curve25519-dalek = { version = "4", default-features = false, features = ["legacy_compatibility"] }
|
||||
dalek-ff-group = { path = "../dalek-ff-group", version = "0.4", default-features = false }
|
||||
|
||||
blake2 = { version = "0.10", default-features = false }
|
||||
blake2 = { version = "0.11.0-rc.0", default-features = false }
|
||||
ciphersuite = { path = "../ciphersuite", version = "0.4", default-features = false }
|
||||
|
||||
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
|
||||
@@ -41,5 +41,5 @@ ff-group-tests = { path = "../ff-group-tests" }
|
||||
|
||||
[features]
|
||||
alloc = ["std-shims", "zeroize/alloc", "prime-field/alloc", "short-weierstrass/alloc", "curve25519-dalek/alloc", "ciphersuite/alloc", "generalized-bulletproofs-ec-gadgets"]
|
||||
std = ["alloc", "std-shims/std", "zeroize/std", "prime-field/std", "short-weierstrass/std", "blake2/std", "ciphersuite/std", "generalized-bulletproofs-ec-gadgets/std"]
|
||||
std = ["alloc", "std-shims/std", "zeroize/std", "prime-field/std", "short-weierstrass/std", "ciphersuite/std", "generalized-bulletproofs-ec-gadgets/std"]
|
||||
default = ["std"]
|
||||
|
||||
@@ -10,7 +10,7 @@ use std_shims::io::{self, Read};
|
||||
|
||||
use prime_field::{subtle::Choice, zeroize::Zeroize};
|
||||
use ciphersuite::group::{
|
||||
ff::{Field, PrimeField, FromUniformBytes},
|
||||
ff::{Field, PrimeField},
|
||||
Group,
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ impl ShortWeierstrass for Embedwards25519 {
|
||||
fn encode_compressed(x: Self::FieldElement, odd_y: Choice) -> Self::Repr {
|
||||
// The LE `x` coordinate, with if `y` is odd in the unused 256th bit
|
||||
let mut res = [0; 32];
|
||||
res.as_mut().copy_from_slice(x.to_repr().as_ref());
|
||||
res.copy_from_slice(x.to_repr().as_ref());
|
||||
res[31] |= odd_y.unwrap_u8() << 7;
|
||||
res
|
||||
}
|
||||
@@ -65,7 +65,10 @@ impl ShortWeierstrass for Embedwards25519 {
|
||||
|
||||
// Copy from the point's representation to the field's
|
||||
let mut repr = <Self::FieldElement as PrimeField>::Repr::default();
|
||||
repr.as_mut().copy_from_slice(&bytes);
|
||||
{
|
||||
let repr: &mut [u8] = repr.as_mut();
|
||||
repr.copy_from_slice(&bytes);
|
||||
}
|
||||
|
||||
(repr, odd_y)
|
||||
}
|
||||
@@ -88,18 +91,6 @@ impl ciphersuite::Ciphersuite for Embedwards25519 {
|
||||
Point::generator()
|
||||
}
|
||||
|
||||
/// `hash_to_F` is implemented with a naive concatenation of the `dst` and `data`, allowing
|
||||
/// transposition between the two. This means `dst: b"abc", data: b"def"`, will produce the same
|
||||
/// scalar as `dst: "abcdef", data: b""`. Please use carefully, not letting `dst` valuess be
|
||||
/// substrings of each other.
|
||||
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
||||
use blake2::Digest;
|
||||
let mut digest = Self::H::new();
|
||||
digest.update(dst);
|
||||
digest.update(data);
|
||||
<Scalar as FromUniformBytes<64>>::from_uniform_bytes(&digest.finalize().into())
|
||||
}
|
||||
|
||||
// We override the provided impl, which compares against the reserialization, because
|
||||
// we already require canonicity
|
||||
#[cfg(feature = "alloc")]
|
||||
|
||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/frost"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["frost", "multisig", "threshold"]
|
||||
edition = "2021"
|
||||
rust-version = "1.79"
|
||||
rust-version = "1.85"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -27,13 +27,14 @@ subtle = { version = "^2.4", default-features = false, features = ["std"] }
|
||||
|
||||
hex = { version = "0.4", default-features = false, features = ["std"], optional = true }
|
||||
|
||||
digest = { version = "0.10", default-features = false, features = ["std"] }
|
||||
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, features = ["std", "recommended"] }
|
||||
|
||||
dalek-ff-group = { path = "../dalek-ff-group", version = "0.4", default-features = false, features = ["std"], optional = true }
|
||||
minimal-ed448 = { path = "../ed448", version = "0.4", default-features = false, features = ["std"], optional = true }
|
||||
|
||||
ciphersuite = { path = "../ciphersuite", version = "^0.4.1", default-features = false, features = ["std"] }
|
||||
sha2 = { version = "0.10.0", default-features = false, optional = true }
|
||||
elliptic-curve = { version = "0.13", default-features = false, features = ["hash2curve"], optional = true }
|
||||
ciphersuite-kp256 = { path = "../ciphersuite/kp256", version = "0.4", default-features = false, features = ["std"], optional = true }
|
||||
|
||||
multiexp = { path = "../multiexp", version = "0.4", default-features = false, features = ["std", "batch"] }
|
||||
@@ -56,8 +57,8 @@ dkg-dealer = { path = "../dkg/dealer", default-features = false, features = ["st
|
||||
ed25519 = ["dalek-ff-group"]
|
||||
ristretto = ["dalek-ff-group"]
|
||||
|
||||
secp256k1 = ["ciphersuite-kp256"]
|
||||
p256 = ["ciphersuite-kp256"]
|
||||
secp256k1 = ["sha2", "elliptic-curve", "ciphersuite-kp256"]
|
||||
p256 = ["sha2", "elliptic-curve", "ciphersuite-kp256"]
|
||||
|
||||
ed448 = ["minimal-ed448"]
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use digest::Digest;
|
||||
|
||||
use ciphersuite::{digest::Digest, FromUniformBytes, Ciphersuite};
|
||||
use dalek_ff_group::Scalar;
|
||||
|
||||
use ciphersuite::Ciphersuite;
|
||||
|
||||
use crate::{curve::Curve, algorithm::Hram};
|
||||
|
||||
macro_rules! dalek_curve {
|
||||
@@ -20,6 +17,13 @@ macro_rules! dalek_curve {
|
||||
|
||||
impl Curve for $Curve {
|
||||
const CONTEXT: &'static [u8] = $CONTEXT;
|
||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
||||
let mut digest = <Self as Ciphersuite>::H::new();
|
||||
digest.update(Self::CONTEXT);
|
||||
digest.update(dst);
|
||||
digest.update(msg);
|
||||
Self::F::from_uniform_bytes(&digest.finalize().into())
|
||||
}
|
||||
}
|
||||
|
||||
/// The challenge function for this ciphersuite.
|
||||
@@ -30,11 +34,13 @@ macro_rules! dalek_curve {
|
||||
fn hram(R: &<$Curve as Ciphersuite>::G, A: &<$Curve as Ciphersuite>::G, m: &[u8]) -> Scalar {
|
||||
let mut hash = <$Curve as Ciphersuite>::H::new();
|
||||
if $chal.len() != 0 {
|
||||
hash.update(&[$CONTEXT.as_ref(), $chal].concat());
|
||||
hash.update($CONTEXT);
|
||||
hash.update($chal);
|
||||
}
|
||||
Scalar::from_hash(
|
||||
hash.chain_update(&[&R.compress().to_bytes(), &A.compress().to_bytes(), m].concat()),
|
||||
)
|
||||
hash.update(R.compress().to_bytes());
|
||||
hash.update(A.compress().to_bytes());
|
||||
hash.update(m);
|
||||
Scalar::from_uniform_bytes(&hash.finalize().into())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
use digest::Digest;
|
||||
|
||||
pub use ciphersuite::{digest::Digest, group::GroupEncoding, FromUniformBytes, Ciphersuite};
|
||||
use minimal_ed448::{Scalar, Point};
|
||||
pub use minimal_ed448::Ed448;
|
||||
pub use ciphersuite::{
|
||||
group::{ff::FromUniformBytes, GroupEncoding},
|
||||
Ciphersuite,
|
||||
};
|
||||
|
||||
use crate::{curve::Curve, algorithm::Hram};
|
||||
|
||||
@@ -13,6 +8,13 @@ const CONTEXT: &[u8] = b"FROST-ED448-SHAKE256-v1";
|
||||
|
||||
impl Curve for Ed448 {
|
||||
const CONTEXT: &'static [u8] = CONTEXT;
|
||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
||||
let mut digest = <Self as Ciphersuite>::H::new();
|
||||
digest.update(Self::CONTEXT);
|
||||
digest.update(dst);
|
||||
digest.update(msg);
|
||||
Self::F::from_uniform_bytes(&digest.finalize().into())
|
||||
}
|
||||
}
|
||||
|
||||
// The RFC-8032 Ed448 challenge function.
|
||||
@@ -21,20 +23,14 @@ pub(crate) struct Ietf8032Ed448Hram;
|
||||
impl Ietf8032Ed448Hram {
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn hram(context: &[u8], R: &Point, A: &Point, m: &[u8]) -> Scalar {
|
||||
Scalar::from_uniform_bytes(
|
||||
&<[u8; 114]>::try_from(
|
||||
<Ed448 as Ciphersuite>::H::digest(
|
||||
[
|
||||
&[b"SigEd448".as_ref(), &[0, u8::try_from(context.len()).unwrap()]].concat(),
|
||||
context,
|
||||
&[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat(),
|
||||
]
|
||||
.concat(),
|
||||
)
|
||||
.as_slice(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
let mut digest = <Ed448 as Ciphersuite>::H::new();
|
||||
digest.update(b"SigEd448");
|
||||
digest.update([0, u8::try_from(context.len()).unwrap()]);
|
||||
digest.update(context);
|
||||
digest.update(R.to_bytes());
|
||||
digest.update(A.to_bytes());
|
||||
digest.update(m);
|
||||
Scalar::from_uniform_bytes(&digest.finalize().into())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,85 @@
|
||||
use ciphersuite::{group::GroupEncoding, Ciphersuite};
|
||||
use core::convert::AsRef;
|
||||
|
||||
use sha2::{digest::Digest, Sha256};
|
||||
|
||||
use ciphersuite::{
|
||||
group::{
|
||||
ff::{Field, PrimeField},
|
||||
GroupEncoding,
|
||||
},
|
||||
Ciphersuite,
|
||||
};
|
||||
|
||||
use elliptic_curve::{
|
||||
zeroize::Zeroize,
|
||||
generic_array::{typenum::U32, GenericArray},
|
||||
bigint::{NonZero, CheckedAdd, Encoding, U384},
|
||||
hash2curve::{Expander, ExpandMsg, ExpandMsgXmd},
|
||||
};
|
||||
|
||||
use crate::{curve::Curve, algorithm::Hram};
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn hash_to_F<C: Ciphersuite<F: PrimeField<Repr = GenericArray<u8, U32>>>>(
|
||||
dst: &[u8],
|
||||
msg: &[u8],
|
||||
) -> C::F {
|
||||
// While one of these two libraries does support directly hashing to the Scalar field, the
|
||||
// other doesn't. While that's probably an oversight, this is a universally working method
|
||||
|
||||
// This method is from
|
||||
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html
|
||||
// Specifically, Section 5
|
||||
|
||||
// While that draft, overall, is intended for hashing to curves, that necessitates
|
||||
// detailing how to hash to a finite field. The draft comments that its mechanism for
|
||||
// doing so, which it uses to derive field elements, is also applicable to the scalar field
|
||||
|
||||
// The hash_to_field function is intended to provide unbiased values
|
||||
// In order to do so, a wide reduction from an extra k bits is applied, minimizing bias to
|
||||
// 2^-k
|
||||
// k is intended to be the bits of security of the suite, which is 128 for secp256k1 and
|
||||
// P-256
|
||||
const K: usize = 128;
|
||||
|
||||
// L is the amount of bytes of material which should be used in the wide reduction
|
||||
// The 256 is for the bit-length of the primes, rounded up to the nearest byte threshold
|
||||
// This is a simplification of the formula from the end of section 5
|
||||
const L: usize = (256 + K) / 8; // 48
|
||||
|
||||
// In order to perform this reduction, we need to use 48-byte numbers
|
||||
// First, convert the modulus to a 48-byte number
|
||||
// This is done by getting -1 as bytes, parsing it into a U384, and then adding back one
|
||||
let mut modulus = [0; L];
|
||||
// The byte repr of scalars will be 32 big-endian bytes
|
||||
// Set the lower 32 bytes of our 48-byte array accordingly
|
||||
modulus[16 ..].copy_from_slice(&(C::F::ZERO - C::F::ONE).to_repr());
|
||||
// Use a checked_add + unwrap since this addition cannot fail (being a 32-byte value with
|
||||
// 48-bytes of space)
|
||||
// While a non-panicking saturating_add/wrapping_add could be used, they'd likely be less
|
||||
// performant
|
||||
let modulus = U384::from_be_slice(&modulus).checked_add(&U384::ONE).unwrap();
|
||||
|
||||
// The defined P-256 and secp256k1 ciphersuites both use expand_message_xmd
|
||||
let mut wide = U384::from_be_bytes({
|
||||
let mut bytes = [0; 48];
|
||||
ExpandMsgXmd::<Sha256>::expand_message(&[msg], &[dst], 48).unwrap().fill_bytes(&mut bytes);
|
||||
bytes
|
||||
})
|
||||
.rem(&NonZero::new(modulus).unwrap())
|
||||
.to_be_bytes();
|
||||
|
||||
// Now that this has been reduced back to a 32-byte value, grab the lower 32-bytes
|
||||
let mut array = *GenericArray::from_slice(&wide[16 ..]);
|
||||
let res = C::F::from_repr(array).unwrap();
|
||||
|
||||
// Zeroize the temp values we can due to the possibility `hash_to_F` is being used for
|
||||
// nonces
|
||||
wide.zeroize();
|
||||
array.zeroize();
|
||||
res
|
||||
}
|
||||
|
||||
macro_rules! kp_curve {
|
||||
(
|
||||
$feature: literal,
|
||||
@@ -15,6 +93,17 @@ macro_rules! kp_curve {
|
||||
|
||||
impl Curve for $Curve {
|
||||
const CONTEXT: &'static [u8] = $CONTEXT;
|
||||
|
||||
// These ciphersuites define their hash as SHA-512, yet FROST uses SHA-256
|
||||
fn hash(dst: &[u8], data: &[u8]) -> impl AsRef<[u8]> {
|
||||
sha2::Sha256::digest([Self::CONTEXT, dst, data].concat())
|
||||
}
|
||||
|
||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
||||
let dst = [Self::CONTEXT, dst].concat();
|
||||
let dst = dst.as_slice();
|
||||
hash_to_F::<Self>(dst, msg)
|
||||
}
|
||||
}
|
||||
|
||||
/// The challenge function for this ciphersuite.
|
||||
@@ -41,3 +130,27 @@ kp_curve!("p256", P256, IetfP256Hram, b"FROST-P256-SHA256-v1");
|
||||
|
||||
#[cfg(feature = "secp256k1")]
|
||||
kp_curve!("secp256k1", Secp256k1, IetfSecp256k1Hram, b"FROST-secp256k1-SHA256-v1");
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_oversize_dst<C: Ciphersuite<F: PrimeField<Repr = GenericArray<u8, U32>>>>() {
|
||||
use sha2::Digest;
|
||||
|
||||
// The draft specifies DSTs >255 bytes should be hashed into a 32-byte DST
|
||||
let oversize_dst = [0x00; 256];
|
||||
let actual_dst = Sha256::digest([b"H2C-OVERSIZE-DST-".as_slice(), &oversize_dst].concat());
|
||||
// Test the hash_to_F function handles this
|
||||
// If it didn't, these would return different values
|
||||
assert_eq!(hash_to_F::<C>(&oversize_dst, &[]), hash_to_F::<C>(&actual_dst, &[]));
|
||||
}
|
||||
|
||||
#[cfg(feature = "secp256k1")]
|
||||
#[test]
|
||||
fn test_secp256k1() {
|
||||
test_oversize_dst::<Secp256k1>();
|
||||
}
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
#[test]
|
||||
fn test_p256() {
|
||||
test_oversize_dst::<P256>();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use core::ops::Deref;
|
||||
use core::{ops::Deref, convert::AsRef};
|
||||
use std::io::{self, Read};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
@@ -6,9 +6,8 @@ use rand_core::{RngCore, CryptoRng};
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
use digest::{Digest, Output};
|
||||
|
||||
pub use ciphersuite::{
|
||||
digest::Digest,
|
||||
group::{
|
||||
ff::{Field, PrimeField},
|
||||
Group,
|
||||
@@ -46,24 +45,23 @@ pub trait Curve: Ciphersuite {
|
||||
const CONTEXT: &'static [u8];
|
||||
|
||||
/// Hash the given dst and data to a byte vector. Used to instantiate H4 and H5.
|
||||
fn hash(dst: &[u8], data: &[u8]) -> Output<Self::H> {
|
||||
fn hash(dst: &[u8], data: &[u8]) -> impl AsRef<[u8]> {
|
||||
Self::H::digest([Self::CONTEXT, dst, data].concat())
|
||||
}
|
||||
|
||||
/// Field element from hash. Used during key gen and by other crates under Serai as a general
|
||||
/// utility. Used to instantiate H1 and H3.
|
||||
/// Field element from hash. Used to instantiate H1 and H3.
|
||||
///
|
||||
/// The `dst` MUST be prefixed by `Self::CONTEXT` by the implementor.
|
||||
#[allow(non_snake_case)]
|
||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
||||
<Self as Ciphersuite>::hash_to_F(&[Self::CONTEXT, dst].concat(), msg)
|
||||
}
|
||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F;
|
||||
|
||||
/// Hash the message for the binding factor. H4 from the IETF draft.
|
||||
fn hash_msg(msg: &[u8]) -> Output<Self::H> {
|
||||
fn hash_msg(msg: &[u8]) -> impl AsRef<[u8]> {
|
||||
Self::hash(b"msg", msg)
|
||||
}
|
||||
|
||||
/// Hash the commitments for the binding factor. H5 from the IETF draft.
|
||||
fn hash_commitments(commitments: &[u8]) -> Output<Self::H> {
|
||||
fn hash_commitments(commitments: &[u8]) -> impl AsRef<[u8]> {
|
||||
Self::hash(b"com", commitments)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/schnorr"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["schnorr", "ff", "group"]
|
||||
edition = "2021"
|
||||
rust-version = "1.79"
|
||||
rust-version = "1.85"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/schnorrkel"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["frost", "multisig", "threshold", "schnorrkel"]
|
||||
edition = "2021"
|
||||
rust-version = "1.79"
|
||||
rust-version = "1.85"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -23,7 +23,7 @@ k256 = { version = "0.13", default-features = false, features = ["arithmetic"] }
|
||||
prime-field = { path = "../prime-field", default-features = false }
|
||||
short-weierstrass = { path = "../short-weierstrass", default-features = false }
|
||||
|
||||
blake2 = { version = "0.10", default-features = false }
|
||||
blake2 = { version = "0.11.0-rc.0", default-features = false }
|
||||
ciphersuite = { path = "../ciphersuite", version = "0.4", default-features = false }
|
||||
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
|
||||
|
||||
@@ -36,5 +36,5 @@ ff-group-tests = { path = "../ff-group-tests" }
|
||||
|
||||
[features]
|
||||
alloc = ["std-shims", "generic-array/alloc", "k256/alloc", "prime-field/alloc", "short-weierstrass/alloc", "ciphersuite/alloc", "generalized-bulletproofs-ec-gadgets"]
|
||||
std = ["alloc", "std-shims/std", "k256/std", "prime-field/std", "blake2/std", "ciphersuite/std", "generalized-bulletproofs-ec-gadgets/std"]
|
||||
std = ["alloc", "std-shims/std", "k256/std", "prime-field/std", "ciphersuite/std", "generalized-bulletproofs-ec-gadgets/std"]
|
||||
default = ["std"]
|
||||
|
||||
@@ -13,10 +13,7 @@ use generic_array::{typenum::U33, GenericArray};
|
||||
use k256::elliptic_curve::{
|
||||
subtle::{Choice, ConstantTimeEq, ConditionallySelectable},
|
||||
zeroize::Zeroize,
|
||||
group::{
|
||||
ff::{PrimeField, FromUniformBytes},
|
||||
Group,
|
||||
},
|
||||
group::{ff::PrimeField, Group},
|
||||
sec1::Tag,
|
||||
};
|
||||
|
||||
@@ -121,18 +118,6 @@ impl ciphersuite::Ciphersuite for Secq256k1 {
|
||||
Point::generator()
|
||||
}
|
||||
|
||||
/// `hash_to_F` is implemented with a naive concatenation of the `dst` and `data`, allowing
|
||||
/// transposition between the two. This means `dst: b"abc", data: b"def"`, will produce the same
|
||||
/// scalar as `dst: "abcdef", data: b""`. Please use carefully, not letting `dst` valuess be
|
||||
/// substrings of each other.
|
||||
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
||||
use blake2::Digest;
|
||||
let mut digest = Self::H::new();
|
||||
digest.update(dst);
|
||||
digest.update(data);
|
||||
<Scalar as FromUniformBytes<64>>::from_uniform_bytes(&digest.finalize().into())
|
||||
}
|
||||
|
||||
// We override the provided impl, which compares against the reserialization, because
|
||||
// we already require canonicity
|
||||
#[cfg(feature = "alloc")]
|
||||
|
||||
@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/transcript"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["transcript"]
|
||||
edition = "2021"
|
||||
rust-version = "1.73"
|
||||
rust-version = "1.85"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -19,17 +19,17 @@ workspace = true
|
||||
[dependencies]
|
||||
zeroize = { version = "^1.5", default-features = false }
|
||||
|
||||
digest = { version = "0.10", default-features = false, features = ["core-api"] }
|
||||
digest = { version = "0.11.0-rc.0", default-features = false, features = ["block-api"] }
|
||||
|
||||
blake2 = { version = "0.10", default-features = false, optional = true }
|
||||
blake2 = { version = "0.11.0-rc.0", default-features = false, optional = true }
|
||||
merlin = { version = "3", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
sha2 = { version = "0.10", default-features = false }
|
||||
blake2 = { version = "0.10", default-features = false }
|
||||
sha2 = { version = "0.11.0-rc.0", default-features = false }
|
||||
blake2 = { version = "0.11.0-rc.0", default-features = false }
|
||||
|
||||
[features]
|
||||
std = ["zeroize/std", "digest/std", "blake2?/std", "merlin?/std"]
|
||||
std = ["zeroize/std", "merlin?/std"]
|
||||
recommended = ["blake2"]
|
||||
tests = []
|
||||
default = ["std"]
|
||||
|
||||
@@ -8,7 +8,7 @@ use digest::{
|
||||
typenum::{
|
||||
consts::U32, marker_traits::NonZero, type_operators::IsGreaterOrEqual, operator_aliases::GrEq,
|
||||
},
|
||||
core_api::BlockSizeUser,
|
||||
block_api::BlockSizeUser,
|
||||
Digest, Output, HashMarker,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user