diff --git a/processor/Cargo.toml b/processor/Cargo.toml index 70756995..9bad24b8 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -35,7 +35,7 @@ serde_json = "1" group = "0.13" transcript = { package = "flexible-transcript", path = "../crypto/transcript" } -frost = { package = "modular-frost", path = "../crypto/frost" } +frost = { package = "modular-frost", path = "../crypto/frost", features = ["ristretto"] } # Bitcoin secp256k1 = { version = "0.24", features = ["global-context", "rand-std"], optional = true } diff --git a/processor/messages/src/lib.rs b/processor/messages/src/lib.rs index 2fc50dc4..9eb2c337 100644 --- a/processor/messages/src/lib.rs +++ b/processor/messages/src/lib.rs @@ -37,8 +37,8 @@ pub mod key_gen { Commitments { id: KeyGenId, commitments: HashMap> }, // Received shares for the specified key generation protocol. Shares { id: KeyGenId, shares: HashMap> }, - // Confirm a key. - ConfirmKey { context: SubstrateContext, id: KeyGenId }, + // Confirm a key pair. + ConfirmKeyPair { context: SubstrateContext, id: KeyGenId }, } #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] @@ -47,8 +47,8 @@ pub mod key_gen { Commitments { id: KeyGenId, commitments: Vec }, // Created shares for the specified key generation protocol. Shares { id: KeyGenId, shares: HashMap> }, - // Resulting key from the specified key generation protocol. - GeneratedKey { id: KeyGenId, key: Vec }, + // Resulting keys from the specified key generation protocol. + GeneratedKeyPair { id: KeyGenId, substrate_key: [u8; 32], coin_key: Vec }, } } diff --git a/processor/src/key_gen.rs b/processor/src/key_gen.rs index 111c4292..06286d6d 100644 --- a/processor/src/key_gen.rs +++ b/processor/src/key_gen.rs @@ -9,7 +9,7 @@ use rand_chacha::ChaCha20Rng; use transcript::{Transcript, RecommendedTranscript}; use group::GroupEncoding; use frost::{ - curve::Ciphersuite, + curve::{Ciphersuite, Ristretto}, dkg::{Participant, ThresholdParams, ThresholdCore, ThresholdKeys, encryption::*, frost::*}, }; @@ -22,7 +22,11 @@ use crate::{DbTxn, Db, coins::Coin}; #[derive(Debug)] pub enum KeyGenEvent { - KeyConfirmed { activation_number: usize, keys: ThresholdKeys }, + KeyConfirmed { + activation_number: usize, + substrate_keys: ThresholdKeys, + coin_keys: ThresholdKeys, + }, ProcessorMessage(ProcessorMessage), } @@ -63,53 +67,57 @@ impl KeyGenDb { ) { txn.put(Self::commitments_key(id), bincode::serialize(commitments).unwrap()); } - fn commitments( - &self, - id: &KeyGenId, - params: ThresholdParams, - ) -> HashMap>> { + fn commitments(&self, id: &KeyGenId) -> HashMap> { bincode::deserialize::>>( &self.0.get(Self::commitments_key(id)).unwrap(), ) .unwrap() - .drain() - .map(|(i, bytes)| { - ( - i, - EncryptionKeyMessage::>::read::<&[u8]>( - &mut bytes.as_ref(), - params, - ) - .unwrap(), - ) - }) - .collect() } fn generated_keys_key(id: &KeyGenId) -> Vec { Self::key_gen_key(b"generated_keys", bincode::serialize(id).unwrap()) } - fn save_keys(&mut self, txn: &mut D::Transaction, id: &KeyGenId, keys: &ThresholdCore) { - txn.put(Self::generated_keys_key(id), keys.serialize()); + fn save_keys( + &mut self, + txn: &mut D::Transaction, + id: &KeyGenId, + substrate_keys: &ThresholdCore, + coin_keys: &ThresholdCore, + ) { + let mut keys = substrate_keys.serialize(); + keys.extend(coin_keys.serialize().iter()); + txn.put(Self::generated_keys_key(id), keys); } fn keys_key(key: &::G) -> Vec { Self::key_gen_key(b"keys", key.to_bytes()) } - fn confirm_keys(&mut self, txn: &mut D::Transaction, id: &KeyGenId) -> ThresholdKeys { - let keys_vec = self.0.get(Self::generated_keys_key(id)).unwrap(); - let mut keys = - ThresholdKeys::new(ThresholdCore::read::<&[u8]>(&mut keys_vec.as_ref()).unwrap()); - C::tweak_keys(&mut keys); - txn.put(Self::keys_key(&keys.group_key()), keys_vec); + #[allow(clippy::type_complexity)] + fn read_keys( + &self, + key: &[u8], + ) -> (Vec, (ThresholdKeys, ThresholdKeys)) { + let keys_vec = self.0.get(key).unwrap(); + let mut keys_ref: &[u8] = keys_vec.as_ref(); + let substrate_keys = ThresholdKeys::new(ThresholdCore::read(&mut keys_ref).unwrap()); + let mut coin_keys = ThresholdKeys::new(ThresholdCore::read(&mut keys_ref).unwrap()); + C::tweak_keys(&mut coin_keys); + (keys_vec, (substrate_keys, coin_keys)) + } + fn confirm_keys( + &mut self, + txn: &mut D::Transaction, + id: &KeyGenId, + ) -> (ThresholdKeys, ThresholdKeys) { + let (keys_vec, keys) = self.read_keys(&Self::generated_keys_key(id)); + txn.put(Self::keys_key(&keys.1.group_key()), keys_vec); keys } - fn keys(&self, key: &::G) -> ThresholdKeys { - let mut keys = ThresholdKeys::new( - ThresholdCore::read::<&[u8]>(&mut self.0.get(Self::keys_key(key)).unwrap().as_ref()).unwrap(), - ); - C::tweak_keys(&mut keys); - keys + fn keys( + &self, + key: &::G, + ) -> (ThresholdKeys, ThresholdKeys) { + self.read_keys(&Self::keys_key(key)).1 } } @@ -121,8 +129,9 @@ pub struct KeyGen { db: KeyGenDb, entropy: Zeroizing<[u8; 32]>, - active_commit: HashMap>, - active_share: HashMap>, + active_commit: + HashMap, SecretShareMachine)>, + active_share: HashMap, KeyMachine)>, } impl KeyGen { @@ -137,7 +146,10 @@ impl KeyGen { } } - pub fn keys(&self, key: &::G) -> ThresholdKeys { + pub fn keys( + &self, + key: &::G, + ) -> (ThresholdKeys, ThresholdKeys) { self.db.keys(key) } @@ -160,8 +172,11 @@ impl KeyGen { let secret_shares_rng = |id| rng(b"Key Gen Secret Shares", id); let share_rng = |id| rng(b"Key Gen Share", id); - let key_gen_machine = |id, params| { - KeyGenMachine::new(params, context(&id)).generate_coefficients(&mut coefficients_rng(id)) + let key_gen_machines = |id, params| { + let mut rng = coefficients_rng(id); + let substrate = KeyGenMachine::new(params, context(&id)).generate_coefficients(&mut rng); + let coin = KeyGenMachine::new(params, context(&id)).generate_coefficients(&mut rng); + ((substrate.0, coin.0), (substrate.1, coin.1)) }; match msg { @@ -180,13 +195,12 @@ impl KeyGen { txn.commit(); } - let (machine, commitments) = key_gen_machine(id, params); - self.active_commit.insert(id.set, machine); + let (machines, commitments) = key_gen_machines(id, params); + let mut serialized = commitments.0.serialize(); + serialized.extend(commitments.1.serialize()); + self.active_commit.insert(id.set, machines); - KeyGenEvent::ProcessorMessage(ProcessorMessage::Commitments { - id, - commitments: commitments.serialize(), - }) + KeyGenEvent::ProcessorMessage(ProcessorMessage::Commitments { id, commitments: serialized }) } CoordinatorMessage::Commitments { id, commitments } => { @@ -201,106 +215,168 @@ impl KeyGen { let params = self.db.params(&id.set); - // Parse the commitments - let parsed = match commitments - .iter() - .map(|(i, commitments)| { - EncryptionKeyMessage::>::read::<&[u8]>( - &mut commitments.as_ref(), - params, - ) - .map(|commitments| (*i, commitments)) - }) - .collect() - { - Ok(commitments) => commitments, - Err(e) => todo!("malicious signer: {:?}", e), - }; - - // Get the machine, rebuilding it if we don't have it + // Unwrap the machines, rebuilding them if we didn't have them in our cache // We won't if the processor rebooted // This *may* be inconsistent if we receive a KeyGen for attempt x, then commitments for // attempt y // The coordinator is trusted to be proper in this regard - let machine = - self.active_commit.remove(&id.set).unwrap_or_else(|| key_gen_machine(id, params).0); + let machines = + self.active_commit.remove(&id.set).unwrap_or_else(|| key_gen_machines(id, params).0); - let (machine, mut shares) = - match machine.generate_secret_shares(&mut secret_shares_rng(id), parsed) { - Ok(res) => res, + let mut rng = secret_shares_rng(id); + + let mut commitments_ref: HashMap = + commitments.iter().map(|(i, commitments)| (*i, commitments.as_ref())).collect(); + + #[allow(clippy::type_complexity)] + fn handle_machine( + rng: &mut ChaCha20Rng, + params: ThresholdParams, + machine: SecretShareMachine, + commitments_ref: &mut HashMap, + ) -> (KeyMachine, HashMap>>) { + // Parse the commitments + let parsed = match commitments_ref + .iter_mut() + .map(|(i, commitments)| { + EncryptionKeyMessage::>::read(commitments, params) + .map(|commitments| (*i, commitments)) + }) + .collect() + { + Ok(commitments) => commitments, Err(e) => todo!("malicious signer: {:?}", e), }; - self.active_share.insert(id.set, machine); + + match machine.generate_secret_shares(rng, parsed) { + Ok(res) => res, + Err(e) => todo!("malicious signer: {:?}", e), + } + } + + let (substrate_machine, mut substrate_shares) = + handle_machine::(&mut rng, params, machines.0, &mut commitments_ref); + let (coin_machine, coin_shares) = + handle_machine(&mut rng, params, machines.1, &mut commitments_ref); + + self.active_share.insert(id.set, (substrate_machine, coin_machine)); + + let mut shares: HashMap<_, _> = + substrate_shares.drain().map(|(i, share)| (i, share.serialize())).collect(); + for (i, share) in shares.iter_mut() { + share.extend(coin_shares[i].serialize()); + } let mut txn = self.db.0.txn(); self.db.save_commitments(&mut txn, &id, &commitments); txn.commit(); - KeyGenEvent::ProcessorMessage(ProcessorMessage::Shares { - id, - shares: shares.drain().map(|(i, share)| (i, share.serialize())).collect(), - }) + KeyGenEvent::ProcessorMessage(ProcessorMessage::Shares { id, shares }) } - CoordinatorMessage::Shares { id, mut shares } => { + CoordinatorMessage::Shares { id, shares } => { info!("Received shares for {:?}", id); let params = self.db.params(&id.set); - // Parse the shares - let shares = match shares - .drain() - .map(|(i, share)| { - EncryptedMessage::::F>>::read::<&[u8]>( - &mut share.as_ref(), - params, - ) - .map(|share| (i, share)) - }) - .collect() - { - Ok(shares) => shares, - Err(e) => todo!("malicious signer: {:?}", e), - }; - // Same commentary on inconsistency as above exists - let machine = self.active_share.remove(&id.set).unwrap_or_else(|| { - key_gen_machine(id, params) - .0 - .generate_secret_shares(&mut secret_shares_rng(id), self.db.commitments(&id, params)) - .unwrap() - .0 + let machines = self.active_share.remove(&id.set).unwrap_or_else(|| { + let machines = key_gen_machines(id, params).0; + let mut rng = secret_shares_rng(id); + let commitments = self.db.commitments(&id); + + let mut commitments_ref: HashMap = + commitments.iter().map(|(i, commitments)| (*i, commitments.as_ref())).collect(); + + fn parse_commitments( + params: ThresholdParams, + commitments_ref: &mut HashMap, + ) -> HashMap>> { + commitments_ref + .iter_mut() + .map(|(i, commitments)| { + (*i, EncryptionKeyMessage::>::read(commitments, params).unwrap()) + }) + .collect() + } + + ( + machines + .0 + .generate_secret_shares(&mut rng, parse_commitments(params, &mut commitments_ref)) + .unwrap() + .0, + machines + .1 + .generate_secret_shares(&mut rng, parse_commitments(params, &mut commitments_ref)) + .unwrap() + .0, + ) }); - // TODO2: Handle the blame machine properly - let keys = (match machine.calculate_share(&mut share_rng(id), shares) { - Ok(res) => res, - Err(e) => todo!("malicious signer: {:?}", e), - }) - .complete(); + let mut rng = share_rng(id); + + let mut shares_ref: HashMap = + shares.iter().map(|(i, shares)| (*i, shares.as_ref())).collect(); + + fn handle_machine( + rng: &mut ChaCha20Rng, + params: ThresholdParams, + machine: KeyMachine, + shares_ref: &mut HashMap, + ) -> ThresholdCore { + // Parse the shares + let shares = match shares_ref + .iter_mut() + .map(|(i, share)| { + EncryptedMessage::>::read(share, params).map(|share| (*i, share)) + }) + .collect() + { + Ok(shares) => shares, + Err(e) => todo!("malicious signer: {:?}", e), + }; + + // TODO2: Handle the blame machine properly + (match machine.calculate_share(rng, shares) { + Ok(res) => res, + Err(e) => todo!("malicious signer: {:?}", e), + }) + .complete() + } + + let substrate_keys = handle_machine(&mut rng, params, machines.0, &mut shares_ref); + let coin_keys = handle_machine(&mut rng, params, machines.1, &mut shares_ref); let mut txn = self.db.0.txn(); - self.db.save_keys(&mut txn, &id, &keys); + self.db.save_keys(&mut txn, &id, &substrate_keys, &coin_keys); txn.commit(); - let mut keys = ThresholdKeys::new(keys); - C::tweak_keys(&mut keys); - KeyGenEvent::ProcessorMessage(ProcessorMessage::GeneratedKey { + let mut coin_keys = ThresholdKeys::new(coin_keys); + C::tweak_keys(&mut coin_keys); + KeyGenEvent::ProcessorMessage(ProcessorMessage::GeneratedKeyPair { id, - key: keys.group_key().to_bytes().as_ref().to_vec(), + substrate_key: substrate_keys.group_key().to_bytes(), + coin_key: coin_keys.group_key().to_bytes().as_ref().to_vec(), }) } - CoordinatorMessage::ConfirmKey { context, id } => { + CoordinatorMessage::ConfirmKeyPair { context, id } => { let mut txn = self.db.0.txn(); - let keys = self.db.confirm_keys(&mut txn, &id); + let (substrate_keys, coin_keys) = self.db.confirm_keys(&mut txn, &id); txn.commit(); - info!("Confirmed key {} from {:?}", hex::encode(keys.group_key().to_bytes()), id); + info!( + "Confirmed key pair {} {} from {:?}", + hex::encode(substrate_keys.group_key().to_bytes()), + hex::encode(coin_keys.group_key().to_bytes()), + id + ); KeyGenEvent::KeyConfirmed { activation_number: context.coin_latest_block_number.try_into().unwrap(), - keys, + substrate_keys, + coin_keys, } } } diff --git a/processor/src/main.rs b/processor/src/main.rs index 51a81b82..e6637a22 100644 --- a/processor/src/main.rs +++ b/processor/src/main.rs @@ -207,7 +207,8 @@ async fn run(raw_db: D, coin: C, mut coordinato for key in &active_keys { // TODO: Load existing schedulers - let signer = Signer::new(raw_db.clone(), coin.clone(), key_gen.keys(key)); + // TODO: Handle the Ristretto key + let signer = Signer::new(raw_db.clone(), coin.clone(), key_gen.keys(key).1); // Load any TXs being actively signed let key = key.to_bytes(); @@ -332,7 +333,8 @@ async fn run(raw_db: D, coin: C, mut coordinato match msg.msg.clone() { CoordinatorMessage::KeyGen(msg) => { match key_gen.handle(msg).await { - KeyGenEvent::KeyConfirmed { activation_number, keys } => { + KeyGenEvent::KeyConfirmed { activation_number, substrate_keys, coin_keys } => { + let keys = coin_keys; let key = keys.group_key(); scanner.rotate_key(activation_number, key).await; schedulers.insert(key.to_bytes().as_ref().to_vec(), Scheduler::::new(key)); diff --git a/processor/src/tests/key_gen.rs b/processor/src/tests/key_gen.rs index a1de1656..6c8f0503 100644 --- a/processor/src/tests/key_gen.rs +++ b/processor/src/tests/key_gen.rs @@ -89,7 +89,11 @@ pub async fn test_key_gen() { for i in 1 ..= 5 { let key_gen = key_gens.get_mut(&i).unwrap(); let i = Participant::new(u16::try_from(i).unwrap()).unwrap(); - if let KeyGenEvent::ProcessorMessage(ProcessorMessage::GeneratedKey { id, key }) = key_gen + if let KeyGenEvent::ProcessorMessage(ProcessorMessage::GeneratedKeyPair { + id, + substrate_key, + coin_key, + }) = key_gen .handle(CoordinatorMessage::Shares { id: ID, shares: all_shares @@ -101,9 +105,9 @@ pub async fn test_key_gen() { { assert_eq!(id, ID); if res.is_none() { - res = Some(key.clone()); + res = Some((substrate_key, coin_key.clone())); } - assert_eq!(res.as_ref().unwrap(), &key); + assert_eq!(res.as_ref().unwrap(), &(substrate_key, coin_key)); } else { panic!("didn't get key back"); } @@ -115,19 +119,25 @@ pub async fn test_key_gen() { for i in 1 ..= 5 { let key_gen = key_gens.get_mut(&i).unwrap(); - if let KeyGenEvent::KeyConfirmed { activation_number, keys } = key_gen - .handle(CoordinatorMessage::ConfirmKey { + if let KeyGenEvent::KeyConfirmed { activation_number, substrate_keys, coin_keys } = key_gen + .handle(CoordinatorMessage::ConfirmKeyPair { context: SubstrateContext { time: 0, coin_latest_block_number: 111 }, id: ID, }) .await { assert_eq!(activation_number, 111); + let params = + ThresholdParams::new(3, 5, Participant::new(u16::try_from(i).unwrap()).unwrap()).unwrap(); + assert_eq!(substrate_keys.params(), params); + assert_eq!(coin_keys.params(), params); assert_eq!( - keys.params(), - ThresholdParams::new(3, 5, Participant::new(u16::try_from(i).unwrap()).unwrap()).unwrap() + &( + substrate_keys.group_key().to_bytes(), + coin_keys.group_key().to_bytes().as_ref().to_vec() + ), + res.as_ref().unwrap() ); - assert_eq!(keys.group_key().to_bytes().as_ref(), res.as_ref().unwrap()); } else { panic!("didn't get key back"); }