1use crate::error::ProtocolError;
7use crate::Result;
8use blvm_secp256k1::ellswift::{ellswift_create, ellswift_xdh};
9use chacha20poly1305::{
10 aead::{Aead, AeadCore, KeyInit},
11 ChaCha20Poly1305, Key, Nonce,
12};
13use getrandom::getrandom;
14use sha2::{Digest, Sha256};
15use std::borrow::Cow;
16
17pub struct V2Transport {
19 send_key: [u8; 32],
21 recv_key: [u8; 32],
23 send_cipher: ChaCha20Poly1305,
25 recv_cipher: ChaCha20Poly1305,
27 send_nonce: u64,
29 recv_nonce: u64,
31}
32
33pub enum V2Handshake {
35 Initiator {
37 private_key: [u8; 32],
38 ellswift: [u8; 64],
39 },
40 Responder {
42 private_key: [u8; 32],
43 initiator_ellswift: Option<[u8; 64]>,
44 },
45}
46
47const REKEY_INTERVAL: u64 = 224;
49
50fn rekey_derive_next_key(key: &[u8; 32], rekey_epoch: u64) -> Result<[u8; 32]> {
51 let mut rekey_nonce = [0u8; 12];
52 rekey_nonce[0..4].copy_from_slice(&[0xff, 0xff, 0xff, 0xff]);
53 rekey_nonce[4..12].copy_from_slice(&rekey_epoch.to_le_bytes());
54 let cipher = ChaCha20Poly1305::new(Key::from_slice(key));
55 let ct = cipher
56 .encrypt(Nonce::from_slice(&rekey_nonce), [0u8; 32].as_slice())
57 .map_err(|e| {
58 ProtocolError::Consensus(blvm_consensus::error::ConsensusError::Serialization(
59 Cow::Owned(format!("BIP324 rekey encrypt failed: {e}")),
60 ))
61 })?;
62 let mut new_key = [0u8; 32];
63 new_key.copy_from_slice(&ct[..32]);
64 Ok(new_key)
65}
66
67impl V2Transport {
68 pub fn new(send_key: [u8; 32], recv_key: [u8; 32]) -> Self {
70 let send_cipher = ChaCha20Poly1305::new(&Key::from_slice(&send_key));
71 let recv_cipher = ChaCha20Poly1305::new(&Key::from_slice(&recv_key));
72
73 Self {
74 send_key,
75 recv_key,
76 send_cipher,
77 recv_cipher,
78 send_nonce: 0,
79 recv_nonce: 0,
80 }
81 }
82
83 pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
85 let mut nonce_bytes = [0u8; 12];
88 nonce_bytes[..8].copy_from_slice(&self.send_nonce.to_le_bytes());
89 let nonce = Nonce::from_slice(&nonce_bytes);
90
91 let ciphertext = self.send_cipher.encrypt(nonce, plaintext).map_err(|e| {
93 ProtocolError::Consensus(blvm_consensus::error::ConsensusError::Serialization(
94 Cow::Owned(format!("Encryption failed: {}", e)),
95 ))
96 })?;
97
98 self.send_nonce += 1;
100
101 if self.send_nonce.is_multiple_of(REKEY_INTERVAL) {
103 let rekey_epoch = (self.send_nonce / REKEY_INTERVAL) - 1;
104 self.send_key = rekey_derive_next_key(&self.send_key, rekey_epoch)?;
105 self.send_cipher = ChaCha20Poly1305::new(Key::from_slice(&self.send_key));
106 }
107
108 let mut packet = Vec::with_capacity(20 + ciphertext.len());
110
111 if ciphertext.len() < 16 {
113 return Err(ProtocolError::Consensus(
114 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
115 "Ciphertext too short".to_string(),
116 )),
117 ));
118 }
119 let tag_start = ciphertext.len() - 16;
120 packet.extend_from_slice(&ciphertext[tag_start..]);
121
122 let payload_len = ciphertext.len() - 16; if payload_len > 0xFFFFFF {
125 return Err(ProtocolError::Consensus(
126 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
127 "Payload too large".to_string(),
128 )),
129 ));
130 }
131 let len_bytes = (payload_len as u32).to_le_bytes();
132 packet.extend_from_slice(&len_bytes[..3]);
133
134 packet.push(0);
136
137 packet.extend_from_slice(&ciphertext[..tag_start]);
139
140 Ok(packet)
141 }
142
143 pub fn decrypt(&mut self, packet: &[u8]) -> Result<Vec<u8>> {
145 if packet.len() < 20 {
147 return Err(ProtocolError::Consensus(
148 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
149 "Packet too short".to_string(),
150 )),
151 ));
152 }
153
154 let tag = &packet[0..16];
156 let length_bytes = [packet[16], packet[17], packet[18], 0];
157 let payload_len = u32::from_le_bytes(length_bytes) as usize;
158 let payload_start = 20;
160 let payload_end = payload_start + payload_len;
161
162 if packet.len() < payload_end {
163 return Err(ProtocolError::Consensus(
164 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
165 "Packet incomplete".to_string(),
166 )),
167 ));
168 }
169
170 let mut ciphertext = Vec::with_capacity(payload_len + 16);
172 ciphertext.extend_from_slice(&packet[payload_start..payload_end]);
173 ciphertext.extend_from_slice(tag);
174
175 let mut nonce_bytes = [0u8; 12];
177 nonce_bytes[..8].copy_from_slice(&self.recv_nonce.to_le_bytes());
178 let nonce = Nonce::from_slice(&nonce_bytes);
179
180 let plaintext = self
182 .recv_cipher
183 .decrypt(nonce, ciphertext.as_slice())
184 .map_err(|e| {
185 ProtocolError::Consensus(blvm_consensus::error::ConsensusError::Serialization(
186 Cow::Owned(format!("Decryption failed: {}", e)),
187 ))
188 })?;
189
190 self.recv_nonce += 1;
192
193 if self.recv_nonce.is_multiple_of(REKEY_INTERVAL) {
194 let rekey_epoch = (self.recv_nonce / REKEY_INTERVAL) - 1;
195 self.recv_key = rekey_derive_next_key(&self.recv_key, rekey_epoch)?;
196 self.recv_cipher = ChaCha20Poly1305::new(Key::from_slice(&self.recv_key));
197 }
198
199 Ok(plaintext)
200 }
201}
202
203impl V2Handshake {
204 pub fn new_initiator() -> (Vec<u8>, Self) {
206 let mut key_bytes = [0u8; 32];
207 getrandom(&mut key_bytes).expect("Failed to generate random bytes");
208 let mut aux_rand = [0u8; 32];
209 getrandom(&mut aux_rand).expect("Failed to generate aux_rand");
210 let ellswift = ellswift_create(&key_bytes, Some(&aux_rand))
211 .expect("Failed to create ElligatorSwift encoding");
212 (
213 ellswift.to_vec(),
214 Self::Initiator {
215 private_key: key_bytes,
216 ellswift,
217 },
218 )
219 }
220
221 pub fn new_responder() -> Self {
223 let mut key_bytes = [0u8; 32];
224 getrandom(&mut key_bytes).expect("Failed to generate random bytes");
225 Self::Responder {
226 private_key: key_bytes,
227 initiator_ellswift: None,
228 }
229 }
230
231 pub fn process_initiator_message(
233 &mut self,
234 initiator_msg: &[u8],
235 ) -> Result<(Vec<u8>, V2Transport)> {
236 if initiator_msg.len() != 64 {
237 return Err(ProtocolError::Consensus(
238 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
239 "Invalid initiator message length".to_string(),
240 )),
241 ));
242 }
243
244 let mut initiator_ell64 = [0u8; 64];
245 initiator_ell64.copy_from_slice(initiator_msg);
246
247 let responder_private = match self {
248 Self::Responder { private_key, .. } => *private_key,
249 _ => {
250 return Err(ProtocolError::Consensus(
251 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
252 "Not a responder handshake".to_string(),
253 )),
254 ));
255 }
256 };
257
258 let mut aux_rand = [0u8; 32];
259 getrandom(&mut aux_rand).expect("Failed to generate aux_rand");
260
261 let responder_ell64 = ellswift_create(&responder_private, Some(&aux_rand))
262 .expect("Failed to create ElligatorSwift encoding");
263
264 let shared_x = ellswift_xdh(
266 &initiator_ell64,
267 &responder_ell64,
268 &responder_private,
269 true, )
271 .expect("ElligatorSwift ECDH failed");
272
273 let send_key = hkdf_sha256(&shared_x, b"bitcoin_v2_shared_secret_send");
274 let recv_key = hkdf_sha256(&shared_x, b"bitcoin_v2_shared_secret_recv");
275 let transport = V2Transport::new(send_key, recv_key);
276
277 if let Self::Responder {
278 initiator_ellswift: ref mut iell,
279 ..
280 } = self
281 {
282 *iell = Some(initiator_ell64);
283 }
284
285 Ok((responder_ell64.to_vec(), transport))
286 }
287
288 pub fn complete_handshake(self, responder_msg: &[u8]) -> Result<V2Transport> {
290 if responder_msg.len() != 64 {
291 return Err(ProtocolError::Consensus(
292 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
293 "Invalid responder message length".to_string(),
294 )),
295 ));
296 }
297
298 let mut responder_ell64 = [0u8; 64];
299 responder_ell64.copy_from_slice(responder_msg);
300
301 let (private_key, initiator_ell64) = match self {
302 Self::Initiator {
303 private_key,
304 ellswift,
305 } => (private_key, ellswift),
306 _ => {
307 return Err(ProtocolError::Consensus(
308 blvm_consensus::error::ConsensusError::Serialization(Cow::Owned(
309 "Not an initiator handshake".to_string(),
310 )),
311 ));
312 }
313 };
314
315 let shared_x = ellswift_xdh(
317 &initiator_ell64,
318 &responder_ell64,
319 &private_key,
320 false, )
322 .expect("ElligatorSwift ECDH failed");
323
324 let send_key = hkdf_sha256(&shared_x, b"bitcoin_v2_shared_secret_send");
325 let recv_key = hkdf_sha256(&shared_x, b"bitcoin_v2_shared_secret_recv");
326 Ok(V2Transport::new(send_key, recv_key))
327 }
328}
329
330fn hkdf_sha256(ikm: &[u8], info: &[u8]) -> [u8; 32] {
332 use hkdf::Hkdf;
333 let hk = Hkdf::<sha2::Sha256>::new(None, ikm);
334 let mut okm = [0u8; 32];
335 hk.expand(info, &mut okm)
336 .expect("HKDF expansion failed (should never happen for 32-byte output)");
337 okm
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn test_v2_transport_encrypt_decrypt() {
346 let key = [0x42; 32];
349 let mut transport = V2Transport::new(key, key);
350
351 let plaintext = b"Hello, Bitcoin!";
352 let encrypted = transport.encrypt(plaintext).unwrap();
353 let decrypted = transport.decrypt(&encrypted).unwrap();
354
355 assert_eq!(plaintext, decrypted.as_slice());
356 }
357
358 #[test]
359 fn test_v2_transport_rekey_round_trip() {
360 let key = [0x11; 32];
361 let mut enc = V2Transport::new(key, key);
362 let mut dec = V2Transport::new(key, key);
363 for i in 0..300 {
364 let pt = format!("msg{i}").into_bytes();
365 let pkt = enc.encrypt(&pt).unwrap();
366 let out = dec.decrypt(&pkt).unwrap();
367 assert_eq!(pt, out, "round-trip failed at i={i}");
368 }
369 }
370
371 #[test]
372 fn test_elligator_swift_encode_decode() {
373 use blvm_secp256k1::ellswift::{ellswift_create, ellswift_xdh};
374 let seckey = [0x01u8; 32];
376 let ell = ellswift_create(&seckey, None).expect("valid key");
377 assert_eq!(ell.len(), 64);
378 let shared = ellswift_xdh(&ell, &ell, &seckey, false);
380 assert!(shared.is_some(), "XDH should not fail on valid inputs");
381 }
382}