diff --git a/ethereum/Cargo.lock b/ethereum/Cargo.lock index e177aba8..fe090fe0 100644 --- a/ethereum/Cargo.lock +++ b/ethereum/Cargo.lock @@ -17,12 +17,43 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "git+https://github.com/lurk-lab/bls12_381.git?branch=zkvm#0d57d6ac0af6a464c4764809b5bf994d15920762" +dependencies = [ + "cfg-if", + "ff", + "group", + "pairing", + "rand_core", + "subtle", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -97,11 +128,18 @@ version = "0.1.0" dependencies = [ "ethereum-lc-core", "ethereum-programs", + "getset", ] [[package]] name = "ethereum-lc-core" version = "0.1.0" +dependencies = [ + "anyhow", + "bls12_381", + "getset", + "thiserror", +] [[package]] name = "ethereum-programs" @@ -111,12 +149,52 @@ dependencies = [ "sphinx-helper", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "getset" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -182,6 +260,39 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -200,6 +311,18 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "ryu" version = "1.0.18" @@ -232,7 +355,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -255,6 +378,23 @@ dependencies = [ "chrono", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.68" @@ -266,6 +406,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "thiserror" version = "1.0.61" @@ -283,7 +429,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", ] [[package]] @@ -292,6 +438,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -313,7 +465,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.68", "wasm-bindgen-shared", ] @@ -335,7 +487,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.68", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -419,6 +571,15 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[patch.unused]] name = "sha2" version = "0.10.8" diff --git a/ethereum/Cargo.toml b/ethereum/Cargo.toml index 9b44a3f0..a7fd1863 100644 --- a/ethereum/Cargo.toml +++ b/ethereum/Cargo.toml @@ -4,7 +4,10 @@ resolver = "2" members = ["core", "ethereum-programs", "light-client"] [workspace.dependencies] +anyhow = "1.0.86" glob = "0.3.1" +getset = "0.1.2" +thiserror = "1.0.61" # Crypto dependencies bls12_381 = { git = "https://github.com/lurk-lab/bls12_381.git", branch = "zkvm" } # Sphinx dependencies diff --git a/ethereum/core/Cargo.toml b/ethereum/core/Cargo.toml index ac494a99..d0808bef 100644 --- a/ethereum/core/Cargo.toml +++ b/ethereum/core/Cargo.toml @@ -4,4 +4,10 @@ version = "0.1.0" edition = "2021" license = "Apache-2.0" homepage = "https://github.com/wormhole-foundation/example-zk-light-clients" -repository = "https://github.com/wormhole-foundation/example-zk-light-clients" \ No newline at end of file +repository = "https://github.com/wormhole-foundation/example-zk-light-clients" + +[dependencies] +anyhow = { workspace = true } +bls12_381 = { workspace = true } +getset = { workspace = true } +thiserror = { workspace = true } \ No newline at end of file diff --git a/ethereum/core/src/crypto/error.rs b/ethereum/core/src/crypto/error.rs new file mode 100644 index 00000000..e94e2b50 --- /dev/null +++ b/ethereum/core/src/crypto/error.rs @@ -0,0 +1,14 @@ +// Copyright (c) Yatima, Inc. +// SPDX-License-Identifier: APACHE-2.0 + +use thiserror::Error; + +/// The error type for the `crypto` module. +#[derive(Debug, Error)] +pub enum CryptoError { + #[error("Internal error occurred: {source}")] + Internal { + #[source] + source: Box, + }, +} diff --git a/ethereum/core/src/crypto/mod.rs b/ethereum/core/src/crypto/mod.rs new file mode 100644 index 00000000..9788ed0d --- /dev/null +++ b/ethereum/core/src/crypto/mod.rs @@ -0,0 +1,17 @@ +// Copyright (c) Yatima, Inc. +// SPDX-License-Identifier: APACHE-2.0 + +//! # Cryptographic Utilities for the Aptos Light Client +//! +//! This module contains cryptographic utilities used by the light client. +//! It is divided into several sub-modules, each with its own specific functionality. +//! +//! ## Sub-modules +//! +//! - `sig`: This sub-module contains the `Signature` and `PublicKey` structures and their associated methods. +//! - `error`: This sub-module contains the ``CryptoError` error type used throughout the `crypto` module. +//! +//! For more detailed information, users should refer to the specific documentation for each sub-module. + +pub mod error; +pub mod sig; diff --git a/ethereum/core/src/crypto/sig.rs b/ethereum/core/src/crypto/sig.rs new file mode 100644 index 00000000..419b401d --- /dev/null +++ b/ethereum/core/src/crypto/sig.rs @@ -0,0 +1,75 @@ +// Copyright (c) Yatima, Inc. +// SPDX-License-Identifier: APACHE-2.0 + +use crate::crypto::error::CryptoError; +use anyhow::Result; +use bls12_381::G1Affine; +use std::cell::OnceCell; + +/// Length of a public key in bytes. +pub const PUB_KEY_LEN: usize = 48; + +/// A structure representing a public key. +/// +/// The public key is represented as a compressed byte array and an optional `G1Affine` point. +/// The `G1Affine` point is computed from the compressed byte array when needed. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PublicKey { + compressed_pubkey: [u8; PUB_KEY_LEN], + pubkey: OnceCell, +} + +impl Default for PublicKey { + fn default() -> Self { + Self { + compressed_pubkey: [0u8; PUB_KEY_LEN], + pubkey: OnceCell::new(), + } + } +} + +impl PublicKey { + /// Returns the `G1Affine` point representing the public key. + /// + /// If the `G1Affine` point has not been computed yet, it is computed from the compressed byte array and stored. + /// + /// # Returns + /// + /// A `G1Affine` point representing the public key. + /// + /// # Note + /// + // All public key data we receive are in a message signed by validators of a (prior) epoch. + // We assume those signers check against rogue key attacks before signing those keys. + #[inline] + fn pubkey(&self) -> &G1Affine { + self.pubkey + .get_or_init(|| G1Affine::from_compressed_unchecked(&self.compressed_pubkey).unwrap()) + } + + /// Aggregates a vector of public keys into a single public key. + /// + /// # Arguments + /// + /// * `pubkeys` - A slice of references to `PublicKey` instances to be aggregated. + /// + /// # Returns + /// + /// A `Result` which is `Ok` if the public keys could be aggregated successfully. If the aggregation fails, + /// the `Result` is `Err` with an error message. + pub fn aggregate(pubkeys: &[&Self]) -> Result { + let aggregate = pubkeys + .iter() + .fold(G1Affine::identity(), |acc, pk| acc.add_affine(pk.pubkey())); + + let pubkey = OnceCell::new(); + pubkey.set(aggregate).map_err(|_| CryptoError::Internal { + source: "Failed to set the aggregate public key value in the cell.".into(), + })?; + + Ok(PublicKey { + compressed_pubkey: [0u8; PUB_KEY_LEN], + pubkey, + }) + } +} diff --git a/ethereum/core/src/lib.rs b/ethereum/core/src/lib.rs index d839d793..29e5dec9 100644 --- a/ethereum/core/src/lib.rs +++ b/ethereum/core/src/lib.rs @@ -1,2 +1,5 @@ // Copyright (c) Yatima, Inc. // SPDX-License-Identifier: APACHE-2.0 + +pub mod crypto; +pub mod types; diff --git a/ethereum/core/src/types/block.rs b/ethereum/core/src/types/block.rs new file mode 100644 index 00000000..a5ed9927 --- /dev/null +++ b/ethereum/core/src/types/block.rs @@ -0,0 +1,49 @@ +// Copyright (c) Yatima, Inc. +// SPDX-License-Identifier: APACHE-2.0 + +use crate::types::Bytes32; +use getset::Getters; + +/// `BeaconBlockHeader` represents the header of a beacon block. +/// +/// From [the CL specifications](https://github.com/ethereum/consensus-specs/blob/v1.3.0/specs/phase0/beacon-chain.md#beaconblockheader). +#[derive(Debug, Default, Clone, Getters)] +#[getset(get = "pub")] +pub struct BeaconBlockHeader { + slot: u64, + proposer_index: u64, + parent_root: Bytes32, + state_root: Bytes32, + body_root: Bytes32, +} + +impl BeaconBlockHeader { + /// Create a new `BeaconBlockHeader`. + /// + /// # Arguments + /// + /// * `slot` - The slot number of the block. + /// * `proposer_index` - The validator registry index of the validator who proposed the block. + /// * `parent_root` - The hash of the parent block's header. + /// * `state_root` - The hash of the beacon state at the start of the block. + /// * `body_root` - The hash representing the block body. + /// + /// # Returns + /// + /// A new `BeaconBlockHeader`. + pub const fn new( + slot: u64, + proposer_index: u64, + parent_root: Bytes32, + state_root: Bytes32, + body_root: Bytes32, + ) -> Self { + Self { + slot, + proposer_index, + parent_root, + state_root, + body_root, + } + } +} diff --git a/ethereum/core/src/types/committee.rs b/ethereum/core/src/types/committee.rs new file mode 100644 index 00000000..a2460807 --- /dev/null +++ b/ethereum/core/src/types/committee.rs @@ -0,0 +1,17 @@ +// Copyright (c) Yatima, Inc. +// SPDX-License-Identifier: APACHE-2.0 + +use crate::crypto::sig::PublicKey; +use crate::types::SYNC_COMMITTEE_SIZE; +use getset::Getters; + +/// `SyncCommittee` is a committee of validators that are responsible for attesting to the latest +/// block. The sync committee is a subset of the full validator set. +/// +/// From [the Altair upgrade specifications](https://github.com/ethereum/consensus-specs/blob/v1.3.0/specs/altair/beacon-chain.md#synccommittee). +#[derive(Debug, Clone, Getters)] +#[getset(get = "pub")] +pub struct SyncCommittee { + pubkeys: [PublicKey; SYNC_COMMITTEE_SIZE], + aggregate_pubkey: PublicKey, +} diff --git a/ethereum/core/src/types/mod.rs b/ethereum/core/src/types/mod.rs new file mode 100644 index 00000000..43d9e009 --- /dev/null +++ b/ethereum/core/src/types/mod.rs @@ -0,0 +1,23 @@ +// Copyright (c) Yatima, Inc. +// SPDX-License-Identifier: APACHE-2.0 + +//! # Types Module +//! +//! This module provides the core data structures and types used in the Ethereum Light Client. +//! +//! ## Sub-modules +//! +//! - `block`: This sub-module contains all the structures related to block data on the Beacon chain. +//! - `committee`: This sub-module contains all the structures related to committees on the Beacon chain. +//! +//! For more detailed information, users should refer to the specific +//! documentation for each sub-module. + +pub mod block; +pub mod committee; + +/// Constant number of validators in the sync committee. +pub const SYNC_COMMITTEE_SIZE: usize = 512; + +/// A 32-byte array. +pub type Bytes32 = [u8; 32]; diff --git a/ethereum/ethereum-programs/src/lib.rs b/ethereum/ethereum-programs/src/lib.rs index 859d1a99..9b097b3a 100644 --- a/ethereum/ethereum-programs/src/lib.rs +++ b/ethereum/ethereum-programs/src/lib.rs @@ -3,4 +3,4 @@ pub const INCLUSION_PROGRAM: &[u8] = include_bytes!("../artifacts/inclusion-program"); -pub const EPOCH_CHANGE_PROGRAM: &[u8] = include_bytes!("../artifacts/epoch-change-program"); +pub const COMMITTEE_CHANGE_PROGRAM: &[u8] = include_bytes!("../artifacts/committee-change-program"); diff --git a/ethereum/light-client/Cargo.toml b/ethereum/light-client/Cargo.toml index fb6b9d01..1d47f7c5 100644 --- a/ethereum/light-client/Cargo.toml +++ b/ethereum/light-client/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +getset = { workspace = true } # LC crates ethereum-lc-core = { path = "../core" } ethereum-programs = { path = "../ethereum-programs" } diff --git a/ethereum/light-client/src/lib.rs b/ethereum/light-client/src/lib.rs index 4bcb7464..8fe61d1d 100644 --- a/ethereum/light-client/src/lib.rs +++ b/ethereum/light-client/src/lib.rs @@ -1,2 +1,4 @@ // Copyright (c) Yatima, Inc. // SPDX-License-Identifier: Apache-2.0 + +pub mod types; diff --git a/ethereum/light-client/src/types/mod.rs b/ethereum/light-client/src/types/mod.rs new file mode 100644 index 00000000..55a5d4dd --- /dev/null +++ b/ethereum/light-client/src/types/mod.rs @@ -0,0 +1,3 @@ +// Copyright (c) Yatima, Inc. +// SPDX-License-Identifier: APACHE-2.0 +pub mod snapshot; diff --git a/ethereum/light-client/src/types/snapshot.rs b/ethereum/light-client/src/types/snapshot.rs new file mode 100644 index 00000000..a82bafc7 --- /dev/null +++ b/ethereum/light-client/src/types/snapshot.rs @@ -0,0 +1,21 @@ +// Copyright (c) Yatima, Inc. +// SPDX-License-Identifier: APACHE-2.0 + +use ethereum_lc_core::types::block::BeaconBlockHeader; +use ethereum_lc_core::types::committee::SyncCommittee; +use getset::Getters; + +/// `LightClientSnapshot` represents the light client's view of the most recent block header that +/// the light client is convinced is securely part of the chain. +/// +/// From [the sync protocol specifications](https://github.com/ethereum/annotated-spec/blob/master/altair/sync-protocol.md#lightclientsnapshot). +#[derive(Debug, Clone, Getters)] +#[getset(get = "pub")] +pub struct LightClientSnapshot { + /// Beacon block header + header: BeaconBlockHeader, + /// Latest validated sync committee + current_sync_committee: SyncCommittee, + /// Next sync committee + next_sync_committee: SyncCommittee, +}