Have the processor DKG output a Ristretto key

This will be used to sign InInstructions.
This commit is contained in:
Luke Parker
2023-03-31 10:15:07 -04:00
parent a4f64e2651
commit 426346dd5a
5 changed files with 212 additions and 124 deletions

View File

@@ -35,7 +35,7 @@ serde_json = "1"
group = "0.13" group = "0.13"
transcript = { package = "flexible-transcript", path = "../crypto/transcript" } transcript = { package = "flexible-transcript", path = "../crypto/transcript" }
frost = { package = "modular-frost", path = "../crypto/frost" } frost = { package = "modular-frost", path = "../crypto/frost", features = ["ristretto"] }
# Bitcoin # Bitcoin
secp256k1 = { version = "0.24", features = ["global-context", "rand-std"], optional = true } secp256k1 = { version = "0.24", features = ["global-context", "rand-std"], optional = true }

View File

@@ -37,8 +37,8 @@ pub mod key_gen {
Commitments { id: KeyGenId, commitments: HashMap<Participant, Vec<u8>> }, Commitments { id: KeyGenId, commitments: HashMap<Participant, Vec<u8>> },
// Received shares for the specified key generation protocol. // Received shares for the specified key generation protocol.
Shares { id: KeyGenId, shares: HashMap<Participant, Vec<u8>> }, Shares { id: KeyGenId, shares: HashMap<Participant, Vec<u8>> },
// Confirm a key. // Confirm a key pair.
ConfirmKey { context: SubstrateContext, id: KeyGenId }, ConfirmKeyPair { context: SubstrateContext, id: KeyGenId },
} }
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
@@ -47,8 +47,8 @@ pub mod key_gen {
Commitments { id: KeyGenId, commitments: Vec<u8> }, Commitments { id: KeyGenId, commitments: Vec<u8> },
// Created shares for the specified key generation protocol. // Created shares for the specified key generation protocol.
Shares { id: KeyGenId, shares: HashMap<Participant, Vec<u8>> }, Shares { id: KeyGenId, shares: HashMap<Participant, Vec<u8>> },
// Resulting key from the specified key generation protocol. // Resulting keys from the specified key generation protocol.
GeneratedKey { id: KeyGenId, key: Vec<u8> }, GeneratedKeyPair { id: KeyGenId, substrate_key: [u8; 32], coin_key: Vec<u8> },
} }
} }

View File

@@ -9,7 +9,7 @@ use rand_chacha::ChaCha20Rng;
use transcript::{Transcript, RecommendedTranscript}; use transcript::{Transcript, RecommendedTranscript};
use group::GroupEncoding; use group::GroupEncoding;
use frost::{ use frost::{
curve::Ciphersuite, curve::{Ciphersuite, Ristretto},
dkg::{Participant, ThresholdParams, ThresholdCore, ThresholdKeys, encryption::*, frost::*}, dkg::{Participant, ThresholdParams, ThresholdCore, ThresholdKeys, encryption::*, frost::*},
}; };
@@ -22,7 +22,11 @@ use crate::{DbTxn, Db, coins::Coin};
#[derive(Debug)] #[derive(Debug)]
pub enum KeyGenEvent<C: Ciphersuite> { pub enum KeyGenEvent<C: Ciphersuite> {
KeyConfirmed { activation_number: usize, keys: ThresholdKeys<C> }, KeyConfirmed {
activation_number: usize,
substrate_keys: ThresholdKeys<Ristretto>,
coin_keys: ThresholdKeys<C>,
},
ProcessorMessage(ProcessorMessage), ProcessorMessage(ProcessorMessage),
} }
@@ -63,53 +67,57 @@ impl<C: Coin, D: Db> KeyGenDb<C, D> {
) { ) {
txn.put(Self::commitments_key(id), bincode::serialize(commitments).unwrap()); txn.put(Self::commitments_key(id), bincode::serialize(commitments).unwrap());
} }
fn commitments( fn commitments(&self, id: &KeyGenId) -> HashMap<Participant, Vec<u8>> {
&self,
id: &KeyGenId,
params: ThresholdParams,
) -> HashMap<Participant, EncryptionKeyMessage<C::Curve, Commitments<C::Curve>>> {
bincode::deserialize::<HashMap<Participant, Vec<u8>>>( bincode::deserialize::<HashMap<Participant, Vec<u8>>>(
&self.0.get(Self::commitments_key(id)).unwrap(), &self.0.get(Self::commitments_key(id)).unwrap(),
) )
.unwrap() .unwrap()
.drain()
.map(|(i, bytes)| {
(
i,
EncryptionKeyMessage::<C::Curve, Commitments<C::Curve>>::read::<&[u8]>(
&mut bytes.as_ref(),
params,
)
.unwrap(),
)
})
.collect()
} }
fn generated_keys_key(id: &KeyGenId) -> Vec<u8> { fn generated_keys_key(id: &KeyGenId) -> Vec<u8> {
Self::key_gen_key(b"generated_keys", bincode::serialize(id).unwrap()) Self::key_gen_key(b"generated_keys", bincode::serialize(id).unwrap())
} }
fn save_keys(&mut self, txn: &mut D::Transaction, id: &KeyGenId, keys: &ThresholdCore<C::Curve>) { fn save_keys(
txn.put(Self::generated_keys_key(id), keys.serialize()); &mut self,
txn: &mut D::Transaction,
id: &KeyGenId,
substrate_keys: &ThresholdCore<Ristretto>,
coin_keys: &ThresholdCore<C::Curve>,
) {
let mut keys = substrate_keys.serialize();
keys.extend(coin_keys.serialize().iter());
txn.put(Self::generated_keys_key(id), keys);
} }
fn keys_key(key: &<C::Curve as Ciphersuite>::G) -> Vec<u8> { fn keys_key(key: &<C::Curve as Ciphersuite>::G) -> Vec<u8> {
Self::key_gen_key(b"keys", key.to_bytes()) Self::key_gen_key(b"keys", key.to_bytes())
} }
fn confirm_keys(&mut self, txn: &mut D::Transaction, id: &KeyGenId) -> ThresholdKeys<C::Curve> { #[allow(clippy::type_complexity)]
let keys_vec = self.0.get(Self::generated_keys_key(id)).unwrap(); fn read_keys(
let mut keys = &self,
ThresholdKeys::new(ThresholdCore::read::<&[u8]>(&mut keys_vec.as_ref()).unwrap()); key: &[u8],
C::tweak_keys(&mut keys); ) -> (Vec<u8>, (ThresholdKeys<Ristretto>, ThresholdKeys<C::Curve>)) {
txn.put(Self::keys_key(&keys.group_key()), keys_vec); 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<Ristretto>, ThresholdKeys<C::Curve>) {
let (keys_vec, keys) = self.read_keys(&Self::generated_keys_key(id));
txn.put(Self::keys_key(&keys.1.group_key()), keys_vec);
keys keys
} }
fn keys(&self, key: &<C::Curve as Ciphersuite>::G) -> ThresholdKeys<C::Curve> { fn keys(
let mut keys = ThresholdKeys::new( &self,
ThresholdCore::read::<&[u8]>(&mut self.0.get(Self::keys_key(key)).unwrap().as_ref()).unwrap(), key: &<C::Curve as Ciphersuite>::G,
); ) -> (ThresholdKeys<Ristretto>, ThresholdKeys<C::Curve>) {
C::tweak_keys(&mut keys); self.read_keys(&Self::keys_key(key)).1
keys
} }
} }
@@ -121,8 +129,9 @@ pub struct KeyGen<C: Coin, D: Db> {
db: KeyGenDb<C, D>, db: KeyGenDb<C, D>,
entropy: Zeroizing<[u8; 32]>, entropy: Zeroizing<[u8; 32]>,
active_commit: HashMap<ValidatorSet, SecretShareMachine<C::Curve>>, active_commit:
active_share: HashMap<ValidatorSet, KeyMachine<C::Curve>>, HashMap<ValidatorSet, (SecretShareMachine<Ristretto>, SecretShareMachine<C::Curve>)>,
active_share: HashMap<ValidatorSet, (KeyMachine<Ristretto>, KeyMachine<C::Curve>)>,
} }
impl<C: Coin, D: Db> KeyGen<C, D> { impl<C: Coin, D: Db> KeyGen<C, D> {
@@ -137,7 +146,10 @@ impl<C: Coin, D: Db> KeyGen<C, D> {
} }
} }
pub fn keys(&self, key: &<C::Curve as Ciphersuite>::G) -> ThresholdKeys<C::Curve> { pub fn keys(
&self,
key: &<C::Curve as Ciphersuite>::G,
) -> (ThresholdKeys<Ristretto>, ThresholdKeys<C::Curve>) {
self.db.keys(key) self.db.keys(key)
} }
@@ -160,8 +172,11 @@ impl<C: Coin, D: Db> KeyGen<C, D> {
let secret_shares_rng = |id| rng(b"Key Gen Secret Shares", id); let secret_shares_rng = |id| rng(b"Key Gen Secret Shares", id);
let share_rng = |id| rng(b"Key Gen Share", id); let share_rng = |id| rng(b"Key Gen Share", id);
let key_gen_machine = |id, params| { let key_gen_machines = |id, params| {
KeyGenMachine::new(params, context(&id)).generate_coefficients(&mut coefficients_rng(id)) 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 { match msg {
@@ -180,13 +195,12 @@ impl<C: Coin, D: Db> KeyGen<C, D> {
txn.commit(); txn.commit();
} }
let (machine, commitments) = key_gen_machine(id, params); let (machines, commitments) = key_gen_machines(id, params);
self.active_commit.insert(id.set, machine); let mut serialized = commitments.0.serialize();
serialized.extend(commitments.1.serialize());
self.active_commit.insert(id.set, machines);
KeyGenEvent::ProcessorMessage(ProcessorMessage::Commitments { KeyGenEvent::ProcessorMessage(ProcessorMessage::Commitments { id, commitments: serialized })
id,
commitments: commitments.serialize(),
})
} }
CoordinatorMessage::Commitments { id, commitments } => { CoordinatorMessage::Commitments { id, commitments } => {
@@ -201,106 +215,168 @@ impl<C: Coin, D: Db> KeyGen<C, D> {
let params = self.db.params(&id.set); let params = self.db.params(&id.set);
// Parse the commitments // Unwrap the machines, rebuilding them if we didn't have them in our cache
let parsed = match commitments
.iter()
.map(|(i, commitments)| {
EncryptionKeyMessage::<C::Curve, Commitments<C::Curve>>::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
// We won't if the processor rebooted // We won't if the processor rebooted
// This *may* be inconsistent if we receive a KeyGen for attempt x, then commitments for // This *may* be inconsistent if we receive a KeyGen for attempt x, then commitments for
// attempt y // attempt y
// The coordinator is trusted to be proper in this regard // The coordinator is trusted to be proper in this regard
let machine = let machines =
self.active_commit.remove(&id.set).unwrap_or_else(|| key_gen_machine(id, params).0); self.active_commit.remove(&id.set).unwrap_or_else(|| key_gen_machines(id, params).0);
let (machine, mut shares) = let mut rng = secret_shares_rng(id);
match machine.generate_secret_shares(&mut secret_shares_rng(id), parsed) {
Ok(res) => res, let mut commitments_ref: HashMap<Participant, &[u8]> =
commitments.iter().map(|(i, commitments)| (*i, commitments.as_ref())).collect();
#[allow(clippy::type_complexity)]
fn handle_machine<C: Ciphersuite>(
rng: &mut ChaCha20Rng,
params: ThresholdParams,
machine: SecretShareMachine<C>,
commitments_ref: &mut HashMap<Participant, &[u8]>,
) -> (KeyMachine<C>, HashMap<Participant, EncryptedMessage<C, SecretShare<C::F>>>) {
// Parse the commitments
let parsed = match commitments_ref
.iter_mut()
.map(|(i, commitments)| {
EncryptionKeyMessage::<C, Commitments<C>>::read(commitments, params)
.map(|commitments| (*i, commitments))
})
.collect()
{
Ok(commitments) => commitments,
Err(e) => todo!("malicious signer: {:?}", e), 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::<Ristretto>(&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(); let mut txn = self.db.0.txn();
self.db.save_commitments(&mut txn, &id, &commitments); self.db.save_commitments(&mut txn, &id, &commitments);
txn.commit(); txn.commit();
KeyGenEvent::ProcessorMessage(ProcessorMessage::Shares { KeyGenEvent::ProcessorMessage(ProcessorMessage::Shares { id, shares })
id,
shares: shares.drain().map(|(i, share)| (i, share.serialize())).collect(),
})
} }
CoordinatorMessage::Shares { id, mut shares } => { CoordinatorMessage::Shares { id, shares } => {
info!("Received shares for {:?}", id); info!("Received shares for {:?}", id);
let params = self.db.params(&id.set); let params = self.db.params(&id.set);
// Parse the shares
let shares = match shares
.drain()
.map(|(i, share)| {
EncryptedMessage::<C::Curve, SecretShare<<C::Curve as Ciphersuite>::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 // Same commentary on inconsistency as above exists
let machine = self.active_share.remove(&id.set).unwrap_or_else(|| { let machines = self.active_share.remove(&id.set).unwrap_or_else(|| {
key_gen_machine(id, params) let machines = key_gen_machines(id, params).0;
.0 let mut rng = secret_shares_rng(id);
.generate_secret_shares(&mut secret_shares_rng(id), self.db.commitments(&id, params)) let commitments = self.db.commitments(&id);
.unwrap()
.0 let mut commitments_ref: HashMap<Participant, &[u8]> =
commitments.iter().map(|(i, commitments)| (*i, commitments.as_ref())).collect();
fn parse_commitments<C: Ciphersuite>(
params: ThresholdParams,
commitments_ref: &mut HashMap<Participant, &[u8]>,
) -> HashMap<Participant, EncryptionKeyMessage<C, Commitments<C>>> {
commitments_ref
.iter_mut()
.map(|(i, commitments)| {
(*i, EncryptionKeyMessage::<C, Commitments<C>>::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 mut rng = share_rng(id);
let keys = (match machine.calculate_share(&mut share_rng(id), shares) {
Ok(res) => res, let mut shares_ref: HashMap<Participant, &[u8]> =
Err(e) => todo!("malicious signer: {:?}", e), shares.iter().map(|(i, shares)| (*i, shares.as_ref())).collect();
})
.complete(); fn handle_machine<C: Ciphersuite>(
rng: &mut ChaCha20Rng,
params: ThresholdParams,
machine: KeyMachine<C>,
shares_ref: &mut HashMap<Participant, &[u8]>,
) -> ThresholdCore<C> {
// Parse the shares
let shares = match shares_ref
.iter_mut()
.map(|(i, share)| {
EncryptedMessage::<C, SecretShare<C::F>>::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(); 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(); txn.commit();
let mut keys = ThresholdKeys::new(keys); let mut coin_keys = ThresholdKeys::new(coin_keys);
C::tweak_keys(&mut keys); C::tweak_keys(&mut coin_keys);
KeyGenEvent::ProcessorMessage(ProcessorMessage::GeneratedKey { KeyGenEvent::ProcessorMessage(ProcessorMessage::GeneratedKeyPair {
id, 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 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(); 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 { KeyGenEvent::KeyConfirmed {
activation_number: context.coin_latest_block_number.try_into().unwrap(), activation_number: context.coin_latest_block_number.try_into().unwrap(),
keys, substrate_keys,
coin_keys,
} }
} }
} }

View File

@@ -207,7 +207,8 @@ async fn run<C: Coin, D: Db, Co: Coordinator>(raw_db: D, coin: C, mut coordinato
for key in &active_keys { for key in &active_keys {
// TODO: Load existing schedulers // 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 // Load any TXs being actively signed
let key = key.to_bytes(); let key = key.to_bytes();
@@ -332,7 +333,8 @@ async fn run<C: Coin, D: Db, Co: Coordinator>(raw_db: D, coin: C, mut coordinato
match msg.msg.clone() { match msg.msg.clone() {
CoordinatorMessage::KeyGen(msg) => { CoordinatorMessage::KeyGen(msg) => {
match key_gen.handle(msg).await { 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(); let key = keys.group_key();
scanner.rotate_key(activation_number, key).await; scanner.rotate_key(activation_number, key).await;
schedulers.insert(key.to_bytes().as_ref().to_vec(), Scheduler::<C>::new(key)); schedulers.insert(key.to_bytes().as_ref().to_vec(), Scheduler::<C>::new(key));

View File

@@ -89,7 +89,11 @@ pub async fn test_key_gen<C: Coin>() {
for i in 1 ..= 5 { for i in 1 ..= 5 {
let key_gen = key_gens.get_mut(&i).unwrap(); let key_gen = key_gens.get_mut(&i).unwrap();
let i = Participant::new(u16::try_from(i).unwrap()).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 { .handle(CoordinatorMessage::Shares {
id: ID, id: ID,
shares: all_shares shares: all_shares
@@ -101,9 +105,9 @@ pub async fn test_key_gen<C: Coin>() {
{ {
assert_eq!(id, ID); assert_eq!(id, ID);
if res.is_none() { 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 { } else {
panic!("didn't get key back"); panic!("didn't get key back");
} }
@@ -115,19 +119,25 @@ pub async fn test_key_gen<C: Coin>() {
for i in 1 ..= 5 { for i in 1 ..= 5 {
let key_gen = key_gens.get_mut(&i).unwrap(); let key_gen = key_gens.get_mut(&i).unwrap();
if let KeyGenEvent::KeyConfirmed { activation_number, keys } = key_gen if let KeyGenEvent::KeyConfirmed { activation_number, substrate_keys, coin_keys } = key_gen
.handle(CoordinatorMessage::ConfirmKey { .handle(CoordinatorMessage::ConfirmKeyPair {
context: SubstrateContext { time: 0, coin_latest_block_number: 111 }, context: SubstrateContext { time: 0, coin_latest_block_number: 111 },
id: ID, id: ID,
}) })
.await .await
{ {
assert_eq!(activation_number, 111); 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!( 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 { } else {
panic!("didn't get key back"); panic!("didn't get key back");
} }