code wiki / (root) / x25519_ephemeral.nx

x25519_ephemeral.nx source

↩ module page · 82 lines · 3169 B

1// x25519_ephemeral.nx -- ephemeral keypair helper for X25519 ECDH. 2// 3// Higher-level wrapper around x25519.nx for the common TLS 1.3 4// handshake pattern: 5// 1. Client: generate ephemeral private key from /dev/urandom 6// 2. Client: derive public key = X25519(private_key, base_point) 7// 3. Client: send public key in ClientHello key_share extension 8// 4. Server: receive public key, generate its own keypair 9// 5. Both: shared_secret = X25519(my_private, peer_public) 10// 11// Curve25519 base point (RFC 7748 ยง4.1): u = 9, encoded as 12// 0x09 followed by 31 zero bytes. Documented here as a constant 13// table to avoid re-construction per call. 14// 15// Invariants: 16// XE1 Private key comes exclusively from rand_bytes 17// (/dev/urandom); never derived, never stored across 18// process lifetime. 19// XE2 Public key derivation uses the same X25519 function as 20// shared-secret computation, keeping one implementation 21// path (no specialised base-point scalar mul that could 22// diverge). 23// XE3 Caller owns all buffers; no persistent state across 24// calls. 25// XE4 Base point represented as a read-only 32-byte constant; 26// derivation is deterministic given the private key. 27 28import "syscalls.nx" 29import "rand.nx" 30import "x25519.nx" 31 32// Write the Curve25519 base point (u = 9) into `out`. 33func x25519_base_point(out: *u8) -> i64 { 34 out[0] = 0x09 35 var i: i64 = 1 36 while i < 32 { out[i] = 0; i = i + 1 } 37 return 0 38} 39 40// Generate a fresh ephemeral private key (32 random bytes). 41// RFC 7748 says the scalar clamp is applied inside x25519(); 42// we don't pre-clamp here. 43func x25519_keypair_private(priv_out: *u8) -> i64 { 44 let n: i64 = rand_bytes(priv_out, 32) 45 if n != 32 { return -1 } 46 return 0 47} 48 49// Derive the public key for a private key: pub = X25519(priv, base). 50func x25519_keypair_public(priv: *u8, pub_out: *u8) -> i64 { 51 let base: *u8 = sys_mmap(32) 52 x25519_base_point(base) 53 return x25519(priv, base, pub_out) 54} 55 56// Complete ephemeral keypair generation: fresh private + derived 57// public. Caller supplies 32-byte output buffers for each. 58func x25519_gen_keypair(priv_out: *u8, pub_out: *u8) -> i64 { 59 let rc: i64 = x25519_keypair_private(priv_out) 60 if rc < 0 { return rc } 61 return x25519_keypair_public(priv_out, pub_out) 62} 63 64// Compute the shared secret: my_private * peer_public via X25519. 65// The result is suitable as HKDF input key material for TLS 1.3's 66// key schedule; don't use it directly as a symmetric key without 67// running it through HKDF-Extract first. 68func x25519_shared_secret(my_priv: *u8, peer_pub: *u8, 69 secret_out: *u8) -> i64 { 70 return x25519(my_priv, peer_pub, secret_out) 71} 72 73// Compile-only smoke. 74func main() -> i64 { 75 let priv: *u8 = sys_mmap(32) 76 let pub: *u8 = sys_mmap(32) 77 x25519_gen_keypair(priv, pub) 78 // Verify that generating two keypairs produces different 79 // privates (non-deterministic by /dev/urandom). Can't fully 80 // check without execution; just verify compile. 81 return pub[0] as i64 82}