Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

summoner: provide phase 1 root during ceremony initialization #3183

Merged
merged 6 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 120 additions & 4 deletions crates/crypto/proof-setup/src/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
use std::array;

use crate::single::{
circuit_degree, group::F, log::ContributionHash, DLogProof, Phase2CRSElements,
Phase2Contribution, Phase2RawCRSElements, Phase2RawContribution,
circuit_degree, group::F, log::ContributionHash, DLogProof, Phase1CRSElements,
Phase1RawCRSElements, Phase2CRSElements, Phase2Contribution, Phase2RawCRSElements,
Phase2RawContribution,
};
use anyhow::{anyhow, Result};
use ark_relations::r1cs::ConstraintMatrices;
Expand All @@ -28,11 +29,16 @@ fn to_bytes<T: CanonicalSerialize>(t: &T) -> Result<Vec<u8>> {
Ok(out)
}

fn to_bytes_uncompressed<T: CanonicalSerialize>(t: &T) -> Result<Vec<u8>> {
let mut out = Vec::new();
t.serialize_uncompressed(&mut out)?;
Ok(out)
}

pub const NUM_CIRCUITS: usize = 7;

/// Generate all of the circuits as matrices.
fn circuits() -> [ConstraintMatrices<F>; NUM_CIRCUITS] {
println!("GENERATING CIRCUITS?");
[
generate_constraint_matrices::<SpendCircuit>(),
generate_constraint_matrices::<OutputCircuit>(),
Expand Down Expand Up @@ -120,7 +126,6 @@ impl TryFrom<Phase2CeremonyCRS> for pb::CeremonyCrs {
impl Phase2CeremonyCRS {
pub fn root() -> Result<Self> {
let [c0, c1, c2, c3, c4, c5, c6] = circuits();
println!("GENERATED CIRCUITS");
Ok(Self([
Phase2CRSElements::dummy_root(circuit_degree(&c0)?),
Phase2CRSElements::dummy_root(circuit_degree(&c1)?),
Expand Down Expand Up @@ -442,3 +447,114 @@ impl Phase2CeremonyContribution {
}))
}
}

// TODO: Make the phase 1 and phase 2 functionality generic

/// Holds all of the CRS elements for phase1 in one struct, before validation.
#[derive(Clone, Debug)]
pub struct Phase1RawCeremonyCRS([Phase1RawCRSElements; NUM_CIRCUITS]);

impl Phase1RawCeremonyCRS {
/// Skip validation, performing the conversion anyways.
///
/// Useful when parsing known good data.
pub fn assume_valid(self) -> Phase1CeremonyCRS {
match self.0 {
[x0, x1, x2, x3, x4, x5, x6] => Phase1CeremonyCRS([
x0.assume_valid(),
x1.assume_valid(),
x2.assume_valid(),
x3.assume_valid(),
x4.assume_valid(),
x5.assume_valid(),
x6.assume_valid(),
]),
}
}

/// This should only be used when the data is known to be from a trusted source.
pub fn unchecked_from_protobuf(value: pb::CeremonyCrs) -> anyhow::Result<Self> {
Ok(Self([
Phase1RawCRSElements::deserialize_uncompressed_unchecked(value.spend.as_slice())?,
Phase1RawCRSElements::deserialize_uncompressed_unchecked(value.output.as_slice())?,
Phase1RawCRSElements::deserialize_uncompressed_unchecked(
value.delegator_vote.as_slice(),
)?,
Phase1RawCRSElements::deserialize_uncompressed_unchecked(
value.undelegate_claim.as_slice(),
)?,
Phase1RawCRSElements::deserialize_uncompressed_unchecked(value.swap.as_slice())?,
Phase1RawCRSElements::deserialize_uncompressed_unchecked(value.swap_claim.as_slice())?,
Phase1RawCRSElements::deserialize_uncompressed_unchecked(
value.nullifer_derivation_crs.as_slice(),
)?,
]))
}
}

impl TryInto<pb::CeremonyCrs> for Phase1RawCeremonyCRS {
type Error = anyhow::Error;

fn try_into(self) -> Result<pb::CeremonyCrs> {
Ok(pb::CeremonyCrs {
spend: to_bytes_uncompressed(&self.0[0])?,
output: to_bytes_uncompressed(&self.0[1])?,
delegator_vote: to_bytes_uncompressed(&self.0[2])?,
undelegate_claim: to_bytes_uncompressed(&self.0[3])?,
swap: to_bytes_uncompressed(&self.0[4])?,
swap_claim: to_bytes_uncompressed(&self.0[5])?,
nullifer_derivation_crs: to_bytes_uncompressed(&self.0[6])?,
})
}
}

impl TryFrom<pb::CeremonyCrs> for Phase1RawCeremonyCRS {
type Error = anyhow::Error;

fn try_from(value: pb::CeremonyCrs) -> std::result::Result<Self, Self::Error> {
Ok(Self([
Phase1RawCRSElements::deserialize_uncompressed(value.spend.as_slice())?,
Phase1RawCRSElements::deserialize_uncompressed(value.output.as_slice())?,
Phase1RawCRSElements::deserialize_uncompressed(value.delegator_vote.as_slice())?,
Phase1RawCRSElements::deserialize_uncompressed(value.undelegate_claim.as_slice())?,
Phase1RawCRSElements::deserialize_uncompressed(value.swap.as_slice())?,
Phase1RawCRSElements::deserialize_uncompressed(value.swap_claim.as_slice())?,
Phase1RawCRSElements::deserialize_uncompressed(
value.nullifer_derivation_crs.as_slice(),
)?,
]))
}
}

/// Holds all of the CRS elements for phase1 in one struct.
#[derive(Clone, Debug, PartialEq)]
pub struct Phase1CeremonyCRS([Phase1CRSElements; NUM_CIRCUITS]);

impl From<Phase1CeremonyCRS> for Phase1RawCeremonyCRS {
fn from(value: Phase1CeremonyCRS) -> Self {
Self(array::from_fn(|i| value.0[i].raw.clone()))
}
}

impl TryFrom<Phase1CeremonyCRS> for pb::CeremonyCrs {
type Error = anyhow::Error;

fn try_from(data: Phase1CeremonyCRS) -> Result<pb::CeremonyCrs> {
Phase1RawCeremonyCRS::from(data).try_into()
}
}

impl Phase1CeremonyCRS {
pub fn root() -> Result<Self> {
let [c0, c1, c2, c3, c4, c5, c6] = circuits();
Ok(Self([
Phase1CRSElements::root(circuit_degree(&c0)?),
Phase1CRSElements::root(circuit_degree(&c1)?),
Phase1CRSElements::root(circuit_degree(&c2)?),
Phase1CRSElements::root(circuit_degree(&c3)?),
Phase1CRSElements::root(circuit_degree(&c4)?),
Phase1CRSElements::root(circuit_degree(&c5)?),
Phase1CRSElements::root(circuit_degree(&c6)?),
]))
}
}
14 changes: 13 additions & 1 deletion crates/crypto/proof-setup/src/single/phase1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ impl RawCRSElements {
raw: self,
})
}

/// Convert without checking validity.
pub(crate) fn assume_valid(self) -> CRSElements {
let d = self
.get_degree()
.expect("can get degree from valid elements");

CRSElements {
degree: d,
raw: self,
}
}
}

impl Hashable for RawCRSElements {
Expand Down Expand Up @@ -169,7 +181,7 @@ impl Hashable for RawCRSElements {
/// The CRS elements we produce in phase 1.
///
/// Not all elements of the final CRS are present here.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct CRSElements {
pub(crate) degree: usize,
pub(crate) raw: RawCRSElements,
Expand Down
2 changes: 1 addition & 1 deletion tools/summonerd/src/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl ContributionHandler {
if let Some(unvalidated) = maybe {
tracing::debug!("validating contribution");
if let Some(contribution) =
unvalidated.validate(&mut OsRng, &self.storage.root().await?)
unvalidated.validate(&mut OsRng, &self.storage.phase_2_root().await?)
{
if contribution.is_linked_to(&parent) {
self.storage
Expand Down
46 changes: 45 additions & 1 deletion tools/summonerd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ use coordinator::Coordinator;
use metrics_tracing_context::MetricsLayer;
use penumbra_keys::FullViewingKey;
use penumbra_proto::tools::summoning::v1alpha1::ceremony_coordinator_service_server::CeremonyCoordinatorServiceServer;
use penumbra_proto::tools::summoning::v1alpha1::CeremonyCrs;
use penumbra_proto::Message;
use std::fs::File;
use std::io::BufReader;
use std::io::Read;
use std::net::SocketAddr;
use storage::Storage;
use tonic::transport::Server;
use tracing_subscriber::{prelude::*, EnvFilter};
use url::Url;

use crate::{penumbra_knower::PenumbraKnower, server::CoordinatorService};
use penumbra_proof_setup::all::{Phase1CeremonyCRS, Phase1RawCeremonyCRS};

/// 100 MIB
const MAX_MESSAGE_SIZE: usize = 100 * 1024 * 1024;
Expand All @@ -40,6 +46,18 @@ struct Opt {

#[derive(Debug, clap::Subcommand)]
enum Command {
/// Generate a phase 1 root (for testing purposes).
GeneratePhase1 {
#[clap(long, display_order = 100)]
output: Utf8PathBuf,
},
/// Initialize the coordinator.
Init {
#[clap(long, display_order = 100)]
storage_dir: Utf8PathBuf,
#[clap(long, display_order = 200)]
phase1_root: Utf8PathBuf,
},
/// Start the coordinator.
Start {
#[clap(long, display_order = 700)]
Expand All @@ -62,7 +80,7 @@ impl Opt {
node,
listen,
} => {
let storage = Storage::load_or_initialize(storage_dir.join("ceremony.db")).await?;
let storage = Storage::load(storage_dir.join("ceremony.db")).await?;
let knower =
PenumbraKnower::load_or_initialize(storage_dir.join("penumbra.db"), &fvk, node)
.await?;
Expand All @@ -87,6 +105,32 @@ impl Opt {
};
Ok(())
}
Command::Init {
storage_dir,
phase1_root,
} => {
let file = File::open(phase1_root)?;
let mut reader = BufReader::new(file);

let mut phase_1_bytes = Vec::new();
reader.read_to_end(&mut phase_1_bytes)?;

let phase_1_raw_root = Phase1RawCeremonyCRS::unchecked_from_protobuf(
CeremonyCrs::decode(&phase_1_bytes[..])?,
)?;

// This is assumed to be valid as it's the starting point for the ceremony.
let phase_1_root = phase_1_raw_root.assume_valid();

Storage::initialize(storage_dir.join("ceremony.db"), phase_1_root).await?;
Ok(())
}
Command::GeneratePhase1 { output } => {
let phase_1_root = Phase1CeremonyCRS::root()?;
let proto_encoded_phase_1_root: CeremonyCrs = phase_1_root.try_into()?;
std::fs::write(output, proto_encoded_phase_1_root.encode_to_vec())?;
Ok(())
}
}
}
}
Expand Down
49 changes: 33 additions & 16 deletions tools/summonerd/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use camino::Utf8Path;
use penumbra_keys::Address;
use penumbra_num::Amount;
use penumbra_proof_setup::all::{
Phase2CeremonyCRS, Phase2CeremonyContribution, Phase2RawCeremonyCRS,
Phase2RawCeremonyContribution,
Phase1CeremonyCRS, Phase1RawCeremonyCRS, Phase2CeremonyCRS, Phase2CeremonyContribution,
Phase2RawCeremonyCRS, Phase2RawCeremonyContribution,
};
use penumbra_proto::{
penumbra::tools::summoning::v1alpha1::{
Expand Down Expand Up @@ -38,16 +38,10 @@ pub struct Storage {
}

impl Storage {
/// If the database at `storage_path` exists, [`Self::load`] it, otherwise, [`Self::initialize`] it.
pub async fn load_or_initialize(storage_path: impl AsRef<Utf8Path>) -> anyhow::Result<Self> {
if storage_path.as_ref().exists() {
return Self::load(storage_path).await;
}

Self::initialize(storage_path).await
}

pub async fn initialize(storage_path: impl AsRef<Utf8Path>) -> anyhow::Result<Self> {
pub async fn initialize(
storage_path: impl AsRef<Utf8Path>,
phase_1_root: Phase1CeremonyCRS,
) -> anyhow::Result<Self> {
// Connect to the database (or create it)
let pool = Self::connect(storage_path)?;

Expand All @@ -58,11 +52,17 @@ impl Storage {

// Create the tables
tx.execute_batch(include_str!("storage/schema.sql"))?;
// TODO: Remove this in favor of a specific command for initializing root
let root = Phase2CeremonyCRS::root()?;

tx.execute(
"INSERT INTO phase1_contributions VALUES (0, 1, ?1, NULL)",
[pb::CeremonyCrs::try_from(phase_1_root)?.encode_to_vec()],
)?;
// TODO(jen): Transition between phase 1 and phase 2, storing deets in the database
// using `phase_1_root`
let phase_2_root = Phase2CeremonyCRS::root()?;
tx.execute(
"INSERT INTO phase2_contributions VALUES (0, 1, ?1, NULL)",
[pb::CeremonyCrs::try_from(root)?.encode_to_vec()],
[pb::CeremonyCrs::try_from(phase_2_root)?.encode_to_vec()],
)?;

tx.commit()?;
Expand Down Expand Up @@ -213,7 +213,24 @@ impl Storage {
Ok(out)
}

pub async fn root(&self) -> Result<Phase2CeremonyCRS> {
/// Get Phase 1 root.
#[allow(dead_code)]
pub async fn phase_1_root(&self) -> Result<Phase1CeremonyCRS> {
let mut conn = self.pool.get()?;
let tx = conn.transaction()?;
let data = tx.query_row(
"SELECT contribution_or_crs FROM phase1_contributions WHERE is_root LIMIT 1",
[],
|row| row.get::<usize, Vec<u8>>(0),
)?;
Ok(
Phase1RawCeremonyCRS::try_from(pb::CeremonyCrs::decode(data.as_slice())?)?
.assume_valid(),
)
}

/// Get Phase 2 root.
pub async fn phase_2_root(&self) -> Result<Phase2CeremonyCRS> {
let mut conn = self.pool.get()?;
let tx = conn.transaction()?;
let data = tx.query_row(
Expand Down
11 changes: 11 additions & 0 deletions tools/summonerd/src/storage/schema.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
-- used for storing phase 1 contribution information
CREATE TABLE phase1_contributions (
slot INTEGER PRIMARY KEY,
-- 1 if this is a root
is_root INTEGER NOT NULL,
-- If this is the root, will be just the elements, and not a full contribution
contribution_or_crs BLOB NOT NULL,
-- NULL in the specific case that this is the root
address BLOB
);

-- used for storing phase 2 contribution information
CREATE TABLE phase2_contributions (
slot INTEGER PRIMARY KEY,
Expand Down
Loading