Skip to content

Commit

Permalink
moved several authority related functions and protocol buffers defini…
Browse files Browse the repository at this point in the history
…tion to subp2p-explorer as shared utilities (#9)

Co-authored-by: rafael <[email protected]>
  • Loading branch information
rvalle and rafael authored Apr 8, 2024
1 parent da19e6d commit c8b9c2b
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 85 deletions.
3 changes: 0 additions & 3 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,5 @@ maxminddb = { workspace = true }
trust-dns-resolver = { workspace = true }
multihash-codetable = { workspace = true, features = ["digest", "serde", "sha2"] }
jsonrpsee = { workspace = true, features = ["async-client", "client-ws-transport-native-tls"] }
prost = "0.12"
ss58-registry = { version = "1.34.0", default-features = false }

[build-dependencies]
prost-build = "0.11"
84 changes: 2 additions & 82 deletions cli/src/commands/authorities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ use libp2p::{
Multiaddr, PeerId, Swarm,
};
use multihash_codetable::{Code, MultihashDigest};
use prost::Message;
use rand::{seq::SliceRandom, thread_rng};
use std::collections::{HashMap, HashSet};
use subp2p_explorer::{
peer_behavior::PeerInfoEvent,
transport::{TransportBuilder, MIB},
util::authorities::{decode_dht_record, hash_authority_id},
util::crypto::sr25519,
util::p2p::get_peer_id,
util::ss58::{ss58hash, to_ss58},
Behaviour, BehaviourEvent,
};
Expand Down Expand Up @@ -62,90 +63,9 @@ async fn runtime_api_autorities(
Ok(authorities)
}

/// Hash the authority ID to obtain the kademlia key at which the record
/// of the authority is stored on the p2p network.
fn hash_authority_id(id: &[u8]) -> KademliaKey {
KademliaKey::new(&Code::Sha2_256.digest(id).digest())
}

/// The maximum number of Kademlia `get-records` queried a time.
const MAX_QUERIES: usize = 8;

/// Protobuf schema for decoding the authority records from the DHT.
mod schema {
include!(concat!(env!("OUT_DIR"), "/authority_discovery_v2.rs"));
}

/// Get the peerId from a p2p multiaddress.
fn get_peer_id(address: &Multiaddr) -> Option<PeerId> {
match address.iter().last() {
Some(multiaddr::Protocol::P2p(key)) => Some(key),
_ => None,
}
}

/// Decode the DHT payload and verify the signatures.
///
/// The DHT payload is composed:
/// - `record` - The authority record containing the addresses of the authority.
/// - `auth_signature` - The signature of the authority over the `record`.
/// - `peer_signature` - The signature of the peer over the `record`.
///
/// The record must contain at least one address in order to discover the peer
/// identity of the authority.
fn decode_dht_record(
value: Vec<u8>,
authority_id: &sr25519::PublicKey,
) -> Result<(PeerId, Vec<Multiaddr>), Box<dyn std::error::Error>> {
// Decode and verify the authority signature.
let payload = schema::SignedAuthorityRecord::decode(value.as_slice())?;
let auth_signature = sr25519::Signature::decode(&mut &payload.auth_signature[..])?;
if !sr25519::verify(&auth_signature, &payload.record, &authority_id) {
return Err("Cannot verify DHT payload".into());
}

// Extract the P2P multiaddresses from the prvoided record.
let record = schema::AuthorityRecord::decode(payload.record.as_slice())?;
let addresses: Vec<Multiaddr> = record
.addresses
.into_iter()
.map(|a| a.try_into())
.collect::<std::result::Result<_, _>>()?;

// At least one address must be provided and all must point to the same peerId.
if addresses.is_empty() {
return Err("No addresses found in the DHT record".into());
}
let peer_ids: HashSet<_> = addresses.iter().filter_map(get_peer_id).collect();
if peer_ids.len() != 1 {
return Err(format!(
"All addresses must point to the same peerId: {:?}",
addresses
)
.into());
}

let peer_id = peer_ids
.iter()
.next()
.expect("At least one peerId; qed")
.clone();

// Verify peer signature.
let Some(peer_signature) = payload.peer_signature else {
return Err("Payload is not signed".into());
};
let public_key = libp2p::identity::PublicKey::try_decode_protobuf(&peer_signature.public_key)?;
if peer_id != public_key.to_peer_id() {
return Err("PeerId does not match the public key".into());
}
if !public_key.verify(&payload.record.as_slice(), &peer_signature.signature) {
return Err("Peer signature verification failed".into());
}

Ok((peer_id, addresses))
}

/// Discover the authorities on the network.
struct AuthorityDiscovery {
/// Drive the network behavior.
Expand Down
5 changes: 5 additions & 0 deletions subp2p-explorer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,9 @@ primitive-types = { workspace = true, default-features = false, features = ["cod
hex = { workspace = true }
schnorrkel = "0.11.4"
blake2 = "0.10.4"
multihash-codetable = { workspace = true, features = ["digest", "serde", "sha2"] }
bs58 = { version = "0.5.0", features = ["alloc"] }
prost = "0.12"

[build-dependencies]
prost-build = "0.11"
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
syntax = "proto3";

// Protocol buffers for Authority Discovery utilities
package authority_discovery_v2;

// First we need to serialize the addresses in order to be able to sign them.
Expand Down
86 changes: 86 additions & 0 deletions subp2p-explorer/src/util/authorities.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2023 Alexandru Vasile
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use codec::Decode;
use libp2p::kad::record::Key as KademliaKey;
use libp2p::{Multiaddr, PeerId};
use multihash_codetable::{Code, MultihashDigest};
use prost::Message;
use std::collections::HashSet;

use crate::util::crypto::sr25519;
use crate::util::p2p::get_peer_id;

/// Protobuf schema for decoding the authority records from the DHT.
mod schema {
include!(concat!(env!("OUT_DIR"), "/authority_discovery_v2.rs"));
}

/// Hash the authority ID to obtain the kademlia key at which the record
/// of the authority is stored on the p2p network.
pub fn hash_authority_id(id: &[u8]) -> KademliaKey {
KademliaKey::new(&Code::Sha2_256.digest(id).digest())
}

/// Decode the DHT payload and verify the signatures.
///
/// The DHT payload is composed:
/// - `record` - The authority record containing the addresses of the authority.
/// - `auth_signature` - The signature of the authority over the `record`.
/// - `peer_signature` - The signature of the peer over the `record`.
///
/// The record must contain at least one address in order to discover the peer
/// identity of the authority.
pub fn decode_dht_record(
value: Vec<u8>,
authority_id: &sr25519::PublicKey,
) -> Result<(PeerId, Vec<Multiaddr>), Box<dyn std::error::Error>> {
// Decode and verify the authority signature.
let payload = schema::SignedAuthorityRecord::decode(value.as_slice())?;
let auth_signature = sr25519::Signature::decode(&mut &payload.auth_signature[..])?;
if !sr25519::verify(&auth_signature, &payload.record, &authority_id) {
return Err("Cannot verify DHT payload".into());
}

// Extract the P2P multiaddresses from the prvoided record.
let record = schema::AuthorityRecord::decode(payload.record.as_slice())?;
let addresses: Vec<Multiaddr> = record
.addresses
.into_iter()
.map(|a| a.try_into())
.collect::<std::result::Result<_, _>>()?;

// At least one address must be provided and all must point to the same peerId.
if addresses.is_empty() {
return Err("No addresses found in the DHT record".into());
}
let peer_ids: HashSet<_> = addresses.iter().filter_map(get_peer_id).collect();
if peer_ids.len() != 1 {
return Err(format!(
"All addresses must point to the same peerId: {:?}",
addresses
)
.into());
}

let peer_id = peer_ids
.iter()
.next()
.expect("At least one peerId; qed")
.clone();

// Verify peer signature.
let Some(peer_signature) = payload.peer_signature else {
return Err("Payload is not signed".into());
};
let public_key = libp2p::identity::PublicKey::try_decode_protobuf(&peer_signature.public_key)?;
if peer_id != public_key.to_peer_id() {
return Err("PeerId does not match the public key".into());
}
if !public_key.verify(&payload.record.as_slice(), &peer_signature.signature) {
return Err("Peer signature verification failed".into());
}

Ok((peer_id, addresses))
}
2 changes: 2 additions & 0 deletions subp2p-explorer/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

pub mod authorities;
pub mod crypto;
pub mod p2p;
pub mod ss58;
13 changes: 13 additions & 0 deletions subp2p-explorer/src/util/p2p.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2023 Alexandru Vasile
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use libp2p::{multiaddr, Multiaddr, PeerId};

/// Get the peerId from a p2p multiaddress.
pub fn get_peer_id(address: &Multiaddr) -> Option<PeerId> {
match address.iter().last() {
Some(multiaddr::Protocol::P2p(key)) => Some(key),
_ => None,
}
}

0 comments on commit c8b9c2b

Please sign in to comment.