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}