sphinx_packet/header/
keys.rs

1// Copyright 2020 Nym Technologies SA
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::constants::INTEGRITY_MAC_KEY_SIZE;
16use crate::crypto::STREAM_CIPHER_KEY_SIZE;
17use crate::header::shared_secret::{expand_shared_secret, ExpandedSharedSecret};
18use crate::route::Node;
19use curve25519_dalek::Scalar;
20use x25519_dalek::{PublicKey, StaticSecret};
21
22pub type StreamCipherKey = [u8; STREAM_CIPHER_KEY_SIZE];
23pub type HeaderIntegrityMacKey = [u8; INTEGRITY_MAC_KEY_SIZE];
24
25pub struct KeyMaterial {
26    pub initial_shared_secret: PublicKey,
27    pub expanded_shared_secrets: Vec<ExpandedSharedSecret>,
28}
29
30impl KeyMaterial {
31    // derive shared keys, group elements, blinding factors
32    pub fn derive(route: &[Node], initial_secret: &StaticSecret) -> Self {
33        let initial_shared_secret = PublicKey::from(initial_secret);
34
35        let mut expanded_shared_secrets = Vec::new();
36        let mut blinding_factors = Vec::new();
37
38        for (i, node) in route.iter().enumerate() {
39            let mut acc = node.pub_key;
40
41            // avoid having to clone the initial secret by just chaining iterators
42            for blinding_factor in std::iter::once(initial_secret).chain(&blinding_factors) {
43                let shared_secret = blinding_factor.diffie_hellman(&acc);
44                acc = PublicKey::from(shared_secret.to_bytes());
45            }
46
47            let expanded_shared_secret = expand_shared_secret(acc.as_bytes());
48
49            if i != route.len() - 1 {
50                blinding_factors.push(expanded_shared_secret.blinding_factor());
51            }
52            expanded_shared_secrets.push(expanded_shared_secret);
53        }
54
55        Self {
56            initial_shared_secret,
57            expanded_shared_secrets,
58        }
59    }
60
61    #[deprecated]
62    pub fn derive_legacy(route: &[Node], initial_secret: &StaticSecret) -> Self {
63        let initial_secret_scalar = Scalar::from_bytes_mod_order(initial_secret.to_bytes());
64
65        let initial_shared_secret =
66            curve25519_dalek::MontgomeryPoint::mul_base(&initial_secret_scalar);
67
68        let mut expanded_shared_secrets = Vec::with_capacity(route.len());
69
70        let mut accumulator = initial_secret_scalar;
71        for (i, node) in route.iter().enumerate() {
72            // pub^{a * b * ...}
73            let pk_mt = curve25519_dalek::MontgomeryPoint(node.pub_key.to_bytes());
74            let shared_key = pk_mt * accumulator;
75
76            let expanded_shared_secret = expand_shared_secret(shared_key.as_bytes());
77
78            // it's not the last iteration
79            if i != route.len() + 1 {
80                // convert the blinding factor to a raw scalar and perform multiplication without
81                // any reduction (UNSAFE since we're not in ristretto)
82                let blinding_factor_scalar =
83                    &Scalar::from_bytes_mod_order(*expanded_shared_secret.blinding_factor_bytes());
84
85                accumulator *= blinding_factor_scalar;
86            }
87
88            expanded_shared_secrets.push(expanded_shared_secret);
89        }
90        Self {
91            initial_shared_secret: PublicKey::from(initial_shared_secret.0),
92            expanded_shared_secrets,
93        }
94    }
95}
96
97#[cfg(test)]
98mod deriving_key_material {
99    use super::*;
100    use crate::route::Node;
101
102    #[cfg(test)]
103    mod with_an_empty_route {
104        use super::*;
105
106        #[test]
107        fn it_returns_no_routing_keys() {
108            let empty_route: Vec<Node> = vec![];
109            let initial_secret = StaticSecret::random();
110            let key_material = KeyMaterial::derive(&empty_route, &initial_secret);
111            assert_eq!(0, key_material.expanded_shared_secrets.len());
112            assert_eq!(
113                PublicKey::from(&initial_secret).as_bytes(),
114                key_material.initial_shared_secret.as_bytes()
115            )
116        }
117    }
118
119    #[cfg(test)]
120    mod for_a_route_with_3_forward_hops {
121        use super::*;
122        use crate::test_utils::random_node;
123
124        fn setup() -> (Vec<Node>, StaticSecret, KeyMaterial) {
125            let route: Vec<Node> = vec![random_node(), random_node(), random_node()];
126            let initial_secret = StaticSecret::random();
127            let key_material = KeyMaterial::derive(&route, &initial_secret);
128            (route, initial_secret, key_material)
129        }
130
131        #[test]
132        fn it_returns_number_of_shared_keys_equal_to_length_of_the_route() {
133            let (_, _, key_material) = setup();
134            assert_eq!(3, key_material.expanded_shared_secrets.len());
135        }
136
137        #[test]
138        fn it_returns_correctly_inited_shared_secret() {
139            let (_, initial_secret, key_material) = setup();
140            assert_eq!(
141                PublicKey::from(&initial_secret).as_bytes(),
142                key_material.initial_shared_secret.as_bytes()
143            );
144        }
145
146        #[test]
147        fn it_generates_correct_expanded_shared_secret() {
148            let (route, initial_secret, key_material) = setup();
149            // The accumulator is the key to our blinding factors working.
150            // If the accumulator value isn't incremented correctly, we risk passing an
151            // incorrectly blinded shared key through the mixnet in the (unencrypted)
152            // Sphinx packet header. So this test ensures that the accumulator gets incremented
153            // properly on each run through the loop.
154            let mut expected_accumulator = vec![initial_secret];
155            for (i, node) in route.iter().enumerate() {
156                let expected_shared_key =
157                    expected_accumulator
158                        .iter()
159                        .fold(node.pub_key, |acc, blinding_factor| {
160                            PublicKey::from(blinding_factor.diffie_hellman(&acc).to_bytes())
161                        });
162
163                let expected_expanded_ss = expand_shared_secret(expected_shared_key.as_bytes());
164
165                expected_accumulator.push(expected_expanded_ss.blinding_factor());
166                assert_eq!(
167                    expected_expanded_ss,
168                    key_material.expanded_shared_secrets[i]
169                )
170            }
171        }
172    }
173}