2022-07-15 01:26:07 -04:00
|
|
|
use std::{
|
2022-10-25 23:17:25 -05:00
|
|
|
io::{self, Read},
|
2022-07-15 01:26:07 -04:00
|
|
|
sync::{Arc, RwLock},
|
|
|
|
|
collections::HashMap,
|
|
|
|
|
};
|
2022-04-30 04:32:19 -04:00
|
|
|
|
2022-05-06 07:33:08 -04:00
|
|
|
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
2022-08-30 20:01:46 -04:00
|
|
|
use rand_chacha::ChaCha20Rng;
|
2022-04-30 04:32:19 -04:00
|
|
|
|
2022-12-08 19:04:35 -05:00
|
|
|
use group::ff::Field;
|
2022-10-25 23:17:25 -05:00
|
|
|
use curve25519_dalek::{traits::Identity, scalar::Scalar, edwards::EdwardsPoint};
|
2022-12-08 19:04:35 -05:00
|
|
|
use dalek_ff_group as dfg;
|
2022-04-30 04:32:19 -04:00
|
|
|
|
2022-06-24 18:58:24 -04:00
|
|
|
use transcript::{Transcript, RecommendedTranscript};
|
2022-06-24 08:40:14 -04:00
|
|
|
use frost::{
|
2022-06-24 19:47:19 -04:00
|
|
|
curve::Ed25519,
|
2022-10-29 03:54:42 -05:00
|
|
|
FrostError, ThresholdKeys,
|
2022-06-24 08:40:14 -04:00
|
|
|
sign::{
|
2022-12-08 19:04:35 -05:00
|
|
|
Writable, Preprocess, CachedPreprocess, SignatureShare, PreprocessMachine, SignMachine,
|
|
|
|
|
SignatureMachine, AlgorithmMachine, AlgorithmSignMachine, AlgorithmSignatureMachine,
|
2022-07-15 01:26:07 -04:00
|
|
|
},
|
2022-06-24 08:40:14 -04:00
|
|
|
};
|
2022-05-03 07:20:24 -04:00
|
|
|
|
2022-04-30 04:32:19 -04:00
|
|
|
use crate::{
|
2022-07-15 01:26:07 -04:00
|
|
|
random_scalar,
|
|
|
|
|
ringct::{
|
2022-12-08 19:04:35 -05:00
|
|
|
clsag::{ClsagInput, ClsagDetails, ClsagAddendum, ClsagMultisig, add_key_image_share},
|
2022-07-15 01:26:07 -04:00
|
|
|
RctPrunable,
|
|
|
|
|
},
|
2022-05-22 02:24:24 -04:00
|
|
|
transaction::{Input, Transaction},
|
2022-04-30 04:32:19 -04:00
|
|
|
rpc::Rpc,
|
2022-07-15 01:26:07 -04:00
|
|
|
wallet::{TransactionError, SignableTransaction, Decoys, key_image_sort, uniqueness},
|
2022-04-30 04:32:19 -04:00
|
|
|
};
|
|
|
|
|
|
2022-09-28 07:44:49 -05:00
|
|
|
/// FROST signing machine to produce a signed transaction.
|
2022-04-30 04:32:19 -04:00
|
|
|
pub struct TransactionMachine {
|
|
|
|
|
signable: SignableTransaction,
|
2022-05-24 21:41:14 -04:00
|
|
|
i: u16,
|
2022-06-24 18:58:24 -04:00
|
|
|
transcript: RecommendedTranscript,
|
2022-05-06 07:33:08 -04:00
|
|
|
|
2022-05-06 19:07:37 -04:00
|
|
|
decoys: Vec<Decoys>,
|
|
|
|
|
|
2022-12-08 19:04:35 -05:00
|
|
|
// Hashed key and scalar offset
|
|
|
|
|
key_images: Vec<(EdwardsPoint, Scalar)>,
|
2022-06-24 08:40:14 -04:00
|
|
|
inputs: Vec<Arc<RwLock<Option<ClsagDetails>>>>,
|
2022-07-15 01:26:07 -04:00
|
|
|
clsags: Vec<AlgorithmMachine<Ed25519, ClsagMultisig>>,
|
2022-06-24 08:40:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct TransactionSignMachine {
|
|
|
|
|
signable: SignableTransaction,
|
|
|
|
|
i: u16,
|
2022-06-24 18:58:24 -04:00
|
|
|
transcript: RecommendedTranscript,
|
2022-06-24 08:40:14 -04:00
|
|
|
|
|
|
|
|
decoys: Vec<Decoys>,
|
2022-05-22 01:56:17 -04:00
|
|
|
|
2022-12-08 19:04:35 -05:00
|
|
|
key_images: Vec<(EdwardsPoint, Scalar)>,
|
2022-06-05 07:33:15 -04:00
|
|
|
inputs: Vec<Arc<RwLock<Option<ClsagDetails>>>>,
|
2022-06-24 08:40:14 -04:00
|
|
|
clsags: Vec<AlgorithmSignMachine<Ed25519, ClsagMultisig>>,
|
|
|
|
|
|
2022-10-25 23:17:25 -05:00
|
|
|
our_preprocess: Vec<Preprocess<Ed25519, ClsagAddendum>>,
|
2022-06-24 08:40:14 -04:00
|
|
|
}
|
2022-05-06 19:07:37 -04:00
|
|
|
|
2022-06-24 08:40:14 -04:00
|
|
|
pub struct TransactionSignatureMachine {
|
|
|
|
|
tx: Transaction,
|
2022-07-15 01:26:07 -04:00
|
|
|
clsags: Vec<AlgorithmSignatureMachine<Ed25519, ClsagMultisig>>,
|
2022-04-30 04:32:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SignableTransaction {
|
2022-09-28 07:44:49 -05:00
|
|
|
/// Create a FROST signing machine out of this signable transaction.
|
|
|
|
|
/// The height is the Monero blockchain height to synchronize around.
|
2022-06-19 12:03:01 -04:00
|
|
|
pub async fn multisig(
|
|
|
|
|
self,
|
2022-04-30 04:32:19 -04:00
|
|
|
rpc: &Rpc,
|
2022-10-29 03:54:42 -05:00
|
|
|
keys: ThresholdKeys<Ed25519>,
|
2022-06-24 18:58:24 -04:00
|
|
|
mut transcript: RecommendedTranscript,
|
2022-06-05 15:10:50 -04:00
|
|
|
height: usize,
|
2022-04-30 04:32:19 -04:00
|
|
|
) -> Result<TransactionMachine, TransactionError> {
|
2022-05-04 06:24:52 -04:00
|
|
|
let mut inputs = vec![];
|
2022-05-18 00:53:13 -04:00
|
|
|
for _ in 0 .. self.inputs.len() {
|
|
|
|
|
// Doesn't resize as that will use a single Rc for the entire Vec
|
2022-06-05 07:33:15 -04:00
|
|
|
inputs.push(Arc::new(RwLock::new(None)));
|
2022-05-18 00:53:13 -04:00
|
|
|
}
|
2022-05-06 19:07:37 -04:00
|
|
|
let mut clsags = vec![];
|
2022-05-04 06:24:52 -04:00
|
|
|
|
|
|
|
|
// Create a RNG out of the input shared keys, which either requires the view key or being every
|
|
|
|
|
// sender, and the payments (address and amount), which a passive adversary may be able to know
|
2022-05-06 07:33:08 -04:00
|
|
|
// depending on how these transactions are coordinated
|
2022-06-03 01:37:12 -04:00
|
|
|
// Being every sender would already let you note rings which happen to use your transactions
|
|
|
|
|
// multiple times, already breaking privacy there
|
2022-05-06 07:33:08 -04:00
|
|
|
|
2022-05-23 03:24:33 -04:00
|
|
|
transcript.domain_separate(b"monero_transaction");
|
2022-05-22 01:56:17 -04:00
|
|
|
// Include the height we're using for our data
|
|
|
|
|
// The data itself will be included, making this unnecessary, yet a lot of this is technically
|
|
|
|
|
// unnecessary. Anything which further increases security at almost no cost should be followed
|
2022-11-05 18:43:36 -04:00
|
|
|
transcript.append_message(b"height", u64::try_from(height).unwrap().to_le_bytes());
|
2022-07-15 01:26:07 -04:00
|
|
|
// Also include the spend_key as below only the key offset is included, so this transcripts the
|
|
|
|
|
// sum product
|
|
|
|
|
// Useful as transcripting the sum product effectively transcripts the key image, further
|
|
|
|
|
// guaranteeing the one time properties noted below
|
2022-11-05 18:43:36 -04:00
|
|
|
transcript.append_message(b"spend_key", keys.group_key().0.compress().to_bytes());
|
2022-04-30 04:32:19 -04:00
|
|
|
for input in &self.inputs {
|
2022-05-06 07:33:08 -04:00
|
|
|
// These outputs can only be spent once. Therefore, it forces all RNGs derived from this
|
|
|
|
|
// transcript (such as the one used to create one time keys) to be unique
|
2022-11-05 18:43:36 -04:00
|
|
|
transcript.append_message(b"input_hash", input.output.absolute.tx);
|
|
|
|
|
transcript.append_message(b"input_output_index", [input.output.absolute.o]);
|
2022-05-06 07:33:08 -04:00
|
|
|
// Not including this, with a doxxed list of payments, would allow brute forcing the inputs
|
|
|
|
|
// to determine RNG seeds and therefore the true spends
|
2022-11-05 18:43:36 -04:00
|
|
|
transcript.append_message(b"input_shared_key", input.key_offset().to_bytes());
|
2022-05-04 06:24:52 -04:00
|
|
|
}
|
|
|
|
|
for payment in &self.payments {
|
2022-07-22 02:34:36 -04:00
|
|
|
transcript.append_message(b"payment_address", payment.0.to_string().as_bytes());
|
2022-11-05 18:43:36 -04:00
|
|
|
transcript.append_message(b"payment_amount", payment.1.to_le_bytes());
|
2022-05-04 06:24:52 -04:00
|
|
|
}
|
2022-04-30 04:32:19 -04:00
|
|
|
|
2022-12-08 19:04:35 -05:00
|
|
|
let mut key_images = vec![];
|
2022-05-04 06:24:52 -04:00
|
|
|
for (i, input) in self.inputs.iter().enumerate() {
|
2022-06-09 04:05:57 -04:00
|
|
|
// Check this the right set of keys
|
2022-12-08 19:04:35 -05:00
|
|
|
let offset = keys.offset(dfg::Scalar(input.key_offset()));
|
2022-08-22 13:35:49 -04:00
|
|
|
if offset.group_key().0 != input.key() {
|
2022-06-09 04:05:57 -04:00
|
|
|
Err(TransactionError::WrongPrivateKey)?;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-08 19:04:35 -05:00
|
|
|
let clsag = ClsagMultisig::new(transcript.clone(), input.key(), inputs[i].clone());
|
|
|
|
|
key_images.push((
|
|
|
|
|
clsag.H,
|
2023-01-10 06:57:25 -05:00
|
|
|
keys.current_offset().unwrap_or_else(dfg::Scalar::zero).0 + self.inputs[i].key_offset(),
|
2022-12-08 19:04:35 -05:00
|
|
|
));
|
|
|
|
|
clsags.push(AlgorithmMachine::new(clsag, offset).map_err(TransactionError::FrostError)?);
|
2022-04-30 04:32:19 -04:00
|
|
|
}
|
|
|
|
|
|
2022-06-09 04:05:57 -04:00
|
|
|
// Select decoys
|
|
|
|
|
// Ideally, this would be done post entropy, instead of now, yet doing so would require sign
|
|
|
|
|
// to be async which isn't preferable. This should be suitably competent though
|
|
|
|
|
// While this inability means we can immediately create the input, moving it out of the
|
|
|
|
|
// Arc RwLock, keeping it within an Arc RwLock keeps our options flexible
|
|
|
|
|
let decoys = Decoys::select(
|
|
|
|
|
// Using a seeded RNG with a specific height, committed to above, should make these decoys
|
|
|
|
|
// committed to. They'll also be committed to later via the TX message as a whole
|
2022-08-30 20:01:46 -04:00
|
|
|
&mut ChaCha20Rng::from_seed(transcript.rng_seed(b"decoys")),
|
2022-06-09 04:05:57 -04:00
|
|
|
rpc,
|
2022-07-27 04:05:43 -05:00
|
|
|
self.protocol.ring_len(),
|
2022-06-09 04:05:57 -04:00
|
|
|
height,
|
2022-07-15 01:26:07 -04:00
|
|
|
&self.inputs,
|
|
|
|
|
)
|
|
|
|
|
.await
|
2022-07-22 02:34:36 -04:00
|
|
|
.map_err(TransactionError::RpcError)?;
|
2022-06-09 04:05:57 -04:00
|
|
|
|
2022-07-15 01:26:07 -04:00
|
|
|
Ok(TransactionMachine {
|
|
|
|
|
signable: self,
|
|
|
|
|
i: keys.params().i(),
|
|
|
|
|
transcript,
|
2022-05-06 19:07:37 -04:00
|
|
|
|
2022-07-15 01:26:07 -04:00
|
|
|
decoys,
|
2022-05-06 19:07:37 -04:00
|
|
|
|
2022-12-08 19:04:35 -05:00
|
|
|
key_images,
|
2022-07-15 01:26:07 -04:00
|
|
|
inputs,
|
|
|
|
|
clsags,
|
|
|
|
|
})
|
2022-04-30 04:32:19 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 08:40:14 -04:00
|
|
|
impl PreprocessMachine for TransactionMachine {
|
2022-10-25 23:17:25 -05:00
|
|
|
type Preprocess = Vec<Preprocess<Ed25519, ClsagAddendum>>;
|
2022-04-30 04:32:19 -04:00
|
|
|
type Signature = Transaction;
|
2022-06-24 08:40:14 -04:00
|
|
|
type SignMachine = TransactionSignMachine;
|
2022-04-30 04:32:19 -04:00
|
|
|
|
|
|
|
|
fn preprocess<R: RngCore + CryptoRng>(
|
2022-06-24 08:40:14 -04:00
|
|
|
mut self,
|
2022-07-15 01:26:07 -04:00
|
|
|
rng: &mut R,
|
2022-10-25 23:17:25 -05:00
|
|
|
) -> (TransactionSignMachine, Self::Preprocess) {
|
2022-04-30 04:32:19 -04:00
|
|
|
// Iterate over each CLSAG calling preprocess
|
2022-10-25 23:17:25 -05:00
|
|
|
let mut preprocesses = Vec::with_capacity(self.clsags.len());
|
2022-07-15 01:26:07 -04:00
|
|
|
let clsags = self
|
|
|
|
|
.clsags
|
|
|
|
|
.drain(..)
|
|
|
|
|
.map(|clsag| {
|
|
|
|
|
let (clsag, preprocess) = clsag.preprocess(rng);
|
2022-10-25 23:17:25 -05:00
|
|
|
preprocesses.push(preprocess);
|
2022-07-15 01:26:07 -04:00
|
|
|
clsag
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
2022-10-25 23:17:25 -05:00
|
|
|
let our_preprocess = preprocesses.clone();
|
2022-04-30 04:32:19 -04:00
|
|
|
|
2022-05-22 01:56:17 -04:00
|
|
|
// We could add further entropy here, and previous versions of this library did so
|
|
|
|
|
// As of right now, the multisig's key, the inputs being spent, and the FROST data itself
|
|
|
|
|
// will be used for RNG seeds. In order to recreate these RNG seeds, breaking privacy,
|
|
|
|
|
// counterparties must have knowledge of the multisig, either the view key or access to the
|
|
|
|
|
// coordination layer, and then access to the actual FROST signing process
|
|
|
|
|
// If the commitments are sent in plain text, then entropy here also would be, making it not
|
|
|
|
|
// increase privacy. If they're not sent in plain text, or are otherwise inaccessible, they
|
|
|
|
|
// already offer sufficient entropy. That's why further entropy is not included
|
2022-04-30 04:32:19 -04:00
|
|
|
|
2022-06-24 08:40:14 -04:00
|
|
|
(
|
|
|
|
|
TransactionSignMachine {
|
|
|
|
|
signable: self.signable,
|
|
|
|
|
i: self.i,
|
|
|
|
|
transcript: self.transcript,
|
|
|
|
|
|
|
|
|
|
decoys: self.decoys,
|
|
|
|
|
|
2022-12-08 19:04:35 -05:00
|
|
|
key_images: self.key_images,
|
2022-06-24 08:40:14 -04:00
|
|
|
inputs: self.inputs,
|
|
|
|
|
clsags,
|
|
|
|
|
|
|
|
|
|
our_preprocess,
|
|
|
|
|
},
|
2022-10-25 23:17:25 -05:00
|
|
|
preprocesses,
|
2022-06-24 08:40:14 -04:00
|
|
|
)
|
2022-04-30 04:32:19 -04:00
|
|
|
}
|
2022-06-24 08:40:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SignMachine<Transaction> for TransactionSignMachine {
|
2022-12-08 19:04:35 -05:00
|
|
|
type Params = ();
|
|
|
|
|
type Keys = ThresholdKeys<Ed25519>;
|
2022-10-25 23:17:25 -05:00
|
|
|
type Preprocess = Vec<Preprocess<Ed25519, ClsagAddendum>>;
|
|
|
|
|
type SignatureShare = Vec<SignatureShare<Ed25519>>;
|
2022-06-24 08:40:14 -04:00
|
|
|
type SignatureMachine = TransactionSignatureMachine;
|
2022-04-30 04:32:19 -04:00
|
|
|
|
2023-01-01 01:54:18 -05:00
|
|
|
fn cache(self) -> CachedPreprocess {
|
2022-12-08 19:04:35 -05:00
|
|
|
unimplemented!(
|
|
|
|
|
"Monero transactions don't support caching their preprocesses due to {}",
|
|
|
|
|
"being already bound to a specific transaction"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-01 01:54:18 -05:00
|
|
|
fn from_cache(_: (), _: ThresholdKeys<Ed25519>, _: CachedPreprocess) -> Result<Self, FrostError> {
|
2022-12-08 19:04:35 -05:00
|
|
|
unimplemented!(
|
|
|
|
|
"Monero transactions don't support caching their preprocesses due to {}",
|
|
|
|
|
"being already bound to a specific transaction"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-25 23:17:25 -05:00
|
|
|
fn read_preprocess<R: Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess> {
|
|
|
|
|
self.clsags.iter().map(|clsag| clsag.read_preprocess(reader)).collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn sign(
|
2022-06-24 08:40:14 -04:00
|
|
|
mut self,
|
2022-10-25 23:17:25 -05:00
|
|
|
mut commitments: HashMap<u16, Self::Preprocess>,
|
2022-07-15 01:26:07 -04:00
|
|
|
msg: &[u8],
|
2022-10-25 23:17:25 -05:00
|
|
|
) -> Result<(TransactionSignatureMachine, Self::SignatureShare), FrostError> {
|
2022-07-22 02:34:36 -04:00
|
|
|
if !msg.is_empty() {
|
2022-07-15 01:26:07 -04:00
|
|
|
Err(FrostError::InternalError(
|
|
|
|
|
"message was passed to the TransactionMachine when it generates its own",
|
|
|
|
|
))?;
|
2022-06-10 00:20:59 -04:00
|
|
|
}
|
|
|
|
|
|
2022-12-08 19:04:35 -05:00
|
|
|
// 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
|
2023-01-20 11:00:18 -05:00
|
|
|
let mut included = commitments.keys().cloned().collect::<Vec<_>>();
|
2022-12-08 19:04:35 -05:00
|
|
|
included.push(self.i);
|
|
|
|
|
included.sort_unstable();
|
|
|
|
|
|
2022-06-24 08:40:14 -04:00
|
|
|
// Convert the unified commitments to a Vec of the individual commitments
|
|
|
|
|
let mut images = vec![EdwardsPoint::identity(); self.clsags.len()];
|
2022-07-15 01:26:07 -04:00
|
|
|
let mut commitments = (0 .. self.clsags.len())
|
|
|
|
|
.map(|c| {
|
2022-12-08 19:04:35 -05:00
|
|
|
included
|
2022-07-15 01:26:07 -04:00
|
|
|
.iter()
|
|
|
|
|
.map(|l| {
|
|
|
|
|
// Add all commitments to the transcript for their entropy
|
|
|
|
|
// While each CLSAG will do this as they need to for security, they have their own
|
|
|
|
|
// transcripts cloned from this TX's initial premise's transcript. For our TX
|
|
|
|
|
// transcript to have the CLSAG data for entropy, it'll have to be added ourselves here
|
2022-11-05 18:43:36 -04:00
|
|
|
self.transcript.append_message(b"participant", (*l).to_be_bytes());
|
2022-10-25 23:17:25 -05:00
|
|
|
|
|
|
|
|
let preprocess = if *l == self.i {
|
|
|
|
|
self.our_preprocess[c].clone()
|
2022-07-15 01:26:07 -04:00
|
|
|
} else {
|
2022-10-25 23:17:25 -05:00
|
|
|
commitments.get_mut(l).ok_or(FrostError::MissingParticipant(*l))?[c].clone()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let mut buf = vec![];
|
|
|
|
|
preprocess.write(&mut buf).unwrap();
|
2022-11-05 18:43:36 -04:00
|
|
|
self.transcript.append_message(b"preprocess", buf);
|
2022-07-15 01:26:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// While here, calculate the key image
|
|
|
|
|
// Clsag will parse/calculate/validate this as needed, yet doing so here as well
|
|
|
|
|
// 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)
|
2022-12-08 19:04:35 -05:00
|
|
|
add_key_image_share(
|
|
|
|
|
&mut images[c],
|
|
|
|
|
self.key_images[c].0,
|
|
|
|
|
self.key_images[c].1,
|
|
|
|
|
&included,
|
|
|
|
|
*l,
|
|
|
|
|
preprocess.addendum.key_image.0,
|
|
|
|
|
);
|
2022-10-25 23:17:25 -05:00
|
|
|
|
|
|
|
|
Ok((*l, preprocess))
|
2022-07-15 01:26:07 -04:00
|
|
|
})
|
|
|
|
|
.collect::<Result<HashMap<_, _>, _>>()
|
|
|
|
|
})
|
|
|
|
|
.collect::<Result<Vec<_>, _>>()?;
|
2022-07-13 02:38:29 -04:00
|
|
|
|
|
|
|
|
// Remove our preprocess which shouldn't be here. It was just the easiest way to implement the
|
|
|
|
|
// above
|
|
|
|
|
for map in commitments.iter_mut() {
|
|
|
|
|
map.remove(&self.i);
|
2022-05-18 00:53:13 -04:00
|
|
|
}
|
2022-04-30 04:32:19 -04:00
|
|
|
|
2022-05-22 01:56:17 -04:00
|
|
|
// Create the actual transaction
|
Utilize zeroize (#76)
* Apply Zeroize to nonces used in Bulletproofs
Also makes bit decomposition constant time for a given amount of
outputs.
* Fix nonce reuse for single-signer CLSAG
* Attach Zeroize to most structures in Monero, and ZOnDrop to anything with private data
* Zeroize private keys and nonces
* Merge prepare_outputs and prepare_transactions
* Ensure CLSAG is constant time
* Pass by borrow where needed, bug fixes
The past few commitments have been one in-progress chunk which I've
broken up as best read.
* Add Zeroize to FROST structs
Still needs to zeroize internally, yet next step. Not quite as
aggressive as Monero, partially due to the limitations of HashMaps,
partially due to less concern about metadata, yet does still delete a
few smaller items of metadata (group key, context string...).
* Remove Zeroize from most Monero multisig structs
These structs largely didn't have private data, just fields with private
data, yet those fields implemented ZeroizeOnDrop making them already
covered. While there is still traces of the transaction left in RAM,
fully purging that was never the intent.
* Use Zeroize within dleq
bitvec doesn't offer Zeroize, so a manual zeroing has been implemented.
* Use Zeroize for random_nonce
It isn't perfect, due to the inability to zeroize the digest, and due to
kp256 requiring a few transformations. It does the best it can though.
Does move the per-curve random_nonce to a provided one, which is allowed
as of https://github.com/cfrg/draft-irtf-cfrg-frost/pull/231.
* Use Zeroize on FROST keygen/signing
* Zeroize constant time multiexp.
* Correct when FROST keygen zeroizes
* Move the FROST keys Arc into FrostKeys
Reduces amount of instances in memory.
* Manually implement Debug for FrostCore to not leak the secret share
* Misc bug fixes
* clippy + multiexp test bug fixes
* Correct FROST key gen share summation
It leaked our own share for ourself.
* Fix cross-group DLEq tests
2022-08-03 03:25:18 -05:00
|
|
|
let (mut tx, output_masks) = {
|
2022-06-24 08:40:14 -04:00
|
|
|
let mut sorted_images = images.clone();
|
|
|
|
|
sorted_images.sort_by(key_image_sort);
|
2022-05-22 01:56:17 -04:00
|
|
|
|
Utilize zeroize (#76)
* Apply Zeroize to nonces used in Bulletproofs
Also makes bit decomposition constant time for a given amount of
outputs.
* Fix nonce reuse for single-signer CLSAG
* Attach Zeroize to most structures in Monero, and ZOnDrop to anything with private data
* Zeroize private keys and nonces
* Merge prepare_outputs and prepare_transactions
* Ensure CLSAG is constant time
* Pass by borrow where needed, bug fixes
The past few commitments have been one in-progress chunk which I've
broken up as best read.
* Add Zeroize to FROST structs
Still needs to zeroize internally, yet next step. Not quite as
aggressive as Monero, partially due to the limitations of HashMaps,
partially due to less concern about metadata, yet does still delete a
few smaller items of metadata (group key, context string...).
* Remove Zeroize from most Monero multisig structs
These structs largely didn't have private data, just fields with private
data, yet those fields implemented ZeroizeOnDrop making them already
covered. While there is still traces of the transaction left in RAM,
fully purging that was never the intent.
* Use Zeroize within dleq
bitvec doesn't offer Zeroize, so a manual zeroing has been implemented.
* Use Zeroize for random_nonce
It isn't perfect, due to the inability to zeroize the digest, and due to
kp256 requiring a few transformations. It does the best it can though.
Does move the per-curve random_nonce to a provided one, which is allowed
as of https://github.com/cfrg/draft-irtf-cfrg-frost/pull/231.
* Use Zeroize on FROST keygen/signing
* Zeroize constant time multiexp.
* Correct when FROST keygen zeroizes
* Move the FROST keys Arc into FrostKeys
Reduces amount of instances in memory.
* Manually implement Debug for FrostCore to not leak the secret share
* Misc bug fixes
* clippy + multiexp test bug fixes
* Correct FROST key gen share summation
It leaked our own share for ourself.
* Fix cross-group DLEq tests
2022-08-03 03:25:18 -05:00
|
|
|
self.signable.prepare_transaction(
|
2022-08-30 20:01:46 -04:00
|
|
|
&mut ChaCha20Rng::from_seed(self.transcript.rng_seed(b"transaction_keys_bulletproofs")),
|
2022-05-22 01:56:17 -04:00
|
|
|
uniqueness(
|
Utilize zeroize (#76)
* Apply Zeroize to nonces used in Bulletproofs
Also makes bit decomposition constant time for a given amount of
outputs.
* Fix nonce reuse for single-signer CLSAG
* Attach Zeroize to most structures in Monero, and ZOnDrop to anything with private data
* Zeroize private keys and nonces
* Merge prepare_outputs and prepare_transactions
* Ensure CLSAG is constant time
* Pass by borrow where needed, bug fixes
The past few commitments have been one in-progress chunk which I've
broken up as best read.
* Add Zeroize to FROST structs
Still needs to zeroize internally, yet next step. Not quite as
aggressive as Monero, partially due to the limitations of HashMaps,
partially due to less concern about metadata, yet does still delete a
few smaller items of metadata (group key, context string...).
* Remove Zeroize from most Monero multisig structs
These structs largely didn't have private data, just fields with private
data, yet those fields implemented ZeroizeOnDrop making them already
covered. While there is still traces of the transaction left in RAM,
fully purging that was never the intent.
* Use Zeroize within dleq
bitvec doesn't offer Zeroize, so a manual zeroing has been implemented.
* Use Zeroize for random_nonce
It isn't perfect, due to the inability to zeroize the digest, and due to
kp256 requiring a few transformations. It does the best it can though.
Does move the per-curve random_nonce to a provided one, which is allowed
as of https://github.com/cfrg/draft-irtf-cfrg-frost/pull/231.
* Use Zeroize on FROST keygen/signing
* Zeroize constant time multiexp.
* Correct when FROST keygen zeroizes
* Move the FROST keys Arc into FrostKeys
Reduces amount of instances in memory.
* Manually implement Debug for FrostCore to not leak the secret share
* Misc bug fixes
* clippy + multiexp test bug fixes
* Correct FROST key gen share summation
It leaked our own share for ourself.
* Fix cross-group DLEq tests
2022-08-03 03:25:18 -05:00
|
|
|
&sorted_images
|
2022-07-15 01:26:07 -04:00
|
|
|
.iter()
|
|
|
|
|
.map(|image| Input::ToKey { amount: 0, key_offsets: vec![], key_image: *image })
|
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
|
),
|
2022-05-22 01:56:17 -04:00
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-24 08:40:14 -04:00
|
|
|
// Sort the inputs, as expected
|
|
|
|
|
let mut sorted = Vec::with_capacity(self.clsags.len());
|
2022-07-22 02:34:36 -04:00
|
|
|
while !self.clsags.is_empty() {
|
2022-05-18 00:53:13 -04:00
|
|
|
sorted.push((
|
2022-06-24 08:40:14 -04:00
|
|
|
images.swap_remove(0),
|
2022-05-18 00:53:13 -04:00
|
|
|
self.signable.inputs.swap_remove(0),
|
|
|
|
|
self.decoys.swap_remove(0),
|
|
|
|
|
self.inputs.swap_remove(0),
|
|
|
|
|
self.clsags.swap_remove(0),
|
2022-07-15 01:26:07 -04:00
|
|
|
commitments.swap_remove(0),
|
2022-05-18 00:53:13 -04:00
|
|
|
));
|
|
|
|
|
}
|
2022-06-24 08:40:14 -04:00
|
|
|
sorted.sort_by(|x, y| key_image_sort(&x.0, &y.0));
|
2022-05-18 00:53:13 -04:00
|
|
|
|
2022-08-30 20:01:46 -04:00
|
|
|
let mut rng = ChaCha20Rng::from_seed(self.transcript.rng_seed(b"pseudo_out_masks"));
|
2022-05-18 00:53:13 -04:00
|
|
|
let mut sum_pseudo_outs = Scalar::zero();
|
2022-07-22 02:34:36 -04:00
|
|
|
while !sorted.is_empty() {
|
2022-05-18 00:53:13 -04:00
|
|
|
let value = sorted.remove(0);
|
2022-05-06 19:07:37 -04:00
|
|
|
|
|
|
|
|
let mut mask = random_scalar(&mut rng);
|
2022-07-22 02:34:36 -04:00
|
|
|
if sorted.is_empty() {
|
2022-06-24 08:40:14 -04:00
|
|
|
mask = output_masks - sum_pseudo_outs;
|
2022-05-06 19:07:37 -04:00
|
|
|
} else {
|
|
|
|
|
sum_pseudo_outs += mask;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-15 01:26:07 -04:00
|
|
|
tx.prefix.inputs.push(Input::ToKey {
|
|
|
|
|
amount: 0,
|
|
|
|
|
key_offsets: value.2.offsets.clone(),
|
|
|
|
|
key_image: value.0,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
*value.3.write().unwrap() = Some(ClsagDetails::new(
|
2022-08-22 12:15:14 -04:00
|
|
|
ClsagInput::new(value.1.commitment().clone(), value.2).map_err(|_| {
|
2022-07-15 01:26:07 -04:00
|
|
|
panic!("Signing an input which isn't present in the ring we created for it")
|
|
|
|
|
})?,
|
|
|
|
|
mask,
|
|
|
|
|
));
|
2022-05-06 19:07:37 -04:00
|
|
|
|
2022-05-18 00:53:13 -04:00
|
|
|
self.clsags.push(value.4);
|
|
|
|
|
commitments.push(value.5);
|
2022-04-30 04:32:19 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-21 15:33:35 -04:00
|
|
|
let msg = tx.signature_hash();
|
2022-04-30 04:32:19 -04:00
|
|
|
|
|
|
|
|
// Iterate over each CLSAG calling sign
|
2022-10-25 23:17:25 -05:00
|
|
|
let mut shares = Vec::with_capacity(self.clsags.len());
|
2022-07-15 01:26:07 -04:00
|
|
|
let clsags = self
|
|
|
|
|
.clsags
|
|
|
|
|
.drain(..)
|
|
|
|
|
.map(|clsag| {
|
|
|
|
|
let (clsag, share) = clsag.sign(commitments.remove(0), &msg)?;
|
2022-10-25 23:17:25 -05:00
|
|
|
shares.push(share);
|
2022-07-15 01:26:07 -04:00
|
|
|
Ok(clsag)
|
|
|
|
|
})
|
|
|
|
|
.collect::<Result<_, _>>()?;
|
2022-04-30 04:32:19 -04:00
|
|
|
|
2022-10-25 23:17:25 -05:00
|
|
|
Ok((TransactionSignatureMachine { tx, clsags }, shares))
|
2022-04-30 04:32:19 -04:00
|
|
|
}
|
2022-06-24 08:40:14 -04:00
|
|
|
}
|
2022-04-30 04:32:19 -04:00
|
|
|
|
2022-06-24 08:40:14 -04:00
|
|
|
impl SignatureMachine<Transaction> for TransactionSignatureMachine {
|
2022-10-25 23:17:25 -05:00
|
|
|
type SignatureShare = Vec<SignatureShare<Ed25519>>;
|
|
|
|
|
|
|
|
|
|
fn read_share<R: Read>(&self, reader: &mut R) -> io::Result<Self::SignatureShare> {
|
|
|
|
|
self.clsags.iter().map(|clsag| clsag.read_share(reader)).collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn complete(
|
|
|
|
|
mut self,
|
|
|
|
|
shares: HashMap<u16, Self::SignatureShare>,
|
|
|
|
|
) -> Result<Transaction, FrostError> {
|
2022-06-24 08:40:14 -04:00
|
|
|
let mut tx = self.tx;
|
2022-05-21 15:33:35 -04:00
|
|
|
match tx.rct_signatures.prunable {
|
|
|
|
|
RctPrunable::Null => panic!("Signing for RctPrunable::Null"),
|
|
|
|
|
RctPrunable::Clsag { ref mut clsags, ref mut pseudo_outs, .. } => {
|
2022-10-25 23:17:25 -05:00
|
|
|
for (c, clsag) in self.clsags.drain(..).enumerate() {
|
2022-06-24 08:40:14 -04:00
|
|
|
let (clsag, pseudo_out) = clsag.complete(
|
2022-10-25 23:17:25 -05:00
|
|
|
shares.iter().map(|(l, shares)| (*l, shares[c].clone())).collect::<HashMap<_, _>>(),
|
2022-06-24 08:40:14 -04:00
|
|
|
)?;
|
2022-05-21 15:33:35 -04:00
|
|
|
clsags.push(clsag);
|
|
|
|
|
pseudo_outs.push(pseudo_out);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-30 04:32:19 -04:00
|
|
|
}
|
|
|
|
|
Ok(tx)
|
|
|
|
|
}
|
|
|
|
|
}
|