mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
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:
@@ -26,6 +26,7 @@ group = { version = "0.12", optional = true }
|
||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", optional = true }
|
||||
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", features = ["recommended"], optional = true }
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", features = ["ed25519"], optional = true }
|
||||
dleq = { path = "../../crypto/dleq", features = ["serialize"], optional = true }
|
||||
|
||||
base58-monero = "1"
|
||||
monero = "0.16"
|
||||
@@ -38,7 +39,7 @@ reqwest = { version = "0.11", features = ["json"] }
|
||||
|
||||
[features]
|
||||
experimental = []
|
||||
multisig = ["rand_chacha", "blake2", "group", "dalek-ff-group", "transcript", "frost"]
|
||||
multisig = ["rand_chacha", "blake2", "group", "dalek-ff-group", "transcript", "frost", "dleq"]
|
||||
|
||||
[dev-dependencies]
|
||||
sha2 = "0.10"
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
use core::convert::TryInto;
|
||||
use std::{convert::TryInto, io::Cursor};
|
||||
|
||||
use thiserror::Error;
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use group::GroupEncoding;
|
||||
use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint};
|
||||
|
||||
use curve25519_dalek::{
|
||||
constants::ED25519_BASEPOINT_TABLE as DTable,
|
||||
scalar::Scalar as DScalar,
|
||||
edwards::EdwardsPoint as DPoint
|
||||
};
|
||||
use group::{Group, GroupEncoding};
|
||||
|
||||
use transcript::{Transcript, RecommendedTranscript};
|
||||
use transcript::RecommendedTranscript;
|
||||
use dalek_ff_group as dfg;
|
||||
|
||||
use crate::random_scalar;
|
||||
use dleq::{Generators, DLEqProof};
|
||||
|
||||
#[derive(Clone, Error, Debug)]
|
||||
pub enum MultisigError {
|
||||
@@ -26,105 +21,34 @@ pub enum MultisigError {
|
||||
InvalidKeyImage(u16)
|
||||
}
|
||||
|
||||
// Used to prove legitimacy of key images and nonces which both involve other basepoints
|
||||
#[derive(Clone)]
|
||||
pub struct DLEqProof {
|
||||
s: DScalar,
|
||||
c: DScalar
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl DLEqProof {
|
||||
fn challenge(H: &DPoint, xG: &DPoint, xH: &DPoint, rG: &DPoint, rH: &DPoint) -> DScalar {
|
||||
pub(crate) fn write_dleq<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
H: EdwardsPoint,
|
||||
x: Scalar
|
||||
) -> Vec<u8> {
|
||||
let mut res = Vec::with_capacity(64);
|
||||
DLEqProof::prove(
|
||||
rng,
|
||||
// Doesn't take in a larger transcript object due to the usage of this
|
||||
// Every prover would immediately write their own DLEq proof, when they can only do so in
|
||||
// the proper order if they want to reach consensus
|
||||
// It'd be a poor API to have CLSAG define a new transcript solely to pass here, just to try to
|
||||
// merge later in some form, when it should instead just merge xH (as it does)
|
||||
let mut transcript = RecommendedTranscript::new(b"DLEq Proof");
|
||||
// Bit redundant, keeps things consistent
|
||||
transcript.domain_separate(b"DLEq");
|
||||
// Doesn't include G which is constant, does include H which isn't, even though H manipulation
|
||||
// shouldn't be possible in practice as it's independently calculated as a product of known data
|
||||
transcript.append_message(b"H", &H.compress().to_bytes());
|
||||
transcript.append_message(b"xG", &xG.compress().to_bytes());
|
||||
transcript.append_message(b"xH", &xH.compress().to_bytes());
|
||||
transcript.append_message(b"rG", &rG.compress().to_bytes());
|
||||
transcript.append_message(b"rH", &rH.compress().to_bytes());
|
||||
DScalar::from_bytes_mod_order_wide(
|
||||
&transcript.challenge(b"challenge").try_into().expect("Blake2b512 output wasn't 64 bytes")
|
||||
)
|
||||
}
|
||||
|
||||
pub fn prove<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
H: &DPoint,
|
||||
secret: &DScalar
|
||||
) -> DLEqProof {
|
||||
let r = random_scalar(rng);
|
||||
let rG = &DTable * &r;
|
||||
let rH = r * H;
|
||||
|
||||
// We can frequently (always?) save a scalar mul if we accept xH as an arg, yet it opens room
|
||||
// for incorrect data to be passed, and therefore faults, making it not worth having
|
||||
// We could also return xH but... it's really micro-optimizing
|
||||
let c = DLEqProof::challenge(H, &(secret * &DTable), &(secret * H), &rG, &rH);
|
||||
let s = r + (c * secret);
|
||||
|
||||
DLEqProof { s, c }
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
H: &DPoint,
|
||||
l: u16,
|
||||
xG: &DPoint,
|
||||
xH: &DPoint
|
||||
) -> Result<(), MultisigError> {
|
||||
let s = self.s;
|
||||
let c = self.c;
|
||||
|
||||
let rG = (&s * &DTable) - (c * xG);
|
||||
let rH = (s * H) - (c * xH);
|
||||
|
||||
if c != DLEqProof::challenge(H, &xG, &xH, &rG, &rH) {
|
||||
Err(MultisigError::InvalidDLEqProof(l))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn serialize(
|
||||
&self
|
||||
) -> Vec<u8> {
|
||||
let mut res = Vec::with_capacity(64);
|
||||
res.extend(self.s.to_bytes());
|
||||
res.extend(self.c.to_bytes());
|
||||
res
|
||||
}
|
||||
|
||||
pub fn deserialize(
|
||||
serialized: &[u8]
|
||||
) -> Option<DLEqProof> {
|
||||
if serialized.len() != 64 {
|
||||
return None;
|
||||
}
|
||||
|
||||
DScalar::from_canonical_bytes(serialized[0 .. 32].try_into().unwrap()).and_then(
|
||||
|s| DScalar::from_canonical_bytes(serialized[32 .. 64].try_into().unwrap()).and_then(
|
||||
|c| Some(DLEqProof { s, c })
|
||||
)
|
||||
)
|
||||
}
|
||||
&mut RecommendedTranscript::new(b"DLEq Proof"),
|
||||
Generators::new(dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(H)),
|
||||
dfg::Scalar(x)
|
||||
).serialize(&mut res).unwrap();
|
||||
res
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn read_dleq(
|
||||
serialized: &[u8],
|
||||
start: usize,
|
||||
H: &DPoint,
|
||||
H: EdwardsPoint,
|
||||
l: u16,
|
||||
xG: &DPoint
|
||||
xG: dfg::EdwardsPoint
|
||||
) -> Result<dfg::EdwardsPoint, MultisigError> {
|
||||
if serialized.len() < start + 96 {
|
||||
Err(MultisigError::InvalidDLEqProof(l))?;
|
||||
@@ -132,17 +56,21 @@ pub(crate) fn read_dleq(
|
||||
|
||||
let bytes = (&serialized[(start + 0) .. (start + 32)]).try_into().unwrap();
|
||||
// dfg ensures the point is torsion free
|
||||
let other = Option::<dfg::EdwardsPoint>::from(
|
||||
let xH = Option::<dfg::EdwardsPoint>::from(
|
||||
dfg::EdwardsPoint::from_bytes(&bytes)).ok_or(MultisigError::InvalidDLEqProof(l)
|
||||
)?;
|
||||
// Ensure this is a canonical point
|
||||
if other.to_bytes() != bytes {
|
||||
if xH.to_bytes() != bytes {
|
||||
Err(MultisigError::InvalidDLEqProof(l))?;
|
||||
}
|
||||
|
||||
DLEqProof::deserialize(&serialized[(start + 32) .. (start + 96)])
|
||||
.ok_or(MultisigError::InvalidDLEqProof(l))?
|
||||
.verify(H, l, xG, &other).map_err(|_| MultisigError::InvalidDLEqProof(l))?;
|
||||
let proof = DLEqProof::<dfg::EdwardsPoint>::deserialize(
|
||||
&mut Cursor::new(&serialized[(start + 32) .. (start + 96)])
|
||||
).map_err(|_| MultisigError::InvalidDLEqProof(l))?;
|
||||
|
||||
Ok(other)
|
||||
let mut transcript = RecommendedTranscript::new(b"DLEq Proof");
|
||||
proof.verify(&mut transcript, Generators::new(dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(H)), (xG, xH))
|
||||
.map_err(|_| MultisigError::InvalidDLEqProof(l))?;
|
||||
|
||||
Ok(xH)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use dalek_ff_group as dfg;
|
||||
|
||||
use crate::{
|
||||
hash_to_point,
|
||||
frost::{MultisigError, DLEqProof, read_dleq},
|
||||
frost::{MultisigError, write_dleq, read_dleq},
|
||||
ringct::clsag::{ClsagInput, Clsag}
|
||||
};
|
||||
|
||||
@@ -133,12 +133,12 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
||||
|
||||
let mut serialized = Vec::with_capacity(ClsagMultisig::serialized_len());
|
||||
serialized.extend((view.secret_share().0 * self.H).compress().to_bytes());
|
||||
serialized.extend(DLEqProof::prove(rng, &self.H, &view.secret_share().0).serialize());
|
||||
serialized.extend(write_dleq(rng, self.H, view.secret_share().0));
|
||||
|
||||
serialized.extend((nonces[0].0 * self.H).compress().to_bytes());
|
||||
serialized.extend(&DLEqProof::prove(rng, &self.H, &nonces[0].0).serialize());
|
||||
serialized.extend(write_dleq(rng, self.H, nonces[0].0));
|
||||
serialized.extend((nonces[1].0 * self.H).compress().to_bytes());
|
||||
serialized.extend(&DLEqProof::prove(rng, &self.H, &nonces[1].0).serialize());
|
||||
serialized.extend(write_dleq(rng, self.H, nonces[1].0));
|
||||
serialized
|
||||
}
|
||||
|
||||
@@ -170,18 +170,18 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
||||
self.image += read_dleq(
|
||||
serialized,
|
||||
cursor,
|
||||
&self.H,
|
||||
self.H,
|
||||
l,
|
||||
&view.verification_share(l).0
|
||||
view.verification_share(l)
|
||||
).map_err(|_| FrostError::InvalidCommitment(l))?.0;
|
||||
cursor += 96;
|
||||
|
||||
self.transcript.append_message(b"commitment_D_H", &serialized[cursor .. (cursor + 32)]);
|
||||
self.AH.0 += read_dleq(serialized, cursor, &self.H, l, &commitments[0]).map_err(|_| FrostError::InvalidCommitment(l))?;
|
||||
self.AH.0 += read_dleq(serialized, cursor, self.H, l, commitments[0]).map_err(|_| FrostError::InvalidCommitment(l))?;
|
||||
cursor += 96;
|
||||
|
||||
self.transcript.append_message(b"commitment_E_H", &serialized[cursor .. (cursor + 32)]);
|
||||
self.AH.1 += read_dleq(serialized, cursor, &self.H, l, &commitments[1]).map_err(|_| FrostError::InvalidCommitment(l))?;
|
||||
self.AH.1 += read_dleq(serialized, cursor, self.H, l, commitments[1]).map_err(|_| FrostError::InvalidCommitment(l))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user