Skip to main content

netget/server/stratum_v2/
noise.rs

1//! The Stratum V2 secure channel, written from the specification (sv2-spec §4) rather than
2//! linked from the Stratum Reference Implementation, so that SRI's binaries are an independent
3//! peer: `Noise_NX_Secp256k1+EllSwift_ChaChaPoly_SHA256`, the authority-signed certificate the
4//! responder proves its static key with, and the transport framing that encrypts a frame header
5//! and its payload separately.
6//!
7//! What is borrowed is primitives only: libsecp256k1 (through the `secp256k1` crate) for the
8//! ElligatorSwift x-only ECDH and BIP 340 Schnorr signatures, `chacha20poly1305` for the AEAD and
9//! `sha2` for SHA-256. The handshake state machine, HMAC, HKDF, the BIP 324 tagged hash over the
10//! ECDH point, the certificate layout and the base58check key encoding are this file's own.
11use anyhow::{anyhow, bail, ensure, Context, Result};
12use chacha20poly1305::{
13    aead::{Aead, KeyInit, Payload},
14    ChaCha20Poly1305, Key, Nonce,
15};
16use secp256k1::{
17    ellswift::{ElligatorSwift, ElligatorSwiftParty, ElligatorSwiftSharedSecret},
18    schnorr::Signature,
19    Keypair, Message, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey,
20};
21use sha2::{Digest, Sha256};
22
23/// The Noise protocol name both sides hash into their initial state.
24pub const PROTOCOL_NAME: &str = "Noise_NX_Secp256k1+EllSwift_ChaChaPoly_SHA256";
25/// An ElligatorSwift-encoded public key.
26pub const ELLSWIFT_LEN: usize = 64;
27/// A ChaCha20-Poly1305 tag.
28pub const MAC_LEN: usize = 16;
29/// `SIGNATURE_NOISE_MESSAGE`: version, valid_from, not_valid_after, signature.
30pub const SIGNATURE_MESSAGE_LEN: usize = 2 + 4 + 4 + 64;
31/// `-> e`: the initiator's ephemeral key in the clear.
32pub const INITIATOR_MESSAGE_LEN: usize = ELLSWIFT_LEN;
33/// `<- e, ee, s, es`: ephemeral key, encrypted static key, encrypted certificate.
34pub const RESPONDER_MESSAGE_LEN: usize =
35    ELLSWIFT_LEN + ELLSWIFT_LEN + MAC_LEN + SIGNATURE_MESSAGE_LEN + MAC_LEN;
36/// The only certificate format version there is.
37pub const CERTIFICATE_VERSION: u16 = 0;
38/// Seconds of clock disagreement a certificate's validity window tolerates at either end.
39pub const CERTIFICATE_LEEWAY_SECS: u32 = 10;
40/// A Noise transport message is at most this long, MAC included.
41pub const MAX_CIPHERTEXT_CHUNK: usize = 65535;
42/// The plaintext one transport message carries at most.
43pub const MAX_PLAINTEXT_CHUNK: usize = MAX_CIPHERTEXT_CHUNK - MAC_LEN;
44/// The base58check version prefix of an authority public key (`[1, 0]`, a little-endian U16).
45pub const AUTHORITY_KEY_VERSION: u16 = 1;
46
47pub fn sha256(parts: &[&[u8]]) -> [u8; 32] {
48    let mut h = Sha256::new();
49    for p in parts {
50        h.update(p);
51    }
52    h.finalize().into()
53}
54
55/// HMAC-SHA256 with a 32-byte key, as the specification spells it out (RFC 2104).
56pub fn hmac_sha256(key: &[u8; 32], data: &[&[u8]]) -> [u8; 32] {
57    let mut ipad = [0x36u8; 64];
58    let mut opad = [0x5cu8; 64];
59    for i in 0..32 {
60        ipad[i] ^= key[i];
61        opad[i] ^= key[i];
62    }
63    let mut inner = Sha256::new();
64    inner.update(ipad);
65    for d in data {
66        inner.update(d);
67    }
68    let inner: [u8; 32] = inner.finalize().into();
69    sha256(&[&opad, &inner])
70}
71
72/// HKDF with two outputs and an empty info field (RFC 5869), the only form Noise uses.
73pub fn hkdf2(chaining_key: &[u8; 32], ikm: &[u8]) -> ([u8; 32], [u8; 32]) {
74    let temp = hmac_sha256(chaining_key, &[ikm]);
75    let out1 = hmac_sha256(&temp, &[&[0x01]]);
76    let out2 = hmac_sha256(&temp, &[&out1, &[0x02]]);
77    (out1, out2)
78}
79
80/// A key and a counter nonce. Without a key it passes data through, as Noise defines.
81#[derive(Clone)]
82pub struct CipherState {
83    key: Option<[u8; 32]>,
84    n: u64,
85}
86
87impl CipherState {
88    pub fn empty() -> Self {
89        Self { key: None, n: 0 }
90    }
91
92    pub fn with_key(key: [u8; 32]) -> Self {
93        Self {
94            key: Some(key),
95            n: 0,
96        }
97    }
98
99    /// Messages this state has encrypted or decrypted.
100    pub fn nonce_value(&self) -> u64 {
101        self.n
102    }
103
104    /// 32 zero bits, then the counter as a little-endian 64-bit value (the Noise convention).
105    fn nonce(&self) -> [u8; 12] {
106        let mut n = [0u8; 12];
107        n[4..].copy_from_slice(&self.n.to_le_bytes());
108        n
109    }
110
111    pub fn encrypt(&mut self, ad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>> {
112        let Some(key) = self.key else {
113            return Ok(plaintext.to_vec());
114        };
115        ensure!(self.n != u64::MAX, "Noise nonce exhausted");
116        let out = ChaCha20Poly1305::new(&Key::from(key))
117            .encrypt(
118                &Nonce::from(self.nonce()),
119                Payload {
120                    msg: plaintext,
121                    aad: ad,
122                },
123            )
124            .map_err(|_| anyhow!("Noise encryption failed"))?;
125        self.n += 1;
126        Ok(out)
127    }
128
129    /// Decrypt and authenticate; a bad MAC is an error and leaves the nonce where it was.
130    pub fn decrypt(&mut self, ad: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> {
131        let Some(key) = self.key else {
132            return Ok(ciphertext.to_vec());
133        };
134        ensure!(self.n != u64::MAX, "Noise nonce exhausted");
135        ensure!(
136            ciphertext.len() >= MAC_LEN,
137            "Noise message shorter than its MAC"
138        );
139        let out = ChaCha20Poly1305::new(&Key::from(key))
140            .decrypt(
141                &Nonce::from(self.nonce()),
142                Payload {
143                    msg: ciphertext,
144                    aad: ad,
145                },
146            )
147            .map_err(|_| anyhow!("Noise MAC check failed"))?;
148        self.n += 1;
149        Ok(out)
150    }
151}
152
153/// The handshake's running state: chaining key, handshake hash and the current cipher.
154struct Symmetric {
155    ck: [u8; 32],
156    h: [u8; 32],
157    cipher: CipherState,
158}
159
160impl Symmetric {
161    fn new() -> Self {
162        // The name is longer than a hash, so h = HASH(name); ck = h; then h = HASH(h).
163        let h = sha256(&[PROTOCOL_NAME.as_bytes()]);
164        Self {
165            ck: h,
166            h: sha256(&[&h]),
167            cipher: CipherState::empty(),
168        }
169    }
170
171    fn mix_hash(&mut self, data: &[u8]) {
172        self.h = sha256(&[&self.h, data]);
173    }
174
175    fn mix_key(&mut self, ikm: &[u8]) {
176        let (ck, k) = hkdf2(&self.ck, ikm);
177        self.ck = ck;
178        self.cipher = CipherState::with_key(k);
179    }
180
181    fn encrypt_and_hash(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
182        let h = self.h;
183        let ct = self.cipher.encrypt(&h, plaintext)?;
184        self.mix_hash(&ct);
185        Ok(ct)
186    }
187
188    fn decrypt_and_hash(&mut self, ciphertext: &[u8]) -> Result<Vec<u8>> {
189        let h = self.h;
190        let pt = self.cipher.decrypt(&h, ciphertext)?;
191        self.mix_hash(ciphertext);
192        Ok(pt)
193    }
194
195    /// (initiator → responder, responder → initiator).
196    fn split(&self) -> (CipherState, CipherState) {
197        let (k1, k2) = hkdf2(&self.ck, &[]);
198        (CipherState::with_key(k1), CipherState::with_key(k2))
199    }
200}
201
202/// A secp256k1 key and its ElligatorSwift encoding (randomised, so it looks like noise).
203pub struct EllKey {
204    secret: SecretKey,
205    ellswift: [u8; ELLSWIFT_LEN],
206}
207
208impl EllKey {
209    pub fn generate() -> Self {
210        loop {
211            if let Ok(secret) = SecretKey::from_slice(&rand::random::<[u8; 32]>()) {
212                return Self::from_secret(secret);
213            }
214        }
215    }
216
217    pub fn from_secret(secret: SecretKey) -> Self {
218        // Encoding needs a context with generator tables, which `ElligatorSwift::new` lacks.
219        let ellswift =
220            ElligatorSwift::from_seckey(&Secp256k1::new(), secret, Some(rand::random())).to_array();
221        Self { secret, ellswift }
222    }
223
224    pub fn ellswift(&self) -> [u8; ELLSWIFT_LEN] {
225        self.ellswift
226    }
227
228    /// The 32-byte x-only (BIP 340) form, which is what a certificate signs.
229    pub fn xonly(&self) -> [u8; 32] {
230        self.secret
231            .x_only_public_key(&Secp256k1::signing_only())
232            .0
233            .serialize()
234    }
235
236    /// `v2_ecdh` (BIP 324): the x coordinate of the shared point from libsecp256k1, hashed here
237    /// with the initiator's encoding first whichever side computes it.
238    fn ecdh(&self, theirs: &[u8; ELLSWIFT_LEN], initiator: bool) -> [u8; 32] {
239        let ours = ElligatorSwift::from_array(self.ellswift);
240        let other = ElligatorSwift::from_array(*theirs);
241        let (a, b, party) = if initiator {
242            (ours, other, ElligatorSwiftParty::A)
243        } else {
244            (other, ours, ElligatorSwiftParty::B)
245        };
246        let mut x32 = [0u8; 32];
247        ElligatorSwift::shared_secret_with_hasher(a, b, self.secret, party, |x, _, _| {
248            x32 = x;
249            ElligatorSwiftSharedSecret::from_secret_bytes([0u8; 32])
250        });
251        let (ell_a, ell_b) = if initiator {
252            (self.ellswift, *theirs)
253        } else {
254            (*theirs, self.ellswift)
255        };
256        let tag = sha256(&[b"bip324_ellswift_xonly_ecdh"]);
257        sha256(&[&tag, &tag, &ell_a, &ell_b, &x32])
258    }
259}
260
261/// The x-only key an ElligatorSwift encoding decodes to.
262pub fn ellswift_xonly(ell: &[u8; ELLSWIFT_LEN]) -> [u8; 32] {
263    PublicKey::from_ellswift(ElligatorSwift::from_array(*ell))
264        .x_only_public_key()
265        .0
266        .serialize()
267}
268
269/// The pool's certificate authority: signs the static key a responder presents.
270pub struct Authority {
271    keypair: Keypair,
272}
273
274impl Authority {
275    pub fn generate() -> Self {
276        let secp = Secp256k1::new();
277        loop {
278            if let Ok(secret) = SecretKey::from_slice(&rand::random::<[u8; 32]>()) {
279                return Self {
280                    keypair: Keypair::from_secret_key(&secp, &secret),
281                };
282            }
283        }
284    }
285
286    pub fn from_secret_bytes(bytes: &[u8]) -> Result<Self> {
287        let secret = SecretKey::from_slice(bytes).context("not a valid secp256k1 secret key")?;
288        Ok(Self {
289            keypair: Keypair::from_secret_key(&Secp256k1::new(), &secret),
290        })
291    }
292
293    pub fn public_xonly(&self) -> [u8; 32] {
294        self.keypair.x_only_public_key().0.serialize()
295    }
296
297    /// `SIGNATURE_NOISE_MESSAGE` for a static key valid over [valid_from, not_valid_after].
298    pub fn certify(
299        &self,
300        server_xonly: &[u8; 32],
301        valid_from: u32,
302        not_valid_after: u32,
303    ) -> [u8; SIGNATURE_MESSAGE_LEN] {
304        let mut out = [0u8; SIGNATURE_MESSAGE_LEN];
305        out[0..2].copy_from_slice(&CERTIFICATE_VERSION.to_le_bytes());
306        out[2..6].copy_from_slice(&valid_from.to_le_bytes());
307        out[6..10].copy_from_slice(&not_valid_after.to_le_bytes());
308        let m = certificate_digest(&out[0..10], server_xonly);
309        let sig = Secp256k1::signing_only().sign_schnorr_with_aux_rand(
310            &Message::from_digest(m),
311            &self.keypair,
312            &rand::random(),
313        );
314        out[10..].copy_from_slice(sig.as_ref());
315        out
316    }
317}
318
319/// m = SHA-256(version || valid_from || not_valid_after || server_public_key).
320fn certificate_digest(signed_head: &[u8], server_xonly: &[u8; 32]) -> [u8; 32] {
321    sha256(&[signed_head, server_xonly])
322}
323
324/// What the responder's certificate said, once it verified.
325#[derive(Debug, Clone)]
326pub struct Certificate {
327    pub version: u16,
328    pub valid_from: u32,
329    pub not_valid_after: u32,
330    pub server_xonly: [u8; 32],
331}
332
333/// Verify a certificate against the authority key the initiator was configured with.
334pub fn verify_certificate(
335    message: &[u8; SIGNATURE_MESSAGE_LEN],
336    server_xonly: &[u8; 32],
337    authority: &[u8; 32],
338    now: u32,
339) -> Result<Certificate> {
340    let version = u16::from_le_bytes([message[0], message[1]]);
341    let valid_from = u32::from_le_bytes(message[2..6].try_into().expect("4 bytes"));
342    let not_valid_after = u32::from_le_bytes(message[6..10].try_into().expect("4 bytes"));
343    ensure!(
344        version == CERTIFICATE_VERSION,
345        "certificate version {version} is not supported"
346    );
347    ensure!(
348        valid_from.saturating_sub(CERTIFICATE_LEEWAY_SECS) <= now
349            && now <= not_valid_after.saturating_add(CERTIFICATE_LEEWAY_SECS),
350        "certificate is valid from {valid_from} to {not_valid_after}, and it is {now}"
351    );
352    let authority = XOnlyPublicKey::from_slice(authority).context("authority key is invalid")?;
353    let sig =
354        Signature::from_slice(&message[10..]).context("certificate signature is malformed")?;
355    let m = certificate_digest(&message[0..10], server_xonly);
356    Secp256k1::verification_only()
357        .verify_schnorr(&sig, &Message::from_digest(m), &authority)
358        .map_err(|_| anyhow!("certificate is not signed by the configured authority"))?;
359    Ok(Certificate {
360        version,
361        valid_from,
362        not_valid_after,
363        server_xonly: *server_xonly,
364    })
365}
366
367/// The two directions of an established session.
368pub struct Session {
369    pub send: CipherState,
370    pub recv: CipherState,
371}
372
373/// Responder side: answer `-> e` with `<- e, ee, s, es` and the certificate as its payload.
374pub fn respond(
375    initiator_message: &[u8; INITIATOR_MESSAGE_LEN],
376    static_key: &EllKey,
377    authority: &Authority,
378    validity_secs: u32,
379    now: u32,
380) -> Result<([u8; RESPONDER_MESSAGE_LEN], Session)> {
381    let mut sym = Symmetric::new();
382    let re = *initiator_message;
383    sym.mix_hash(&re);
384    sym.decrypt_and_hash(&[])?;
385
386    let e = EllKey::generate();
387    let mut out = Vec::with_capacity(RESPONDER_MESSAGE_LEN);
388    out.extend_from_slice(&e.ellswift());
389    sym.mix_hash(&e.ellswift());
390    sym.mix_key(&e.ecdh(&re, false));
391    out.extend_from_slice(&sym.encrypt_and_hash(&static_key.ellswift())?);
392    sym.mix_key(&static_key.ecdh(&re, false));
393    let certificate =
394        authority.certify(&static_key.xonly(), now, now.saturating_add(validity_secs));
395    out.extend_from_slice(&sym.encrypt_and_hash(&certificate)?);
396    let (c1, c2) = sym.split();
397    let message: [u8; RESPONDER_MESSAGE_LEN] = out
398        .try_into()
399        .map_err(|_| anyhow!("responder message has the wrong length"))?;
400    Ok((message, Session { send: c2, recv: c1 }))
401}
402
403/// Initiator side, between its two messages.
404pub struct Initiator {
405    sym: Symmetric,
406    e: EllKey,
407}
408
409impl Initiator {
410    /// The state and the `-> e` message to send.
411    pub fn start() -> Result<(Self, [u8; INITIATOR_MESSAGE_LEN])> {
412        let mut sym = Symmetric::new();
413        let e = EllKey::generate();
414        sym.mix_hash(&e.ellswift());
415        sym.encrypt_and_hash(&[])?;
416        let msg = e.ellswift();
417        Ok((Self { sym, e }, msg))
418    }
419
420    /// Process `<- e, ee, s, es`, then check the certificate before trusting anything.
421    pub fn finish(
422        mut self,
423        message: &[u8; RESPONDER_MESSAGE_LEN],
424        authority: &[u8; 32],
425        now: u32,
426    ) -> Result<(Session, Certificate)> {
427        let re: [u8; ELLSWIFT_LEN] = message[..ELLSWIFT_LEN].try_into().expect("64 bytes");
428        self.sym.mix_hash(&re);
429        self.sym.mix_key(&self.e.ecdh(&re, true));
430        let rs = self
431            .sym
432            .decrypt_and_hash(&message[ELLSWIFT_LEN..ELLSWIFT_LEN * 2 + MAC_LEN])
433            .context("the responder's static key did not authenticate")?;
434        let rs: [u8; ELLSWIFT_LEN] = rs
435            .try_into()
436            .map_err(|_| anyhow!("static key has the wrong length"))?;
437        self.sym.mix_key(&self.e.ecdh(&rs, true));
438        let cert = self
439            .sym
440            .decrypt_and_hash(&message[ELLSWIFT_LEN * 2 + MAC_LEN..])
441            .context("the responder's certificate did not authenticate")?;
442        let cert: [u8; SIGNATURE_MESSAGE_LEN] = cert
443            .try_into()
444            .map_err(|_| anyhow!("certificate has the wrong length"))?;
445        let certificate = verify_certificate(&cert, &ellswift_xonly(&rs), authority, now)?;
446        let (c1, c2) = self.sym.split();
447        Ok((Session { send: c1, recv: c2 }, certificate))
448    }
449}
450
451const BASE58: &[u8; 58] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
452
453/// Base58 with a four-byte double-SHA-256 checksum.
454pub fn base58check_encode(payload: &[u8]) -> String {
455    let check = sha256(&[&sha256(&[payload])]);
456    let mut data = payload.to_vec();
457    data.extend_from_slice(&check[..4]);
458    let zeros = data.iter().take_while(|b| **b == 0).count();
459    let mut digits: Vec<u8> = Vec::new();
460    for &byte in &data {
461        let mut carry = u32::from(byte);
462        for d in digits.iter_mut() {
463            carry += u32::from(*d) << 8;
464            *d = (carry % 58) as u8;
465            carry /= 58;
466        }
467        while carry > 0 {
468            digits.push((carry % 58) as u8);
469            carry /= 58;
470        }
471    }
472    let mut out = String::with_capacity(zeros + digits.len());
473    out.extend(std::iter::repeat_n('1', zeros));
474    out.extend(digits.iter().rev().map(|d| BASE58[*d as usize] as char));
475    out
476}
477
478pub fn base58check_decode(text: &str) -> Result<Vec<u8>> {
479    ensure!(text.len() <= 128, "base58 string is too long");
480    let zeros = text.bytes().take_while(|b| *b == b'1').count();
481    let mut bytes: Vec<u8> = Vec::new();
482    for c in text.bytes() {
483        let Some(value) = BASE58.iter().position(|b| *b == c) else {
484            bail!("{:?} is not a base58 character", c as char);
485        };
486        let mut carry = value as u32;
487        for b in bytes.iter_mut() {
488            carry += u32::from(*b) * 58;
489            *b = (carry & 0xff) as u8;
490            carry >>= 8;
491        }
492        while carry > 0 {
493            bytes.push((carry & 0xff) as u8);
494            carry >>= 8;
495        }
496    }
497    let mut data = vec![0u8; zeros];
498    data.extend(bytes.iter().rev());
499    ensure!(data.len() >= 4, "base58check string is too short");
500    let (payload, check) = data.split_at(data.len() - 4);
501    ensure!(
502        sha256(&[&sha256(&[payload])])[..4] == *check,
503        "base58check checksum does not match"
504    );
505    Ok(payload.to_vec())
506}
507
508/// An authority public key as pools publish it: base58check of `[1, 0] || x-only key`.
509pub fn encode_authority_key(xonly: &[u8; 32]) -> String {
510    let mut payload = AUTHORITY_KEY_VERSION.to_le_bytes().to_vec();
511    payload.extend_from_slice(xonly);
512    base58check_encode(&payload)
513}
514
515pub fn decode_authority_key(text: &str) -> Result<[u8; 32]> {
516    let payload = base58check_decode(text.trim())?;
517    ensure!(
518        payload.len() == 34,
519        "an authority public key is 34 bytes once decoded"
520    );
521    let version = u16::from_le_bytes([payload[0], payload[1]]);
522    ensure!(
523        version == AUTHORITY_KEY_VERSION,
524        "authority key version {version} is not supported"
525    );
526    let key: [u8; 32] = payload[2..].try_into().expect("32 bytes");
527    XOnlyPublicKey::from_slice(&key).context("authority key is not a secp256k1 point")?;
528    Ok(key)
529}
530
531/// An authority secret key as SRI's configuration writes it: base58check of the 32 bytes.
532pub fn decode_secret_key(text: &str) -> Result<Authority> {
533    let payload = base58check_decode(text.trim())?;
534    ensure!(payload.len() == 32, "a secret key is 32 bytes once decoded");
535    Authority::from_secret_bytes(&payload)
536}
537
538pub fn encode_secret_key(bytes: &[u8; 32]) -> String {
539    base58check_encode(bytes)
540}
541
542impl Authority {
543    pub fn secret_bytes(&self) -> [u8; 32] {
544        self.keypair.secret_bytes()
545    }
546}