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}