1use crate::error::ProtocolError;
7use crate::Result;
8use chacha20poly1305::{
9 aead::{Aead, AeadCore, KeyInit},
10 ChaCha20Poly1305, Key, Nonce,
11};
12use getrandom::getrandom;
13use secp256k1::{
14 ellswift::{ElligatorSwift, ElligatorSwiftSharedSecret},
15 PublicKey, Scalar, Secp256k1, SecretKey, XOnlyPublicKey,
16};
17use sha2::{Digest, Sha256};
18use std::borrow::Cow;
19
20pub struct V2Transport {
22 send_key: [u8; 32],
24 recv_key: [u8; 32],
26 send_cipher: ChaCha20Poly1305,
28 recv_cipher: ChaCha20Poly1305,
30 send_nonce: u64,
32 recv_nonce: u64,
34}
35
36pub enum V2Handshake {
38 Initiator {
40 private_key: SecretKey,
41 ellswift: ElligatorSwift, },
43 Responder {
45 private_key: SecretKey,
46 initiator_ellswift: Option<ElligatorSwift>, },
48}
49
50const REKEY_INTERVAL: u64 = 224;
52
53fn rekey_derive_next_key(key: &[u8; 32], rekey_epoch: u64) -> Result<[u8; 32]> {
54 let mut rekey_nonce = [0u8; 12];
55 rekey_nonce[0..4].copy_from_slice(&[0xff, 0xff, 0xff, 0xff]);
56 rekey_nonce[4..12].copy_from_slice(&rekey_epoch.to_le_bytes());
57 let cipher = ChaCha20Poly1305::new(Key::from_slice(key));
58 let ct = cipher
59 .encrypt(Nonce::from_slice(&rekey_nonce), [0u8; 32].as_slice())
60 .map_err(|e| {
61 ProtocolError::Consensus(blvm_consensus::error::ConsensusError::Serialization(
62 Cow::Owned(format!("BIP324 rekey encrypt failed: {e}")),
63 ))
64 })?;
65 let mut new_key = [0u8; 32];
66 new_key.copy_from_slice(&ct[..32]);
67 Ok(new_key)
68}
69
70impl V2Transport {
71 pub fn new(send_key: [u8; 32], recv_key: [u8; 32]) -> Self {
73 let send_cipher = ChaCha20Poly1305::new(&Key::from_slice(&send_key));
74 let recv_cipher = ChaCha20Poly1305::new(&Key::from_slice(&recv_key));
75
76 Self {
77 send_key,
78 recv_key,
79 send_cipher,
80 recv_cipher,
81 send_nonce: 0,
82 recv_nonce: 0,
83 }
84 }
85
86 pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
88 let mut nonce_bytes = [0u8; 12];
91 nonce_bytes[..8].copy_from_slice(&self.send_nonce.to_le_bytes());
92 let nonce = Nonce::from_slice(&nonce_bytes);
93
94 let ciphertext = self.send_cipher.encrypt(nonce, plaintext).map_err(|e| {
96 ProtocolError::Consensus(blvm_consensus::error::ConsensusError::Serialization(
97 Cow::Owned(format!("Encryption failed: {}", e)),
98 ))
99 })?;
100
101 self.send_nonce += 1;
103
104 if self.send_nonce.is_multiple_of(REKEY_INTERVAL) {
106 let rekey_epoch = (self.send_nonce / REKEY_INTERVAL) - 1;
107 self.send_key = rekey_derive_next_key(&self.send_key, rekey_epoch)?;
108 self.send_cipher = ChaCha20Poly1305::new(Key::from_slice(&self.send_key));
109 }
110
111 let mut packet = Vec::with_capacity(20 + ciphertext.len());
113
114 if ciphertext.len() < 16 {
116 return Err(ProtocolError::Consensus(
117 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
118 "Ciphertext too short".to_string(),
119 )),
120 ));
121 }
122 let tag_start = ciphertext.len() - 16;
123 packet.extend_from_slice(&ciphertext[tag_start..]);
124
125 let payload_len = ciphertext.len() - 16; if payload_len > 0xFFFFFF {
128 return Err(ProtocolError::Consensus(
129 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
130 "Payload too large".to_string(),
131 )),
132 ));
133 }
134 let len_bytes = (payload_len as u32).to_le_bytes();
135 packet.extend_from_slice(&len_bytes[..3]);
136
137 packet.push(0);
139
140 packet.extend_from_slice(&ciphertext[..tag_start]);
142
143 Ok(packet)
144 }
145
146 pub fn decrypt(&mut self, packet: &[u8]) -> Result<Vec<u8>> {
148 if packet.len() < 20 {
150 return Err(ProtocolError::Consensus(
151 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
152 "Packet too short".to_string(),
153 )),
154 ));
155 }
156
157 let tag = &packet[0..16];
159 let length_bytes = [packet[16], packet[17], packet[18], 0];
160 let payload_len = u32::from_le_bytes(length_bytes) as usize;
161 let payload_start = 20;
163 let payload_end = payload_start + payload_len;
164
165 if packet.len() < payload_end {
166 return Err(ProtocolError::Consensus(
167 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
168 "Packet incomplete".to_string(),
169 )),
170 ));
171 }
172
173 let mut ciphertext = Vec::with_capacity(payload_len + 16);
175 ciphertext.extend_from_slice(&packet[payload_start..payload_end]);
176 ciphertext.extend_from_slice(tag);
177
178 let mut nonce_bytes = [0u8; 12];
180 nonce_bytes[..8].copy_from_slice(&self.recv_nonce.to_le_bytes());
181 let nonce = Nonce::from_slice(&nonce_bytes);
182
183 let plaintext = self
185 .recv_cipher
186 .decrypt(nonce, ciphertext.as_slice())
187 .map_err(|e| {
188 ProtocolError::Consensus(blvm_consensus::error::ConsensusError::Serialization(
189 Cow::Owned(format!("Decryption failed: {}", e)),
190 ))
191 })?;
192
193 self.recv_nonce += 1;
195
196 if self.recv_nonce.is_multiple_of(REKEY_INTERVAL) {
197 let rekey_epoch = (self.recv_nonce / REKEY_INTERVAL) - 1;
198 self.recv_key = rekey_derive_next_key(&self.recv_key, rekey_epoch)?;
199 self.recv_cipher = ChaCha20Poly1305::new(Key::from_slice(&self.recv_key));
200 }
201
202 Ok(plaintext)
203 }
204}
205
206impl V2Handshake {
207 pub fn new_initiator() -> (Vec<u8>, Self) {
209 let secp = Secp256k1::new();
210 let mut key_bytes = [0u8; 32];
212 getrandom(&mut key_bytes).expect("Failed to generate random bytes");
213 let private_key = SecretKey::from_slice(&key_bytes).expect("Failed to generate secret key");
214
215 let mut aux_rand = [0u8; 32];
217 getrandom(&mut aux_rand).expect("Failed to generate aux_rand");
218
219 let ellswift = ElligatorSwift::from_seckey(&secp, private_key, Some(aux_rand));
221 let encoded = ellswift.to_array();
222
223 (
224 encoded.to_vec(),
225 Self::Initiator {
226 private_key,
227 ellswift,
228 },
229 )
230 }
231
232 pub fn new_responder() -> Self {
234 let mut key_bytes = [0u8; 32];
236 getrandom(&mut key_bytes).expect("Failed to generate random bytes");
237 let private_key = SecretKey::from_slice(&key_bytes).expect("Failed to generate secret key");
238
239 Self::Responder {
240 private_key,
241 initiator_ellswift: None,
242 }
243 }
244
245 pub fn process_initiator_message(
247 &mut self,
248 initiator_msg: &[u8],
249 ) -> Result<(Vec<u8>, V2Transport)> {
250 if initiator_msg.len() != 64 {
251 return Err(ProtocolError::Consensus(
252 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
253 "Invalid initiator message length".to_string(),
254 )),
255 ));
256 }
257
258 let mut initiator_msg_array = [0u8; 64];
260 initiator_msg_array.copy_from_slice(initiator_msg);
261 let initiator_ellswift = elligator_swift_decode(&initiator_msg_array);
262
263 let secp = Secp256k1::new();
265 let responder_private = match self {
266 Self::Responder { private_key, .. } => *private_key,
267 _ => {
268 return Err(ProtocolError::Consensus(
269 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
270 "Not a responder handshake".to_string(),
271 )),
272 ));
273 }
274 };
275
276 let mut aux_rand = [0u8; 32];
278 getrandom(&mut aux_rand).expect("Failed to generate aux_rand");
279
280 let responder_ellswift =
282 ElligatorSwift::from_seckey(&secp, responder_private, Some(aux_rand));
283 let responder_ellswift_bytes = responder_ellswift.to_array();
284
285 use secp256k1::ellswift::ElligatorSwiftParty;
288
289 let shared_secret = ElligatorSwift::shared_secret(
291 initiator_ellswift,
292 responder_ellswift,
293 responder_private,
294 ElligatorSwiftParty::B, None, );
297
298 let shared_x = shared_secret.to_secret_bytes();
299
300 let send_key = hkdf_sha256(&shared_x, b"bitcoin_v2_shared_secret_send");
302 let recv_key = hkdf_sha256(&shared_x, b"bitcoin_v2_shared_secret_recv");
303
304 let transport = V2Transport::new(send_key, recv_key);
306
307 if let Self::Responder {
309 initiator_ellswift: ref mut iell,
310 ..
311 } = self
312 {
313 *iell = Some(initiator_ellswift);
314 }
315
316 Ok((responder_ellswift_bytes.to_vec(), transport))
317 }
318
319 pub fn complete_handshake(self, responder_msg: &[u8]) -> Result<V2Transport> {
321 if responder_msg.len() != 64 {
322 return Err(ProtocolError::Consensus(
323 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
324 "Invalid responder message length".to_string(),
325 )),
326 ));
327 }
328
329 let mut responder_msg_array = [0u8; 64];
331 responder_msg_array.copy_from_slice(responder_msg);
332 let responder_ellswift = elligator_swift_decode(&responder_msg_array);
333
334 use secp256k1::ellswift::ElligatorSwiftParty;
337 let (private_key, initiator_ellswift) = match self {
338 Self::Initiator {
339 private_key,
340 ellswift,
341 ..
342 } => {
343 (private_key, ellswift) }
345 _ => {
346 return Err(ProtocolError::Consensus(
347 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
348 "Not an initiator handshake".to_string(),
349 )),
350 ));
351 }
352 };
353
354 let shared_secret = ElligatorSwift::shared_secret(
356 initiator_ellswift,
357 responder_ellswift,
358 private_key,
359 ElligatorSwiftParty::A, None, );
362
363 let shared_x = shared_secret.to_secret_bytes();
364
365 let send_key = hkdf_sha256(&shared_x, b"bitcoin_v2_shared_secret_send");
367 let recv_key = hkdf_sha256(&shared_x, b"bitcoin_v2_shared_secret_recv");
368
369 Ok(V2Transport::new(send_key, recv_key))
371 }
372}
373
374fn elligator_swift_encode(pubkey: &PublicKey) -> [u8; 64] {
377 let ellswift = ElligatorSwift::from_pubkey(*pubkey);
379 ellswift.to_array()
380}
381
382fn elligator_swift_decode(encoded: &[u8; 64]) -> ElligatorSwift {
388 ElligatorSwift::from_array(*encoded)
389}
390
391fn hkdf_sha256(ikm: &[u8], info: &[u8]) -> [u8; 32] {
397 use hkdf::Hkdf;
398
399 let hk = Hkdf::<sha2::Sha256>::new(None, ikm);
401 let mut okm = [0u8; 32];
402 hk.expand(info, &mut okm)
403 .expect("HKDF expansion failed (should never happen for 32-byte output)");
404 okm
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410
411 #[test]
412 fn test_v2_transport_encrypt_decrypt() {
413 let key = [0x42; 32];
416 let mut transport = V2Transport::new(key, key);
417
418 let plaintext = b"Hello, Bitcoin!";
419 let encrypted = transport.encrypt(plaintext).unwrap();
420 let decrypted = transport.decrypt(&encrypted).unwrap();
421
422 assert_eq!(plaintext, decrypted.as_slice());
423 }
424
425 #[test]
426 fn test_v2_transport_rekey_round_trip() {
427 let key = [0x11; 32];
428 let mut enc = V2Transport::new(key, key);
429 let mut dec = V2Transport::new(key, key);
430 for i in 0..300 {
431 let pt = format!("msg{i}").into_bytes();
432 let pkt = enc.encrypt(&pt).unwrap();
433 let out = dec.decrypt(&pkt).unwrap();
434 assert_eq!(pt, out, "round-trip failed at i={i}");
435 }
436 }
437
438 #[test]
439 fn test_elligator_swift_encode_decode() {
440 let secp = Secp256k1::new();
441 let private_key = SecretKey::from_slice(&[0x01; 32]).unwrap();
442 let public_key = PublicKey::from_secret_key(&secp, &private_key);
443
444 let encoded = elligator_swift_encode(&public_key);
445 let decoded_ellswift = elligator_swift_decode(&encoded);
446
447 let re_encoded = decoded_ellswift.to_array();
449 assert_eq!(encoded, re_encoded);
450 }
451}