hopr_crypto_packet/
expand.rs1use hopr_types::crypto::{
2 errors::Result,
3 types::{ExpandedOffchainPublicKey, OffchainPublicKey},
4};
5
6use crate::{HoprSphinxSuite, sphinx::prelude::SphinxSuite};
7
8pub trait KeyExpander: Sync {
18 fn expand(&self, key: &OffchainPublicKey) -> Result<ExpandedOffchainPublicKey>;
20
21 fn expand_path(&self, keys: &[OffchainPublicKey]) -> Result<Vec<ExpandedOffchainPublicKey>> {
23 keys.iter().map(|key| self.expand(key)).collect()
24 }
25}
26
27#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
32pub struct DirectKeyExpander;
33
34impl KeyExpander for DirectKeyExpander {
35 fn expand(&self, key: &OffchainPublicKey) -> Result<ExpandedOffchainPublicKey> {
36 HoprSphinxSuite::expand_public(key)
37 }
38}
39
40impl<T: KeyExpander + ?Sized> KeyExpander for &T {
41 fn expand(&self, key: &OffchainPublicKey) -> Result<ExpandedOffchainPublicKey> {
42 (**self).expand(key)
43 }
44
45 fn expand_path(&self, keys: &[OffchainPublicKey]) -> Result<Vec<ExpandedOffchainPublicKey>> {
47 (**self).expand_path(keys)
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use std::sync::atomic::{AtomicUsize, Ordering};
54
55 use anyhow::Context;
56 use hopr_types::crypto::keypairs::{Keypair, OffchainKeypair};
57
58 use super::*;
59
60 #[derive(Default)]
62 struct CountingExpander {
63 expand_calls: AtomicUsize,
64 expand_path_calls: AtomicUsize,
65 }
66
67 impl KeyExpander for CountingExpander {
68 fn expand(&self, key: &OffchainPublicKey) -> Result<ExpandedOffchainPublicKey> {
69 self.expand_calls.fetch_add(1, Ordering::Relaxed);
70 DirectKeyExpander.expand(key)
71 }
72
73 fn expand_path(&self, keys: &[OffchainPublicKey]) -> Result<Vec<ExpandedOffchainPublicKey>> {
74 self.expand_path_calls.fetch_add(1, Ordering::Relaxed);
75 keys.iter().map(|key| DirectKeyExpander.expand(key)).collect()
76 }
77 }
78
79 fn expand_with<K: KeyExpander>(expander: K, keys: &[OffchainPublicKey]) -> Result<Vec<ExpandedOffchainPublicKey>> {
81 expander.expand_path(keys)
82 }
83
84 fn path(len: usize) -> Vec<OffchainPublicKey> {
85 (0..len).map(|_| *OffchainKeypair::random().public()).collect()
86 }
87
88 #[test]
89 fn expanding_a_path_through_a_reference_reaches_the_override() -> anyhow::Result<()> {
90 let expander = CountingExpander::default();
91 let keys = path(3);
92
93 let expanded = expand_with(&expander, &keys).context("expanding the path through a reference")?;
94
95 assert_eq!(expanded.len(), 3);
96 assert_eq!(expander.expand_path_calls.load(Ordering::Relaxed), 1);
97 assert_eq!(
98 expander.expand_calls.load(Ordering::Relaxed),
99 0,
100 "the override must not fall back to per-key calls"
101 );
102 Ok(())
103 }
104
105 #[test]
106 fn the_default_expand_path_expands_every_key_in_order() -> anyhow::Result<()> {
107 let keys = path(4);
108
109 let expanded = DirectKeyExpander.expand_path(&keys).context("expanding the path")?;
110
111 for (key, expanded) in keys.iter().zip(&expanded) {
112 assert_eq!(&OffchainPublicKey::from(expanded), key);
113 }
114 Ok(())
115 }
116}