Skip to main content

blvm_protocol/
v2_transport.rs

1//! BIP324 version 2 P2P encrypted transport.
2//!
3//! Key schedule, garbage terminators, and packet framing follow the specification.
4//! The length field is FSChaCha20. The header and contents are FSChaCha20-Poly1305.
5
6use crate::Result;
7use crate::error::ProtocolError;
8use blvm_secp256k1::ellswift::{ellswift_create, ellswift_xdh};
9use chacha20poly1305::{
10    ChaCha20Poly1305, Key, Nonce,
11    aead::{Aead, KeyInit, Payload},
12};
13use getrandom::getrandom;
14use hkdf::Hkdf;
15use sha2::Sha256;
16use std::borrow::Cow;
17
18/// Rekey after this many packets in one direction.
19const REKEY_INTERVAL: u64 = 224;
20const MAINNET_MAGIC: [u8; 4] = [0xf9, 0xbe, 0xb4, 0xd9];
21/// Garbage longer than this is not a valid handshake.
22pub const GARBAGE_MAX: usize = 4095;
23
24/// `magic || "version" || five zero bytes`. A v1 connection begins with these 16 bytes.
25pub fn v1_version_prefix(magic: [u8; 4]) -> [u8; 16] {
26    let mut prefix = [0u8; 16];
27    prefix[..4].copy_from_slice(&magic);
28    prefix[4..].copy_from_slice(b"version\x00\x00\x00\x00\x00");
29    prefix
30}
31
32/// Keys derived from one ECDH shared secret.
33#[derive(Clone, Debug)]
34pub struct SessionKeys {
35    pub initiator_l: [u8; 32],
36    pub initiator_p: [u8; 32],
37    pub responder_l: [u8; 32],
38    pub responder_p: [u8; 32],
39    pub send_garbage_terminator: [u8; 16],
40    pub recv_garbage_terminator: [u8; 16],
41    pub session_id: [u8; 32],
42}
43
44/// Outbound length and content ciphers.
45pub struct V2Send {
46    send_l: FsChaCha20,
47    send_p: FsChaCha20Poly1305,
48}
49
50/// Inbound length and content ciphers.
51pub struct V2Recv {
52    recv_l: FsChaCha20,
53    recv_p: FsChaCha20Poly1305,
54}
55
56/// Packet encryption state for one direction pair.
57pub struct V2Transport {
58    send: V2Send,
59    recv: V2Recv,
60}
61
62/// A finished handshake. Terminators and the session id stay with the ciphers.
63pub struct V2Session {
64    transport: V2Transport,
65    pub session_id: [u8; 32],
66    pub send_garbage_terminator: [u8; 16],
67    pub recv_garbage_terminator: [u8; 16],
68}
69
70/// Initiator state after the ellswift and garbage have been chosen.
71pub struct InitiatorPending {
72    private_key: [u8; 32],
73    ellswift: [u8; 64],
74    garbage: Vec<u8>,
75}
76
77/// Responder state after the shared secret, before its garbage and version packet are sent.
78pub struct ResponderPending {
79    ellswift: [u8; 64],
80    session: V2Session,
81    garbage: Vec<u8>,
82}
83
84/// BIP324 handshake state.
85pub enum V2Handshake {
86    Initiator {
87        private_key: [u8; 32],
88        ellswift: [u8; 64],
89    },
90    Responder {
91        private_key: [u8; 32],
92        initiator_ellswift: Option<[u8; 64]>,
93    },
94}
95
96struct FsChaCha20 {
97    key: [u8; 32],
98    block_counter: u32,
99    chunk_counter: u64,
100    keystream: Vec<u8>,
101}
102
103struct FsChaCha20Poly1305 {
104    key: [u8; 32],
105    packet_counter: u64,
106}
107
108fn chacha20_block(key: &[u8; 32], nonce: &[u8; 12], counter: u32) -> [u8; 64] {
109    fn quarter(s: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
110        s[a] = s[a].wrapping_add(s[b]);
111        s[d] ^= s[a];
112        s[d] = s[d].rotate_left(16);
113        s[c] = s[c].wrapping_add(s[d]);
114        s[b] ^= s[c];
115        s[b] = s[b].rotate_left(12);
116        s[a] = s[a].wrapping_add(s[b]);
117        s[d] ^= s[a];
118        s[d] = s[d].rotate_left(8);
119        s[c] = s[c].wrapping_add(s[d]);
120        s[b] ^= s[c];
121        s[b] = s[b].rotate_left(7);
122    }
123    let mut init = [0u32; 16];
124    init[0] = 0x6170_7865;
125    init[1] = 0x3320_646e;
126    init[2] = 0x7962_2d32;
127    init[3] = 0x6b20_6574;
128    for i in 0..8 {
129        init[4 + i] = u32::from_le_bytes(key[4 * i..4 * i + 4].try_into().unwrap());
130    }
131    init[12] = counter;
132    for i in 0..3 {
133        init[13 + i] = u32::from_le_bytes(nonce[4 * i..4 * i + 4].try_into().unwrap());
134    }
135    let mut state = init;
136    for _ in 0..10 {
137        quarter(&mut state, 0, 4, 8, 12);
138        quarter(&mut state, 1, 5, 9, 13);
139        quarter(&mut state, 2, 6, 10, 14);
140        quarter(&mut state, 3, 7, 11, 15);
141        quarter(&mut state, 0, 5, 10, 15);
142        quarter(&mut state, 1, 6, 11, 12);
143        quarter(&mut state, 2, 7, 8, 13);
144        quarter(&mut state, 3, 4, 9, 14);
145    }
146    let mut out = [0u8; 64];
147    for i in 0..16 {
148        let word = state[i].wrapping_add(init[i]);
149        out[4 * i..4 * i + 4].copy_from_slice(&word.to_le_bytes());
150    }
151    out
152}
153
154impl FsChaCha20 {
155    fn new(key: [u8; 32]) -> Self {
156        Self {
157            key,
158            block_counter: 0,
159            chunk_counter: 0,
160            keystream: Vec::new(),
161        }
162    }
163
164    fn keystream_bytes(&mut self, nbytes: usize) -> Vec<u8> {
165        while self.keystream.len() < nbytes {
166            let mut nonce = [0u8; 12];
167            nonce[4..12].copy_from_slice(&(self.chunk_counter / REKEY_INTERVAL).to_le_bytes());
168            self.keystream.extend_from_slice(&chacha20_block(
169                &self.key,
170                &nonce,
171                self.block_counter,
172            ));
173            self.block_counter += 1;
174        }
175        let out = self.keystream[..nbytes].to_vec();
176        self.keystream.drain(..nbytes);
177        out
178    }
179
180    fn crypt(&mut self, chunk: &[u8]) -> Vec<u8> {
181        let ks = self.keystream_bytes(chunk.len());
182        let out: Vec<u8> = chunk.iter().zip(ks).map(|(b, k)| b ^ k).collect();
183        if (self.chunk_counter + 1) % REKEY_INTERVAL == 0 {
184            let next = self.keystream_bytes(32);
185            self.key.copy_from_slice(&next);
186            self.block_counter = 0;
187        }
188        self.chunk_counter += 1;
189        out
190    }
191}
192
193impl FsChaCha20Poly1305 {
194    fn new(key: [u8; 32]) -> Self {
195        Self {
196            key,
197            packet_counter: 0,
198        }
199    }
200
201    fn crypt(&mut self, aad: &[u8], text: &[u8], decrypt: bool) -> Result<Vec<u8>> {
202        let mut nonce = [0u8; 12];
203        nonce[..4].copy_from_slice(&((self.packet_counter % REKEY_INTERVAL) as u32).to_le_bytes());
204        nonce[4..].copy_from_slice(&(self.packet_counter / REKEY_INTERVAL).to_le_bytes());
205        let cipher = ChaCha20Poly1305::new(Key::from_slice(&self.key));
206        let nonce_ref = Nonce::from_slice(&nonce);
207        let payload = Payload { msg: text, aad };
208        let ret = if decrypt {
209            cipher.decrypt(nonce_ref, payload)
210        } else {
211            cipher.encrypt(nonce_ref, payload)
212        }
213        .map_err(|_| protocol_err("packet authentication failed"))?;
214        if (self.packet_counter + 1) % REKEY_INTERVAL == 0 {
215            let mut rekey_nonce = nonce;
216            rekey_nonce[..4].copy_from_slice(&[0xff, 0xff, 0xff, 0xff]);
217            let rekey = ChaCha20Poly1305::new(Key::from_slice(&self.key));
218            let mixed = rekey
219                .encrypt(
220                    Nonce::from_slice(&rekey_nonce),
221                    Payload {
222                        msg: &[0u8; 32],
223                        aad: &[],
224                    },
225                )
226                .map_err(|_| protocol_err("rekey failed"))?;
227            self.key.copy_from_slice(&mixed[..32]);
228        }
229        self.packet_counter += 1;
230        Ok(ret)
231    }
232}
233
234fn protocol_err(msg: &str) -> ProtocolError {
235    ProtocolError::Consensus(blvm_consensus::error::ConsensusError::Serialization(
236        Cow::Owned(msg.to_string()),
237    ))
238}
239
240fn hkdf_sha256(salt: &[u8], ikm: &[u8], info: &[u8], out: &mut [u8]) {
241    let hk = Hkdf::<Sha256>::new(Some(salt), ikm);
242    hk.expand(info, out)
243        .expect("HKDF expansion fits in 255 blocks");
244}
245
246/// Derive the session keys. `initiating` selects which pair this peer sends with.
247pub fn session_keys(shared_secret: &[u8; 32], initiating: bool, magic: [u8; 4]) -> SessionKeys {
248    let mut salt = b"bitcoin_v2_shared_secret".to_vec();
249    salt.extend_from_slice(&magic);
250    let mut initiator_l = [0u8; 32];
251    let mut initiator_p = [0u8; 32];
252    let mut responder_l = [0u8; 32];
253    let mut responder_p = [0u8; 32];
254    let mut garbage = [0u8; 32];
255    let mut session_id = [0u8; 32];
256    hkdf_sha256(&salt, shared_secret, b"initiator_L", &mut initiator_l);
257    hkdf_sha256(&salt, shared_secret, b"initiator_P", &mut initiator_p);
258    hkdf_sha256(&salt, shared_secret, b"responder_L", &mut responder_l);
259    hkdf_sha256(&salt, shared_secret, b"responder_P", &mut responder_p);
260    hkdf_sha256(&salt, shared_secret, b"garbage_terminators", &mut garbage);
261    hkdf_sha256(&salt, shared_secret, b"session_id", &mut session_id);
262    let (send_garbage, recv_garbage) = if initiating {
263        (
264            garbage[..16].try_into().unwrap(),
265            garbage[16..].try_into().unwrap(),
266        )
267    } else {
268        (
269            garbage[16..].try_into().unwrap(),
270            garbage[..16].try_into().unwrap(),
271        )
272    };
273    SessionKeys {
274        initiator_l,
275        initiator_p,
276        responder_l,
277        responder_p,
278        send_garbage_terminator: send_garbage,
279        recv_garbage_terminator: recv_garbage,
280        session_id,
281    }
282}
283
284impl SessionKeys {
285    fn transport(self, initiating: bool) -> V2Transport {
286        let (send_l, send_p, recv_l, recv_p) = if initiating {
287            (
288                self.initiator_l,
289                self.initiator_p,
290                self.responder_l,
291                self.responder_p,
292            )
293        } else {
294            (
295                self.responder_l,
296                self.responder_p,
297                self.initiator_l,
298                self.initiator_p,
299            )
300        };
301        V2Transport {
302            send: V2Send {
303                send_l: FsChaCha20::new(send_l),
304                send_p: FsChaCha20Poly1305::new(send_p),
305            },
306            recv: V2Recv {
307                recv_l: FsChaCha20::new(recv_l),
308                recv_p: FsChaCha20Poly1305::new(recv_p),
309            },
310        }
311    }
312
313    fn into_session(self, initiating: bool) -> V2Session {
314        let session_id = self.session_id;
315        let send_garbage_terminator = self.send_garbage_terminator;
316        let recv_garbage_terminator = self.recv_garbage_terminator;
317        V2Session {
318            transport: self.transport(initiating),
319            session_id,
320            send_garbage_terminator,
321            recv_garbage_terminator,
322        }
323    }
324}
325
326impl V2Send {
327    /// Encrypt contents. The ignore bit is clear and there is no associated data.
328    pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
329        self.encrypt_packet(plaintext, &[], false)
330    }
331
332    /// Encrypt one packet. `ignore` sets the header bit. `aad` is authenticated with this packet only.
333    pub fn encrypt_packet(&mut self, contents: &[u8], aad: &[u8], ignore: bool) -> Result<Vec<u8>> {
334        if contents.len() > 0x00ff_ffff {
335            return Err(protocol_err("contents longer than 2^24-1"));
336        }
337        let mut plain = Vec::with_capacity(1 + contents.len());
338        plain.push(if ignore { 0x80 } else { 0 });
339        plain.extend_from_slice(contents);
340        let body = self.send_p.crypt(aad, &plain, false)?;
341        let len = (contents.len() as u32).to_le_bytes();
342        let enc_len = self.send_l.crypt(&len[..3]);
343        let mut packet = enc_len;
344        packet.extend_from_slice(&body);
345        Ok(packet)
346    }
347}
348
349impl V2Recv {
350    /// Decrypt the 3-byte length and return the contents length.
351    pub fn decrypt_length(&mut self, enc_len: &[u8]) -> Result<usize> {
352        if enc_len.len() != 3 {
353            return Err(protocol_err("length is 3 bytes"));
354        }
355        let len_bytes = self.recv_l.crypt(enc_len);
356        Ok(u32::from_le_bytes([len_bytes[0], len_bytes[1], len_bytes[2], 0]) as usize)
357    }
358
359    /// Decrypt the header byte and contents. The length cipher is not touched.
360    pub fn decrypt_body(&mut self, body: &[u8], aad: &[u8]) -> Result<(Vec<u8>, bool)> {
361        let plain = self.recv_p.crypt(aad, body, true)?;
362        if plain.is_empty() {
363            return Err(protocol_err("decrypted packet has no header"));
364        }
365        let ignore = plain[0] & 0x80 != 0;
366        Ok((plain[1..].to_vec(), ignore))
367    }
368
369    /// Decrypt one packet and return its contents.
370    pub fn decrypt(&mut self, packet: &[u8]) -> Result<Vec<u8>> {
371        self.decrypt_packet(packet, &[])
372            .map(|(contents, _)| contents)
373    }
374
375    /// Decrypt one packet. Returns the contents and the ignore bit.
376    pub fn decrypt_packet(&mut self, packet: &[u8], aad: &[u8]) -> Result<(Vec<u8>, bool)> {
377        if packet.len() < 3 + 1 + 16 {
378            return Err(protocol_err("packet too short"));
379        }
380        let contents_len = self.decrypt_length(&packet[..3])?;
381        let body_len = 1 + contents_len + 16;
382        if packet.len() != 3 + body_len {
383            return Err(protocol_err("packet length does not match"));
384        }
385        let (contents, ignore) = self.decrypt_body(&packet[3..], aad)?;
386        if contents.len() != contents_len {
387            return Err(protocol_err("decrypted contents length mismatch"));
388        }
389        Ok((contents, ignore))
390    }
391}
392
393impl V2Transport {
394    /// Same key on the length and content ciphers, in both directions. For round-trip tests.
395    pub fn new(send_key: [u8; 32], recv_key: [u8; 32]) -> Self {
396        Self {
397            send: V2Send {
398                send_l: FsChaCha20::new(send_key),
399                send_p: FsChaCha20Poly1305::new(send_key),
400            },
401            recv: V2Recv {
402                recv_l: FsChaCha20::new(recv_key),
403                recv_p: FsChaCha20Poly1305::new(recv_key),
404            },
405        }
406    }
407
408    /// Encrypt contents. The ignore bit is clear and there is no associated data.
409    pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
410        self.send.encrypt(plaintext)
411    }
412
413    /// Decrypt one packet and return its contents.
414    pub fn decrypt(&mut self, packet: &[u8]) -> Result<Vec<u8>> {
415        self.recv.decrypt(packet)
416    }
417
418    /// Encrypt one packet. `ignore` sets the header bit. `aad` is authenticated with this packet only.
419    pub fn encrypt_packet(&mut self, contents: &[u8], aad: &[u8], ignore: bool) -> Result<Vec<u8>> {
420        self.send.encrypt_packet(contents, aad, ignore)
421    }
422
423    /// Decrypt one packet. Returns the contents and the ignore bit.
424    pub fn decrypt_packet(&mut self, packet: &[u8], aad: &[u8]) -> Result<(Vec<u8>, bool)> {
425        self.recv.decrypt_packet(packet, aad)
426    }
427
428    /// Decrypt the 3-byte length and return the contents length.
429    pub fn decrypt_length(&mut self, enc_len: &[u8]) -> Result<usize> {
430        self.recv.decrypt_length(enc_len)
431    }
432
433    /// Decrypt the header byte and contents.
434    pub fn decrypt_body(&mut self, body: &[u8], aad: &[u8]) -> Result<(Vec<u8>, bool)> {
435        self.recv.decrypt_body(body, aad)
436    }
437}
438
439impl V2Session {
440    /// Encrypt contents. The ignore bit is clear and there is no associated data.
441    pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
442        self.transport.encrypt(plaintext)
443    }
444
445    /// Decrypt one packet and return its contents.
446    pub fn decrypt(&mut self, packet: &[u8]) -> Result<Vec<u8>> {
447        self.transport.decrypt(packet)
448    }
449
450    /// Encrypt one packet. `ignore` sets the header bit. `aad` is authenticated with this packet only.
451    pub fn encrypt_packet(&mut self, contents: &[u8], aad: &[u8], ignore: bool) -> Result<Vec<u8>> {
452        self.transport.encrypt_packet(contents, aad, ignore)
453    }
454
455    /// Decrypt one packet. Returns the contents and the ignore bit.
456    pub fn decrypt_packet(&mut self, packet: &[u8], aad: &[u8]) -> Result<(Vec<u8>, bool)> {
457        self.transport.decrypt_packet(packet, aad)
458    }
459
460    /// Decrypt the 3-byte length and return the contents length.
461    pub fn decrypt_length(&mut self, enc_len: &[u8]) -> Result<usize> {
462        self.transport.decrypt_length(enc_len)
463    }
464
465    /// Decrypt the header byte and contents.
466    pub fn decrypt_body(&mut self, body: &[u8], aad: &[u8]) -> Result<(Vec<u8>, bool)> {
467        self.transport.decrypt_body(body, aad)
468    }
469
470    /// Separate the directions so a reader and a writer can own one each.
471    pub fn split(self) -> (V2Send, V2Recv) {
472        (self.transport.send, self.transport.recv)
473    }
474}
475
476fn shared_secret(
477    ours: &[u8; 64],
478    theirs: &[u8; 64],
479    private_key: &[u8; 32],
480    initiating: bool,
481) -> Result<[u8; 32]> {
482    let secret = if initiating {
483        ellswift_xdh(ours, theirs, private_key, false)
484    } else {
485        ellswift_xdh(theirs, ours, private_key, true)
486    };
487    secret.ok_or_else(|| protocol_err("ElligatorSwift ECDH failed"))
488}
489
490fn transport_from_secret(secret: &[u8; 32], initiating: bool) -> V2Transport {
491    session_keys(secret, initiating, MAINNET_MAGIC).transport(initiating)
492}
493
494impl V2Handshake {
495    /// Create a new initiator handshake. The bytes are the 64-byte ellswift opening.
496    pub fn new_initiator() -> (Vec<u8>, Self) {
497        let mut key_bytes = [0u8; 32];
498        getrandom(&mut key_bytes).expect("Failed to generate random bytes");
499        let mut aux_rand = [0u8; 32];
500        getrandom(&mut aux_rand).expect("Failed to generate aux_rand");
501        let ellswift = ellswift_create(&key_bytes, Some(&aux_rand))
502            .expect("Failed to create ElligatorSwift encoding");
503        (
504            ellswift.to_vec(),
505            Self::Initiator {
506                private_key: key_bytes,
507                ellswift,
508            },
509        )
510    }
511
512    /// Create a new responder handshake.
513    pub fn new_responder() -> Self {
514        let mut key_bytes = [0u8; 32];
515        getrandom(&mut key_bytes).expect("Failed to generate random bytes");
516        Self::Responder {
517            private_key: key_bytes,
518            initiator_ellswift: None,
519        }
520    }
521
522    /// Process the initiator opening. Returns this side's ellswift and the receive transport.
523    pub fn process_initiator_message(
524        &mut self,
525        initiator_msg: &[u8],
526    ) -> Result<(Vec<u8>, V2Transport)> {
527        if initiator_msg.len() != 64 {
528            return Err(protocol_err("Invalid initiator message length"));
529        }
530        let mut initiator_ell64 = [0u8; 64];
531        initiator_ell64.copy_from_slice(initiator_msg);
532        let responder_private = match self {
533            Self::Responder { private_key, .. } => *private_key,
534            _ => return Err(protocol_err("Not a responder handshake")),
535        };
536        let mut aux_rand = [0u8; 32];
537        getrandom(&mut aux_rand).expect("Failed to generate aux_rand");
538        let responder_ell64 = ellswift_create(&responder_private, Some(&aux_rand))
539            .expect("Failed to create ElligatorSwift encoding");
540        let secret = shared_secret(
541            &responder_ell64,
542            &initiator_ell64,
543            &responder_private,
544            false,
545        )?;
546        if let Self::Responder {
547            initiator_ellswift, ..
548        } = self
549        {
550            *initiator_ellswift = Some(initiator_ell64);
551        }
552        Ok((
553            responder_ell64.to_vec(),
554            transport_from_secret(&secret, false),
555        ))
556    }
557
558    /// Complete the initiator handshake with the responder's 64-byte ellswift.
559    pub fn complete_handshake(self, responder_msg: &[u8]) -> Result<V2Transport> {
560        if responder_msg.len() != 64 {
561            return Err(protocol_err("Invalid responder message length"));
562        }
563        let mut responder_ell64 = [0u8; 64];
564        responder_ell64.copy_from_slice(responder_msg);
565        let (private_key, initiator_ell64) = match self {
566            Self::Initiator {
567                private_key,
568                ellswift,
569            } => (private_key, ellswift),
570            _ => return Err(protocol_err("Not an initiator handshake")),
571        };
572        let secret = shared_secret(&initiator_ell64, &responder_ell64, &private_key, true)?;
573        Ok(transport_from_secret(&secret, true))
574    }
575}
576
577fn fresh_ellswift() -> ([u8; 32], [u8; 64]) {
578    let mut key_bytes = [0u8; 32];
579    getrandom(&mut key_bytes).expect("Failed to generate random bytes");
580    let mut aux_rand = [0u8; 32];
581    getrandom(&mut aux_rand).expect("Failed to generate aux_rand");
582    let ellswift = ellswift_create(&key_bytes, Some(&aux_rand))
583        .expect("Failed to create ElligatorSwift encoding");
584    (key_bytes, ellswift)
585}
586
587fn garbage_ok(garbage: &[u8]) -> Result<()> {
588    if garbage.len() > GARBAGE_MAX {
589        return Err(protocol_err("garbage longer than 4095"));
590    }
591    Ok(())
592}
593
594impl InitiatorPending {
595    /// Garbage this side already sent after its ellswift public key.
596    pub fn garbage(&self) -> &[u8] {
597        &self.garbage
598    }
599}
600
601/// Opening bytes are ellswift || garbage. Garbage may be empty.
602pub fn begin_initiator(garbage: &[u8]) -> Result<(Vec<u8>, InitiatorPending)> {
603    garbage_ok(garbage)?;
604    let (private_key, ellswift) = fresh_ellswift();
605    let mut opening = ellswift.to_vec();
606    opening.extend_from_slice(garbage);
607    Ok((
608        opening,
609        InitiatorPending {
610            private_key,
611            ellswift,
612            garbage: garbage.to_vec(),
613        },
614    ))
615}
616
617/// Responder keys from the initiator's 64-byte ellswift. `magic` is the network salt.
618pub fn responder_accept(
619    initiator_ellswift: &[u8],
620    garbage: &[u8],
621    magic: [u8; 4],
622) -> Result<ResponderPending> {
623    garbage_ok(garbage)?;
624    if initiator_ellswift.len() != 64 {
625        return Err(protocol_err("ellswift is 64 bytes"));
626    }
627    let mut initiator_ell64 = [0u8; 64];
628    initiator_ell64.copy_from_slice(initiator_ellswift);
629    let (private_key, ellswift) = fresh_ellswift();
630    let secret = shared_secret(&ellswift, &initiator_ell64, &private_key, false)?;
631    Ok(ResponderPending {
632        ellswift,
633        session: session_keys(&secret, false, magic).into_session(false),
634        garbage: garbage.to_vec(),
635    })
636}
637
638impl ResponderPending {
639    /// ellswift || garbage || terminator || version packet. The packet authenticates `garbage`.
640    pub fn send_version(self, version: &[u8]) -> Result<(Vec<u8>, V2Session)> {
641        self.send_with_decoys(&[], version)
642    }
643
644    /// Same as [`Self::send_version`], with ignore-bit decoys before the version packet.
645    /// The first packet authenticates `garbage`. Later packets use empty associated data.
646    pub fn send_with_decoys(
647        mut self,
648        decoys: &[&[u8]],
649        version: &[u8],
650    ) -> Result<(Vec<u8>, V2Session)> {
651        let garbage = std::mem::take(&mut self.garbage);
652        let mut wire = self.ellswift.to_vec();
653        wire.extend_from_slice(&garbage);
654        wire.extend_from_slice(&self.session.send_garbage_terminator);
655        wire.extend_from_slice(&write_handshake_packets(
656            &mut self.session,
657            &garbage,
658            decoys,
659            version,
660        )?);
661        Ok((wire, self.session))
662    }
663}
664
665/// Initiator keys once the responder ellswift is known. `magic` is the network salt.
666pub fn initiator_session(
667    pending: &InitiatorPending,
668    responder_ellswift: &[u8],
669    magic: [u8; 4],
670) -> Result<V2Session> {
671    if responder_ellswift.len() != 64 {
672        return Err(protocol_err("ellswift is 64 bytes"));
673    }
674    let mut responder_ell64 = [0u8; 64];
675    responder_ell64.copy_from_slice(responder_ellswift);
676    let secret = shared_secret(
677        &pending.ellswift,
678        &responder_ell64,
679        &pending.private_key,
680        true,
681    )?;
682    Ok(session_keys(&secret, true, magic).into_session(true))
683}
684
685/// Terminator || version packet. The packet authenticates the garbage this side already sent.
686pub fn append_terminator_and_version(
687    session: &mut V2Session,
688    garbage: &[u8],
689    version: &[u8],
690) -> Result<Vec<u8>> {
691    append_handshake(session, garbage, &[], version)
692}
693
694/// Terminator, then optional decoys, then the version packet.
695/// The first packet authenticates `garbage`. Later packets use empty associated data.
696pub fn append_handshake(
697    session: &mut V2Session,
698    garbage: &[u8],
699    decoys: &[&[u8]],
700    version: &[u8],
701) -> Result<Vec<u8>> {
702    let mut tail = session.send_garbage_terminator.to_vec();
703    tail.extend_from_slice(&write_handshake_packets(session, garbage, decoys, version)?);
704    Ok(tail)
705}
706
707fn write_handshake_packets(
708    session: &mut V2Session,
709    garbage: &[u8],
710    decoys: &[&[u8]],
711    version: &[u8],
712) -> Result<Vec<u8>> {
713    let mut out = Vec::new();
714    let mut first = true;
715    for decoy in decoys {
716        let aad: &[u8] = if first { garbage } else { &[] };
717        first = false;
718        out.extend_from_slice(&session.encrypt_packet(decoy, aad, true)?);
719    }
720    let aad: &[u8] = if first { garbage } else { &[] };
721    out.extend_from_slice(&session.encrypt_packet(version, aad, false)?);
722    Ok(out)
723}
724
725/// Packets after the garbage terminator. Decoys are skipped. Only the first packet uses `garbage` as associated data.
726pub fn take_version_packet(
727    session: &mut V2Session,
728    mut bytes: &[u8],
729    garbage: &[u8],
730) -> Result<Vec<u8>> {
731    let mut first = true;
732    loop {
733        if bytes.len() < 3 + 1 + 16 {
734            return Err(protocol_err("handshake ended before the version packet"));
735        }
736        let contents_len = session.decrypt_length(&bytes[..3])?;
737        let total = 3 + 1 + contents_len + 16;
738        if bytes.len() < total {
739            return Err(protocol_err("truncated handshake packet"));
740        }
741        let aad: &[u8] = if first { garbage } else { &[] };
742        first = false;
743        let (contents, ignore) = session.decrypt_body(&bytes[3..total], aad)?;
744        if contents.len() != contents_len {
745            return Err(protocol_err("decrypted contents length mismatch"));
746        }
747        bytes = &bytes[total..];
748        if !ignore {
749            if !bytes.is_empty() {
750                return Err(protocol_err("bytes follow the version packet"));
751            }
752            return Ok(contents);
753        }
754    }
755}
756
757#[cfg(test)]
758mod tests {
759    use super::*;
760
761    #[test]
762    fn test_v2_transport_encrypt_decrypt() {
763        let key = [0x42; 32];
764        let mut transport = V2Transport::new(key, key);
765        let plaintext = b"Hello, Bitcoin!";
766        let encrypted = transport.encrypt(plaintext).unwrap();
767        let decrypted = transport.decrypt(&encrypted).unwrap();
768        assert_eq!(plaintext, decrypted.as_slice());
769    }
770
771    #[test]
772    fn test_v2_transport_rekey_round_trip() {
773        let key = [0x11; 32];
774        let mut enc = V2Transport::new(key, key);
775        let mut dec = V2Transport::new(key, key);
776        for i in 0..300 {
777            let pt = format!("msg{i}").into_bytes();
778            let pkt = enc.encrypt(&pt).unwrap();
779            let out = dec.decrypt(&pkt).unwrap();
780            assert_eq!(pt, out, "round-trip failed at i={i}");
781        }
782    }
783
784    #[test]
785    fn test_elligator_swift_encode_decode() {
786        let seckey = [0x01u8; 32];
787        let ell = ellswift_create(&seckey, None).expect("valid key");
788        assert_eq!(ell.len(), 64);
789        let shared = ellswift_xdh(&ell, &ell, &seckey, false);
790        assert!(shared.is_some(), "XDH should not fail on valid inputs");
791    }
792
793    #[test]
794    fn packet_encoding_matches_the_reference_vectors() {
795        use blvm_secp256k1::ecdsa::pubkey_from_secret;
796        use blvm_secp256k1::ellswift::{ellswift_decode_x, ellswift_ecdh_xonly, ellswift_xdh};
797        use blvm_secp256k1::scalar::Scalar;
798
799        let csv = include_str!("../tests/data/bip324/packet_encoding_test_vectors.csv");
800        let mut rows = 0;
801        for line in csv.lines().skip(1) {
802            if line.is_empty() {
803                continue;
804            }
805            let col: Vec<&str> = line.split(',').collect();
806            assert_eq!(col.len(), 22, "vector row has {} columns", col.len());
807            let initiating = col[4] == "1";
808            let priv_ours = hex32(col[1]);
809            let ell_ours = hex64(col[2]);
810            let ell_theirs = hex64(col[3]);
811            let mut scalar = Scalar::zero();
812            assert!(!scalar.set_b32(&priv_ours));
813            let point = pubkey_from_secret(&scalar);
814            let mut x = point.x;
815            x.normalize();
816            let mut x_ours = [0u8; 32];
817            x.get_b32(&mut x_ours);
818            assert_eq!(hex::encode(x_ours), col[9]);
819            assert_eq!(hex::encode(ellswift_decode_x(&ell_ours)), col[9]);
820            assert_eq!(hex::encode(ellswift_decode_x(&ell_theirs)), col[10]);
821            let x_shared = ellswift_ecdh_xonly(&ell_theirs, &priv_ours).unwrap();
822            assert_eq!(hex::encode(x_shared), col[11]);
823            let secret = if initiating {
824                ellswift_xdh(&ell_ours, &ell_theirs, &priv_ours, false).unwrap()
825            } else {
826                ellswift_xdh(&ell_theirs, &ell_ours, &priv_ours, true).unwrap()
827            };
828            assert_eq!(hex::encode(secret), col[12]);
829            let keys = session_keys(&secret, initiating, MAINNET_MAGIC);
830            assert_eq!(hex::encode(keys.initiator_l), col[13]);
831            assert_eq!(hex::encode(keys.initiator_p), col[14]);
832            assert_eq!(hex::encode(keys.responder_l), col[15]);
833            assert_eq!(hex::encode(keys.responder_p), col[16]);
834            assert_eq!(hex::encode(keys.send_garbage_terminator), col[17]);
835            assert_eq!(hex::encode(keys.recv_garbage_terminator), col[18]);
836            assert_eq!(hex::encode(keys.session_id), col[19]);
837            let mut transport = keys.transport(initiating);
838            let mut peer = session_keys(&secret, !initiating, MAINNET_MAGIC).transport(!initiating);
839            let idx: usize = col[0].parse().unwrap();
840            for _ in 0..idx {
841                let empty = transport.encrypt_packet(&[], &[], false).unwrap();
842                let (body, empty_ignore) = peer.decrypt_packet(&empty, &[]).unwrap();
843                assert!(body.is_empty() && !empty_ignore);
844            }
845            let multiply: usize = col[6].parse().unwrap();
846            let mut contents = hex::decode(col[5]).unwrap();
847            if multiply != 1 {
848                contents = contents.repeat(multiply);
849            }
850            let aad = hex::decode(col[7]).unwrap();
851            let ignore = col[8] == "1";
852            let ciphertext = transport.encrypt_packet(&contents, &aad, ignore).unwrap();
853            let got = hex::encode(&ciphertext);
854            if !col[20].is_empty() {
855                assert_eq!(got, col[20]);
856            }
857            if !col[21].is_empty() {
858                assert!(
859                    got.ends_with(col[21]),
860                    "ciphertext suffix mismatch at idx {idx}"
861                );
862            }
863            let (opened, opened_ignore) = peer.decrypt_packet(&ciphertext, &aad).unwrap();
864            assert_eq!(opened, contents);
865            assert_eq!(opened_ignore, ignore);
866            rows += 1;
867        }
868        assert_eq!(rows, 7);
869    }
870
871    #[test]
872    fn garbage_terminator_and_version_complete_the_handshake() {
873        let regtest = [0xfa, 0xbf, 0xb5, 0xda];
874        for (ours, theirs) in [(b"" as &[u8], b"" as &[u8]), (b"ig", b"rg-garbage")] {
875            let (opening, pending) = begin_initiator(ours).unwrap();
876            assert_eq!(&opening[64..], ours);
877            let responder = responder_accept(&opening[..64], theirs, regtest).unwrap();
878            let (wire, mut responder_session) = responder.send_version(b"resp-version").unwrap();
879            let mut initiator = initiator_session(&pending, &wire[..64], regtest).unwrap();
880            assert_eq!(initiator.session_id, responder_session.session_id);
881            assert_ne!(initiator.session_id, [0u8; 32]);
882            assert_eq!(
883                initiator.send_garbage_terminator,
884                responder_session.recv_garbage_terminator
885            );
886            let rest = &wire[64..];
887            assert!(rest.starts_with(theirs));
888            let term = initiator.recv_garbage_terminator;
889            assert_eq!(&rest[theirs.len()..theirs.len() + 16], &term);
890            let packet = &rest[theirs.len() + 16..];
891            assert!(
892                initiator
893                    .decrypt_packet(packet, b"not-the-garbage")
894                    .is_err()
895            );
896            let mut initiator = initiator_session(&pending, &wire[..64], regtest).unwrap();
897            let (version, ignore) = initiator.decrypt_packet(packet, theirs).unwrap();
898            assert_eq!(version, b"resp-version");
899            assert!(!ignore);
900            let tail =
901                append_terminator_and_version(&mut initiator, ours, b"init-version").unwrap();
902            assert_eq!(&tail[..16], &responder_session.recv_garbage_terminator);
903            let (version, ignore) = responder_session.decrypt_packet(&tail[16..], ours).unwrap();
904            assert_eq!(version, b"init-version");
905            assert!(!ignore);
906            let next = initiator.encrypt(b"ping").unwrap();
907            assert_eq!(responder_session.decrypt(&next).unwrap(), b"ping");
908        }
909    }
910
911    #[test]
912    fn a_decoy_authenticates_the_garbage_and_the_version_follows() {
913        let regtest = [0xfa, 0xbf, 0xb5, 0xda];
914        let (opening, pending) = begin_initiator(b"ig").unwrap();
915        let responder = responder_accept(&opening[..64], b"rg", regtest).unwrap();
916        let (wire, mut responder_session) = responder
917            .send_with_decoys(&[b"\x00"], b"resp-version")
918            .unwrap();
919        let mut initiator = initiator_session(&pending, &wire[..64], regtest).unwrap();
920        let packets = &wire[64 + 2 + 16..];
921        let version = take_version_packet(&mut initiator, packets, b"rg").unwrap();
922        assert_eq!(version, b"resp-version");
923        let tail = append_handshake(&mut initiator, b"ig", &[b"\x00"], b"init-version").unwrap();
924        let version = take_version_packet(&mut responder_session, &tail[16..], b"ig").unwrap();
925        assert_eq!(version, b"init-version");
926        let next = initiator.encrypt(b"ping").unwrap();
927        assert_eq!(responder_session.decrypt(&next).unwrap(), b"ping");
928
929        let (opening, pending) = begin_initiator(b"ig").unwrap();
930        let responder = responder_accept(&opening[..64], b"rg", regtest).unwrap();
931        let (wire, _) = responder
932            .send_with_decoys(&[b"\x00"], b"resp-version")
933            .unwrap();
934        let mut wrong = initiator_session(&pending, &wire[..64], regtest).unwrap();
935        assert!(take_version_packet(&mut wrong, &wire[64 + 2 + 16..], b"no").is_err());
936    }
937
938    #[test]
939    fn garbage_longer_than_4095_is_rejected() {
940        assert!(begin_initiator(&vec![0u8; GARBAGE_MAX + 1]).is_err());
941        assert!(begin_initiator(&vec![0u8; GARBAGE_MAX]).is_ok());
942    }
943
944    #[test]
945    fn a_different_network_magic_does_not_open_the_packet() {
946        let regtest = [0xfa, 0xbf, 0xb5, 0xda];
947        let (opening, pending) = begin_initiator(b"ig").unwrap();
948        let responder = responder_accept(&opening[..64], b"rg", regtest).unwrap();
949        let (wire, _) = responder.send_version(b"resp-version").unwrap();
950        let mut wrong = initiator_session(&pending, &wire[..64], MAINNET_MAGIC).unwrap();
951        let packet = &wire[64 + 2 + 16..];
952        assert!(wrong.decrypt_packet(packet, b"rg").is_err());
953    }
954
955    #[test]
956    fn the_v1_prefix_is_magic_plus_the_version_command() {
957        let prefix = v1_version_prefix(MAINNET_MAGIC);
958        assert_eq!(&prefix[..4], &MAINNET_MAGIC);
959        assert_eq!(&prefix[4..], b"version\x00\x00\x00\x00\x00");
960    }
961
962    fn hex32(s: &str) -> [u8; 32] {
963        hex::decode(s).unwrap().try_into().unwrap()
964    }
965
966    fn hex64(s: &str) -> [u8; 64] {
967        hex::decode(s).unwrap().try_into().unwrap()
968    }
969}