Implement a DLEq library

While Serai only needs the simple DLEq which was already present under 
monero, this migrates the implementation of the cross-group DLEq I 
maintain into Serai. This was to have full access to the ecosystem of 
libraries built under Serai while also ensuring support for it.

The cross_group curve, which is extremely experimental, is feature 
flagged off. So is the built in serialization functionality, as this 
should be possible to make nostd once const generics are full featured, 
yet the implemented serialization adds the additional barrier of 
std::io.
This commit is contained in:
Luke Parker
2022-06-30 05:42:29 -04:00
parent 2e168204f0
commit 5d115f1e1c
15 changed files with 854 additions and 111 deletions

View File

@@ -0,0 +1,54 @@
mod scalar;
mod schnorr;
use hex_literal::hex;
use rand_core::OsRng;
use ff::Field;
use group::{Group, GroupEncoding};
use k256::{Scalar, ProjectivePoint};
use dalek_ff_group::{EdwardsPoint, CompressedEdwardsY};
use transcript::RecommendedTranscript;
use crate::{Generators, cross_group::DLEqProof};
#[test]
fn test_dleq() {
let transcript = || RecommendedTranscript::new(b"Cross-Group DLEq Proof Test");
let generators = (
Generators::new(
ProjectivePoint::GENERATOR,
ProjectivePoint::from_bytes(
&(hex!("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0").into())
).unwrap()
),
Generators::new(
EdwardsPoint::generator(),
CompressedEdwardsY::new(
hex!("8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94")
).decompress().unwrap()
)
);
let key = Scalar::random(&mut OsRng);
let (proof, keys) = DLEqProof::prove(&mut OsRng, &mut transcript(), generators, key);
let public_keys = proof.verify(&mut transcript(), generators).unwrap();
assert_eq!(generators.0.primary * keys.0, public_keys.0);
assert_eq!(generators.1.primary * keys.1, public_keys.1);
#[cfg(feature = "serialize")]
{
let mut buf = vec![];
proof.serialize(&mut buf).unwrap();
let deserialized = DLEqProof::<ProjectivePoint, EdwardsPoint>::deserialize(
&mut std::io::Cursor::new(&buf)
).unwrap();
assert_eq!(proof, deserialized);
deserialized.verify(&mut transcript(), generators).unwrap();
}
}

View File

@@ -0,0 +1,47 @@
use rand_core::OsRng;
use ff::{Field, PrimeField};
use k256::Scalar as K256Scalar;
use dalek_ff_group::Scalar as DalekScalar;
use crate::cross_group::scalar::{scalar_normalize, scalar_convert};
#[test]
fn test_scalar() {
assert_eq!(
scalar_normalize::<_, DalekScalar>(K256Scalar::zero()),
(K256Scalar::zero(), DalekScalar::zero())
);
assert_eq!(
scalar_normalize::<_, DalekScalar>(K256Scalar::one()),
(K256Scalar::one(), DalekScalar::one())
);
let mut initial;
while {
initial = K256Scalar::random(&mut OsRng);
let (k, ed) = scalar_normalize::<_, DalekScalar>(initial);
// The initial scalar should equal the new scalar with Ed25519's capacity
let mut initial_bytes = (&initial.to_repr()).to_vec();
// Drop the first 4 bits to hit 252
initial_bytes[0] = initial_bytes[0] & 0b00001111;
let k_bytes = (&k.to_repr()).to_vec();
assert_eq!(initial_bytes, k_bytes);
let mut ed_bytes = ed.to_repr().as_ref().to_vec();
// Reverse to big endian
ed_bytes.reverse();
assert_eq!(k_bytes, ed_bytes);
// Verify conversion works as expected
assert_eq!(scalar_convert::<_, DalekScalar>(k), Some(ed));
// Run this test again if this secp256k1 scalar didn't have any bits cleared
initial == k
} {}
// Verify conversion returns None when the scalar isn't mutually valid
assert!(scalar_convert::<_, DalekScalar>(initial).is_none());
}

View File

@@ -0,0 +1,31 @@
use rand_core::OsRng;
use group::{ff::Field, prime::PrimeGroup};
use transcript::RecommendedTranscript;
use crate::cross_group::schnorr::SchnorrPoK;
fn test_schnorr<G: PrimeGroup>() {
let private = G::Scalar::random(&mut OsRng);
let transcript = RecommendedTranscript::new(b"Schnorr Test");
assert!(
SchnorrPoK::prove(
&mut OsRng,
&mut transcript.clone(),
G::generator(),
private
).verify(&mut transcript.clone(), G::generator(), G::generator() * private)
);
}
#[test]
fn test_secp256k1() {
test_schnorr::<k256::ProjectivePoint>();
}
#[test]
fn test_ed25519() {
test_schnorr::<dalek_ff_group::EdwardsPoint>();
}

View File

@@ -0,0 +1,43 @@
#[cfg(feature = "cross_group")]
mod cross_group;
use hex_literal::hex;
use rand_core::OsRng;
use ff::Field;
use group::GroupEncoding;
use k256::{Scalar, ProjectivePoint};
use transcript::RecommendedTranscript;
use crate::{Generators, DLEqProof};
#[test]
fn test_dleq() {
let transcript = || RecommendedTranscript::new(b"DLEq Proof Test");
let generators = Generators::new(
ProjectivePoint::GENERATOR,
ProjectivePoint::from_bytes(
&(hex!("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0").into())
).unwrap()
);
let key = Scalar::random(&mut OsRng);
let proof = DLEqProof::prove(&mut OsRng, &mut transcript(), generators, key);
let keys = (generators.primary * key, generators.alt * key);
proof.verify(&mut transcript(), generators, keys).unwrap();
#[cfg(feature = "serialize")]
{
let mut buf = vec![];
proof.serialize(&mut buf).unwrap();
let deserialized = DLEqProof::<ProjectivePoint>::deserialize(
&mut std::io::Cursor::new(&buf)
).unwrap();
assert_eq!(proof, deserialized);
deserialized.verify(&mut transcript(), generators, keys).unwrap();
}
}