mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
This commit is contained in:
@@ -7,7 +7,7 @@ use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint};
|
||||
|
||||
use group::{Group, GroupEncoding};
|
||||
|
||||
use transcript::RecommendedTranscript;
|
||||
use transcript::{Transcript, RecommendedTranscript};
|
||||
use dalek_ff_group as dfg;
|
||||
use dleq::{Generators, DLEqProof};
|
||||
|
||||
@@ -21,6 +21,10 @@ pub enum MultisigError {
|
||||
InvalidKeyImage(u16)
|
||||
}
|
||||
|
||||
fn transcript() -> RecommendedTranscript {
|
||||
RecommendedTranscript::new(b"monero_key_image_dleq")
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn write_dleq<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
@@ -35,7 +39,7 @@ pub(crate) fn write_dleq<R: RngCore + CryptoRng>(
|
||||
// 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)
|
||||
&mut RecommendedTranscript::new(b"DLEq Proof"),
|
||||
&mut transcript(),
|
||||
Generators::new(dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(H)),
|
||||
dfg::Scalar(x)
|
||||
).serialize(&mut res).unwrap();
|
||||
@@ -45,16 +49,15 @@ pub(crate) fn write_dleq<R: RngCore + CryptoRng>(
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn read_dleq(
|
||||
serialized: &[u8],
|
||||
start: usize,
|
||||
H: EdwardsPoint,
|
||||
l: u16,
|
||||
xG: dfg::EdwardsPoint
|
||||
) -> Result<dfg::EdwardsPoint, MultisigError> {
|
||||
if serialized.len() < start + 96 {
|
||||
if serialized.len() != 96 {
|
||||
Err(MultisigError::InvalidDLEqProof(l))?;
|
||||
}
|
||||
|
||||
let bytes = (&serialized[(start + 0) .. (start + 32)]).try_into().unwrap();
|
||||
let bytes = (&serialized[.. 32]).try_into().unwrap();
|
||||
// dfg ensures the point is torsion free
|
||||
let xH = Option::<dfg::EdwardsPoint>::from(
|
||||
dfg::EdwardsPoint::from_bytes(&bytes)).ok_or(MultisigError::InvalidDLEqProof(l)
|
||||
@@ -64,13 +67,13 @@ pub(crate) fn read_dleq(
|
||||
Err(MultisigError::InvalidDLEqProof(l))?;
|
||||
}
|
||||
|
||||
let proof = DLEqProof::<dfg::EdwardsPoint>::deserialize(
|
||||
&mut Cursor::new(&serialized[(start + 32) .. (start + 96)])
|
||||
DLEqProof::<dfg::EdwardsPoint>::deserialize(
|
||||
&mut Cursor::new(&serialized[32 ..])
|
||||
).map_err(|_| MultisigError::InvalidDLEqProof(l))?.verify(
|
||||
&mut transcript(),
|
||||
Generators::new(dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(H)),
|
||||
(xG, xH)
|
||||
).map_err(|_| MultisigError::InvalidDLEqProof(l))?;
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use rand_chacha::ChaCha12Rng;
|
||||
|
||||
use curve25519_dalek::{
|
||||
constants::ED25519_BASEPOINT_TABLE,
|
||||
traits::Identity,
|
||||
traits::{Identity, IsIdentity},
|
||||
scalar::Scalar,
|
||||
edwards::EdwardsPoint
|
||||
};
|
||||
@@ -76,7 +76,6 @@ pub struct ClsagMultisig {
|
||||
H: EdwardsPoint,
|
||||
// Merged here as CLSAG needs it, passing it would be a mess, yet having it beforehand requires a round
|
||||
image: EdwardsPoint,
|
||||
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
|
||||
|
||||
details: Arc<RwLock<Option<ClsagDetails>>>,
|
||||
|
||||
@@ -87,15 +86,15 @@ pub struct ClsagMultisig {
|
||||
impl ClsagMultisig {
|
||||
pub fn new(
|
||||
transcript: RecommendedTranscript,
|
||||
output_key: EdwardsPoint,
|
||||
details: Arc<RwLock<Option<ClsagDetails>>>
|
||||
) -> Result<ClsagMultisig, MultisigError> {
|
||||
Ok(
|
||||
ClsagMultisig {
|
||||
transcript,
|
||||
|
||||
H: EdwardsPoint::identity(),
|
||||
H: hash_to_point(output_key),
|
||||
image: EdwardsPoint::identity(),
|
||||
AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()),
|
||||
|
||||
details,
|
||||
|
||||
@@ -106,7 +105,7 @@ impl ClsagMultisig {
|
||||
}
|
||||
|
||||
pub fn serialized_len() -> usize {
|
||||
3 * (32 + 64)
|
||||
32 + (2 * 32)
|
||||
}
|
||||
|
||||
fn input(&self) -> ClsagInput {
|
||||
@@ -122,22 +121,18 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
||||
type Transcript = RecommendedTranscript;
|
||||
type Signature = (Clsag, EdwardsPoint);
|
||||
|
||||
fn nonces(&self) -> Vec<Vec<dfg::EdwardsPoint>> {
|
||||
vec![vec![dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(self.H)]]
|
||||
}
|
||||
|
||||
fn preprocess_addendum<R: RngCore + CryptoRng>(
|
||||
&mut self,
|
||||
rng: &mut R,
|
||||
view: &FrostView<Ed25519>,
|
||||
nonces: &[dfg::Scalar; 2]
|
||||
view: &FrostView<Ed25519>
|
||||
) -> Vec<u8> {
|
||||
self.H = hash_to_point(view.group_key().0);
|
||||
|
||||
let mut serialized = Vec::with_capacity(ClsagMultisig::serialized_len());
|
||||
let mut serialized = Vec::with_capacity(Self::serialized_len());
|
||||
serialized.extend((view.secret_share().0 * self.H).compress().to_bytes());
|
||||
serialized.extend(write_dleq(rng, self.H, view.secret_share().0));
|
||||
|
||||
serialized.extend((nonces[0].0 * self.H).compress().to_bytes());
|
||||
serialized.extend(write_dleq(rng, self.H, nonces[0].0));
|
||||
serialized.extend((nonces[1].0 * self.H).compress().to_bytes());
|
||||
serialized.extend(write_dleq(rng, self.H, nonces[1].0));
|
||||
serialized
|
||||
}
|
||||
|
||||
@@ -145,42 +140,27 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
||||
&mut self,
|
||||
view: &FrostView<Ed25519>,
|
||||
l: u16,
|
||||
commitments: &[dfg::EdwardsPoint; 2],
|
||||
serialized: &[u8]
|
||||
) -> Result<(), FrostError> {
|
||||
if serialized.len() != ClsagMultisig::serialized_len() {
|
||||
if serialized.len() != Self::serialized_len() {
|
||||
// Not an optimal error but...
|
||||
Err(FrostError::InvalidCommitment(l))?;
|
||||
}
|
||||
|
||||
if self.AH.0.is_identity().into() {
|
||||
if self.image.is_identity().into() {
|
||||
self.transcript.domain_separate(b"CLSAG");
|
||||
self.input().transcript(&mut self.transcript);
|
||||
self.transcript.append_message(b"mask", &self.mask().to_bytes());
|
||||
}
|
||||
|
||||
// Uses the same format FROST does for the expected commitments (nonce * G where this is nonce * H)
|
||||
// The following technically shouldn't need to be committed to, as we've committed to equivalents,
|
||||
// yet it doesn't hurt and may resolve some unknown issues
|
||||
self.transcript.append_message(b"participant", &l.to_be_bytes());
|
||||
|
||||
let mut cursor = 0;
|
||||
self.transcript.append_message(b"image_share", &serialized[cursor .. (cursor + 32)]);
|
||||
self.transcript.append_message(b"key_image_share", &serialized[.. 32]);
|
||||
self.image += read_dleq(
|
||||
serialized,
|
||||
cursor,
|
||||
self.H,
|
||||
l,
|
||||
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))?;
|
||||
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))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -192,14 +172,10 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
||||
fn sign_share(
|
||||
&mut self,
|
||||
view: &FrostView<Ed25519>,
|
||||
nonce_sum: dfg::EdwardsPoint,
|
||||
b: dfg::Scalar,
|
||||
nonce: dfg::Scalar,
|
||||
nonce_sums: &[Vec<dfg::EdwardsPoint>],
|
||||
nonces: &[dfg::Scalar],
|
||||
msg: &[u8]
|
||||
) -> dfg::Scalar {
|
||||
// Apply the binding factor to the H variant of the nonce
|
||||
self.AH.0 += self.AH.1 * b;
|
||||
|
||||
// Use the transcript to get a seeded random number generator
|
||||
// The transcript contains private data, preventing passive adversaries from recreating this
|
||||
// process even if they have access to commitments (specifically, the ring index being signed
|
||||
@@ -216,12 +192,12 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
||||
&self.input(),
|
||||
self.mask(),
|
||||
&self.msg.as_ref().unwrap(),
|
||||
nonce_sum.0,
|
||||
self.AH.0.0
|
||||
nonce_sums[0][0].0,
|
||||
nonce_sums[0][1].0
|
||||
);
|
||||
self.interim = Some(Interim { p, c, clsag, pseudo_out });
|
||||
|
||||
let share = dfg::Scalar(nonce.0 - (p * view.secret_share().0));
|
||||
let share = dfg::Scalar(nonces[0].0 - (p * view.secret_share().0));
|
||||
|
||||
share
|
||||
}
|
||||
@@ -230,7 +206,7 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
||||
fn verify(
|
||||
&self,
|
||||
_: dfg::EdwardsPoint,
|
||||
_: dfg::EdwardsPoint,
|
||||
_: &[Vec<dfg::EdwardsPoint>],
|
||||
sum: dfg::Scalar
|
||||
) -> Option<Self::Signature> {
|
||||
let interim = self.interim.as_ref().unwrap();
|
||||
@@ -251,12 +227,12 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
||||
fn verify_share(
|
||||
&self,
|
||||
verification_share: dfg::EdwardsPoint,
|
||||
nonce: dfg::EdwardsPoint,
|
||||
nonces: &[Vec<dfg::EdwardsPoint>],
|
||||
share: dfg::Scalar,
|
||||
) -> bool {
|
||||
let interim = self.interim.as_ref().unwrap();
|
||||
return (&share.0 * &ED25519_BASEPOINT_TABLE) == (
|
||||
nonce.0 - (interim.p * verification_share.0)
|
||||
nonces[0][0].0 - (interim.p * verification_share.0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use rand::{RngCore, rngs::OsRng};
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
||||
|
||||
#[cfg(feature = "multisig")]
|
||||
use transcript::RecommendedTranscript;
|
||||
use transcript::{Transcript, RecommendedTranscript};
|
||||
#[cfg(feature = "multisig")]
|
||||
use frost::curve::Ed25519;
|
||||
|
||||
@@ -102,6 +102,7 @@ fn clsag_multisig() -> Result<(), MultisigError> {
|
||||
&mut OsRng,
|
||||
ClsagMultisig::new(
|
||||
RecommendedTranscript::new(b"Monero Serai CLSAG Test"),
|
||||
keys[&1].group_key().0,
|
||||
Arc::new(RwLock::new(Some(
|
||||
ClsagDetails::new(
|
||||
ClsagInput::new(
|
||||
|
||||
@@ -112,6 +112,7 @@ impl SignableTransaction {
|
||||
AlgorithmMachine::new(
|
||||
ClsagMultisig::new(
|
||||
transcript.clone(),
|
||||
input.key,
|
||||
inputs[i].clone()
|
||||
).map_err(|e| TransactionError::MultisigError(e))?,
|
||||
Arc::new(offset),
|
||||
@@ -159,7 +160,10 @@ impl PreprocessMachine for TransactionMachine {
|
||||
rng: &mut R
|
||||
) -> (TransactionSignMachine, Vec<u8>) {
|
||||
// Iterate over each CLSAG calling preprocess
|
||||
let mut serialized = Vec::with_capacity(self.clsags.len() * (64 + ClsagMultisig::serialized_len()));
|
||||
let mut serialized = Vec::with_capacity(
|
||||
// D_{G, H}, E_{G, H}, DLEqs, key image addendum
|
||||
self.clsags.len() * ((2 * (32 + 32)) + (2 * (32 + 32)) + ClsagMultisig::serialized_len())
|
||||
);
|
||||
let clsags = self.clsags.drain(..).map(|clsag| {
|
||||
let (clsag, preprocess) = clsag.preprocess(rng);
|
||||
serialized.extend(&preprocess);
|
||||
@@ -224,8 +228,8 @@ impl SignMachine<Transaction> for TransactionSignMachine {
|
||||
}
|
||||
}
|
||||
|
||||
// FROST commitments, image, H commitments, and their proofs
|
||||
let clsag_len = 64 + ClsagMultisig::serialized_len();
|
||||
// FROST commitments and their DLEqs, and the image and its DLEq
|
||||
let clsag_len = (2 * (32 + 32)) + (2 * (32 + 32)) + ClsagMultisig::serialized_len();
|
||||
for (l, commitments) in &commitments {
|
||||
if commitments.len() != (self.clsags.len() * clsag_len) {
|
||||
Err(FrostError::InvalidCommitment(*l))?;
|
||||
@@ -246,7 +250,7 @@ impl SignMachine<Transaction> for TransactionSignMachine {
|
||||
for c in 0 .. self.clsags.len() {
|
||||
for (l, preprocess) in &commitments[c] {
|
||||
images[c] += CompressedEdwardsY(
|
||||
preprocess[64 .. 96].try_into().map_err(|_| FrostError::InvalidCommitment(*l))?
|
||||
preprocess[(clsag_len - 96) .. (clsag_len - 64)].try_into().map_err(|_| FrostError::InvalidCommitment(*l))?
|
||||
).decompress().ok_or(FrostError::InvalidCommitment(*l))?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
|
||||
#[cfg(feature = "multisig")]
|
||||
use dalek_ff_group::Scalar;
|
||||
#[cfg(feature = "multisig")]
|
||||
use transcript::RecommendedTranscript;
|
||||
use transcript::{Transcript, RecommendedTranscript};
|
||||
#[cfg(feature = "multisig")]
|
||||
use frost::{curve::Ed25519, tests::{THRESHOLD, key_gen, sign}};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user