nx_p256_ecdh.nx
buildroot/runtime/nx_p256_ecdh.nx
about
nx_p256_ecdh.nx -- ECDH key exchange on P-256 (secp256r1) for the
TLS 1.3 key_share path (browser arc rung B4-P256-KEYSHARE).
The P-256 stack (field, point, scalar_mul) shipped for ECDSA cert
VERIFICATION; this module composes the same primitives into the
ECDHE role so the ClientHello can carry a secp256r1 KeyShareEntry
next to the X25519 one. Servers that insist on P-256 (the AWS-
fronted class behind the B2-HTTPBIN-CH alert) then never need a
HelloRetryRequest: they just pick our P-256 share.
Public API:
p256_ecdh_derive_priv(seed32, out_priv32) -> verdict
Domain-separated ephemeral: SHA-256("NISHI-TLS13-P256-ECDH-V1"
|| seed32), reduced mod n, forced nonzero. Lets the session
grow a P-256 keypair from the caller's existing 32 random
bytes without changing the session-new entropy contract.
p256_ecdh_pub(priv32, out_pub65) -> verdict
out = 0x04 || X || Y (RFC 8446 wire form for secp256r1).
p256_ecdh_shared(priv32, peer_pub65, peer_len, out_shared32) -> verdict
Validates peer point (format, on-curve, not-infinity) then
writes the x-coordinate of priv * peer as the 32-byte shared
secret (RFC 8446 section 7.4.2).
Variable-time scalar_mul note: the scalar here is an EPHEMERAL
per-session secret, unlike ECDSA verify's public scalars. The
shipped double-and-add is variable-time; constant-time ladder is
a named follow-up rung (same lane as the x25519 ladder), accepted
for B4 because the scalar lives for one handshake.
KAT: RFC 5903 section 8.1 vectors in nx_p256_keyshare_test.nx
(vectors re-read from rfc-editor.org 2026-06-10, not from memory).
license_tier: INDEPENDENT_REDERIVE
genealogy_id: international-research-sources/ietf/rfc_5903 + ietf/rfc_8446
lineage_id: nishi_p256_ecdh_q10
dependencies 8 imports · 11 importers
diagram shows first 10 each side; +0 more imports, +1 more importers in the complete lists below.
imports: nx_syscalls.nxnx_sha256.nxnx_u256.nxnx_p256_field.nxnx_p256_point.nxnx_p256_point_add.nxnx_p256_scalar_mul.nxnx_p256_modn.nx
imported by: nx_mtls_ecdsa.nxnx_p256_keyshare_test.nxnx_tls12_client.nxnx_tls12_client_session.nxnx_tls12_mutant.nxnx_tls12_req.nxnx_tls13_client_session.nxnx_tls13_client_session_recv_sh.nxnx_tls13_clientauth_ecdsa.nxnx_tls13_server_session.nxnx_tls13_server_session_derive_hs.nx
structs
| none |
consts
| 46 | const NX_P256_ECDH_OK: i64 = 1 |
| 47 | const NX_P256_ECDH_BAD_PRIV: i64 = 2 // scalar zero or >= n |
| 48 | const NX_P256_ECDH_BAD_POINT: i64 = 3 // format/on-curve/infinity reject |
| 49 | const NX_P256_ECDH_VERDICT_N: i64 = 4 |
functions
| 51 | func nx_p256_ecdh_verdict_is_valid(v: i64) -> i64 |
| 59 | func _p256_ecdh_load_scalar(priv32: *u8, out_k: *i64) -> i64 called by 2: p256_ecdh_pubp256_ecdh_shared calls 5: u256_load_beu256_is_zerou256_allocp256_modn_load_nu256_cmp |
| 72 | func p256_ecdh_derive_priv(seed32: *u8, out_priv32: *u8) -> i64 |
| 93 | func p256_ecdh_pub(priv32: *u8, out_pub65: *u8) -> i64 |
| 115 | func p256_ecdh_shared( |