use std::net::SocketAddr;
use sha2::{Digest, Sha256};
use dig_identity::{g1_subgroup_check, verify_signature};
const DESCRIPTOR_SIG_CONTEXT: &[u8] = b"dig-nat/relay-descriptor/v1";
pub type DidResolver<'a> = dyn Fn(&str) -> Option<[u8; 48]> + 'a;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RelayDescriptor {
pub peer_id_spki_hash: [u8; 32],
pub bls_pub: [u8; 48],
pub addresses: Vec<SocketAddr>,
pub network_id: String,
pub did: Option<String>,
pub signature: [u8; 96],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum RelayDescriptorError {
#[error("relay descriptor BLS pubkey failed the G1 subgroup check")]
BadBlsPubkey,
#[error("relay descriptor signature did not verify")]
BadSignature,
#[error("relay descriptor peer_id does not match the presented certificate")]
PeerIdMismatch,
#[error("relay descriptor DID resolves to a different BLS key")]
DidMismatch,
}
impl RelayDescriptor {
pub fn signing_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(DESCRIPTOR_SIG_CONTEXT);
out.extend_from_slice(&self.peer_id_spki_hash);
out.extend_from_slice(&self.bls_pub);
out.extend_from_slice(&(self.addresses.len() as u32).to_le_bytes());
for addr in &self.addresses {
let s = addr.to_string();
out.extend_from_slice(&(s.len() as u32).to_le_bytes());
out.extend_from_slice(s.as_bytes());
}
out.extend_from_slice(&(self.network_id.len() as u32).to_le_bytes());
out.extend_from_slice(self.network_id.as_bytes());
match &self.did {
None => out.push(0),
Some(did) => {
out.push(1);
out.extend_from_slice(&(did.len() as u32).to_le_bytes());
out.extend_from_slice(did.as_bytes());
}
}
out
}
}
pub fn verify_relay_descriptor(
descriptor: &RelayDescriptor,
presented_spki_der: Option<&[u8]>,
did_resolver: Option<&DidResolver<'_>>,
) -> Result<(), RelayDescriptorError> {
if !g1_subgroup_check(&descriptor.bls_pub) {
return Err(RelayDescriptorError::BadBlsPubkey);
}
if !verify_signature(
&descriptor.bls_pub,
&descriptor.signing_bytes(),
&descriptor.signature,
) {
return Err(RelayDescriptorError::BadSignature);
}
if let Some(spki) = presented_spki_der {
let hash: [u8; 32] = Sha256::digest(spki).into();
if hash != descriptor.peer_id_spki_hash {
return Err(RelayDescriptorError::PeerIdMismatch);
}
}
if let (Some(did), Some(resolve)) = (&descriptor.did, did_resolver) {
if let Some(resolved) = resolve(did) {
if resolved != descriptor.bls_pub {
return Err(RelayDescriptorError::DidMismatch);
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use dig_identity::{
bls::SecretKey, derive_identity_sk, master_secret_key_from_seed, public_key_bytes,
sign_message,
};
fn node_bls_sk(label: &str) -> SecretKey {
let seed: [u8; 32] = Sha256::digest(label.as_bytes()).into();
derive_identity_sk(&master_secret_key_from_seed(&seed))
}
fn signed_descriptor(bls_sk: &SecretKey, spki: &[u8], did: Option<String>) -> RelayDescriptor {
let peer_id_spki_hash: [u8; 32] = Sha256::digest(spki).into();
let mut d = RelayDescriptor {
peer_id_spki_hash,
bls_pub: public_key_bytes(bls_sk),
addresses: vec![
"[::1]:9450".parse().unwrap(),
"127.0.0.1:9450".parse().unwrap(),
],
network_id: "DIG_MAINNET".to_string(),
did,
signature: [0u8; 96],
};
d.signature = sign_message(bls_sk, &d.signing_bytes());
d
}
#[test]
fn valid_descriptor_verifies() {
let sk = node_bls_sk("relay-desc/valid");
let spki = b"fake-spki-der-bytes";
let d = signed_descriptor(&sk, spki, None);
assert!(verify_relay_descriptor(&d, Some(spki), None).is_ok());
assert!(verify_relay_descriptor(&d, None, None).is_ok());
}
#[test]
fn tampered_signature_rejected() {
let sk = node_bls_sk("relay-desc/tamper");
let spki = b"fake-spki";
let mut d = signed_descriptor(&sk, spki, None);
d.addresses.push("10.0.0.1:1".parse().unwrap()); assert_eq!(
verify_relay_descriptor(&d, Some(spki), None),
Err(RelayDescriptorError::BadSignature)
);
}
#[test]
fn peer_id_spki_mismatch_rejected() {
let sk = node_bls_sk("relay-desc/peerid");
let d = signed_descriptor(&sk, b"spki-A", None);
assert_eq!(
verify_relay_descriptor(&d, Some(b"spki-B"), None),
Err(RelayDescriptorError::PeerIdMismatch)
);
}
#[test]
fn substituted_bls_pubkey_rejected() {
let sk = node_bls_sk("relay-desc/sub");
let attacker = node_bls_sk("relay-desc/sub-attacker");
let spki = b"spki";
let mut d = signed_descriptor(&sk, spki, None);
d.bls_pub = public_key_bytes(&attacker);
assert_eq!(
verify_relay_descriptor(&d, Some(spki), None),
Err(RelayDescriptorError::BadSignature)
);
}
#[test]
fn bad_g1_point_rejected() {
let sk = node_bls_sk("relay-desc/g1");
let spki = b"spki";
let mut d = signed_descriptor(&sk, spki, None);
d.bls_pub = [0xFFu8; 48];
assert_eq!(
verify_relay_descriptor(&d, Some(spki), None),
Err(RelayDescriptorError::BadBlsPubkey)
);
}
#[test]
fn did_resolving_to_other_key_rejected() {
let sk = node_bls_sk("relay-desc/did");
let spki = b"spki";
let d = signed_descriptor(&sk, spki, Some("did:dig:relayX".to_string()));
let other = public_key_bytes(&node_bls_sk("relay-desc/did-other"));
let resolver = |_did: &str| -> Option<[u8; 48]> { Some(other) };
assert_eq!(
verify_relay_descriptor(&d, Some(spki), Some(&resolver)),
Err(RelayDescriptorError::DidMismatch)
);
}
#[test]
fn did_resolving_to_same_key_accepted() {
let sk = node_bls_sk("relay-desc/did-ok");
let spki = b"spki";
let d = signed_descriptor(&sk, spki, Some("did:dig:relayY".to_string()));
let pk = public_key_bytes(&sk);
let resolver = |_did: &str| -> Option<[u8; 48]> { Some(pk) };
assert!(verify_relay_descriptor(&d, Some(spki), Some(&resolver)).is_ok());
}
#[test]
fn unresolvable_did_tolerated() {
let sk = node_bls_sk("relay-desc/did-none");
let spki = b"spki";
let d = signed_descriptor(&sk, spki, Some("did:dig:unknown".to_string()));
let resolver = |_did: &str| -> Option<[u8; 48]> { None };
assert!(verify_relay_descriptor(&d, Some(spki), Some(&resolver)).is_ok());
}
}