Cargo.lock
Cargo.lock
This file has not been changed.
Cargo.toml
Cargo.toml
This file has not been changed.
sachy-crypto/Cargo.toml
sachy-crypto/Cargo.toml
This file has not been changed.
sachy-crypto/README.md
sachy-crypto/README.md
This file has not been changed.
+76
-65
sachy-crypto/src/lib.rs
+76
-65
sachy-crypto/src/lib.rs
···
3
3
use core::ops::AddAssign;
4
4
5
5
use chacha20poly1305::{
6
-
AeadCore, AeadInOut, ChaCha20Poly1305, KeyInit,
7
-
aead::{self, Buffer, array::Array, common::array::typenum::Unsigned},
6
+
AeadInOut, ChaCha20Poly1305, KeyInit,
7
+
aead::{self, Buffer},
8
8
};
9
9
use dhkem::{
10
-
Encapsulate, Generate, Kem, Secp256k1DecapsulationKey, Secp256k1EncapsulationKey, Secp256k1Kem,
10
+
Encapsulate, Kem, Secp256k1DecapsulationKey, Secp256k1EncapsulationKey, Secp256k1Kem,
11
11
TryDecapsulate,
12
12
kem::{Ciphertext, SharedKey},
13
13
};
···
41
41
42
42
pub struct EncapsulatedPublicKey(Secp256k1EncapsulationKey);
43
43
44
+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
45
+
pub enum Role {
46
+
Client,
47
+
Server,
48
+
}
49
+
44
50
impl EncapsulatedPublicKey {
45
51
pub fn serialize(&self) -> Sec1Point {
46
52
self.0.to_sec1_point(true)
···
70
76
.try_decapsulate_slice(ciphertext)
71
77
.map_err(|_| ProtoError)?;
72
78
73
-
TransportState::init(psk, shared)
79
+
TransportState::init(psk, shared, Role::Client)
74
80
}
75
81
}
76
82
···
86
92
}
87
93
88
94
pub fn finish(self, psk: &[u8; 32]) -> Result<TransportState, ProtoError> {
89
-
TransportState::init(psk, self.0)
95
+
TransportState::init(psk, self.0, Role::Server)
90
96
}
91
97
}
92
98
···
104
110
/// Value to use when incrementing the Transport counter (i.e. one)
105
111
const COUNTER_INCR: Self::Counter;
106
112
113
+
/// Maximum number of messages allowed to be sent via Transport
114
+
const COUNTER_MAX: Self::Counter;
115
+
107
116
/// Encrypt an AEAD message in-place at the given position in the Transport.
108
117
fn encrypt_in_place(
109
118
&self,
···
130
139
pub fn encrypt(&mut self, msg: &mut alloc::vec::Vec<u8>) -> Result<(), ProtoError> {
131
140
let counter = self.counter.to_be_bytes();
132
141
133
-
// Nonce is randomised to act as a OTP when mixed with the counter state
134
-
let mut epstein = Array::generate();
135
-
136
-
self.transport.encrypt_in_place(&epstein, &counter, msg)?;
137
-
138
-
msg.extend(Self::mix_nonce(&mut epstein, &counter));
142
+
self.transport.encrypt_in_place(
143
+
&self.transport.mix_nonce(&counter, true),
144
+
&counter,
145
+
msg,
146
+
)?;
139
147
140
148
self.counter = self.counter.wrapping_add(TransportState::COUNTER_INCR);
141
149
142
150
// If we wrapped around and equal the finish value, we have maxed out the amount of
143
151
// messages we can send.
144
-
if self.counter.ct_eq(&self.transport.finish).into() {
152
+
if self.counter.ct_eq(&TransportState::COUNTER_MAX).into() {
145
153
Err(ProtoError)
146
154
} else {
147
155
Ok(())
148
156
}
149
157
}
150
-
151
-
fn mix_nonce<'a>(
152
-
epstein: &'a mut aead::Nonce<ChaCha20Poly1305>,
153
-
position: &'a [u8; 8],
154
-
) -> &'a aead::Nonce<ChaCha20Poly1305> {
155
-
epstein[..position.len()]
156
-
.iter_mut()
157
-
.zip(position)
158
-
.for_each(|(byte, count)| *byte ^= *count);
159
-
160
-
epstein
161
-
}
162
158
}
163
159
164
160
pub struct ReceivingState<'a> {
···
170
166
pub fn decrypt(&mut self, msg: &mut alloc::vec::Vec<u8>) -> Result<(), ProtoError> {
171
167
let counter = self.counter.to_be_bytes();
172
168
173
-
// Extract the nonce from the payload as this does not need to be decrypted
174
-
let epstein = Self::extract_nonce(&counter, msg)?;
175
-
176
-
self.transport.decrypt_in_place(&epstein, &counter, msg)?;
169
+
self.transport.decrypt_in_place(
170
+
&self.transport.mix_nonce(&counter, false),
171
+
&counter,
172
+
msg,
173
+
)?;
177
174
178
175
self.counter = self.counter.wrapping_add(TransportState::COUNTER_INCR);
179
176
180
177
// If we wrapped around and equal the finish value, we have maxed out the amount of
181
178
// messages we can send.
182
-
if self.counter.ct_eq(&self.transport.finish).into() {
179
+
if self.counter.ct_eq(&TransportState::COUNTER_MAX).into() {
183
180
Err(ProtoError)
184
181
} else {
185
182
Ok(())
186
183
}
187
184
}
188
-
189
-
fn extract_nonce(
190
-
position: &[u8; 8],
191
-
msg: &mut alloc::vec::Vec<u8>,
192
-
) -> Result<aead::Nonce<ChaCha20Poly1305>, ProtoError> {
193
-
let index = msg
194
-
.len()
195
-
.checked_sub(<ChaCha20Poly1305 as AeadCore>::NonceSize::to_usize())
196
-
.ok_or(ProtoError)?;
197
-
198
-
let mut epstein = Array::try_from(&msg[index..]).map_err(|_| ProtoError)?;
199
-
200
-
epstein[..position.len()]
201
-
.iter_mut()
202
-
.zip(position)
203
-
.for_each(|(keyed, count)| {
204
-
*keyed ^= *count;
205
-
});
206
-
207
-
msg.truncate(index);
208
-
209
-
Ok(epstein)
210
-
}
211
185
}
212
186
213
187
impl TransportPrimitive<ChaCha20Poly1305> for TransportState {
···
215
189
216
190
const COUNTER_INCR: Self::Counter = 1;
217
191
192
+
const COUNTER_MAX: Self::Counter = u64::MAX;
193
+
218
194
fn encrypt_in_place(
219
195
&self,
220
196
epstein: &aead::Nonce<ChaCha20Poly1305>,
···
238
214
}
239
215
}
240
216
217
+
#[repr(align(4))]
241
218
pub struct TransportState {
242
219
aead: ChaCha20Poly1305,
243
-
finish: u64,
220
+
client: aead::Nonce<ChaCha20Poly1305>,
221
+
server: aead::Nonce<ChaCha20Poly1305>,
222
+
role: Role,
244
223
}
245
224
246
225
impl TransportState {
247
-
pub fn init(psk: &[u8; 32], shared: impl Into<SharedSecret>) -> Result<Self, ProtoError> {
226
+
pub fn init(
227
+
psk: &[u8; 32],
228
+
shared: impl Into<SharedSecret>,
229
+
role: Role,
230
+
) -> Result<Self, ProtoError> {
248
231
let noncer = shared.into();
249
232
let kdf = noncer.extract::<sha2::Sha256>(Some(psk));
250
233
251
234
let mut key = [0u8; 32];
252
235
253
-
let mut finish = [0u8; 8];
236
+
let mut client = aead::Nonce::<ChaCha20Poly1305>::default();
237
+
let mut server = aead::Nonce::<ChaCha20Poly1305>::default();
254
238
255
239
kdf.expand(b"SachY-Crypt0", &mut key)
256
240
.map_err(|_| ProtoError)?;
257
241
258
-
kdf.expand(b"PickANumber", &mut finish)
259
-
.map_err(|_| ProtoError)?;
242
+
kdf.expand(b"Client", &mut client).map_err(|_| ProtoError)?;
243
+
kdf.expand(b"Server", &mut server).map_err(|_| ProtoError)?;
260
244
261
245
Ok(Self {
262
246
aead: ChaCha20Poly1305::new(&key.into()),
263
-
finish: u64::from_le_bytes(finish),
247
+
client,
248
+
server,
249
+
role,
264
250
})
265
251
}
266
252
267
253
pub fn split(&self) -> (SendingState<'_>, ReceivingState<'_>) {
268
-
let counter = self.finish;
269
-
270
254
(
271
255
SendingState {
272
256
transport: self,
273
-
counter,
257
+
counter: 0,
274
258
},
275
259
ReceivingState {
276
260
transport: self,
277
-
counter,
261
+
counter: 0,
278
262
},
279
263
)
280
264
}
265
+
266
+
fn mix_nonce(&self, position: &[u8; 8], send: bool) -> aead::Nonce<ChaCha20Poly1305> {
267
+
let mut trump = aead::Nonce::<ChaCha20Poly1305>::default();
268
+
269
+
let epstein = match (self.role, send) {
270
+
(Role::Client, true) | (Role::Server, false) => &self.client,
271
+
(Role::Server, true) | (Role::Client, false) => &self.server,
272
+
};
273
+
274
+
let (head, tail) = trump.split_at_mut(position.len());
275
+
let (first, second) = epstein.split_at(position.len());
276
+
277
+
head.iter_mut()
278
+
.zip(first)
279
+
.zip(position)
280
+
.for_each(|((head, ep), pos)| *head = ep ^ pos);
281
+
tail.iter_mut()
282
+
.zip(second)
283
+
.for_each(|(tail, ep)| *tail = *ep);
284
+
285
+
trump
286
+
}
281
287
}
282
288
283
289
#[cfg(test)]
284
290
mod tests {
285
291
use alloc::vec;
292
+
use dhkem::Generate;
293
+
use elliptic_curve::array::Array;
286
294
287
295
use super::*;
288
296
···
336
344
132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251,
337
345
];
338
346
339
-
let alice = TransportState::init(&psk, Array(shared_secret))?;
340
-
let bob = TransportState::init(&psk, Array(shared_secret))?;
347
+
let alice = TransportState::init(&psk, Array(shared_secret), Role::Client)?;
348
+
let bob = TransportState::init(&psk, Array(shared_secret), Role::Server)?;
341
349
342
350
let (mut alice_send, mut alice_recv) = alice.split();
343
351
let (mut bob_send, mut bob_recv) = bob.split();
···
345
353
// Have to be synchronised on both ends, so the counter state matches between the two
346
354
// and thus messages can be encrypted/decrypted statefully. But the actual number is
347
355
// "random", making it harder to guess the position state.
348
-
assert_eq!(alice.finish, bob.finish);
356
+
assert_eq!(alice.client, bob.client);
357
+
assert_eq!(alice.server, bob.server);
358
+
assert_ne!(alice.client, alice.server);
359
+
assert_ne!(bob.client, bob.server);
349
360
350
361
let orig = b"Test Message, Please ignore.";
351
362
History
18 rounds
0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
pull request successfully merged
1 commit
expand
collapse
Sachy's crypto scheme lmao
1/2 failed, 1/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
1/2 failed, 1/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 failed
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao
2/2 success
expand
collapse
expand 0 comments
1 commit
expand
collapse
Sachy's crypto scheme lmao