1#[cfg(rings_native)]
2use std::sync::atomic::AtomicU64;
3#[cfg(rings_native)]
4use std::sync::atomic::Ordering;
5
6use bytes::Bytes;
7use rings_core::dht::Did;
8use rings_core::ecc::elgamal::impls::secp256k1::encrypt_aead_with_rng;
9use rings_core::ecc::elgamal::impls::secp256k1::AeadCiphertext;
10use rings_core::ecc::PublicKey;
11use rings_core::message::MessageVerification;
12use rings_core::session::SessionSk;
13use rings_core::utils::get_epoch_ms;
14use serde::Serialize;
15
16use super::cell::seal_message;
17use super::codec::OnionWireMessage;
18use super::OnionAuthenticatedPayload;
19use super::OnionBackwardFrame;
20use super::OnionBackwardNonce;
21use super::OnionBackwardPath;
22use super::OnionBackwardSequence;
23use super::OnionCircuitId;
24use super::OnionCircuitPayload;
25use super::OnionClientReturn;
26use super::OnionForwardFrame;
27use super::OnionForwardLayer;
28use super::OnionForwardNonce;
29use super::OnionForwardSequence;
30use super::OnionLink;
31use super::OnionLinkSender;
32use super::OnionReturnId;
33use super::OnionVerifiedPayload;
34use super::ONION_AEAD_NAMESPACE;
35use super::ONION_FORWARD_EXPIRY_QUANTUM_MS;
36use super::ONION_FORWARD_PAYLOAD_TTL_MS;
37use crate::error::Error;
38use crate::error::Result;
39use crate::extension::ext::Scope;
40use crate::onion::OnionExitDescriptor;
41use crate::onion::OnionRoute;
42use crate::onion::OnionRouteError;
43use crate::onion::OnionRouteHop;
44#[cfg(rings_native)]
45use crate::onion::OnionServiceName;
46
47pub fn encode_initial_forward(
53 client: OnionClientReturn,
54 route: &OnionRoute,
55 circuit_id: OnionCircuitId,
56 payload: OnionCircuitPayload,
57) -> Result<(Did, Bytes)> {
58 encode_initial_forward_link(client, route, circuit_id, payload)
59 .map(|(link, payload)| (link.peer, payload))
60}
61
62pub(crate) fn encode_initial_forward_link(
64 client: OnionClientReturn,
65 route: &OnionRoute,
66 circuit_id: OnionCircuitId,
67 payload: OnionCircuitPayload,
68) -> Result<(OnionLink, Bytes)> {
69 validate_route_payload_service(route, &payload)?;
70 let first = route_first_link(route)?;
71 let layer = build_forward_layers(
72 client,
73 route.encryption_hops(),
74 circuit_id,
75 OnionForwardSequence::FIRST,
76 payload,
77 )?;
78 let frame = OnionForwardFrame { circuit_id, layer };
79 seal_message(&OnionWireMessage::Forward(frame), first.recipient, None)
80 .map(|payload| (first, payload))
81}
82
83#[cfg(rings_native)]
89#[derive(Debug)]
90pub(crate) struct OnionCircuitPath {
91 route: OnionRoute,
92 first_circuit_id: OnionCircuitId,
93 edge_circuit_ids: Vec<OnionCircuitId>,
94 next_forward_sequence: AtomicU64,
95}
96
97#[cfg(rings_native)]
98impl OnionCircuitPath {
99 pub(crate) fn new(route: OnionRoute, first_circuit_id: OnionCircuitId) -> Result<Self> {
101 let edge_circuit_ids = edge_circuit_ids(route.encryption_hops().len(), first_circuit_id)?;
102 Ok(Self {
103 route,
104 first_circuit_id,
105 edge_circuit_ids,
106 next_forward_sequence: AtomicU64::new(0),
107 })
108 }
109
110 pub(crate) fn encode_forward(
112 &self,
113 client: OnionClientReturn,
114 payload: OnionCircuitPayload,
115 ) -> Result<(OnionLink, Bytes)> {
116 validate_route_payload_service(&self.route, &payload)?;
117 let sequence = self
118 .next_forward_sequence
119 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
120 value.checked_add(1)
121 })
122 .map(OnionForwardSequence::new)
123 .map_err(|_| Error::OnionRouteError(OnionRouteError::SequenceExhausted))?;
124 let first = route_first_link(&self.route)?;
125 let layer = build_forward_layers_with_ids(
126 client,
127 self.route.encryption_hops(),
128 self.edge_circuit_ids.as_slice(),
129 sequence,
130 payload,
131 )?;
132 let frame = OnionForwardFrame {
133 circuit_id: self.first_circuit_id,
134 layer,
135 };
136 seal_message(&OnionWireMessage::Forward(frame), first.recipient, None)
137 .map(|payload| (first, payload))
138 }
139
140 pub(crate) fn service_name(&self) -> &OnionServiceName {
142 self.route.service_name()
143 }
144}
145
146pub fn route_first_hop(route: &OnionRoute) -> Result<Did> {
151 route_first_link(route).map(|link| link.peer)
152}
153
154pub(crate) fn route_first_link(route: &OnionRoute) -> Result<OnionLink> {
156 route
157 .encryption_hops()
158 .first()
159 .map(|hop| OnionLink::new(hop.did, hop.session_public_key))
160 .ok_or_else(|| Error::OnionRouteError(OnionRouteError::RouteHasNoHops))
161}
162
163pub async fn send_backward(
165 link_sender: &OnionLinkSender,
166 scope: &Scope,
167 signer: &SessionSk,
168 path: OnionBackwardPath,
169 sequence: OnionBackwardSequence,
170 payload: OnionCircuitPayload,
171) -> Result<()> {
172 let frame = OnionBackwardFrame {
173 circuit_id: path.circuit_id,
174 payload: encrypt_client_payload_at_sequence(
175 path.client.return_id,
176 sequence,
177 payload,
178 path.client.session_public_key,
179 signer,
180 )?,
181 };
182 let payload = seal_message(
183 &OnionWireMessage::Backward(frame),
184 path.return_session_public_key,
185 None,
186 )?;
187 link_sender
188 .send_sealed(
189 scope.clone(),
190 OnionLink::new(path.return_peer, path.return_session_public_key),
191 payload,
192 )
193 .await
194}
195
196fn build_forward_layers(
197 client: OnionClientReturn,
198 hops: &[OnionRouteHop],
199 first_circuit_id: OnionCircuitId,
200 sequence: OnionForwardSequence,
201 payload: OnionCircuitPayload,
202) -> Result<AeadCiphertext> {
203 let circuit_ids = edge_circuit_ids(hops.len(), first_circuit_id)?;
204 build_forward_layers_with_ids(client, hops, circuit_ids.as_slice(), sequence, payload)
205}
206
207fn build_forward_layers_with_ids(
208 client: OnionClientReturn,
209 hops: &[OnionRouteHop],
210 circuit_ids: &[OnionCircuitId],
211 sequence: OnionForwardSequence,
212 payload: OnionCircuitPayload,
213) -> Result<AeadCiphertext> {
214 let Some(exit) = hops.last().copied() else {
215 return Err(Error::OnionRouteError(OnionRouteError::RouteHasNoHops));
216 };
217 if hops.len() != circuit_ids.len() {
218 return Err(Error::OnionRouteError(
219 OnionRouteError::CircuitPathLengthMismatch {
220 hop_count: hops.len(),
221 edge_count: circuit_ids.len(),
222 },
223 ));
224 }
225 let expires_at_ms = quantized_forward_expiry(get_epoch_ms());
226 let exit_circuit_id = *circuit_ids
227 .last()
228 .ok_or_else(|| Error::OnionRouteError(OnionRouteError::RouteHasNoHops))?;
229 let exit_return_session_public_key = hops
230 .iter()
231 .rev()
232 .nth(1)
233 .map_or(client.session_public_key, |hop| hop.session_public_key);
234 let mut layer = encrypt_forward_layer(
235 exit_circuit_id,
236 OnionForwardLayer::Exit {
237 client,
238 return_session_public_key: exit_return_session_public_key,
239 expires_at_ms,
240 forward_nonce: OnionForwardNonce::random(),
241 forward_sequence: sequence,
242 payload,
243 },
244 exit.session_public_key,
245 )?;
246
247 for (index, hop) in hops.iter().copied().enumerate().rev().skip(1) {
248 let next_index = index.saturating_add(1);
249 let next_hop = hops
250 .get(next_index)
251 .copied()
252 .ok_or_else(|| Error::OnionRouteError(OnionRouteError::MissingNextHop))?;
253 let current_circuit_id = circuit_ids
254 .get(index)
255 .copied()
256 .ok_or_else(|| Error::OnionRouteError(OnionRouteError::MissingNextHop))?;
257 let next_circuit_id = circuit_ids
258 .get(next_index)
259 .copied()
260 .ok_or_else(|| Error::OnionRouteError(OnionRouteError::MissingNextHop))?;
261 layer = encrypt_forward_layer(
262 current_circuit_id,
263 OnionForwardLayer::Relay {
264 next_hop: next_hop.did,
265 next_circuit_id,
266 next_session_public_key: next_hop.session_public_key,
267 return_session_public_key: if index == 0 {
268 client.session_public_key
269 } else {
270 hops.get(index.saturating_sub(1))
271 .map(|previous| previous.session_public_key)
272 .ok_or_else(|| Error::OnionRouteError(OnionRouteError::MissingNextHop))?
273 },
274 inner: layer,
275 },
276 hop.session_public_key,
277 )?;
278 }
279 Ok(layer)
280}
281
282fn quantized_forward_expiry(now_ms: u128) -> u128 {
288 let deadline = now_ms.saturating_add(ONION_FORWARD_PAYLOAD_TTL_MS);
289 deadline
290 .saturating_add(ONION_FORWARD_EXPIRY_QUANTUM_MS - 1)
291 .checked_div(ONION_FORWARD_EXPIRY_QUANTUM_MS)
292 .and_then(|bucket| bucket.checked_mul(ONION_FORWARD_EXPIRY_QUANTUM_MS))
293 .unwrap_or(u128::MAX)
294}
295
296fn edge_circuit_ids(
297 hop_count: usize,
298 first_circuit_id: OnionCircuitId,
299) -> Result<Vec<OnionCircuitId>> {
300 edge_circuit_ids_with(hop_count, first_circuit_id, OnionCircuitId::random)
301}
302
303pub(super) fn edge_circuit_ids_with(
304 hop_count: usize,
305 first_circuit_id: OnionCircuitId,
306 mut next_id: impl FnMut() -> OnionCircuitId,
307) -> Result<Vec<OnionCircuitId>> {
308 const MAX_ALLOCATION_ATTEMPTS_PER_EDGE: usize = 16;
309 if hop_count == 0 || hop_count > usize::from(super::MAX_ONION_CIRCUIT_HOPS) {
310 return Err(Error::OnionRouteError(
311 OnionRouteError::HopCountOutOfBounds {
312 hop_count,
313 max_hops: super::MAX_ONION_CIRCUIT_HOPS,
314 },
315 ));
316 }
317 let mut ids = Vec::with_capacity(hop_count);
318 ids.push(first_circuit_id);
319 while ids.len() < hop_count {
320 let next = (0..MAX_ALLOCATION_ATTEMPTS_PER_EDGE)
321 .map(|_| next_id())
322 .find(|candidate| !ids.contains(candidate))
323 .ok_or_else(|| Error::OnionRouteError(OnionRouteError::CircuitIdAllocationFailed))?;
324 ids.push(next);
325 }
326 Ok(ids)
327}
328
329fn encrypt_forward_layer(
330 circuit_id: OnionCircuitId,
331 layer: OnionForwardLayer,
332 recipient: PublicKey<33>,
333) -> Result<AeadCiphertext> {
334 let plaintext = rings_codec::serialize(&layer).map_err(|_| Error::EncodeError)?;
335 let aad = onion_aead_context(OnionAeadDirection::Forward, circuit_id)?;
336 let mut rng = rand::thread_rng();
337 encrypt_aead_with_rng(&plaintext, &aad, recipient, &mut rng).map_err(Error::CoreError)
338}
339
340pub(super) fn decrypt_forward_layer(
341 session_sk: &SessionSk,
342 circuit_id: OnionCircuitId,
343 sealed: &AeadCiphertext,
344) -> Result<OnionForwardLayer> {
345 let aad = onion_aead_context(OnionAeadDirection::Forward, circuit_id)?;
346 let plaintext = session_sk
347 .decrypt_elgamal_aead(sealed, &aad)
348 .map_err(Error::CoreError)?;
349 rings_codec::deserialize(&plaintext).map_err(|_| Error::DecodeError)
350}
351
352#[cfg(test)]
353pub(super) fn encrypt_client_payload(
354 return_id: OnionReturnId,
355 payload: OnionCircuitPayload,
356 recipient: PublicKey<33>,
357 signer: &SessionSk,
358) -> Result<AeadCiphertext> {
359 encrypt_client_payload_at_sequence(
360 return_id,
361 OnionBackwardSequence::FIRST,
362 payload,
363 recipient,
364 signer,
365 )
366}
367
368pub(super) fn encrypt_client_payload_at_sequence(
369 return_id: OnionReturnId,
370 sequence: OnionBackwardSequence,
371 payload: OnionCircuitPayload,
372 recipient: PublicKey<33>,
373 signer: &SessionSk,
374) -> Result<AeadCiphertext> {
375 let authenticated =
376 OnionAuthenticatedPayload::new_signed_at_sequence(return_id, sequence, payload, signer)?;
377 let plaintext = rings_codec::serialize(&authenticated).map_err(|_| Error::EncodeError)?;
378 let aad = backward_aead_context()?;
382 let mut rng = rand::thread_rng();
383 encrypt_aead_with_rng(&plaintext, &aad, recipient, &mut rng).map_err(Error::CoreError)
384}
385
386pub(super) fn decrypt_client_payload(
387 session_sk: &SessionSk,
388 sealed: &AeadCiphertext,
389) -> Result<OnionAuthenticatedPayload> {
390 let aad = backward_aead_context()?;
391 let plaintext = session_sk
392 .decrypt_elgamal_aead(sealed, &aad)
393 .map_err(Error::CoreError)?;
394 rings_codec::deserialize(&plaintext).map_err(|_| Error::DecodeError)
395}
396
397impl OnionAuthenticatedPayload {
398 pub fn new_signed(
400 return_id: OnionReturnId,
401 payload: OnionCircuitPayload,
402 signer: &SessionSk,
403 ) -> Result<Self> {
404 Self::new_signed_at_sequence(return_id, OnionBackwardSequence::FIRST, payload, signer)
405 }
406
407 pub fn new_signed_at_sequence(
409 return_id: OnionReturnId,
410 sequence: OnionBackwardSequence,
411 payload: OnionCircuitPayload,
412 signer: &SessionSk,
413 ) -> Result<Self> {
414 let nonce = OnionBackwardNonce::random();
415 let authentication = MessageVerification::new(
416 &backward_payload_authentication_data(
417 return_id,
418 nonce,
419 sequence,
420 signer.session_public_key(),
421 &payload,
422 )?,
423 signer,
424 )
425 .map_err(Error::CoreError)?;
426 Ok(Self {
427 return_id,
428 nonce,
429 sequence,
430 authentication,
431 payload,
432 })
433 }
434
435 pub fn into_verified_payload(
443 self,
444 return_id: OnionReturnId,
445 expected_exit: &OnionExitDescriptor,
446 ) -> Result<OnionVerifiedPayload> {
447 if self.return_id != return_id {
448 return Err(Error::OnionRouteError(
449 OnionRouteError::BackwardReturnIdMismatch,
450 ));
451 }
452 let signer = &self.authentication.session;
453 if signer.account_did() != expected_exit.did {
454 return Err(Error::OnionRouteError(
455 OnionRouteError::BackwardSignerMismatch,
456 ));
457 }
458 let public_key = signer
459 .account_verification_pubkey()
460 .map_err(Error::CoreError)?;
461 if public_key != expected_exit.public_key {
462 return Err(Error::OnionRouteError(
463 OnionRouteError::BackwardAccountKeyMismatch,
464 ));
465 }
466 if signer.session_did() != Did::from(expected_exit.session_public_key.address()) {
467 return Err(Error::OnionRouteError(
468 OnionRouteError::BackwardSessionKeyMismatch,
469 ));
470 }
471 let data = backward_payload_authentication_data(
472 return_id,
473 self.nonce,
474 self.sequence,
475 expected_exit.session_public_key,
476 &self.payload,
477 )?;
478 if !self.authentication.verify_unexpired(&data) {
479 return Err(Error::OnionRouteError(
480 OnionRouteError::InvalidBackwardSignature,
481 ));
482 }
483 Ok(OnionVerifiedPayload {
484 return_id: self.return_id,
485 nonce: self.nonce,
486 sequence: self.sequence,
487 payload: self.payload,
488 })
489 }
490}
491
492#[derive(Serialize)]
493struct OnionAeadContext {
494 namespace: &'static str,
495 direction: OnionAeadDirection,
496 circuit_id: OnionCircuitId,
497}
498
499#[derive(Serialize)]
500struct OnionBackwardAuthenticationData<'a> {
501 namespace: &'static str,
502 direction: OnionAeadDirection,
503 return_id: OnionReturnId,
504 nonce: OnionBackwardNonce,
505 sequence: OnionBackwardSequence,
506 exit_session_public_key: PublicKey<33>,
507 payload: &'a OnionCircuitPayload,
508}
509
510#[derive(Clone, Copy, Serialize)]
511pub(super) enum OnionAeadDirection {
512 Forward,
513 Backward,
514}
515
516fn onion_aead_context(
517 direction: OnionAeadDirection,
518 circuit_id: OnionCircuitId,
519) -> Result<Vec<u8>> {
520 rings_codec::serialize(&OnionAeadContext {
521 namespace: ONION_AEAD_NAMESPACE,
522 direction,
523 circuit_id,
524 })
525 .map_err(|_| Error::EncodeError)
526}
527
528fn backward_aead_context() -> Result<Vec<u8>> {
529 rings_codec::serialize(&OnionAeadDirectionContext {
530 namespace: ONION_AEAD_NAMESPACE,
531 direction: OnionAeadDirection::Backward,
532 })
533 .map_err(|_| Error::EncodeError)
534}
535
536fn backward_payload_authentication_data(
537 return_id: OnionReturnId,
538 nonce: OnionBackwardNonce,
539 sequence: OnionBackwardSequence,
540 exit_session_public_key: PublicKey<33>,
541 payload: &OnionCircuitPayload,
542) -> Result<Vec<u8>> {
543 rings_codec::serialize(&OnionBackwardAuthenticationData {
544 namespace: ONION_AEAD_NAMESPACE,
545 direction: OnionAeadDirection::Backward,
546 return_id,
547 nonce,
548 sequence,
549 exit_session_public_key,
550 payload,
551 })
552 .map_err(|_| Error::EncodeError)
553}
554
555#[derive(Serialize)]
556struct OnionAeadDirectionContext {
557 namespace: &'static str,
558 direction: OnionAeadDirection,
559}
560
561fn validate_route_payload_service(route: &OnionRoute, payload: &OnionCircuitPayload) -> Result<()> {
562 if !payload.is_service(route.service_name()) {
563 return Err(Error::OnionRouteError(
564 OnionRouteError::PayloadServiceMismatch {
565 payload_service: payload.service().to_string(),
566 route_service: route.service().to_string(),
567 },
568 ));
569 }
570 Ok(())
571}