Feature/dkg dealing (#1708)

* Reintroduce epoch states

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Use admin address for sensible txs

* Validator-api watch contract and handle events

* Handle dealing exchange

* Dealing exchange

* Recover raw verification keys for 5 dkgs

* Test coconut with dkg keys

* Split dealing storage

* Finish dkg task when it achieved its purpose

* Temporary fix for clippy

* Fix clippy

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
This commit is contained in:
Bogdan-Ștefan Neacşu
2022-11-21 18:00:47 +02:00
committed by GitHub
parent fea6f44a57
commit b71a8708db
45 changed files with 1256 additions and 462 deletions
+7 -7
View File
@@ -3,9 +3,9 @@
use crate::coconut::error::Result;
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
use coconut_dkg_common::dealer::DealerDetailsResponse;
use coconut_dkg_common::types::EncodedBTEPublicKeyWithProof;
use contracts_common::commitment::ContractSafeCommitment;
use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse};
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState};
use contracts_common::dealings::ContractSafeBytes;
use multisig_contract_common::msg::ProposalResponse;
use validator_client::nymd::cosmwasm_client::types::ExecuteResult;
use validator_client::nymd::{AccountId, Fee, TxResponse};
@@ -19,13 +19,13 @@ pub trait Client {
&self,
blinded_serial_number: String,
) -> Result<SpendCredentialResponse>;
async fn get_current_epoch_state(&self) -> Result<EpochState>;
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse>;
async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>>;
async fn get_dealings(&self, idx: usize) -> Result<Vec<ContractDealing>>;
async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option<Fee>)
-> Result<()>;
async fn register_dealer(&self, bte_key: EncodedBTEPublicKeyWithProof)
-> Result<ExecuteResult>;
async fn submit_dealing_commitment(
&self,
commitment: ContractSafeCommitment,
) -> Result<ExecuteResult>;
async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result<ExecuteResult>;
}
@@ -3,40 +3,56 @@
use crate::coconut::client::Client;
use crate::coconut::error::CoconutError;
use coconut_dkg_common::dealer::DealerDetailsResponse;
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, NodeIndex};
use contracts_common::commitment::ContractSafeCommitment;
use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse};
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState, NodeIndex};
use contracts_common::dealings::ContractSafeBytes;
use validator_client::nymd::cosmwasm_client::logs::{find_attribute, NODE_INDEX};
use validator_client::nymd::AccountId;
pub(crate) struct Publisher {
client: Box<dyn Client + Send + Sync>,
pub(crate) struct DkgClient {
inner: Box<dyn Client + Send + Sync>,
}
impl Publisher {
impl DkgClient {
pub(crate) fn new<C>(nymd_client: C) -> Self
where
C: Client + Send + Sync + 'static,
{
let client = Box::new(nymd_client);
Publisher { client }
DkgClient {
inner: Box::new(nymd_client),
}
}
pub(crate) async fn _get_address(&self) -> AccountId {
self.client.address().await
self.inner.address().await
}
pub(crate) async fn get_current_epoch_state(&self) -> Result<EpochState, CoconutError> {
self.inner.get_current_epoch_state().await
}
pub(crate) async fn get_self_registered_dealer_details(
&self,
) -> Result<DealerDetailsResponse, CoconutError> {
self.client.get_self_registered_dealer_details().await
self.inner.get_self_registered_dealer_details().await
}
pub(crate) async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>, CoconutError> {
self.inner.get_current_dealers().await
}
pub(crate) async fn get_dealings(
&self,
idx: usize,
) -> Result<Vec<ContractDealing>, CoconutError> {
self.inner.get_dealings(idx).await
}
pub(crate) async fn register_dealer(
&self,
bte_key: EncodedBTEPublicKeyWithProof,
) -> Result<NodeIndex, CoconutError> {
let res = self.client.register_dealer(bte_key).await?;
let res = self.inner.register_dealer(bte_key).await?;
let node_index = find_attribute(&res.logs, "wasm", NODE_INDEX)
.ok_or(CoconutError::NodeIndexRecoveryError {
reason: String::from("node index not found"),
@@ -50,11 +66,11 @@ impl Publisher {
Ok(node_index)
}
pub(crate) async fn _submit_dealing_commitment(
pub(crate) async fn submit_dealing(
&self,
commitment: ContractSafeCommitment,
dealing_bytes: ContractSafeBytes,
) -> Result<(), CoconutError> {
self.client.submit_dealing_commitment(commitment).await?;
self.inner.submit_dealing(dealing_bytes).await?;
Ok(())
}
}
@@ -0,0 +1,46 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use dkg::error::DkgError;
pub(crate) enum ComplaintReason {
MalformedBTEPublicKey,
MissingDealing,
MalformedDealing(DkgError),
DealingVerificationError(DkgError),
}
// pub(crate) async fn complaint_period(
// dkg_client: &DkgClient,
// state: &mut State,
// ) -> Result<(), CoconutError> {
// let dkg_params = dkg::bte::setup();
// let threshold = state
// .threshold()
// .expect("We should have a tentative threshold by now");
// let dealings = dkg_client.get_dealings().await?;
// for contract_dealing in dealings {
// match Dealing::try_from_bytes(&contract_dealing.dealing) {
// Ok(dealing) => {
// if let Err(err) =
// dealing.verify(&dkg_params, threshold, &state.current_receivers(), None)
// {
// state.remove_good_dealer(&contract_dealing.dealer);
// state.add_bad_dealer(
// contract_dealing.dealer,
// ComplaintReason::DealingVerificationError(err),
// );
// }
// }
// Err(err) => {
// state.remove_good_dealer(&contract_dealing.dealer);
// state.add_bad_dealer(
// contract_dealing.dealer,
// ComplaintReason::MalformedDealing(err),
// );
// }
// }
// }
//
// Ok(())
// }
+109
View File
@@ -0,0 +1,109 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::state::{ConsistentState, State};
use crate::coconut::dkg::{
dealing::dealing_exchange, public_key::public_key_submission,
verification_key::verification_key_submission,
};
use crate::coconut::keypair::KeyPair as CoconutKeyPair;
use crate::{nymd_client, Config};
use anyhow::Result;
use coconut_dkg_common::types::EpochState;
use dkg::bte::keys::KeyPair as DkgKeyPair;
use rand::rngs::OsRng;
use rand::RngCore;
use std::time::Duration;
use task::ShutdownListener;
use tokio::time::interval;
use validator_client::nymd::SigningNymdClient;
pub(crate) fn init_keypair(config: &Config) -> Result<()> {
let mut rng = OsRng;
let dkg_params = dkg::bte::setup();
let kp = DkgKeyPair::new(&dkg_params, &mut rng);
pemstore::store_keypair(
&kp,
&pemstore::KeyPairPath::new(
config.decryption_key_path(),
config.public_key_with_proof_path(),
),
)?;
Ok(())
}
pub(crate) struct DkgController<R> {
dkg_client: DkgClient,
state: State,
rng: R,
polling_rate: Duration,
}
impl<R: RngCore + Clone> DkgController<R> {
pub(crate) fn new(
config: &Config,
nymd_client: nymd_client::Client<SigningNymdClient>,
coconut_keypair: CoconutKeyPair,
rng: R,
) -> Result<Self> {
let dkg_keypair = pemstore::load_keypair(&pemstore::KeyPairPath::new(
config.decryption_key_path(),
config.public_key_with_proof_path(),
))?;
Ok(DkgController {
dkg_client: DkgClient::new(nymd_client),
state: State::new(dkg_keypair, coconut_keypair),
rng,
polling_rate: config.get_dkg_contract_polling_rate(),
})
}
async fn handle_epoch_state(&mut self) -> bool {
match self.dkg_client.get_current_epoch_state().await {
Err(e) => warn!("Could not get current epoch state {}", e),
Ok(epoch_state) => {
if let Err(e) = self.state.is_consistent(epoch_state) {
error!(
"Epoch state is corrupted - {}, the process should be terminated",
e
);
}
let ret = match epoch_state {
EpochState::PublicKeySubmission => {
public_key_submission(&self.dkg_client, &mut self.state).await
}
EpochState::DealingExchange => {
dealing_exchange(&self.dkg_client, &mut self.state, self.rng.clone()).await
}
EpochState::VerificationKeySubmission => {
verification_key_submission(&self.dkg_client, &mut self.state).await
}
EpochState::InProgress => return true,
};
if let Err(e) = ret {
warn!("Could not handle this iteration for the epoch state: {}", e);
}
}
}
false
}
pub(crate) async fn run(mut self, mut shutdown: ShutdownListener) {
let mut interval = interval(self.polling_rate);
while !shutdown.is_shutdown() {
tokio::select! {
_ = interval.tick() => {
if self.handle_epoch_state().await {
// If dkg finished, we can finish this task
break;
}
}
_ = shutdown.recv() => {
trace!("DkgController: Received shutdown");
}
}
}
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::state::{ConsistentState, State};
use crate::coconut::error::CoconutError;
use coconut_dkg_common::types::TOTAL_DEALINGS;
use contracts_common::dealings::ContractSafeBytes;
use dkg::bte::setup;
use dkg::Dealing;
use rand::RngCore;
pub(crate) async fn dealing_exchange(
dkg_client: &DkgClient,
state: &mut State,
rng: impl RngCore + Clone,
) -> Result<(), CoconutError> {
if state.receiver_index().is_some() {
info!("Already have index {}", state.receiver_index().unwrap());
return Ok(());
}
let dealers = dkg_client.get_current_dealers().await?;
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
let threshold = (2 * dealers.len() as u64 + 3 - 1) / 3;
state.set_dealers(dealers);
state.set_threshold(threshold);
let receivers = state.current_dealers_by_idx();
let params = setup();
let dealer_index = state.node_index_value()?;
let receiver_index = receivers
.keys()
.position(|node_index| *node_index == dealer_index);
for _ in 0..TOTAL_DEALINGS {
let (dealing, _) = Dealing::create(
rng.clone(),
&params,
dealer_index,
threshold,
&receivers,
None,
);
dkg_client
.submit_dealing(ContractSafeBytes::from(&dealing))
.await?;
}
info!("Setting index to {}", receiver_index.unwrap());
state.set_receiver_index(receiver_index);
Ok(())
}
+7 -68
View File
@@ -1,71 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::coconut::client::Client;
use crate::coconut::dkg::publisher::Publisher;
use crate::Config;
use anyhow::Result;
use dkg::bte::keys::KeyPair;
use rand::rngs::OsRng;
use task::ShutdownListener;
pub(crate) mod publisher;
pub(crate) fn init_keypair(config: &Config) -> Result<()> {
let mut rng = OsRng;
let dkg_params = dkg::bte::setup();
let kp = KeyPair::new(&dkg_params, &mut rng);
pemstore::store_keypair(
&kp,
&pemstore::KeyPairPath::new(
config.decryption_key_path(),
config.public_key_with_proof_path(),
),
)?;
Ok(())
}
pub(crate) struct DkgController {
publisher: Publisher,
keypair: KeyPair,
}
impl DkgController {
pub(crate) fn new<C>(config: &Config, nymd_client: C) -> Result<Self>
where
C: Client + Send + Sync + 'static,
{
let publisher = Publisher::new(nymd_client);
let keypair = pemstore::load_keypair(&pemstore::KeyPairPath::new(
config.decryption_key_path(),
config.public_key_with_proof_path(),
))?;
Ok(DkgController { publisher, keypair })
}
pub(crate) async fn run(&self, mut shutdown: ShutdownListener) {
let bte_key = bs58::encode(&self.keypair.public_key().to_bytes()).into_string();
let index = if let Some(details) = self
.publisher
.get_self_registered_dealer_details()
.await
.expect("Could not query for dealer details")
.details
{
details.assigned_index
} else {
self.publisher
.register_dealer(bte_key)
.await
.expect("Could not register dealer in dkg protocol")
};
info!("Starting dkg protocol with index {}", index);
while !shutdown.is_shutdown() {
tokio::select! {
_ = shutdown.recv() => {
trace!("DkgController: Received shutdown");
}
}
}
}
}
pub(crate) mod client;
pub(crate) mod complaints;
pub(crate) mod controller;
pub(crate) mod dealing;
pub(crate) mod public_key;
pub(crate) mod state;
pub(crate) mod verification_key;
@@ -0,0 +1,30 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::state::State;
use crate::coconut::error::CoconutError;
pub(crate) async fn public_key_submission(
dkg_client: &DkgClient,
state: &mut State,
) -> Result<(), CoconutError> {
if state.node_index().is_some() {
return Ok(());
}
let bte_key = bs58::encode(&state.dkg_keypair().public_key().to_bytes()).into_string();
let index = if let Some(details) = dkg_client
.get_self_registered_dealer_details()
.await?
.details
{
details.assigned_index
} else {
dkg_client.register_dealer(bte_key).await?
};
state.set_node_index(index);
info!("Starting dkg protocol with index {}", index);
Ok(())
}
+190
View File
@@ -0,0 +1,190 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::coconut::dkg::complaints::ComplaintReason;
use crate::coconut::error::CoconutError;
use crate::coconut::keypair::KeyPair as CoconutKeyPair;
use coconut_dkg_common::dealer::DealerDetails;
use coconut_dkg_common::types::EpochState;
use cosmwasm_std::Addr;
use dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof};
use dkg::{NodeIndex, Threshold};
use std::collections::BTreeMap;
// note: each dealer is also a receiver which simplifies some logic significantly
#[derive(Debug)]
pub(crate) struct DkgParticipant {
pub(crate) _address: Addr,
pub(crate) bte_public_key_with_proof: PublicKeyWithProof,
pub(crate) assigned_index: NodeIndex,
}
impl TryFrom<DealerDetails> for DkgParticipant {
type Error = ComplaintReason;
fn try_from(dealer: DealerDetails) -> Result<Self, Self::Error> {
let bte_public_key_with_proof = bs58::decode(dealer.bte_public_key_with_proof)
.into_vec()
.map(|bytes| PublicKeyWithProof::try_from_bytes(&bytes))
.map_err(|_| ComplaintReason::MalformedBTEPublicKey)?
.map_err(|_| ComplaintReason::MalformedBTEPublicKey)?;
Ok(DkgParticipant {
_address: dealer.address,
bte_public_key_with_proof,
assigned_index: dealer.assigned_index,
})
}
}
pub(crate) trait ConsistentState {
fn node_index_value(&self) -> Result<NodeIndex, CoconutError>;
fn receiver_index_value(&self) -> Result<usize, CoconutError>;
fn threshold(&self) -> Result<Threshold, CoconutError>;
fn is_consistent(&self, epoch_state: EpochState) -> Result<(), CoconutError> {
match epoch_state {
EpochState::PublicKeySubmission => {}
EpochState::DealingExchange => {
self.node_index_value()?;
}
EpochState::VerificationKeySubmission => {
self.node_index_value()?;
self.receiver_index_value()?;
self.threshold()?;
}
EpochState::InProgress => {
self.node_index_value()?;
self.receiver_index_value()?;
self.threshold()?;
}
}
Ok(())
}
}
pub(crate) struct State {
dkg_keypair: DkgKeyPair,
coconut_keypair: CoconutKeyPair,
node_index: Option<NodeIndex>,
dealers: BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>>,
receiver_index: Option<usize>,
threshold: Option<Threshold>,
}
impl ConsistentState for State {
fn node_index_value(&self) -> Result<NodeIndex, CoconutError> {
self.node_index.ok_or(CoconutError::UnrecoverableState {
reason: String::from("Node index should have been set"),
})
}
fn receiver_index_value(&self) -> Result<usize, CoconutError> {
self.receiver_index.ok_or(CoconutError::UnrecoverableState {
reason: String::from("Receiver index should have been set"),
})
}
fn threshold(&self) -> Result<Threshold, CoconutError> {
let threshold = self.threshold.ok_or(CoconutError::UnrecoverableState {
reason: String::from("Threshold should have been set"),
})?;
if self.current_dealers_by_idx().len() < threshold as usize {
Err(CoconutError::UnrecoverableState {
reason: String::from(
"Not enough good dealers in the signer set to achieve threshold",
),
})
} else {
Ok(threshold)
}
}
}
impl State {
pub fn new(dkg_keypair: DkgKeyPair, coconut_keypair: CoconutKeyPair) -> Self {
State {
dkg_keypair,
coconut_keypair,
node_index: None,
dealers: BTreeMap::new(),
receiver_index: None,
threshold: None,
}
}
pub fn dkg_keypair(&self) -> &DkgKeyPair {
&self.dkg_keypair
}
pub async fn coconut_keypair_is_some(&self) -> bool {
self.coconut_keypair.get().await.is_some()
}
pub fn node_index(&self) -> Option<NodeIndex> {
self.node_index
}
pub fn receiver_index(&self) -> Option<usize> {
self.receiver_index
}
pub fn current_dealers_by_addr(&self) -> BTreeMap<Addr, NodeIndex> {
self.dealers
.iter()
.filter_map(|(addr, dealer)| {
dealer
.as_ref()
.ok()
.map(|participant| (addr.clone(), participant.assigned_index))
})
.collect()
}
pub fn current_dealers_by_idx(&self) -> BTreeMap<NodeIndex, PublicKey> {
self.dealers
.iter()
.filter_map(|(_, dealer)| {
dealer.as_ref().ok().map(|participant| {
(
participant.assigned_index,
*participant.bte_public_key_with_proof.public_key(),
)
})
})
.collect()
}
pub async fn set_coconut_keypair(&mut self, coconut_keypair: coconut_interface::KeyPair) {
self.coconut_keypair.set(coconut_keypair).await
}
pub fn set_node_index(&mut self, node_index: NodeIndex) {
self.node_index = Some(node_index);
}
pub fn set_dealers(&mut self, dealers: Vec<DealerDetails>) {
self.dealers = BTreeMap::from_iter(
dealers
.into_iter()
.map(|details| (details.address.clone(), DkgParticipant::try_from(details))),
)
}
pub fn mark_bad_dealer(&mut self, dealer_addr: &Addr, reason: ComplaintReason) {
if let Some((_, value)) = self
.dealers
.iter_mut()
.find(|(addr, _)| *addr == dealer_addr)
{
*value = Err(reason);
}
}
pub fn set_receiver_index(&mut self, receiver_index: Option<usize>) {
self.receiver_index = receiver_index;
}
pub fn set_threshold(&mut self, threshold: Threshold) {
self.threshold = Some(threshold);
}
}
@@ -0,0 +1,141 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::complaints::ComplaintReason;
use crate::coconut::dkg::state::{ConsistentState, State};
use crate::coconut::error::CoconutError;
use coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS};
use coconut_interface::KeyPair as CoconutKeyPair;
use cosmwasm_std::Addr;
use credentials::coconut::bandwidth::{PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES};
use dkg::bte::{decrypt_share, setup};
use dkg::{combine_shares, Dealing};
use nymcoconut::{KeyPair, Parameters, SecretKey};
use std::collections::BTreeMap;
// Filter the dealers based on what dealing they posted (or not) in the contract
async fn deterministic_filter_dealers(
dkg_client: &DkgClient,
state: &mut State,
) -> Result<Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>, CoconutError> {
let mut dealings_maps = vec![];
let initial_dealers_by_addr = state.current_dealers_by_addr();
let initial_receivers = state.current_dealers_by_idx();
let threshold = state.threshold()?;
let params = setup();
let retries = 3;
for idx in 0..TOTAL_DEALINGS {
let mut try_no = 0;
let dealings = loop {
// this is a really ugly way to get the dealings, but for some reason the first query
// always fails with a RPC error.
try_no += 1;
if let Ok(dealings) = dkg_client.get_dealings(idx).await {
break dealings;
} else if try_no == retries {
return Err(CoconutError::UnrecoverableState {
reason: String::from("Could not get dealings"),
});
}
};
let dealings_map =
BTreeMap::from_iter(dealings.into_iter().filter_map(|contract_dealing| {
match Dealing::try_from(&contract_dealing.dealing) {
Ok(dealing) => {
if let Err(err) =
dealing.verify(&params, threshold, &initial_receivers, None)
{
state.mark_bad_dealer(
&contract_dealing.dealer,
ComplaintReason::DealingVerificationError(err),
);
None
} else if let Some(idx) =
initial_dealers_by_addr.get(&contract_dealing.dealer)
{
Some((*idx, (contract_dealing.dealer, dealing)))
} else {
None
}
}
Err(err) => {
state.mark_bad_dealer(
&contract_dealing.dealer,
ComplaintReason::MalformedDealing(err),
);
None
}
}
}));
dealings_maps.push(dealings_map);
}
for (addr, _) in initial_dealers_by_addr.iter() {
for dealings_map in dealings_maps.iter() {
if !dealings_map.iter().any(|(_, (address, _))| address == addr) {
state.mark_bad_dealer(addr, ComplaintReason::MissingDealing);
break;
}
}
}
Ok(dealings_maps)
}
fn derive_partial_keypair(
state: &State,
dealings_maps: Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>,
) -> Result<KeyPair, CoconutError> {
let filtered_receivers_by_idx = state.current_dealers_by_idx();
let filtered_dealers_by_addr = state.current_dealers_by_addr();
let dk = state.dkg_keypair().private_key();
let node_index_value = state.receiver_index_value()?;
let mut scalars = vec![];
for dealings_map in dealings_maps.into_iter() {
let filtered_dealings: Vec<_> = dealings_map
.into_iter()
.filter_map(|(_, (addr, dealing))| {
if filtered_dealers_by_addr.keys().any(|a| addr == *a) {
Some(dealing)
} else {
None
}
})
.collect();
let shares = filtered_dealings
.iter()
.map(|dealing| decrypt_share(dk, node_index_value, &dealing.ciphertexts, None))
.collect::<Result<_, _>>()?;
let scalar = combine_shares(
shares,
&filtered_receivers_by_idx
.keys()
.copied()
.collect::<Vec<_>>(),
)?;
scalars.push(scalar);
}
let params = Parameters::new(PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES)?;
let x = scalars.pop().unwrap();
let sk = SecretKey::create_from_raw(x, scalars);
let vk = sk.verification_key(&params);
Ok(CoconutKeyPair::from_keys(sk, vk))
}
pub(crate) async fn verification_key_submission(
dkg_client: &DkgClient,
state: &mut State,
) -> Result<(), CoconutError> {
if state.coconut_keypair_is_some().await {
return Ok(());
}
let dealings_maps = deterministic_filter_dealers(dkg_client, state).await?;
let coconut_keypair = derive_partial_keypair(state, dealings_maps)?;
state.set_coconut_keypair(coconut_keypair).await;
Ok(())
}
+13
View File
@@ -11,6 +11,7 @@ use crypto::asymmetric::{
encryption::KeyRecoveryError,
identity::{Ed25519RecoveryError, SignatureError},
};
use dkg::error::DkgError;
use validator_client::nymd::error::NymdError;
use crate::node_status_api::models::ValidatorApiStorageError;
@@ -31,6 +32,9 @@ pub enum CoconutError {
#[error("Nymd error - {0}")]
NymdError(#[from] NymdError),
#[error("Validator client error - {0}")]
ValidatorClientError(#[from] validator_client::ValidatorClientError),
#[error("Coconut internal error - {0}")]
CoconutInternalError(#[from] nymcoconut::CoconutError),
@@ -78,8 +82,17 @@ pub enum CoconutError {
#[error("Invalid status of credential: {status}")]
InvalidCredentialStatus { status: String },
#[error("DKG error: {0}")]
DkgError(#[from] DkgError),
#[error("Failed to recover assigned node index: {reason}")]
NodeIndexRecoveryError { reason: String },
#[error("Unrecoverable state: {reason}. Process should be restarted")]
UnrecoverableState { reason: String },
#[error("DKG has not finished yet in order to derive the coconut key")]
KeyPairNotDerivedYet,
}
impl<'r, 'o: 'r> Responder<'r, 'o> for CoconutError {
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::sync::Arc;
use tokio::sync::{RwLock, RwLockReadGuard};
#[derive(Clone, Debug)]
pub struct KeyPair {
inner: Arc<RwLock<Option<coconut_interface::KeyPair>>>,
}
impl KeyPair {
pub fn new() -> Self {
Self {
inner: Arc::new(RwLock::new(None)),
}
}
pub async fn get(&self) -> RwLockReadGuard<'_, Option<nymcoconut::KeyPair>> {
self.inner.read().await
}
pub async fn set(&self, keypair: coconut_interface::KeyPair) {
let mut w_lock = self.inner.write().await;
*w_lock = Some(keypair);
}
}
+17 -6
View File
@@ -6,6 +6,7 @@ pub(crate) mod comm;
mod deposit;
pub(crate) mod dkg;
pub(crate) mod error;
pub(crate) mod keypair;
#[cfg(test)]
mod tests;
@@ -17,8 +18,9 @@ use crate::ValidatorApiStorage;
use coconut_bandwidth_contract_common::spend_credential::{
funds_from_cosmos_msgs, SpendCredentialStatus,
};
use coconut_interface::KeyPair as CoconutKeyPair;
use coconut_interface::{
Attribute, BlindSignRequest, BlindedSignature, KeyPair, Parameters, VerificationKey,
Attribute, BlindSignRequest, BlindedSignature, Parameters, VerificationKey,
};
use config::defaults::VALIDATOR_API_VERSION;
use credentials::coconut::params::{
@@ -27,6 +29,7 @@ use credentials::coconut::params::{
use crypto::asymmetric::encryption;
use crypto::shared_key::new_ephemeral_shared_key;
use crypto::symmetric::stream_cipher;
use keypair::KeyPair;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
@@ -193,7 +196,7 @@ impl InternalSignRequest {
}
}
fn blind_sign(request: InternalSignRequest, key_pair: &KeyPair) -> Result<BlindedSignature> {
fn blind_sign(request: InternalSignRequest, key_pair: &CoconutKeyPair) -> Result<BlindedSignature> {
let params = Parameters::new(request.total_params())?;
Ok(coconut_interface::blind_sign(
&params,
@@ -226,7 +229,11 @@ pub async fn post_blind_sign(
blind_sign_request_body.public_attributes(),
blind_sign_request_body.blind_sign_request().clone(),
);
let blinded_signature = blind_sign(internal_request, &state.key_pair)?;
let blinded_signature = if let Some(keypair) = state.key_pair.get().await.as_ref() {
blind_sign(internal_request, keypair)?
} else {
return Err(CoconutError::KeyPairNotDerivedYet);
};
let response = state
.encrypt_and_store(
@@ -255,9 +262,13 @@ pub async fn post_partial_bandwidth_credential(
pub async fn get_verification_key(
state: &RocketState<State>,
) -> Result<Json<VerificationKeyResponse>> {
Ok(Json(VerificationKeyResponse::new(
state.key_pair.verification_key(),
)))
state
.key_pair
.get()
.await
.as_ref()
.map(|keypair| Json(VerificationKeyResponse::new(keypair.verification_key())))
.ok_or(CoconutError::KeyPairNotDerivedYet)
}
#[get("/cosmos-address")]
+44 -15
View File
@@ -39,9 +39,9 @@ use validator_client::validator_api::routes::{
use crate::coconut::State;
use crate::ValidatorApiStorage;
use async_trait::async_trait;
use coconut_dkg_common::dealer::DealerDetailsResponse;
use coconut_dkg_common::types::EncodedBTEPublicKeyWithProof;
use contracts_common::commitment::ContractSafeCommitment;
use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse};
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState};
use contracts_common::dealings::ContractSafeBytes;
use crypto::asymmetric::{encryption, identity};
use rand_07::rngs::OsRng;
use rocket::http::Status;
@@ -121,10 +121,22 @@ impl super::client::Client for DummyClient {
})
}
async fn get_current_epoch_state(&self) -> Result<EpochState> {
todo!()
}
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse> {
todo!()
}
async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>> {
todo!()
}
async fn get_dealings(&self, idx: usize) -> Result<Vec<ContractDealing>> {
todo!()
}
async fn vote_proposal(
&self,
proposal_id: u64,
@@ -148,10 +160,7 @@ impl super::client::Client for DummyClient {
todo!()
}
async fn submit_dealing_commitment(
&self,
commitment: ContractSafeCommitment,
) -> Result<ExecuteResult> {
async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result<ExecuteResult> {
todo!()
}
}
@@ -209,11 +218,13 @@ async fn check_signer_verif_key(key_pair: KeyPair) {
&Arc::new(RwLock::new(HashMap::new())),
);
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
let staged_key_pair = crate::coconut::KeyPair::new();
staged_key_pair.set(key_pair).await;
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
TEST_COIN_DENOM.to_string(),
key_pair,
staged_key_pair,
comm_channel,
storage,
));
@@ -303,11 +314,13 @@ async fn signed_before() {
&Arc::new(RwLock::new(HashMap::new())),
);
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
let staged_key_pair = crate::coconut::KeyPair::new();
staged_key_pair.set(key_pair).await;
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
TEST_COIN_DENOM.to_string(),
key_pair,
staged_key_pair,
comm_channel,
storage.clone(),
));
@@ -373,10 +386,12 @@ async fn state_functions() {
db_dir.push(&key_pair.verification_key().to_bs58()[..8]);
let storage = ValidatorApiStorage::init(db_dir).await.unwrap();
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
let staged_key_pair = crate::coconut::KeyPair::new();
staged_key_pair.set(key_pair).await;
let state = State::new(
nymd_client,
TEST_COIN_DENOM.to_string(),
key_pair,
staged_key_pair,
comm_channel,
storage.clone(),
);
@@ -543,11 +558,13 @@ async fn blind_sign_correct() {
&Arc::new(RwLock::new(HashMap::new())),
);
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
let staged_key_pair = crate::coconut::KeyPair::new();
staged_key_pair.set(key_pair).await;
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
TEST_COIN_DENOM.to_string(),
key_pair,
staged_key_pair,
comm_channel,
storage.clone(),
));
@@ -622,11 +639,13 @@ async fn signature_test() {
&Arc::new(RwLock::new(HashMap::new())),
);
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
let staged_key_pair = crate::coconut::KeyPair::new();
staged_key_pair.set(key_pair).await;
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
TEST_COIN_DENOM.to_string(),
key_pair,
staged_key_pair,
comm_channel,
storage.clone(),
));
@@ -694,10 +713,12 @@ async fn get_cosmos_address() {
db_dir.push(&key_pair.verification_key().to_bs58()[..8]);
let storage = ValidatorApiStorage::init(db_dir).await.unwrap();
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
let staged_key_pair = crate::coconut::KeyPair::new();
staged_key_pair.set(key_pair).await;
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
TEST_COIN_DENOM.to_string(),
key_pair,
staged_key_pair,
comm_channel,
storage.clone(),
));
@@ -740,15 +761,23 @@ async fn verification_of_bandwidth_credential() {
hash_to_scalar(voucher_value.to_string()),
hash_to_scalar(voucher_info),
];
let theta = theta_from_keys_and_attributes(&params, &key_pairs, &public_attributes).unwrap();
let indices: Vec<u64> = key_pairs
.iter()
.enumerate()
.map(|(idx, _)| (idx + 1) as u64)
.collect();
let theta =
theta_from_keys_and_attributes(&params, &key_pairs, &indices, &public_attributes).unwrap();
let key_pair = key_pairs.remove(0);
db_dir.push(&key_pair.verification_key().to_bs58()[..8]);
let storage1 = ValidatorApiStorage::init(db_dir).await.unwrap();
let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key());
let staged_key_pair = crate::coconut::KeyPair::new();
staged_key_pair.set(key_pair).await;
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client.clone(),
TEST_COIN_DENOM.to_string(),
key_pair,
staged_key_pair,
comm_channel.clone(),
storage1.clone(),
));
+12 -1
View File
@@ -12,6 +12,8 @@ mod template;
pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657";
pub const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(10);
const DEFAULT_GATEWAY_SENDING_RATE: usize = 200;
const DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS: usize = 50;
const DEFAULT_PACKET_DELIVERY_TIMEOUT: Duration = Duration::from_secs(20);
@@ -277,6 +279,9 @@ pub struct CoconutSigner {
/// Path to the dkg dealer public key with proof.
public_key_with_proof_path: PathBuf,
/// Duration of the interval for polling the dkg contract.
dkg_contract_polling_rate: Duration,
/// Specifies list of all validators on the network issuing coconut credentials.
/// A special care must be taken to ensure they are in correct order.
/// The list must also contain THIS validator that is running the test
@@ -303,6 +308,7 @@ impl Default for CoconutSigner {
keypair_path: Default::default(),
decryption_key_path: CoconutSigner::default_dkg_decryption_key_path(),
public_key_with_proof_path: CoconutSigner::default_dkg_public_key_with_proof_path(),
dkg_contract_polling_rate: DEFAULT_DKG_CONTRACT_POLLING_RATE,
all_validator_apis: Default::default(),
}
}
@@ -481,7 +487,7 @@ impl Config {
}
#[cfg(feature = "coconut")]
pub fn keypair_path(&self) -> PathBuf {
pub fn _keypair_path(&self) -> PathBuf {
self.coconut_signer.keypair_path.clone()
}
@@ -495,6 +501,11 @@ impl Config {
self.coconut_signer.public_key_with_proof_path.clone()
}
#[cfg(feature = "coconut")]
pub fn get_dkg_contract_polling_rate(&self) -> Duration {
self.coconut_signer.dkg_contract_polling_rate
}
// fix dead code warnings as this method is only ever used with coconut feature
#[cfg(feature = "coconut")]
pub fn get_all_validator_api_endpoints(&self) -> Vec<Url> {
+12 -9
View File
@@ -41,12 +41,12 @@ use crate::epoch_operations::RewardedSetUpdater;
#[cfg(feature = "coconut")]
use coconut::{
comm::QueryCommunicationChannel,
dkg::{init_keypair, DkgController},
dkg::controller::{init_keypair, DkgController},
InternalSignRequest,
};
#[cfg(feature = "coconut")]
use coconut_interface::{Base58, KeyPair};
use logging::setup_logging;
#[cfg(feature = "coconut")]
use validator_client::nymd::bip32::secp256k1::elliptic_curve::rand_core::OsRng;
pub(crate) mod config;
pub(crate) mod contract_cache;
@@ -419,6 +419,7 @@ async fn setup_rocket(
_mix_denom: String,
liftoff_notify: Arc<Notify>,
_nymd_client: Client<SigningNymdClient>,
#[cfg(feature = "coconut")] coconut_keypair: coconut::keypair::KeyPair,
) -> Result<Rocket<Ignite>> {
let openapi_settings = rocket_okapi::settings::OpenApiSettings::default();
let mut rocket = rocket::build();
@@ -451,14 +452,10 @@ async fn setup_rocket(
#[cfg(feature = "coconut")]
let rocket = if config.get_coconut_signer_enabled() {
let keypair_bs58 = fs::read_to_string(config.keypair_path())?
.trim()
.to_string();
let keypair = KeyPair::try_from_bs58(keypair_bs58)?;
rocket.attach(InternalSignRequest::stage(
_nymd_client,
_mix_denom,
keypair,
coconut_keypair,
QueryCommunicationChannel::new(config.get_all_validator_api_endpoints()),
storage.clone().unwrap(),
))
@@ -544,12 +541,17 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> {
// We need a bigger timeout
let shutdown = ShutdownNotifier::new(10);
#[cfg(feature = "coconut")]
let coconut_keypair = coconut::keypair::KeyPair::new();
// let's build our rocket!
let rocket = setup_rocket(
&config,
mix_denom,
Arc::clone(&liftoff_notify),
signing_nymd_client.clone(),
#[cfg(feature = "coconut")]
coconut_keypair.clone(),
)
.await?;
let monitor_builder = setup_network_monitor(&config, system_version, &rocket);
@@ -559,7 +561,8 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> {
#[cfg(feature = "coconut")]
{
let dkg_controller = DkgController::new(&config, signing_nymd_client.clone())?;
let dkg_controller =
DkgController::new(&config, signing_nymd_client.clone(), coconut_keypair, OsRng)?;
let shutdown_listener = shutdown.subscribe();
tokio::spawn(async move { dkg_controller.run(shutdown_listener).await });
}
+21 -6
View File
@@ -26,11 +26,11 @@ use async_trait::async_trait;
#[cfg(feature = "coconut")]
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
#[cfg(feature = "coconut")]
use coconut_dkg_common::dealer::DealerDetailsResponse;
use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse};
#[cfg(feature = "coconut")]
use coconut_dkg_common::types::EncodedBTEPublicKeyWithProof;
use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState};
#[cfg(feature = "coconut")]
use contracts_common::commitment::ContractSafeCommitment;
use contracts_common::dealings::ContractSafeBytes;
#[cfg(feature = "coconut")]
use multisig_contract_common::msg::ProposalResponse;
#[cfg(feature = "coconut")]
@@ -304,6 +304,10 @@ where
.await?)
}
async fn get_current_epoch_state(&self) -> crate::coconut::error::Result<EpochState> {
Ok(self.0.read().await.nymd.get_current_epoch_state().await?)
}
async fn get_self_registered_dealer_details(
&self,
) -> crate::coconut::error::Result<DealerDetailsResponse> {
@@ -317,6 +321,17 @@ where
.await?)
}
async fn get_current_dealers(&self) -> crate::coconut::error::Result<Vec<DealerDetails>> {
Ok(self.0.read().await.get_all_nymd_current_dealers().await?)
}
async fn get_dealings(
&self,
idx: usize,
) -> crate::coconut::error::Result<Vec<ContractDealing>> {
Ok(self.0.read().await.get_all_nymd_epoch_dealings(idx).await?)
}
async fn vote_proposal(
&self,
proposal_id: u64,
@@ -345,16 +360,16 @@ where
.await?)
}
async fn submit_dealing_commitment(
async fn submit_dealing(
&self,
commitment: ContractSafeCommitment,
dealing_bytes: ContractSafeBytes,
) -> Result<ExecuteResult, CoconutError> {
Ok(self
.0
.write()
.await
.nymd
.submit_dealing_commitment(commitment, None)
.submit_dealing_bytes(dealing_bytes, None)
.await?)
}
}