Embed trusted setup in network config (#3851)

* Load trusted setup in network config

* Fix trusted setup serialize and deserialize

* Load trusted setup from hardcoded preset instead of a file

* Truncate after deserialising trusted setup

* Fix beacon node script

* Remove hardcoded setup file

* Add length checks
This commit is contained in:
Pawan Dhananjay 2023-01-09 12:34:16 +05:30 committed by GitHub
parent 33ff84743d
commit ba410c3012
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 278 additions and 8235 deletions

5
Cargo.lock generated
View File

@ -472,6 +472,7 @@ dependencies = [
"node_test_rig",
"sensitive_url",
"serde",
"serde_json",
"slasher",
"slog",
"store",
@ -787,7 +788,7 @@ dependencies = [
[[package]]
name = "c-kzg"
version = "0.1.0"
source = "git+https://github.com/pawanjay176/c-kzg-4844?rev=69bde8f4e0bbf0da30d92601b7db138bdd7e6a04#69bde8f4e0bbf0da30d92601b7db138bdd7e6a04"
source = "git+https://github.com/pawanjay176/c-kzg-4844?rev=c9e4fa0dabdd000738b7fcdf85a72880a5da8748#c9e4fa0dabdd000738b7fcdf85a72880a5da8748"
dependencies = [
"hex",
"libc",
@ -1902,6 +1903,8 @@ dependencies = [
"enr",
"eth2_config",
"eth2_ssz",
"kzg",
"serde_json",
"serde_yaml",
"tempfile",
"types",

View File

@ -39,6 +39,7 @@ eth2_network_config = { path = "../common/eth2_network_config" }
execution_layer = { path = "execution_layer" }
lighthouse_network = { path = "./lighthouse_network" }
serde = "1.0.116"
serde_json = "1.0.58"
clap_utils = { path = "../common/clap_utils" }
hyper = "0.14.4"
lighthouse_version = { path = "../common/lighthouse_version" }

View File

@ -21,7 +21,7 @@ use eth1::Config as Eth1Config;
use execution_layer::ExecutionLayer;
use fork_choice::{ForkChoice, ResetPayloadStatuses};
use futures::channel::mpsc::Sender;
use kzg::Kzg;
use kzg::{Kzg, TrustedSetup};
use operation_pool::{OperationPool, PersistedOperationPool};
use parking_lot::RwLock;
use proto_array::ReOrgThreshold;
@ -29,7 +29,6 @@ use slasher::Slasher;
use slog::{crit, error, info, Logger};
use slot_clock::{SlotClock, TestingSlotClock};
use std::marker::PhantomData;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use store::{Error as StoreError, HotColdDB, ItemStore, KeyValueStoreOp};
@ -97,7 +96,7 @@ pub struct BeaconChainBuilder<T: BeaconChainTypes> {
// Pending I/O batch that is constructed during building and should be executed atomically
// alongside `PersistedBeaconChain` storage when `BeaconChainBuilder::build` is called.
pending_io_batch: Vec<KeyValueStoreOp>,
trusted_setup_path: Option<PathBuf>,
trusted_setup: Option<TrustedSetup>,
task_executor: Option<TaskExecutor>,
}
@ -137,7 +136,7 @@ where
slasher: None,
validator_monitor: None,
pending_io_batch: vec![],
trusted_setup_path: None,
trusted_setup: None,
task_executor: None,
}
}
@ -594,8 +593,8 @@ where
self
}
pub fn trusted_setup(mut self, trusted_setup_file_path: PathBuf) -> Self {
self.trusted_setup_path = Some(trusted_setup_file_path);
pub fn trusted_setup(mut self, trusted_setup: TrustedSetup) -> Self {
self.trusted_setup = Some(trusted_setup);
self
}
@ -640,8 +639,8 @@ where
slot_clock.now().ok_or("Unable to read slot")?
};
let kzg = if let Some(trusted_setup_file) = self.trusted_setup_path {
let kzg = Kzg::new_from_file(trusted_setup_file)
let kzg = if let Some(trusted_setup) = self.trusted_setup {
let kzg = Kzg::new_from_trusted_setup(trusted_setup)
.map_err(|e| format!("Failed to load trusted setup: {:?}", e))?;
Some(Arc::new(kzg))
} else {

View File

@ -70,6 +70,7 @@ pub use events::ServerSentEventHandler;
pub use execution_layer::EngineState;
pub use execution_payload::NotifyExecutionLayer;
pub use fork_choice::{ExecutionStatus, ForkchoiceUpdateParameters};
pub use kzg::TrustedSetup;
pub use metrics::scrape_for_metrics;
pub use parking_lot;
pub use slot_clock;

View File

@ -185,8 +185,8 @@ where
builder
};
let builder = if let Some(trusted_setup_file) = config.trusted_setup_file {
builder.trusted_setup(trusted_setup_file)
let builder = if let Some(trusted_setup) = config.trusted_setup {
builder.trusted_setup(trusted_setup)
} else {
builder
};

View File

@ -1,3 +1,4 @@
use beacon_chain::TrustedSetup;
use directory::DEFAULT_ROOT_DIR;
use environment::LoggerConfig;
use network::NetworkConfig;
@ -68,7 +69,7 @@ pub struct Config {
pub chain: beacon_chain::ChainConfig,
pub eth1: eth1::Config,
pub execution_layer: Option<execution_layer::Config>,
pub trusted_setup_file: Option<PathBuf>,
pub trusted_setup: Option<TrustedSetup>,
pub http_api: http_api::Config,
pub http_metrics: http_metrics::Config,
pub monitoring_api: Option<monitoring_api::Config>,
@ -91,7 +92,7 @@ impl Default for Config {
sync_eth1_chain: false,
eth1: <_>::default(),
execution_layer: None,
trusted_setup_file: None,
trusted_setup: None,
graffiti: Graffiti::default(),
http_api: <_>::default(),
http_metrics: <_>::default(),

View File

@ -513,13 +513,12 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
)
/* 4844 settings */
.arg(
Arg::with_name("trusted-setup-file")
.long("trusted-setup-file")
Arg::with_name("trusted-setup-file-override")
.long("trusted-setup-file-override")
.value_name("FILE")
.help("File containing the trusted setup parameters. \
NOTE: This is only for the devnet, the trusted setup params \
must be embedded into the ethspec once parameter loading \
is supported in the ckzg library")
.help("Path to a json file containing the trusted setup params. \
NOTE: This will override the trusted setup that is generated \
from the mainnet kzg ceremony. Use with caution")
.takes_value(true)
)
/*

View File

@ -2,6 +2,7 @@ use beacon_chain::chain_config::{
ReOrgThreshold, DEFAULT_PREPARE_PAYLOAD_LOOKAHEAD_FACTOR,
DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION, DEFAULT_RE_ORG_THRESHOLD,
};
use beacon_chain::TrustedSetup;
use clap::ArgMatches;
use clap_utils::flags::DISABLE_MALLOC_TUNING_FLAG;
use client::{ClientConfig, ClientGenesis};
@ -371,8 +372,19 @@ pub fn get_config<E: EthSpec>(
}
// 4844 params
if let Some(trusted_setup_file) = cli_args.value_of("trusted-setup-file") {
client_config.trusted_setup_file = Some(PathBuf::from(trusted_setup_file));
client_config.trusted_setup = context
.eth2_network_config
.as_ref()
.and_then(|config| config.kzg_trusted_setup.clone());
// Override default trusted setup file if required
// TODO: consider removing this when we get closer to launch
if let Some(trusted_setup_file_path) = cli_args.value_of("trusted-setup-file-override") {
let file = std::fs::File::open(trusted_setup_file_path)
.map_err(|e| format!("Failed to open trusted setup file: {}", e))?;
let trusted_setup: TrustedSetup = serde_json::from_reader(file)
.map_err(|e| format!("Unable to read trusted setup file: {}", e))?;
client_config.trusted_setup = Some(trusted_setup);
}
if let Some(freezer_dir) = cli_args.value_of("freezer-dir") {

View File

@ -15,7 +15,9 @@ tempfile = "3.1.0"
[dependencies]
serde_yaml = "0.8.13"
serde_json = "1.0.58"
types = { path = "../../consensus/types"}
kzg = { path = "../../crypto/kzg" }
eth2_ssz = "0.4.1"
eth2_config = { path = "../eth2_config"}
enr = { version = "0.6.2", features = ["ed25519", "k256"] }

File diff suppressed because one or more lines are too long

View File

@ -13,10 +13,11 @@
use enr::{CombinedKey, Enr};
use eth2_config::{instantiate_hardcoded_nets, HardcodedNet};
use kzg::TrustedSetup;
use std::fs::{create_dir_all, File};
use std::io::{Read, Write};
use std::path::PathBuf;
use types::{BeaconState, ChainSpec, Config, EthSpec, EthSpecId};
use types::{BeaconState, ChainSpec, Config, Epoch, EthSpec, EthSpecId};
pub const DEPLOY_BLOCK_FILE: &str = "deploy_block.txt";
pub const BOOT_ENR_FILE: &str = "boot_enr.yaml";
@ -32,6 +33,14 @@ instantiate_hardcoded_nets!(eth2_config);
pub const DEFAULT_HARDCODED_NETWORK: &str = "mainnet";
/// Contains the bytes from the trusted setup json.
/// The mainnet trusted setup is also reused in testnets.
///
/// This is done to ensure that testnets also inherit the high security and
/// randomness of the mainnet kzg trusted setup ceremony.
pub const TRUSTED_SETUP: &[u8] =
include_bytes!("../built_in_network_configs/testing_trusted_setups.json");
/// Specifies an Eth2 network.
///
/// See the crate-level documentation for more details.
@ -43,6 +52,7 @@ pub struct Eth2NetworkConfig {
pub boot_enr: Option<Vec<Enr<CombinedKey>>>,
pub genesis_state_bytes: Option<Vec<u8>>,
pub config: Config,
pub kzg_trusted_setup: Option<TrustedSetup>,
}
impl Eth2NetworkConfig {
@ -58,6 +68,20 @@ impl Eth2NetworkConfig {
/// Instantiates `Self` from a `HardcodedNet`.
fn from_hardcoded_net(net: &HardcodedNet) -> Result<Self, String> {
let config: Config = serde_yaml::from_reader(net.config)
.map_err(|e| format!("Unable to parse yaml config: {:?}", e))?;
let kzg_trusted_setup = if let Some(epoch) = config.eip4844_fork_epoch {
// Only load the trusted setup if the eip4844 fork epoch is set
if epoch.value != Epoch::max_value() {
let trusted_setup: TrustedSetup = serde_json::from_reader(TRUSTED_SETUP)
.map_err(|e| format!("Unable to read trusted setup file: {}", e))?;
Some(trusted_setup)
} else {
None
}
} else {
None
};
Ok(Self {
deposit_contract_deploy_block: serde_yaml::from_reader(net.deploy_block)
.map_err(|e| format!("Unable to parse deploy block: {:?}", e))?,
@ -67,8 +91,8 @@ impl Eth2NetworkConfig {
),
genesis_state_bytes: Some(net.genesis_state_bytes.to_vec())
.filter(|bytes| !bytes.is_empty()),
config: serde_yaml::from_reader(net.config)
.map_err(|e| format!("Unable to parse yaml config: {:?}", e))?,
config,
kzg_trusted_setup,
})
}
@ -194,7 +218,7 @@ impl Eth2NetworkConfig {
let deposit_contract_deploy_block = load_from_file!(DEPLOY_BLOCK_FILE);
let boot_enr = optional_load_from_file!(BOOT_ENR_FILE);
let config = load_from_file!(BASE_CONFIG_FILE);
let config: Config = load_from_file!(BASE_CONFIG_FILE);
// The genesis state is a special case because it uses SSZ, not YAML.
let genesis_file_path = base_dir.join(GENESIS_STATE_FILE);
@ -212,11 +236,25 @@ impl Eth2NetworkConfig {
None
};
let kzg_trusted_setup = if let Some(epoch) = config.eip4844_fork_epoch {
// Only load the trusted setup if the eip4844 fork epoch is set
if epoch.value != Epoch::max_value() {
let trusted_setup: TrustedSetup = serde_json::from_reader(TRUSTED_SETUP)
.map_err(|e| format!("Unable to read trusted setup file: {}", e))?;
Some(trusted_setup)
} else {
None
}
} else {
None
};
Ok(Self {
deposit_contract_deploy_block,
boot_enr,
genesis_state_bytes,
config,
kzg_trusted_setup,
})
}
}

View File

@ -18,7 +18,7 @@ eth2_serde_utils = "0.1.1"
hex = "0.4.2"
eth2_hashing = "0.3.0"
ethereum-types = "0.12.1"
c-kzg = {git = "https://github.com/pawanjay176/c-kzg-4844", rev = "69bde8f4e0bbf0da30d92601b7db138bdd7e6a04" }
c-kzg = {git = "https://github.com/pawanjay176/c-kzg-4844", rev = "c9e4fa0dabdd000738b7fcdf85a72880a5da8748" }
[features]
default = ["mainnet-spec"]

View File

@ -1,20 +1,17 @@
mod kzg_commitment;
mod kzg_proof;
mod trusted_setup;
pub use crate::{kzg_commitment::KzgCommitment, kzg_proof::KzgProof};
pub use crate::{kzg_commitment::KzgCommitment, kzg_proof::KzgProof, trusted_setup::TrustedSetup};
pub use c_kzg::bytes_to_g1;
pub use c_kzg::{
Error as CKzgError, KZGSettings, BYTES_PER_BLOB, BYTES_PER_FIELD_ELEMENT,
Blob, Error as CKzgError, KzgSettings, BYTES_PER_BLOB, BYTES_PER_FIELD_ELEMENT,
FIELD_ELEMENTS_PER_BLOB,
};
use std::path::PathBuf;
/// The consensus type `Blob` is generic over EthSpec, so it cannot be imported
/// in this crate without creating a cyclic dependency between the kzg and consensus/types crates.
/// So need to use a Vec here unless we think of a smarter way of doing this
type Blob = [u8; BYTES_PER_BLOB];
#[derive(Debug)]
/// TODO(pawan): add docs after the c_kzg interface changes to bytes only.
pub enum Error {
InvalidTrustedSetup(CKzgError),
InvalidKzgCommitment(CKzgError),
@ -27,23 +24,46 @@ pub enum Error {
/// A wrapper over a kzg library that holds the trusted setup parameters.
pub struct Kzg {
trusted_setup: KZGSettings,
trusted_setup: KzgSettings,
}
impl Kzg {
/// Load the kzg trusted setup parameters from a vec of G1 and G2 points.
///
/// The number of G1 points should be equal to FIELD_ELEMENTS_PER_BLOB
/// Note: this number changes based on the preset values.
/// The number of G2 points should be equal to 65.
pub fn new_from_trusted_setup(trusted_setup: TrustedSetup) -> Result<Self, Error> {
Ok(Self {
trusted_setup: KzgSettings::load_trusted_setup(
trusted_setup.g1_points(),
trusted_setup.g2_points(),
)
.map_err(Error::InvalidTrustedSetup)?,
})
}
/// Loads a trusted setup given the path to the file containing the trusted setup values.
/// The format is specified in `c_kzg::KzgSettings::load_trusted_setup_file`.
///
/// Note: This function will likely be deprecated. Use `Kzg::new_from_trusted_setup` instead.
#[deprecated]
pub fn new_from_file(file_path: PathBuf) -> Result<Self, Error> {
Ok(Self {
trusted_setup: KZGSettings::load_trusted_setup_file(file_path)
trusted_setup: KzgSettings::load_trusted_setup_file(file_path)
.map_err(Error::InvalidTrustedSetup)?,
})
}
/// Compute the aggregated kzg proof given an array of blobs.
pub fn compute_aggregate_kzg_proof(&self, blobs: &[Blob]) -> Result<KzgProof, Error> {
c_kzg::KZGProof::compute_aggregate_kzg_proof(blobs, &self.trusted_setup)
c_kzg::KzgProof::compute_aggregate_kzg_proof(blobs, &self.trusted_setup)
.map_err(Error::KzgProofComputationFailed)
.map(|proof| KzgProof(proof.to_bytes()))
}
/// Verify an aggregate kzg proof given the blobs that generated the proof, the kzg commitments
/// and the kzg proof.
pub fn verify_aggregate_kzg_proof(
&self,
blobs: &[Blob],
@ -58,19 +78,20 @@ impl Kzg {
let commitments = expected_kzg_commitments
.into_iter()
.map(|comm| {
c_kzg::KZGCommitment::from_bytes(&comm.0).map_err(Error::InvalidKzgCommitment)
c_kzg::KzgCommitment::from_bytes(&comm.0).map_err(Error::InvalidKzgCommitment)
})
.collect::<Result<Vec<c_kzg::KZGCommitment>, Error>>()?;
.collect::<Result<Vec<c_kzg::KzgCommitment>, Error>>()?;
let proof =
c_kzg::KZGProof::from_bytes(&kzg_aggregated_proof.0).map_err(Error::InvalidKzgProof)?;
c_kzg::KzgProof::from_bytes(&kzg_aggregated_proof.0).map_err(Error::InvalidKzgProof)?;
proof
.verify_aggregate_kzg_proof(blobs, &commitments, &self.trusted_setup)
.map_err(Error::InvalidKzgProof)
}
/// Converts a blob to a kzg commitment.
pub fn blob_to_kzg_commitment(&self, blob: Blob) -> KzgCommitment {
KzgCommitment(
c_kzg::KZGCommitment::blob_to_kzg_commitment(blob, &self.trusted_setup).to_bytes(),
c_kzg::KzgCommitment::blob_to_kzg_commitment(blob, &self.trusted_setup).to_bytes(),
)
}
}

View File

@ -0,0 +1,160 @@
use c_kzg::{BYTES_PER_G1_POINT, BYTES_PER_G2_POINT, FIELD_ELEMENTS_PER_BLOB};
use serde::{
de::{self, Deserializer, Visitor},
Deserialize, Serialize,
};
/// Wrapper over a BLS G1 point's byte representation.
#[derive(Debug, Clone, PartialEq)]
struct G1Point([u8; BYTES_PER_G1_POINT]);
/// Wrapper over a BLS G2 point's byte representation.
#[derive(Debug, Clone, PartialEq)]
struct G2Point([u8; BYTES_PER_G2_POINT]);
/// Contains the trusted setup parameters that are required to instantiate a
/// `c_kzg::KzgSettings` object.
///
/// The serialize/deserialize implementations are written according to
/// the format specified in the the ethereum consensus specs trusted setup files.
///
/// See https://github.com/ethereum/consensus-specs/blob/dev/presets/mainnet/trusted_setups/testing_trusted_setups.json
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TrustedSetup {
#[serde(rename = "setup_G1")]
#[serde(deserialize_with = "deserialize_g1_points")]
g1_points: Vec<G1Point>,
#[serde(rename = "setup_G2")]
g2_points: Vec<G2Point>,
}
impl TrustedSetup {
pub fn g1_points(&self) -> Vec<[u8; BYTES_PER_G1_POINT]> {
self.g1_points.iter().map(|p| p.0).collect()
}
pub fn g2_points(&self) -> Vec<[u8; BYTES_PER_G2_POINT]> {
self.g2_points.iter().map(|p| p.0).collect()
}
pub fn g1_len(&self) -> usize {
self.g1_points.len()
}
}
impl Serialize for G1Point {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let point = hex::encode(self.0);
serializer.serialize_str(&point)
}
}
impl Serialize for G2Point {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let point = hex::encode(self.0);
serializer.serialize_str(&point)
}
}
impl<'de> Deserialize<'de> for G1Point {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct G1PointVisitor;
impl<'de> Visitor<'de> for G1PointVisitor {
type Value = G1Point;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("A 48 byte hex encoded string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let point = hex::decode(strip_prefix(v))
.map_err(|e| de::Error::custom(format!("Failed to decode G1 point: {}", e)))?;
if point.len() != BYTES_PER_G1_POINT {
return Err(de::Error::custom(format!(
"G1 point has invalid length. Expected {} got {}",
BYTES_PER_G1_POINT,
point.len()
)));
}
let mut res = [0; BYTES_PER_G1_POINT];
res.copy_from_slice(&point);
Ok(G1Point(res))
}
}
deserializer.deserialize_str(G1PointVisitor)
}
}
impl<'de> Deserialize<'de> for G2Point {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct G2PointVisitor;
impl<'de> Visitor<'de> for G2PointVisitor {
type Value = G2Point;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("A 96 byte hex encoded string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let point = hex::decode(strip_prefix(v))
.map_err(|e| de::Error::custom(format!("Failed to decode G2 point: {}", e)))?;
if point.len() != BYTES_PER_G2_POINT {
return Err(de::Error::custom(format!(
"G2 point has invalid length. Expected {} got {}",
BYTES_PER_G2_POINT,
point.len()
)));
}
let mut res = [0; BYTES_PER_G2_POINT];
res.copy_from_slice(&point);
Ok(G2Point(res))
}
}
deserializer.deserialize_str(G2PointVisitor)
}
}
fn deserialize_g1_points<'de, D>(deserializer: D) -> Result<Vec<G1Point>, D::Error>
where
D: Deserializer<'de>,
{
let mut decoded: Vec<G1Point> = serde::de::Deserialize::deserialize(deserializer)?;
// FIELD_ELEMENTS_PER_BLOB is a compile time parameter that
// depends on whether lighthouse is compiled with minimal or mainnet features.
// Minimal and mainnet trusted setup parameters differ only by the
// number of G1 points they contain.
//
// Hence, we truncate the number of G1 points after deserialisation
// to ensure that we have the right number of g1 points in the
// trusted setup.
decoded.truncate(FIELD_ELEMENTS_PER_BLOB);
Ok(decoded)
}
fn strip_prefix(s: &str) -> &str {
if let Some(stripped) = s.strip_prefix("0x") {
stripped
} else {
s
}
}

View File

@ -62,5 +62,4 @@ exec $lighthouse_binary \
--disable-packet-filter \
--target-peers $((BN_COUNT - 1)) \
--execution-endpoint $execution_endpoint \
--trusted-setup-file ./trusted_setup.txt \
--execution-jwt $execution_jwt

File diff suppressed because it is too large Load Diff