Skip to content
Open
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
17 changes: 17 additions & 0 deletions BREAKING_CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@

### `malachitebft-core-types`

- Added new `ValidatorProof<Ctx>` type for the Proof-of-Validator protocol (ADR-006)
- Added new associated type `Timeouts` to the `Context` trait (use `LinearTimeouts` for default implementation) ([#1227](https://github.com/circlefin/malachite/pull/1227))
- Remove `initial_validator_set` and `initial_height` fields from `Params` struct ([#1190](https://github.com/circlefin/malachite/pull/1190))

### `malachitebft-signing`

- Added two new methods to `SigningProviderExt` trait for Proof-of-Validator (ADR-006):
- `sign_validator_certificate(&self, public_key: Vec<u8>, peer_id: Vec<u8>) -> Result<ValidatorProof<Ctx>, Error>`
- `verify_validator_certificate(&self, certificate: &ValidatorProof<Ctx>) -> Result<VerificationResult, Error>`
- These have default implementations using `sign_bytes`/`verify_signed_bytes`

### `malachitebft-core-driver`

- Changed `Driver::new()` signature - removed `timeouts` parameter ([#1227](https://github.com/circlefin/malachite/pull/1227))
Expand All @@ -27,6 +35,9 @@

### `malachitebft-engine`

- Added new `NetworkEvent::ValidatorProofReceived` variant for receiving validator proofs (ADR-006)
- Added new `Msg::ValidatorProofVerified` variant for communicating proof verification results
- Network codec trait bounds now require `Codec<ValidatorProof<Ctx>>` implementation
- Changed `Next::Start` variant from `Start(Height, ValidatorSet)` to `Start(Height, HeightParams)` ([#1227](https://github.com/circlefin/malachite/pull/1227))
- Changed `Next::Restart` variant from `Restart(Height, ValidatorSet)` to `Restart(Height, HeightParams)` ([#1227](https://github.com/circlefin/malachite/pull/1227))
- Changed `HostMsg::ConsensusReady` reply type from `(Ctx::Height, Ctx::ValidatorSet)` to `(Ctx::Height, HeightParams<Ctx>)` ([#1227](https://github.com/circlefin/malachite/pull/1227))
Expand All @@ -41,6 +52,12 @@

### `malachitebft-app-channel`

- Changed `NetworkContext` struct for Proof-of-Validator support (ADR-006):
- Old: `NetworkContext::new(identity: NetworkIdentity, codec: Codec)` (where `NetworkIdentity` had no proof)
- New: `NetworkContext::new(identity: NetworkIdentity, codec: Codec)` (where `NetworkIdentity` carries pre-signed proof bytes)
- The application is now responsible for signing the validator proof and building `NetworkIdentity::new_validator(moniker, keypair, address, proof_bytes)` before passing it to `NetworkContext`
- Re-exported `SigningProviderExt` from `malachitebft_app_channel` (needed by apps to call `sign_validator_proof`)
- Network codec now requires `Codec<ValidatorProof<Ctx>>` implementation
- Changed `AppMsg::ConsensusReady` reply type from `(Ctx::Height, Ctx::ValidatorSet)` to `(Ctx::Height, HeightParams<Ctx>)` ([#1227](https://github.com/circlefin/malachite/pull/1227))
- Changed `ConsensusMsg::StartHeight` from `StartHeight(Height, ValidatorSet)` to `StartHeight(Height, HeightParams)` ([#1227](https://github.com/circlefin/malachite/pull/1227))
- Changed `ConsensusMsg::RestartHeight` from `RestartHeight(Height, ValidatorSet)` to `RestartHeight(Height, HeightParams)` ([#1227](https://github.com/circlefin/malachite/pull/1227))
Expand Down
24 changes: 24 additions & 0 deletions code/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions code/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ advisory-lock = "0.3.0"
arbtest = "0.3.2"
async-recursion = "1.1"
async-trait = "0.1.88"
asynchronous-codec = "0.7.0"
axum = "0.7"
base64 = "0.22.0"
borsh = {version = "1", features = ["de_strict_order", "derive"]}
Expand Down Expand Up @@ -146,6 +147,7 @@ libp2p = { version = "0.56.0", features = ["macros", "identify", "to
libp2p-identity = "0.2.12"
libp2p-broadcast = { version = "0.3.0", package = "libp2p-scatter" }
libp2p-gossipsub = { version = "0.49.0", features = ["metrics"] }
libp2p-stream = "0.4.0-alpha"
multiaddr = "0.18.2"
multihash = { version = "0.19.3", default-features = false }
nix = { version = "0.29.0", features = ["signal"] }
Expand Down Expand Up @@ -183,3 +185,4 @@ toml = "0.8.21"
tracing = { version = "0.1.41", default-features = false }
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
unsigned-varint = { version = "0.8", features = ["codec", "asynchronous_codec"] }
5 changes: 5 additions & 0 deletions code/crates/app-channel/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ pub enum RequestBuilder {
///
/// # Example: All defaults
/// ```rust,ignore
/// // Sign the validator proof (ADR-006) and build the network identity
/// let proof = signer.sign_validator_proof(public_key_bytes, peer_id_bytes).await?;
/// let proof_bytes = codec.encode(&proof)?;
/// let identity = NetworkIdentity::new_validator(moniker, keypair, address, proof_bytes);
///
/// let (channels, handle) = EngineBuilder::new(ctx, config)
/// .with_default_wal(WalContext::new(path, codec))
/// .with_default_network(NetworkContext::new(identity, codec))
Expand Down
6 changes: 6 additions & 0 deletions code/crates/app-channel/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use malachitebft_engine::node::NodeRef;
use malachitebft_signing::SigningProvider;

pub use malachitebft_engine::network::NetworkIdentity;
pub use malachitebft_signing::SigningProviderExt;

// Re-export context structs from builder module
pub use crate::builder::{
Expand Down Expand Up @@ -43,6 +44,11 @@ impl EngineHandle {
///
/// # Example
/// ```rust,ignore
/// // Sign the validator proof (ADR-006) and build the network identity
/// let proof = signer.sign_validator_proof(public_key_bytes, peer_id_bytes).await?;
/// let proof_bytes = net_codec.encode(&proof)?;
/// let identity = NetworkIdentity::new_validator(moniker, keypair, address, proof_bytes);
///
/// let (channels, handle) = start_engine(
/// ctx,
/// config,
Expand Down
1 change: 1 addition & 0 deletions code/crates/app/src/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ fn make_network_config(cfg: &ConsensusConfig, value_sync_cfg: &ValueSyncConfig)
discovery_kad: cfg.p2p.protocol_names.discovery_kad.clone(),
discovery_regres: cfg.p2p.protocol_names.discovery_regres.clone(),
sync: cfg.p2p.protocol_names.sync.clone(),
validator_proof: cfg.p2p.protocol_names.validator_proof.clone(),
},
}
}
14 changes: 14 additions & 0 deletions code/crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pub struct ProtocolNames {
pub discovery_regres: String,

pub sync: String,

pub validator_proof: String,
}

impl Default for ProtocolNames {
Expand All @@ -27,6 +29,7 @@ impl Default for ProtocolNames {
discovery_kad: "/malachitebft-discovery/kad/v1beta1".to_string(),
discovery_regres: "/malachitebft-discovery/reqres/v1beta1".to_string(),
sync: "/malachitebft-sync/v1beta1".to_string(),
validator_proof: "/malachitebft-validator-proof/v1".to_string(),
}
}
}
Expand Down Expand Up @@ -911,6 +914,10 @@ mod tests {
"/malachitebft-discovery/reqres/v1beta1"
);
assert_eq!(protocol_names.sync, "/malachitebft-sync/v1beta1");
assert_eq!(
protocol_names.validator_proof,
"/malachitebft-validator-proof/v1"
);
}

#[test]
Expand All @@ -923,6 +930,7 @@ mod tests {
discovery_kad: "/custom-discovery/kad/v1".to_string(),
discovery_regres: "/custom-discovery/reqres/v1".to_string(),
sync: "/custom-sync/v1".to_string(),
validator_proof: "/custom-validator-proof/v1".to_string(),
};

let json = serde_json::to_string(&protocol_names).unwrap();
Expand All @@ -945,6 +953,7 @@ mod tests {
discovery_kad: "/test-network/discovery/kad/v1".to_string(),
discovery_regres: "/test-network/discovery/reqres/v1".to_string(),
sync: "/test-network/sync/v1".to_string(),
validator_proof: "/test-network/validator-proof/v1".to_string(),
};

let config_with_custom = P2pConfig {
Expand Down Expand Up @@ -978,6 +987,7 @@ mod tests {
discovery_kad = "/custom-network/discovery/kad/v2"
discovery_regres = "/custom-network/discovery/reqres/v2"
sync = "/custom-network/sync/v2"
validator_proof = "/custom-network/validator-proof/v2"

[p2p.protocol]
type = "gossipsub"
Expand All @@ -998,6 +1008,10 @@ mod tests {
"/custom-network/discovery/reqres/v2"
);
assert_eq!(config.p2p.protocol_names.sync, "/custom-network/sync/v2");
assert_eq!(
config.p2p.protocol_names.validator_proof,
"/custom-network/validator-proof/v2"
);
}

#[test]
Expand Down
2 changes: 2 additions & 0 deletions code/crates/core-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod signing;
mod threshold;
mod timeout;
mod timeouts;
mod validator_proof;
mod validator_set;
mod value;
mod vote;
Expand Down Expand Up @@ -78,6 +79,7 @@ pub use signing::SigningScheme;
pub use threshold::{Threshold, ThresholdParam, ThresholdParams};
pub use timeout::{Timeout, TimeoutKind};
pub use timeouts::{LinearTimeouts, Timeouts};
pub use validator_proof::ValidatorProof;
pub use validator_set::{Address, Validator, ValidatorSet, VotingPower};
pub use value::{NilOrVal, Value, ValueOrigin, ValuePayload};
pub use vote::{Vote, VoteType};
Expand Down
8 changes: 7 additions & 1 deletion code/crates/core-types/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub trait SigningScheme
where
Self: Clone + Debug + Eq,
{
/// Errors that can occur when decoding a signature from a byte array.
/// Errors that can occur when decoding a signature or public key from a byte array.
type DecodingError: Display;

/// The type of signatures produced by this signing scheme.
Expand All @@ -30,4 +30,10 @@ where

/// Encode a signature to a byte array.
fn encode_signature(signature: &Self::Signature) -> Vec<u8>;

/// Decode a public key from a byte array.
fn decode_public_key(bytes: &[u8]) -> Result<Self::PublicKey, Self::DecodingError>;

/// Encode a public key to a byte array.
fn encode_public_key(public_key: &Self::PublicKey) -> Vec<u8>;
}
58 changes: 58 additions & 0 deletions code/crates/core-types/src/validator_proof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Validator Proof type for the Proof-of-Validator protocol.

use alloc::vec::Vec;
use derive_where::derive_where;

use crate::{Context, Signature};

/// Separator bytes for Proof-of-Validator signatures.
/// The 3-byte ASCII string "PoV" (0x50 0x6F 0x56).
const POV_SEPARATOR: &[u8] = b"PoV";

/// A proof that a libp2p peer ID is controlled by a validator.
///
/// This allows nodes to cryptographically verify that a peer claiming to be
/// a validator actually controls the corresponding consensus private key.
///
/// The proof binds a libp2p peer ID to a consensus public key,
/// signed by the corresponding consensus private key. This allows immediate
/// signature verification without needing to look up the public key from the
/// validator set.
#[derive_where(Clone, Debug, PartialEq, Eq)]
pub struct ValidatorProof<Ctx: Context> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just familiarizing myself with the ADR and this implementation: Do we need to version this data type? Seems to go through the wire unversioned.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently I use the same approach as for sync and consensus, using protocol names that have version in their names. Haven't added it to defaults yet. But if two nodes use different versions, streams will not be established.
Currently hardcoded with:

pub const PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/malachitebft-validator-proof/v1")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// The validator's consensus public key (raw bytes)
pub public_key: Vec<u8>,
/// The libp2p peer ID bytes
pub peer_id: Vec<u8>,
/// Signature over (public_key, peer_id) using the validator's consensus key
pub signature: Signature<Ctx>,
}

impl<Ctx: Context> ValidatorProof<Ctx> {
/// Creates a new `ValidatorProof`.
pub fn new(public_key: Vec<u8>, peer_id: Vec<u8>, signature: Signature<Ctx>) -> Self {
Self {
public_key,
peer_id,
signature,
}
}

/// Returns the bytes to be signed for this proof.
///
/// Format: SEPARATOR || len(public_key) || public_key || len(peer_id) || peer_id
///
/// Where:
/// - SEPARATOR is "PoV" (0x50 0x6F 0x56)
/// - len() is encoded as 4 bytes (u32 big-endian)
pub fn signing_bytes(public_key: &[u8], peer_id: &[u8]) -> Vec<u8> {
let mut bytes =
Vec::with_capacity(POV_SEPARATOR.len() + 4 + public_key.len() + 4 + peer_id.len());
bytes.extend_from_slice(POV_SEPARATOR);
bytes.extend_from_slice(&(public_key.len() as u32).to_be_bytes());
bytes.extend_from_slice(public_key);
bytes.extend_from_slice(&(peer_id.len() as u32).to_be_bytes());
bytes.extend_from_slice(peer_id);
bytes
}
}
Loading