-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
1,126 additions
and
4 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Generated by Cargo | ||
target/ | ||
|
||
# These are backup files generated by rustfmt | ||
**/*.rs.bk | ||
|
||
# MSVC Windows builds of rustc generate these, which store debugging information | ||
*.pdb | ||
|
||
.idea | ||
.vscode | ||
.zed | ||
|
||
.direnv | ||
.DS_Store | ||
# Solc Compiler files | ||
cache/ | ||
out/ | ||
|
||
# Ignores development broadcast logs | ||
!/broadcast | ||
/broadcast/*/31337/ | ||
/broadcast/**/dry-run/ | ||
|
||
# Docs | ||
docs/ | ||
|
||
# Dotenv file | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
[package] | ||
name = "dfns-cggmp21-blueprint" | ||
version = "0.1.1" | ||
description = "A DFNS CGGMP21 Blueprint that can run keygen and signing jobs on demand from the Tangle network" | ||
authors.workspace = true | ||
edition.workspace = true | ||
license.workspace = true | ||
homepage.workspace = true | ||
repository.workspace = true | ||
publish = false | ||
|
||
[dependencies] | ||
tracing = { workspace = true } | ||
async-trait = { workspace = true } | ||
gadget-sdk = { workspace = true, features = ["std"] } | ||
color-eyre = { workspace = true } | ||
lock_api = { workspace = true } | ||
tokio = { workspace = true, default-features = false, features = ["full"] } | ||
tokio-util = { workspace = true } | ||
sp-core = { workspace = true } | ||
subxt-signer = { workspace = true, features = ["sr25519", "subxt", "std"] } | ||
parking_lot = { workspace = true } | ||
libp2p = { workspace = true } | ||
ed25519-zebra = { workspace = true, features = ["pkcs8", "default", "der", "std", "serde", "pem"] } | ||
hex = { workspace = true } | ||
k256 = { workspace = true } | ||
serde_json = { workspace = true } | ||
bincode2 = { workspace = true } | ||
|
||
cggmp21 = { git = "https://github.com/LFDT-Lockness/cggmp21", features = ["curve-secp256k1"] } | ||
rand_chacha = "0.3.1" | ||
serde = { version = "1.0.214", features = ["derive"] } | ||
round-based = { version = "0.3.2", features = ["runtime-tokio"] } | ||
key-share = { git = "https://github.com/LFDT-Lockness/cggmp21", features = ["serde"] } | ||
|
||
[build-dependencies] | ||
blueprint-metadata = { workspace = true } | ||
|
||
[features] | ||
default = ["std"] | ||
std = [] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
fn main() { | ||
println!("cargo:rerun-if-changed=src/cli"); | ||
println!("cargo:rerun-if-changed=src/lib.rs"); | ||
println!("cargo:rerun-if-changed=src/main.rs"); | ||
println!("cargo:rerun-if-changed=src/*"); | ||
blueprint_metadata::generate_json(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
//! FROST Blueprint | ||
use cggmp21::security_level::SecurityLevel128; | ||
use cggmp21::supported_curves::Secp256k1; | ||
use cggmp21::KeyShare; | ||
use color_eyre::eyre; | ||
use gadget_sdk as sdk; | ||
use gadget_sdk::network::{NetworkMultiplexer, StreamKey}; | ||
use gadget_sdk::store::LocalDatabase; | ||
use gadget_sdk::subxt_core::ext::sp_core::{ecdsa, keccak_256}; | ||
use gadget_sdk::subxt_core::utils::AccountId32; | ||
use key_share::CoreKeyShare; | ||
use sdk::ctx::{KeystoreContext, ServicesContext, TangleClientContext}; | ||
use sdk::tangle_subxt::tangle_testnet_runtime::api; | ||
use serde::{Deserialize, Serialize}; | ||
use sp_core::ecdsa::Public; | ||
use std::collections::BTreeMap; | ||
use std::path::PathBuf; | ||
use std::sync::Arc; | ||
|
||
/// The network protocol for the FROST service | ||
const NETWORK_PROTOCOL: &str = "/dfns/cggmp21/1.0.0"; | ||
|
||
/// FROST Service Context that holds all the necessary context for the service | ||
/// to run | ||
#[derive(Clone, KeystoreContext, TangleClientContext, ServicesContext)] | ||
pub struct DfnsContext { | ||
/// The overreaching configuration for the service | ||
#[config] | ||
pub config: sdk::config::StdGadgetConfiguration, | ||
/// The gossip handle for the network | ||
pub network_backend: Arc<NetworkMultiplexer>, | ||
/// The key-value store for the service | ||
pub store: Arc<sdk::store::LocalDatabase<DfnsStore>>, | ||
/// Identity | ||
pub identity: ecdsa::Pair, | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Clone, Default)] | ||
pub struct DfnsStore { | ||
pub inner: Option<CoreKeyShare<Secp256k1>>, | ||
pub refreshed_key: Option<KeyShare<Secp256k1, SecurityLevel128>>, | ||
} | ||
|
||
impl DfnsContext { | ||
/// Create a new service context | ||
pub fn new(config: sdk::config::StdGadgetConfiguration) -> eyre::Result<Self> { | ||
let network_config = config.libp2p_network_config(NETWORK_PROTOCOL)?; | ||
let identity = network_config.ecdsa_key.clone(); | ||
let gossip_handle = sdk::network::setup::start_p2p_network(network_config) | ||
.map_err(|e| eyre::eyre!("Failed to start the network: {e:?}"))?; | ||
let keystore_dir = PathBuf::from(config.keystore_uri.clone()).join("dfns.json"); | ||
|
||
Ok(Self { | ||
store: Arc::new(LocalDatabase::open(keystore_dir)), | ||
identity, | ||
config, | ||
network_backend: Arc::new(NetworkMultiplexer::new(gossip_handle)), | ||
}) | ||
} | ||
|
||
/// Get the key-value store | ||
pub fn store(&self) -> Arc<LocalDatabase<DfnsStore>> { | ||
self.store.clone() | ||
} | ||
|
||
/// Get the configuration | ||
pub fn config(&self) -> &sdk::config::StdGadgetConfiguration { | ||
&self.config | ||
} | ||
|
||
/// Get the network protocol | ||
pub fn network_protocol(&self) -> &str { | ||
NETWORK_PROTOCOL | ||
} | ||
|
||
/// Get the current blueprint id | ||
pub fn blueprint_id(&self) -> eyre::Result<u64> { | ||
self.config() | ||
.protocol_specific | ||
.tangle() | ||
.map(|c| c.blueprint_id) | ||
.map_err(|e| eyre::eyre!("Failed to get blueprint id: {e}")) | ||
} | ||
|
||
pub async fn get_party_index_and_operators( | ||
&self, | ||
) -> eyre::Result<(usize, BTreeMap<AccountId32, Public>)> { | ||
let parties = self.current_service_operators_ecdsa_keys().await?; | ||
let ecdsa_id = self.config.first_ecdsa_signer()?.into_inner(); | ||
let my_id = ecdsa_id.account_id(); | ||
let index_of_my_id = parties | ||
.iter() | ||
.position(|(id, _)| id == my_id) | ||
.ok_or_else(|| eyre::eyre!("Failed to get party index"))?; | ||
|
||
Ok((index_of_my_id, parties)) | ||
} | ||
|
||
/// Get Current Service Operators' ECDSA Keys as a map. | ||
pub async fn current_service_operators_ecdsa_keys( | ||
&self, | ||
) -> eyre::Result<BTreeMap<AccountId32, ecdsa::Public>> { | ||
let client = self.tangle_client().await?; | ||
let current_blueprint = self.blueprint_id()?; | ||
let current_service_op = self.current_service_operators(&client).await?; | ||
let storage = client.storage().at_latest().await?; | ||
let mut map = BTreeMap::new(); | ||
for (operator, _) in current_service_op { | ||
let addr = api::storage() | ||
.services() | ||
.operators(current_blueprint, &operator); | ||
let maybe_pref = storage.fetch(&addr).await?; | ||
if let Some(pref) = maybe_pref { | ||
map.insert(operator, ecdsa::Public(pref.key)); | ||
} else { | ||
return Err(eyre::eyre!( | ||
"Failed to get operator's {operator} public ecdsa key" | ||
)); | ||
} | ||
} | ||
|
||
Ok(map) | ||
} | ||
|
||
/// Get the current call id for this job. | ||
pub async fn current_call_id(&self) -> Result<u64, eyre::Error> { | ||
let client = self.tangle_client().await?; | ||
let addr = api::storage().services().next_job_call_id(); | ||
let storage = client.storage().at_latest().await?; | ||
let maybe_call_id = storage.fetch_or_default(&addr).await?; | ||
Ok(maybe_call_id.saturating_sub(1)) | ||
} | ||
|
||
/// Get the network backend for keygen job | ||
pub fn keygen_network_backend(&self, call_id: u64) -> impl sdk::network::Network { | ||
self.network_backend.multiplex(StreamKey { | ||
task_hash: keccak_256(&[&b"keygen"[..], &call_id.to_le_bytes()[..]].concat()), | ||
round_id: -1, | ||
}) | ||
} | ||
|
||
/// Get the network backend for signing job | ||
pub fn signing_network_backend(&self, call_id: u64) -> impl sdk::network::Network { | ||
self.network_backend.multiplex(StreamKey { | ||
task_hash: keccak_256(&[&b"signing"[..], &call_id.to_le_bytes()[..]].concat()), | ||
round_id: -1, | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
use crate::context::DfnsContext; | ||
use cggmp21::security_level::SecurityLevel128; | ||
use cggmp21::supported_curves::Secp256k1; | ||
use cggmp21::{ExecutionId, PregeneratedPrimes}; | ||
use color_eyre::eyre::OptionExt; | ||
use gadget_sdk::event_listener::tangle::jobs::{services_post_processor, services_pre_processor}; | ||
use gadget_sdk::event_listener::tangle::TangleEventListener; | ||
use gadget_sdk::network::round_based_compat::NetworkDeliveryWrapper; | ||
use gadget_sdk::network::StreamKey; | ||
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::events::JobCalled; | ||
use gadget_sdk::{compute_sha256_hash, job}; | ||
use rand_chacha::rand_core::{RngCore, SeedableRng}; | ||
use round_based::runtime::TokioRuntime; | ||
use sp_core::ecdsa::Public; | ||
use std::collections::BTreeMap; | ||
|
||
#[job( | ||
id = 0, | ||
params(n), | ||
event_listener( | ||
listener = TangleEventListener<DfnsContext, JobCalled>, | ||
pre_processor = services_pre_processor, | ||
post_processor = services_post_processor, | ||
), | ||
)] | ||
/// Runs a [t; n] keygen using DFNS-CGGMP21. Returns the public key | ||
pub async fn key_refresh(n: u16, context: DfnsContext) -> Result<Vec<u8>, gadget_sdk::Error> { | ||
let blueprint_id = context.blueprint_id()?; | ||
let call_id = context.current_call_id().await?; | ||
let meta_deterministic_hash = compute_sha256_hash!( | ||
n.to_be_bytes(), | ||
blueprint_id.to_be_bytes(), | ||
call_id.to_be_bytes(), | ||
"dfns" | ||
); | ||
let deterministic_hash = compute_sha256_hash!(meta_deterministic_hash, "dfns-key-refresh"); | ||
// TODO: make sure it's okay to have a different execution id for signing vs keygen even if same between for all keygen or all signing | ||
let eid = ExecutionId::new(&deterministic_hash); | ||
let (i, operators) = context.get_party_index_and_operators().await?; | ||
let parties: BTreeMap<u16, Public> = operators | ||
.into_iter() | ||
.enumerate() | ||
.map(|(j, (_, ecdsa))| (j as u16, ecdsa)) | ||
.collect(); | ||
|
||
gadget_sdk::info!( | ||
"Starting DFNS-CGGMP21 Signing for party {i}, n={n}, eid={}", | ||
hex::encode(eid.as_bytes()) | ||
); | ||
|
||
let mut rng = rand_chacha::ChaChaRng::from_seed(deterministic_hash); | ||
let network = context.network_backend.multiplex(StreamKey { | ||
task_hash: deterministic_hash, | ||
round_id: 0, | ||
}); | ||
let delivery = NetworkDeliveryWrapper::new(network, i as _, deterministic_hash, parties); | ||
let party = round_based::party::MpcParty::connected(delivery).set_runtime(TokioRuntime); | ||
|
||
let key = hex::encode(meta_deterministic_hash); | ||
let mut cggmp21_state = context | ||
.store | ||
.get(&key) | ||
.ok_or_eyre("Keygen output not found in DB")?; | ||
let keygen_output = cggmp21_state | ||
.inner | ||
.as_ref() | ||
.ok_or_eyre("Keygen output not found")?; | ||
|
||
// This generate_pregenerated_orimes function can take awhile to run | ||
let pregenerated_primes = generate_pregenerated_primes(rng.clone()).await?; | ||
|
||
// TODO: parameterize this | ||
let result = cggmp21::key_refresh::<Secp256k1, SecurityLevel128>( | ||
eid, | ||
keygen_output, | ||
pregenerated_primes, | ||
) | ||
.enforce_reliable_broadcast(true) | ||
.start(&mut rng, party) | ||
.await | ||
.map_err(|err| gadget_sdk::Error::Other(err.to_string()))?; | ||
|
||
cggmp21_state.refreshed_key = Some(result.clone()); | ||
|
||
context.store.set(&key, cggmp21_state); | ||
|
||
let public_key = | ||
bincode2::serialize(&result.shared_public_key).expect("Failed to serialize public key"); | ||
// TODO: Note: Earlier this year, bincode failed to serialize this DirtyKeyShare | ||
let serializable_share = | ||
bincode2::serialize(&result.into_inner()).expect("Failed to serialize share"); | ||
|
||
Ok(public_key) | ||
} | ||
|
||
async fn generate_pregenerated_primes<R: RngCore + Send + 'static>( | ||
mut rng: R, | ||
) -> Result<PregeneratedPrimes, gadget_sdk::Error> { | ||
let pregenerated_primes = tokio::task::spawn_blocking(move || { | ||
cggmp21::PregeneratedPrimes::<SecurityLevel128>::generate(&mut rng) | ||
}) | ||
.await | ||
.map_err(|err| format!("Failed to generate pregenerated primes: {err:?}"))?; | ||
Ok(pregenerated_primes) | ||
} |
Oops, something went wrong.