Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support caching preprocesses in FROST #190

Merged
merged 5 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion coins/ethereum/tests/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ async fn test_ecrecover_hack() {

let full_message = &[chain_id.to_be_byte_array().as_slice(), &hashed_message].concat();

let algo = Algo::<Secp256k1, crypto::EthereumHram>::new();
let sig = sign(
&mut OsRng,
algorithm_machines(&mut OsRng, Algo::<Secp256k1, crypto::EthereumHram>::new(), &keys),
algo.clone(),
keys.clone(),
algorithm_machines(&mut OsRng, algo, &keys),
full_message,
);
let mut processed_sig =
Expand Down
8 changes: 7 additions & 1 deletion coins/ethereum/tests/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@ fn test_signing() {

const MESSAGE: &'static [u8] = b"Hello, World!";

let algo = Schnorr::<Secp256k1, EthereumHram>::new();
let _sig = sign(
&mut OsRng,
algo.clone(),
keys.clone(),
algorithm_machines(&mut OsRng, Schnorr::<Secp256k1, EthereumHram>::new(), &keys),
MESSAGE,
);
Expand All @@ -67,9 +70,12 @@ fn test_ecrecover_hack() {

let full_message = &[chain_id.to_be_byte_array().as_slice(), &hashed_message].concat();

let algo = Schnorr::<Secp256k1, EthereumHram>::new();
let sig = sign(
&mut OsRng,
algorithm_machines(&mut OsRng, Schnorr::<Secp256k1, EthereumHram>::new(), &keys),
algo.clone(),
keys.clone(),
algorithm_machines(&mut OsRng, algo, &keys),
full_message,
);

Expand Down
2 changes: 2 additions & 0 deletions coins/monero/src/ringct/clsag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use crate::{
mod multisig;
#[cfg(feature = "multisig")]
pub use multisig::{ClsagDetails, ClsagAddendum, ClsagMultisig};
#[cfg(feature = "multisig")]
pub(crate) use multisig::add_key_image_share;

lazy_static! {
static ref INV_EIGHT: Scalar = Scalar::from(8u8).invert();
Expand Down
36 changes: 29 additions & 7 deletions coins/monero/src/ringct/clsag/multisig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ use transcript::{Transcript, RecommendedTranscript};
use dalek_ff_group as dfg;
use dleq::DLEqProof;
use frost::{
dkg::lagrange,
curve::Ed25519,
FrostError, ThresholdView,
FrostError, ThresholdKeys, ThresholdView,
algorithm::{WriteAddendum, Algorithm},
};

Expand Down Expand Up @@ -103,7 +104,7 @@ struct Interim {
pub struct ClsagMultisig {
transcript: RecommendedTranscript,

H: EdwardsPoint,
pub(crate) H: EdwardsPoint,
// Merged here as CLSAG needs it, passing it would be a mess, yet having it beforehand requires
// an extra round
image: EdwardsPoint,
Expand Down Expand Up @@ -142,6 +143,20 @@ impl ClsagMultisig {
}
}

pub(crate) fn add_key_image_share(
image: &mut EdwardsPoint,
generator: EdwardsPoint,
offset: Scalar,
included: &[u16],
participant: u16,
share: EdwardsPoint,
) {
if image.is_identity() {
*image = generator * offset;
}
*image += share * lagrange::<dfg::Scalar>(participant, included).0;
}

impl Algorithm<Ed25519> for ClsagMultisig {
type Transcript = RecommendedTranscript;
type Addendum = ClsagAddendum;
Expand All @@ -154,10 +169,10 @@ impl Algorithm<Ed25519> for ClsagMultisig {
fn preprocess_addendum<R: RngCore + CryptoRng>(
&mut self,
rng: &mut R,
view: &ThresholdView<Ed25519>,
keys: &ThresholdKeys<Ed25519>,
) -> ClsagAddendum {
ClsagAddendum {
key_image: dfg::EdwardsPoint(self.H) * view.secret_share().deref(),
key_image: dfg::EdwardsPoint(self.H) * keys.secret_share().deref(),
dleq: DLEqProof::prove(
rng,
// Doesn't take in a larger transcript object due to the usage of this
Expand All @@ -167,7 +182,7 @@ impl Algorithm<Ed25519> for ClsagMultisig {
// try to merge later in some form, when it should instead just merge xH (as it does)
&mut dleq_transcript(),
&[dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(self.H)],
view.secret_share(),
keys.secret_share(),
),
}
}
Expand Down Expand Up @@ -205,12 +220,19 @@ impl Algorithm<Ed25519> for ClsagMultisig {
.verify(
&mut dleq_transcript(),
&[dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(self.H)],
&[view.verification_share(l), addendum.key_image],
&[view.original_verification_share(l), addendum.key_image],
)
.map_err(|_| FrostError::InvalidPreprocess(l))?;

self.transcript.append_message(b"key_image_share", addendum.key_image.compress().to_bytes());
self.image += addendum.key_image.0;
add_key_image_share(
&mut self.image,
self.H,
view.offset().0,
&view.included(),
l,
addendum.key_image.0,
);

Ok(())
}
Expand Down
40 changes: 20 additions & 20 deletions coins/monero/src/tests/clsag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,28 +101,28 @@ fn clsag_multisig() {
}

let mask_sum = random_scalar(&mut OsRng);
let algorithm = ClsagMultisig::new(
RecommendedTranscript::new(b"Monero Serai CLSAG Test"),
keys[&1].group_key().0,
Arc::new(RwLock::new(Some(ClsagDetails::new(
ClsagInput::new(
Commitment::new(randomness, AMOUNT),
Decoys {
i: RING_INDEX,
offsets: (1 ..= RING_LEN).into_iter().collect(),
ring: ring.clone(),
},
)
.unwrap(),
mask_sum,
)))),
);

sign(
&mut OsRng,
algorithm_machines(
&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(
Commitment::new(randomness, AMOUNT),
Decoys {
i: RING_INDEX,
offsets: (1 ..= RING_LEN).into_iter().collect(),
ring: ring.clone(),
},
)
.unwrap(),
mask_sum,
)))),
),
&keys,
),
algorithm.clone(),
keys.clone(),
algorithm_machines(&mut OsRng, algorithm, &keys),
&[1; 32],
);
}
78 changes: 55 additions & 23 deletions coins/monero/src/wallet/send/multisig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,28 @@ use std::{
collections::HashMap,
};

use zeroize::Zeroizing;
use rand_core::{RngCore, CryptoRng, SeedableRng};
use rand_chacha::ChaCha20Rng;

use group::ff::Field;
use curve25519_dalek::{traits::Identity, scalar::Scalar, edwards::EdwardsPoint};
use dalek_ff_group as dfg;

use transcript::{Transcript, RecommendedTranscript};
use frost::{
curve::Ed25519,
FrostError, ThresholdKeys,
sign::{
Writable, Preprocess, SignatureShare, PreprocessMachine, SignMachine, SignatureMachine,
AlgorithmMachine, AlgorithmSignMachine, AlgorithmSignatureMachine,
Writable, Preprocess, CachedPreprocess, SignatureShare, PreprocessMachine, SignMachine,
SignatureMachine, AlgorithmMachine, AlgorithmSignMachine, AlgorithmSignatureMachine,
},
};

use crate::{
random_scalar,
ringct::{
clsag::{ClsagInput, ClsagDetails, ClsagAddendum, ClsagMultisig},
clsag::{ClsagInput, ClsagDetails, ClsagAddendum, ClsagMultisig, add_key_image_share},
RctPrunable,
},
transaction::{Input, Transaction},
Expand All @@ -34,23 +37,24 @@ use crate::{
pub struct TransactionMachine {
signable: SignableTransaction,
i: u16,
included: Vec<u16>,
transcript: RecommendedTranscript,

decoys: Vec<Decoys>,

// Hashed key and scalar offset
key_images: Vec<(EdwardsPoint, Scalar)>,
inputs: Vec<Arc<RwLock<Option<ClsagDetails>>>>,
clsags: Vec<AlgorithmMachine<Ed25519, ClsagMultisig>>,
}

pub struct TransactionSignMachine {
signable: SignableTransaction,
i: u16,
included: Vec<u16>,
transcript: RecommendedTranscript,

decoys: Vec<Decoys>,

key_images: Vec<(EdwardsPoint, Scalar)>,
inputs: Vec<Arc<RwLock<Option<ClsagDetails>>>>,
clsags: Vec<AlgorithmSignMachine<Ed25519, ClsagMultisig>>,

Expand All @@ -71,7 +75,6 @@ impl SignableTransaction {
keys: ThresholdKeys<Ed25519>,
mut transcript: RecommendedTranscript,
height: usize,
mut included: Vec<u16>,
) -> Result<TransactionMachine, TransactionError> {
let mut inputs = vec![];
for _ in 0 .. self.inputs.len() {
Expand Down Expand Up @@ -110,24 +113,20 @@ impl SignableTransaction {
transcript.append_message(b"payment_amount", payment.1.to_le_bytes());
}

// Sort included before cloning it around
included.sort_unstable();

let mut key_images = vec![];
for (i, input) in self.inputs.iter().enumerate() {
// Check this the right set of keys
let offset = keys.offset(dalek_ff_group::Scalar(input.key_offset()));
let offset = keys.offset(dfg::Scalar(input.key_offset()));
if offset.group_key().0 != input.key() {
Err(TransactionError::WrongPrivateKey)?;
}

clsags.push(
AlgorithmMachine::new(
ClsagMultisig::new(transcript.clone(), input.key(), inputs[i].clone()),
offset,
&included,
)
.map_err(TransactionError::FrostError)?,
);
let clsag = ClsagMultisig::new(transcript.clone(), input.key(), inputs[i].clone());
key_images.push((
clsag.H,
keys.current_offset().unwrap_or(dfg::Scalar::zero()).0 + self.inputs[i].key_offset(),
));
clsags.push(AlgorithmMachine::new(clsag, offset).map_err(TransactionError::FrostError)?);
}

// Select decoys
Expand All @@ -150,11 +149,11 @@ impl SignableTransaction {
Ok(TransactionMachine {
signable: self,
i: keys.params().i(),
included,
transcript,

decoys,

key_images,
inputs,
clsags,
})
Expand Down Expand Up @@ -196,11 +195,11 @@ impl PreprocessMachine for TransactionMachine {
TransactionSignMachine {
signable: self.signable,
i: self.i,
included: self.included,
transcript: self.transcript,

decoys: self.decoys,

key_images: self.key_images,
inputs: self.inputs,
clsags,

Expand All @@ -212,10 +211,30 @@ impl PreprocessMachine for TransactionMachine {
}

impl SignMachine<Transaction> for TransactionSignMachine {
type Params = ();
type Keys = ThresholdKeys<Ed25519>;
type Preprocess = Vec<Preprocess<Ed25519, ClsagAddendum>>;
type SignatureShare = Vec<SignatureShare<Ed25519>>;
type SignatureMachine = TransactionSignatureMachine;

fn cache(self) -> Zeroizing<CachedPreprocess> {
unimplemented!(
"Monero transactions don't support caching their preprocesses due to {}",
"being already bound to a specific transaction"
);
}

fn from_cache(
_: (),
_: ThresholdKeys<Ed25519>,
_: Zeroizing<CachedPreprocess>,
) -> Result<Self, FrostError> {
unimplemented!(
"Monero transactions don't support caching their preprocesses due to {}",
"being already bound to a specific transaction"
);
}

fn read_preprocess<R: Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess> {
self.clsags.iter().map(|clsag| clsag.read_preprocess(reader)).collect()
}
Expand All @@ -231,12 +250,18 @@ impl SignMachine<Transaction> for TransactionSignMachine {
))?;
}

// Find out who's included
// This may not be a valid set of signers yet the algorithm machine will error if it's not
commitments.remove(&self.i); // Remove, if it was included for some reason
let mut included = commitments.keys().into_iter().cloned().collect::<Vec<_>>();
included.push(self.i);
included.sort_unstable();

// Convert the unified commitments to a Vec of the individual commitments
let mut images = vec![EdwardsPoint::identity(); self.clsags.len()];
let mut commitments = (0 .. self.clsags.len())
.map(|c| {
self
.included
included
.iter()
.map(|l| {
// Add all commitments to the transcript for their entropy
Expand All @@ -262,7 +287,14 @@ impl SignMachine<Transaction> for TransactionSignMachine {
// provides the easiest API overall, as this is where the TX is (which needs the key
// images in its message), along with where the outputs are determined (where our
// outputs may need these in order to guarantee uniqueness)
images[c] += preprocess.addendum.key_image.0;
add_key_image_share(
&mut images[c],
self.key_images[c].0,
self.key_images[c].1,
&included,
*l,
preprocess.addendum.key_image.0,
);

Ok((*l, preprocess))
})
Expand Down
Loading