Luke Parker
2022-07-12 01:28:01 -04:00
parent cf28967754
commit 5eb61f3a87
11 changed files with 198 additions and 123 deletions

View File

@@ -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)
}

View File

@@ -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)
);
}
}

View File

@@ -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(

View File

@@ -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))?;
}
}

View File

@@ -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}};