mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-05 10:29:40 +00:00
Re-organize testing strategy and document Ciphersuite::hash_to_F.
This commit is contained in:
parent
35a4f5bf9f
commit
da8e7e73e0
13 changed files with 114 additions and 40 deletions
|
@ -33,6 +33,9 @@ k256 = { version = "0.11", features = ["arithmetic", "bits", "hash2curve"], opti
|
||||||
|
|
||||||
minimal-ed448 = { path = "../ed448", version = "^0.1.2", optional = true }
|
minimal-ed448 = { path = "../ed448", version = "^0.1.2", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
ff-group-tests = { version = "0.12", path = "../ff-group-tests" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = []
|
std = []
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,35 @@
|
||||||
# Ciphersuite
|
# Ciphersuite
|
||||||
|
|
||||||
Ciphersuites for elliptic curves premised on ff/group.
|
Ciphersuites for elliptic curves premised on ff/group.
|
||||||
|
|
||||||
|
### Secp256k1/P-256
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Ed25519/Ristretto
|
||||||
|
|
||||||
|
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-rtf-cfrg-ristretto255-decaf448-05.html).
|
||||||
|
The domain-separation tag is naively prefixed to the message.
|
||||||
|
|
||||||
|
### Ed448
|
||||||
|
|
||||||
|
Ed448 is offered via [minimal-ed448](https://crates.io/crates/minimal-ed448), an
|
||||||
|
explicitly not recommended 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.
|
||||||
|
|
|
@ -39,6 +39,16 @@ macro_rules! dalek_curve {
|
||||||
|
|
||||||
#[cfg(any(test, feature = "ristretto"))]
|
#[cfg(any(test, feature = "ristretto"))]
|
||||||
dalek_curve!("ristretto", Ristretto, RistrettoPoint, b"ristretto");
|
dalek_curve!("ristretto", Ristretto, RistrettoPoint, b"ristretto");
|
||||||
|
#[cfg(any(test, feature = "ristretto"))]
|
||||||
|
#[test]
|
||||||
|
fn test_ristretto() {
|
||||||
|
ff_group_tests::group::test_prime_group_bits::<RistrettoPoint>();
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ed25519")]
|
#[cfg(feature = "ed25519")]
|
||||||
dalek_curve!("ed25519", Ed25519, EdwardsPoint, b"edwards25519");
|
dalek_curve!("ed25519", Ed25519, EdwardsPoint, b"edwards25519");
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
#[test]
|
||||||
|
fn test_ed25519() {
|
||||||
|
ff_group_tests::group::test_prime_group_bits::<EdwardsPoint>();
|
||||||
|
}
|
||||||
|
|
|
@ -65,3 +65,9 @@ impl Ciphersuite for Ed448 {
|
||||||
Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_ref().try_into().unwrap())
|
Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_ref().try_into().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ed448() {
|
||||||
|
// TODO: Enable once ed448 passes these tests
|
||||||
|
//ff_group_tests::group::test_prime_group_bits::<Point>();
|
||||||
|
}
|
||||||
|
|
|
@ -67,6 +67,16 @@ macro_rules! kp_curve {
|
||||||
|
|
||||||
#[cfg(feature = "p256")]
|
#[cfg(feature = "p256")]
|
||||||
kp_curve!("p256", p256, P256, b"P-256");
|
kp_curve!("p256", p256, P256, b"P-256");
|
||||||
|
#[cfg(feature = "p256")]
|
||||||
|
#[test]
|
||||||
|
fn test_p256() {
|
||||||
|
ff_group_tests::group::test_prime_group_bits::<p256::ProjectivePoint>();
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "secp256k1")]
|
#[cfg(feature = "secp256k1")]
|
||||||
kp_curve!("secp256k1", k256, Secp256k1, b"secp256k1");
|
kp_curve!("secp256k1", k256, Secp256k1, b"secp256k1");
|
||||||
|
#[cfg(feature = "secp256k1")]
|
||||||
|
#[test]
|
||||||
|
fn test_secp256k1() {
|
||||||
|
ff_group_tests::group::test_prime_group_bits::<k256::ProjectivePoint>();
|
||||||
|
}
|
||||||
|
|
|
@ -58,7 +58,14 @@ pub trait Ciphersuite: Clone + Copy + PartialEq + Eq + Debug + Zeroize {
|
||||||
// While group does provide this in its API, privacy coins may want to use a custom basepoint
|
// While group does provide this in its API, privacy coins may want to use a custom basepoint
|
||||||
fn generator() -> Self::G;
|
fn generator() -> Self::G;
|
||||||
|
|
||||||
/// Hash the provided dst and message to a scalar.
|
/// 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)]
|
#[allow(non_snake_case)]
|
||||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F;
|
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F;
|
||||||
|
|
||||||
|
|
|
@ -32,5 +32,8 @@ ciphersuite = { path = "../ciphersuite", version = "0.1", features = ["std"] }
|
||||||
schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "0.2" }
|
schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "0.2" }
|
||||||
dleq = { path = "../dleq", version = "0.2", features = ["serialize"] }
|
dleq = { path = "../dleq", version = "0.2", features = ["serialize"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
ciphersuite = { path = "../ciphersuite", version = "0.1", features = ["std", "ristretto"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tests = []
|
tests = []
|
||||||
|
|
|
@ -68,3 +68,8 @@ pub fn test_ciphersuite<R: RngCore + CryptoRng, C: Ciphersuite>(rng: &mut R) {
|
||||||
key_gen::<_, C>(rng);
|
key_gen::<_, C>(rng);
|
||||||
test_generator_promotion::<_, C>(rng);
|
test_generator_promotion::<_, C>(rng);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_ristretto() {
|
||||||
|
test_ciphersuite::<_, ciphersuite::Ristretto>(&mut rand_core::OsRng);
|
||||||
|
}
|
||||||
|
|
|
@ -52,6 +52,38 @@ fn test_dleq() {
|
||||||
keys[k] = generators[k] * key.deref();
|
keys[k] = generators[k] * key.deref();
|
||||||
}
|
}
|
||||||
proof.verify(&mut transcript(), &generators[.. i], &keys[.. i]).unwrap();
|
proof.verify(&mut transcript(), &generators[.. i], &keys[.. i]).unwrap();
|
||||||
|
// Different challenge
|
||||||
|
assert!(proof
|
||||||
|
.verify(
|
||||||
|
&mut RecommendedTranscript::new(b"different challenge"),
|
||||||
|
&generators[.. i],
|
||||||
|
&keys[.. i]
|
||||||
|
)
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
// We could edit these tests to always test with at least two generators
|
||||||
|
// Then we don't test proofs with zero/one generator(s)
|
||||||
|
// While those are stupid, and pointless, and potentially point to a failure in the caller,
|
||||||
|
// it could also be part of a dynamic system which deals with variable amounts of generators
|
||||||
|
// Not panicking in such use cases, even if they're inefficient, provides seamless behavior
|
||||||
|
if i >= 2 {
|
||||||
|
// Different generators
|
||||||
|
assert!(proof
|
||||||
|
.verify(
|
||||||
|
&mut transcript(),
|
||||||
|
generators[.. i].iter().cloned().rev().collect::<Vec<_>>().as_ref(),
|
||||||
|
&keys[.. i]
|
||||||
|
)
|
||||||
|
.is_err());
|
||||||
|
// Different keys
|
||||||
|
assert!(proof
|
||||||
|
.verify(
|
||||||
|
&mut transcript(),
|
||||||
|
&generators[.. i],
|
||||||
|
keys[.. i].iter().cloned().rev().collect::<Vec<_>>().as_ref()
|
||||||
|
)
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serialize")]
|
#[cfg(feature = "serialize")]
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
Inefficient, barebones implementation of Ed448 bound to the ff/group API,
|
Inefficient, barebones implementation of Ed448 bound to the ff/group API,
|
||||||
rejecting torsion to achieve a PrimeGroup definition. This likely should not be
|
rejecting torsion to achieve a PrimeGroup definition. This likely should not be
|
||||||
used and was only done so another library under Serai could confirm its
|
used and was only done so another library under Serai could confirm its
|
||||||
completion. It is minimally tested, yet should be correct for what it has.
|
completion. It is minimally tested, yet should be correct for what it has. The
|
||||||
Multiple functions remain unimplemented.
|
functions it doesn't have are marked `unimplemented!()`. This has not undergone
|
||||||
|
auditing.
|
||||||
|
|
||||||
constant time and no_std.
|
constant time and no_std.
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
use rand_core::{RngCore, CryptoRng};
|
|
||||||
|
|
||||||
use group::Group;
|
|
||||||
|
|
||||||
use crate::Curve;
|
|
||||||
|
|
||||||
// Test successful multiexp, with enough pairs to trigger its variety of algorithms
|
|
||||||
// Multiexp has its own tests, yet only against k256 and Ed25519 (which should be sufficient
|
|
||||||
// as-is to prove multiexp), and this doesn't hurt
|
|
||||||
pub fn test_multiexp<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
|
||||||
let mut pairs = Vec::with_capacity(1000);
|
|
||||||
let mut sum = C::G::identity();
|
|
||||||
for _ in 0 .. 10 {
|
|
||||||
for _ in 0 .. 100 {
|
|
||||||
pairs.push((C::random_nonzero_F(&mut *rng), C::generator() * C::random_nonzero_F(&mut *rng)));
|
|
||||||
sum += pairs[pairs.len() - 1].1 * pairs[pairs.len() - 1].0;
|
|
||||||
}
|
|
||||||
assert_eq!(multiexp::multiexp(&pairs), sum);
|
|
||||||
assert_eq!(multiexp::multiexp_vartime(&pairs), sum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn test_curve<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
|
||||||
// TODO: Test the Curve functions themselves
|
|
||||||
|
|
||||||
test_multiexp::<_, C>(rng);
|
|
||||||
}
|
|
|
@ -10,8 +10,6 @@ use crate::{
|
||||||
sign::{Writable, PreprocessMachine, SignMachine, SignatureMachine, AlgorithmMachine},
|
sign::{Writable, PreprocessMachine, SignMachine, SignatureMachine, AlgorithmMachine},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Curve tests.
|
|
||||||
pub mod curve;
|
|
||||||
/// Vectorized test suite to ensure consistency.
|
/// Vectorized test suite to ensure consistency.
|
||||||
pub mod vectors;
|
pub mod vectors;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use group::{ff::PrimeField, GroupEncoding};
|
use group::{ff::PrimeField, GroupEncoding};
|
||||||
|
|
||||||
use dkg::tests::{key_gen, test_ciphersuite as test_dkg};
|
use dkg::tests::key_gen;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
curve::Curve,
|
curve::Curve,
|
||||||
|
@ -19,7 +19,7 @@ use crate::{
|
||||||
Nonce, GeneratorCommitments, NonceCommitments, Commitments, Writable, Preprocess, SignMachine,
|
Nonce, GeneratorCommitments, NonceCommitments, Commitments, Writable, Preprocess, SignMachine,
|
||||||
SignatureMachine, AlgorithmMachine,
|
SignatureMachine, AlgorithmMachine,
|
||||||
},
|
},
|
||||||
tests::{clone_without, recover_key, algorithm_machines, sign, curve::test_curve},
|
tests::{clone_without, recover_key, algorithm_machines, sign},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Vectors {
|
pub struct Vectors {
|
||||||
|
@ -118,12 +118,6 @@ pub fn test_with_vectors<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
vectors: Vectors,
|
vectors: Vectors,
|
||||||
) {
|
) {
|
||||||
// Do basic tests before trying the vectors
|
|
||||||
test_curve::<_, C>(&mut *rng);
|
|
||||||
|
|
||||||
// Test the DKG
|
|
||||||
test_dkg::<_, C>(&mut *rng);
|
|
||||||
|
|
||||||
// Test a basic Schnorr signature
|
// Test a basic Schnorr signature
|
||||||
{
|
{
|
||||||
let keys = key_gen(&mut *rng);
|
let keys = key_gen(&mut *rng);
|
||||||
|
|
Loading…
Reference in a new issue