dig_nat/
relay_descriptor.rs1use std::net::SocketAddr;
17
18use sha2::{Digest, Sha256};
19
20use dig_identity::{g1_subgroup_check, verify_signature};
21
22const DESCRIPTOR_SIG_CONTEXT: &[u8] = b"dig-nat/relay-descriptor/v1";
25
26pub type DidResolver<'a> = dyn Fn(&str) -> Option<[u8; 48]> + 'a;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct RelayDescriptor {
35 pub peer_id_spki_hash: [u8; 32],
37 pub bls_pub: [u8; 48],
39 pub addresses: Vec<SocketAddr>,
41 pub network_id: String,
43 pub did: Option<String>,
46 pub signature: [u8; 96],
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
53pub enum RelayDescriptorError {
54 #[error("relay descriptor BLS pubkey failed the G1 subgroup check")]
56 BadBlsPubkey,
57 #[error("relay descriptor signature did not verify")]
59 BadSignature,
60 #[error("relay descriptor peer_id does not match the presented certificate")]
62 PeerIdMismatch,
63 #[error("relay descriptor DID resolves to a different BLS key")]
65 DidMismatch,
66}
67
68impl RelayDescriptor {
69 pub fn signing_bytes(&self) -> Vec<u8> {
75 let mut out = Vec::new();
76 out.extend_from_slice(DESCRIPTOR_SIG_CONTEXT);
77 out.extend_from_slice(&self.peer_id_spki_hash);
78 out.extend_from_slice(&self.bls_pub);
79
80 out.extend_from_slice(&(self.addresses.len() as u32).to_le_bytes());
81 for addr in &self.addresses {
82 let s = addr.to_string();
83 out.extend_from_slice(&(s.len() as u32).to_le_bytes());
84 out.extend_from_slice(s.as_bytes());
85 }
86
87 out.extend_from_slice(&(self.network_id.len() as u32).to_le_bytes());
88 out.extend_from_slice(self.network_id.as_bytes());
89
90 match &self.did {
91 None => out.push(0),
92 Some(did) => {
93 out.push(1);
94 out.extend_from_slice(&(did.len() as u32).to_le_bytes());
95 out.extend_from_slice(did.as_bytes());
96 }
97 }
98 out
99 }
100}
101
102pub fn verify_relay_descriptor(
111 descriptor: &RelayDescriptor,
112 presented_spki_der: Option<&[u8]>,
113 did_resolver: Option<&DidResolver<'_>>,
114) -> Result<(), RelayDescriptorError> {
115 if !g1_subgroup_check(&descriptor.bls_pub) {
117 return Err(RelayDescriptorError::BadBlsPubkey);
118 }
119 if !verify_signature(
121 &descriptor.bls_pub,
122 &descriptor.signing_bytes(),
123 &descriptor.signature,
124 ) {
125 return Err(RelayDescriptorError::BadSignature);
126 }
127 if let Some(spki) = presented_spki_der {
129 let hash: [u8; 32] = Sha256::digest(spki).into();
130 if hash != descriptor.peer_id_spki_hash {
131 return Err(RelayDescriptorError::PeerIdMismatch);
132 }
133 }
134 if let (Some(did), Some(resolve)) = (&descriptor.did, did_resolver) {
136 if let Some(resolved) = resolve(did) {
137 if resolved != descriptor.bls_pub {
138 return Err(RelayDescriptorError::DidMismatch);
139 }
140 }
141 }
142 Ok(())
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use dig_identity::{
149 bls::SecretKey, derive_identity_sk, master_secret_key_from_seed, public_key_bytes,
150 sign_message,
151 };
152
153 fn node_bls_sk(label: &str) -> SecretKey {
154 let seed: [u8; 32] = Sha256::digest(label.as_bytes()).into();
155 derive_identity_sk(&master_secret_key_from_seed(&seed))
156 }
157
158 fn signed_descriptor(bls_sk: &SecretKey, spki: &[u8], did: Option<String>) -> RelayDescriptor {
160 let peer_id_spki_hash: [u8; 32] = Sha256::digest(spki).into();
161 let mut d = RelayDescriptor {
162 peer_id_spki_hash,
163 bls_pub: public_key_bytes(bls_sk),
164 addresses: vec![
165 "[::1]:9450".parse().unwrap(),
166 "127.0.0.1:9450".parse().unwrap(),
167 ],
168 network_id: "DIG_MAINNET".to_string(),
169 did,
170 signature: [0u8; 96],
171 };
172 d.signature = sign_message(bls_sk, &d.signing_bytes());
173 d
174 }
175
176 #[test]
177 fn valid_descriptor_verifies() {
178 let sk = node_bls_sk("relay-desc/valid");
179 let spki = b"fake-spki-der-bytes";
180 let d = signed_descriptor(&sk, spki, None);
181 assert!(verify_relay_descriptor(&d, Some(spki), None).is_ok());
182 assert!(verify_relay_descriptor(&d, None, None).is_ok());
184 }
185
186 #[test]
187 fn tampered_signature_rejected() {
188 let sk = node_bls_sk("relay-desc/tamper");
189 let spki = b"fake-spki";
190 let mut d = signed_descriptor(&sk, spki, None);
191 d.addresses.push("10.0.0.1:1".parse().unwrap()); assert_eq!(
193 verify_relay_descriptor(&d, Some(spki), None),
194 Err(RelayDescriptorError::BadSignature)
195 );
196 }
197
198 #[test]
199 fn peer_id_spki_mismatch_rejected() {
200 let sk = node_bls_sk("relay-desc/peerid");
201 let d = signed_descriptor(&sk, b"spki-A", None);
202 assert_eq!(
204 verify_relay_descriptor(&d, Some(b"spki-B"), None),
205 Err(RelayDescriptorError::PeerIdMismatch)
206 );
207 }
208
209 #[test]
210 fn substituted_bls_pubkey_rejected() {
211 let sk = node_bls_sk("relay-desc/sub");
213 let attacker = node_bls_sk("relay-desc/sub-attacker");
214 let spki = b"spki";
215 let mut d = signed_descriptor(&sk, spki, None);
216 d.bls_pub = public_key_bytes(&attacker);
217 assert_eq!(
218 verify_relay_descriptor(&d, Some(spki), None),
219 Err(RelayDescriptorError::BadSignature)
220 );
221 }
222
223 #[test]
224 fn bad_g1_point_rejected() {
225 let sk = node_bls_sk("relay-desc/g1");
226 let spki = b"spki";
227 let mut d = signed_descriptor(&sk, spki, None);
228 d.bls_pub = [0xFFu8; 48];
229 assert_eq!(
230 verify_relay_descriptor(&d, Some(spki), None),
231 Err(RelayDescriptorError::BadBlsPubkey)
232 );
233 }
234
235 #[test]
236 fn did_resolving_to_other_key_rejected() {
237 let sk = node_bls_sk("relay-desc/did");
238 let spki = b"spki";
239 let d = signed_descriptor(&sk, spki, Some("did:dig:relayX".to_string()));
240 let other = public_key_bytes(&node_bls_sk("relay-desc/did-other"));
241 let resolver = |_did: &str| -> Option<[u8; 48]> { Some(other) };
242 assert_eq!(
243 verify_relay_descriptor(&d, Some(spki), Some(&resolver)),
244 Err(RelayDescriptorError::DidMismatch)
245 );
246 }
247
248 #[test]
249 fn did_resolving_to_same_key_accepted() {
250 let sk = node_bls_sk("relay-desc/did-ok");
251 let spki = b"spki";
252 let d = signed_descriptor(&sk, spki, Some("did:dig:relayY".to_string()));
253 let pk = public_key_bytes(&sk);
254 let resolver = |_did: &str| -> Option<[u8; 48]> { Some(pk) };
255 assert!(verify_relay_descriptor(&d, Some(spki), Some(&resolver)).is_ok());
256 }
257
258 #[test]
259 fn unresolvable_did_tolerated() {
260 let sk = node_bls_sk("relay-desc/did-none");
261 let spki = b"spki";
262 let d = signed_descriptor(&sk, spki, Some("did:dig:unknown".to_string()));
263 let resolver = |_did: &str| -> Option<[u8; 48]> { None };
264 assert!(verify_relay_descriptor(&d, Some(spki), Some(&resolver)).is_ok());
265 }
266}