code wiki / (root) / nx_x25519_ephemeral.nx

nx_x25519_ephemeral.nx source

↩ module page · 92 lines · 3453 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// 28// license_tier: INDEPENDENT_REDERIVE 29// genealogy_id: international-research-sources/ietf/rfc_7748 30// 31 32// nx_safety_envelope: 33// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 34// sil_target: SIL1 35// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 36// verdict: NOT_YET_EVALUATED 37 38import "nx_syscalls.nx" 39import "nx_rand.nx" 40import "nx_x25519.nx" 41 42// Write the Curve25519 base point (u = 9) into `out`. 43func x25519_base_point(out: *u8) -> i64 { 44 out[0] = 0x09 45 var i: i64 = 1 46 while i < 32 { out[i] = 0; i = i + 1 } 47 return 0 48} 49 50// Generate a fresh ephemeral private key (32 random bytes). 51// RFC 7748 says the scalar clamp is applied inside x25519(); 52// we don't pre-clamp here. 53func x25519_keypair_private(priv_out: *u8) -> i64 { 54 let n: i64 = rand_bytes(priv_out, 32) 55 if n != 32 { return -1 } 56 return 0 57} 58 59// Derive the public key for a private key: pub = X25519(priv, base). 60func x25519_keypair_public(priv: *u8, pub_out: *u8) -> i64 { 61 let base: *u8 = sys_mmap(32) 62 x25519_base_point(base) 63 return x25519(priv, base, pub_out) 64} 65 66// Complete ephemeral keypair generation: fresh private + derived 67// public. Caller supplies 32-byte output buffers for each. 68func x25519_gen_keypair(priv_out: *u8, pub_out: *u8) -> i64 { 69 let rc: i64 = x25519_keypair_private(priv_out) 70 if rc < 0 { return rc } 71 return x25519_keypair_public(priv_out, pub_out) 72} 73 74// Compute the shared secret: my_private * peer_public via X25519. 75// The result is suitable as HKDF input key material for TLS 1.3's 76// key schedule; don't use it directly as a symmetric key without 77// running it through HKDF-Extract first. 78func x25519_shared_secret(my_priv: *u8, peer_pub: *u8, 79 secret_out: *u8) -> i64 { 80 return x25519(my_priv, peer_pub, secret_out) 81} 82 83// Compile-only smoke. 84func main() -> i64 { 85 let priv: *u8 = sys_mmap(32) 86 let pub: *u8 = sys_mmap(32) 87 x25519_gen_keypair(priv, pub) 88 // Verify that generating two keypairs produces different 89 // privates (non-deterministic by /dev/urandom). Can't fully 90 // check without execution; just verify compile. 91 return pub[0] as i64 92}