| 1 |
std.crypto.sha256 / sha512 |
— |
Streaming Sha256 / Sha512 types with init / update / finalize + convenience sha256(span) → [32]u8. Wrap vetted C. |
NIST FIPS 180-4 + CAVP byte-test vectors |
native/zero-c/runtime/zero_crypto_sha2.c (new), native/zero-c/src/std_sig.c, docs/articles/modules/crypto.md, conformance/run.mjs cases |
~400 |
File hashing, Git-style blob IDs, ETag generation, content addressing |
| 2 |
std.crypto.hmacSha256 / hmacSha512 |
PR 1 |
Pure HMAC construction on top of PR 1; same streaming shape. |
RFC 4231 §4.2–4.8 |
zero_crypto_hmac.c (new), std_sig.c, doc, conformance |
~150 |
GitHub/Stripe webhook signature verify, JWT HS256/HS512, AWS Signature v4 |
| 3 |
std.crypto.hkdfExtract / hkdfExpand |
PR 1 + PR 2 (transitive) |
Two-stage HKDF for SHA-256; SHA-512 variant follows. |
RFC 5869 Appendix A test cases 1–7 |
zero_crypto_hkdf.c (new), std_sig.c, doc, conformance |
~120 |
TLS-style key schedule, secure cookie keying, deterministic sub-key derivation |
| 4 |
std.crypto.keccak256 + sha3_256 |
— (independent of SHA-2 chain) |
Both pre-NIST Keccak (Ethereum) and NIST SHA-3 padding flavors. |
NIST FIPS 202 + ethereum/tests GeneralStateTests + eth-hash fixtures |
zero_crypto_keccak.c (new), std_sig.c, doc, conformance |
~350 |
Ethereum address derivation, EVM function selectors, IPFS CID v1 with keccak |
| 5 |
std.crypto.secp256k1Sign + Verify + Recover + secureRandomBytes |
PR 1 (SHA-256 for hashing) |
Wrap libsecp256k1. Must pass secp256k1_nonce_function_rfc6979 explicitly to secp256k1_ecdsa_sign — the default nonce function on some build configurations falls back to OS entropy without RFC 6979 hardening, which leaks the key on entropy failure. secureRandomBytes(MutSpan<u8>) ships here because key generation needs ≥256 bits of entropy and secureRandomU32 only gives 32. |
Wycheproof ecdsa_secp256k1_sha256_test.json (Google's adversarial test suite, language-neutral JSON, hundreds of ECDSA cases including known edge cases like high-S, leading zeros, malleability) + libsecp256k1's own src/modules/ecdsa/tests/ |
zero_crypto_secp256k1.c (new wrapper), std_sig.c, doc, conformance, vendored libsecp256k1 under native/zero-c/runtime/vendor/secp256k1/ (~20–30k LOC of standalone C — reviewed as "do we trust the Bitcoin Core team?" not line-by-line) |
~300 Zero/glue |
Bitcoin/Ethereum/Lightning signing, EIP-191 personal_sign, ECDSA webhook auth |
| 6 |
std.crypto.eip712Digest (+ supporting std.mem.writeU64BE / writeU32BE intrinsics) |
PR 4 (Keccak) |
Produces the 32-byte EIP-712 digest. Encoding requires byte-level decomposition of integers, which without bitwise ops needs new std.mem.writeU{32,64}BE intrinsics (~30 LOC each). First form accepts bytes32 / address / bool / uintN<=64; arbitrary uint256 fields deferred to a follow-up gated on bigint or callers supplying pre-encoded [32]u8. Signing is a separate secp256k1Sign call (PR 5) — splits cleanly so a digest can be relayed to a hardware wallet. |
EIP-712 "Mail" reference + ethereum/eth-abi-utils typed-data fixtures + Foundry forge-std EIP712.t.sol; cross-check digests against ethers.js TypedDataEncoder |
std/eip712.0 (new, source-backed — pure structural encoding using PR 4 hashes + new writeBE intrinsics), updates to zero_runtime.c for the BE-writes, doc, conformance |
~300 |
EIP-712 typed-data signing — Permit2, OpenSea-style orders, gasless meta-transactions, most modern dapp signature flows |
| 7a (optional) |
std.crypto.macaroon |
PR 2 |
L402 / payment-auth scaffolding mirroring libmacaroons' ~10-function surface. |
Macaroons paper §3 test fixtures; libmacaroons reference impl |
TBD |
TBD |
L402 HTTP 402 payment auth, capability tokens with caveats |
| 7b (optional) |
std.crypto.x25519 + ed25519 |
— (curve25519 family, independent) |
Wrap libsodium. Decoupled from the secp256k1 chain — different curve, different use cases (Noise / age / WireGuard / Signal-style). |
RFC 7748 §6.1 + RFC 8032 §7.1 |
TBD |
TBD |
Noise protocols, age encryption, modern non-Ethereum signing |
| 7c (optional, high-leverage) |
std.codec.bech32 + bech32m |
— (no crypto deps; just polymod) |
Bech32 / bech32m encoding (BIP-173 / BIP-350). No bitwise required for the polymod — it's table-driven mod arithmetic over u32 (verified against std/math.0 operator set). Naturally source-backed; lives in std.codec not std.crypto. |
BIP-173 §6 test vectors + BIP-350 reference vectors |
std/bech32.0 (new, source-backed), docs/articles/modules/codec.md, conformance |
~180 |
Lightning invoice addressing (BOLT11 prefix lnbc...), Lightning Address local-part, modern Bitcoin addresses (bc1...), NIP-19 Nostr entities |
| 7d (optional, high-leverage) |
std.bolt11.decode (+ optional encode) |
PR 1 (SHA-256) + PR 5 (secp256k1 recover) + PR 7c (bech32) |
BOLT11 invoice decoder — extract amount, payment hash, payee pubkey (via signature recovery), routing hints, expiry. Most consumers only need decode; encode gated on signer availability. |
BOLT11 spec test vectors |
std/bolt11.0 (new, source-backed; pure structural decode + intrinsic hash/recover calls), docs/articles/modules/bolt11.md (new), conformance |
~250 |
Any Lightning Network client work — invoice display, amount/payee verification before payment, payment-hash extraction for HTLC tracking |
Filing this as a roadmap-shaped proposal rather than a single design issue, because the underlying capability (real digests, HMAC/HKDF, Keccak, secp256k1, EIP-712) splits cleanly into ~6 mergeable PRs of <500 LOC each, and the order is forced by dependencies that shouldn't have to be re-derived later. No ask for immediate action — same posture as #47 took before PR #64 landed — but recording the sequence now lets any single PR be picked up in isolation when stdlib expansion next resumes after the 2026-05-25
std.math/std.path/std.strtriple.Load-bearing architectural choice up front:
std.cryptotoday exposeshash32/hmac32/constantTimeEql/secureRandomU32and is explicitly "not a full cryptography suite" (crypto.md). Source-backed expansion would be the natural shape matching PR #273'sstd.mathprecedent — except Zero has no surface-level bitwise operators today (verified: no^ & | << >> ~instd/math.0,std/str.0,std/path.0, no token names innative/zero-c/src/parser.c/checker.c/ir.c). Every hash in this roadmap is XOR + rotate + shift at its core, so pure.0source-backed implementations are blocked on a language change. Recommendation throughout: runtime intrinsics following thezero_http_curl.cpattern — typedstd.crypto.*signatures innative/zero-c/src/std_sig.c, C bodies in newnative/zero-c/runtime/zero_crypto_*.cfiles. For SHA-2 / HMAC / HKDF / Keccak the C body can wrap any vetted source (BoringSSLcrypto/, libsodium, HACL*). For secp256k1, libsecp256k1 is the only mainstream vetted option — BoringSSL and libsodium don't ship it. Rolling crypto in source carries CVE risk that the README's "Security vulnerabilities should be expected" posture acknowledges but applications touching real money shouldn't take on.Motivation
satringis the concrete external project driving this — a payment-protocol client that talks to three rails (Ethereum/EIP-712, Lightning, and an HTTPS REST provider). Two of the three need primitivesstd.cryptodoesn't expose today. The same primitives unlock a much wider class of agent-facing apps:std?std.http.fetchreceive sidestd.codecfor base64urlpersonal_sign, EIP-712 typed data)std.codec,std.memstd.http.fetch,std.parsestd.fs,std.memstd.http.fetchThe roadmap below sequences low-risk wins (SHA-2, HMAC, HKDF) before higher-risk ECC, and gates each PR on explicit dependencies so no PR ships an orphaned API or blocks on a not-yet-merged sibling.
PR roadmap
Each row is sized to (a) compile and add user-visible value alone, (b) not orphan APIs if the next slips, (c) stay under ~500 LOC of diff.
std.crypto.sha256/sha512Sha256/Sha512types withinit / update / finalize+ conveniencesha256(span) → [32]u8. Wrap vetted C.native/zero-c/runtime/zero_crypto_sha2.c(new),native/zero-c/src/std_sig.c,docs/articles/modules/crypto.md,conformance/run.mjscasesstd.crypto.hmacSha256/hmacSha512zero_crypto_hmac.c(new),std_sig.c, doc, conformancestd.crypto.hkdfExtract/hkdfExpandzero_crypto_hkdf.c(new),std_sig.c, doc, conformancestd.crypto.keccak256+sha3_256GeneralStateTests+ eth-hash fixtureszero_crypto_keccak.c(new),std_sig.c, doc, conformancestd.crypto.secp256k1Sign+Verify+Recover+secureRandomBytessecp256k1_nonce_function_rfc6979explicitly tosecp256k1_ecdsa_sign— the default nonce function on some build configurations falls back to OS entropy without RFC 6979 hardening, which leaks the key on entropy failure.secureRandomBytes(MutSpan<u8>)ships here because key generation needs ≥256 bits of entropy andsecureRandomU32only gives 32.ecdsa_secp256k1_sha256_test.json(Google's adversarial test suite, language-neutral JSON, hundreds of ECDSA cases including known edge cases like high-S, leading zeros, malleability) + libsecp256k1's ownsrc/modules/ecdsa/tests/zero_crypto_secp256k1.c(new wrapper),std_sig.c, doc, conformance, vendored libsecp256k1 undernative/zero-c/runtime/vendor/secp256k1/(~20–30k LOC of standalone C — reviewed as "do we trust the Bitcoin Core team?" not line-by-line)personal_sign, ECDSA webhook authstd.crypto.eip712Digest(+ supportingstd.mem.writeU64BE/writeU32BEintrinsics)std.mem.writeU{32,64}BEintrinsics (~30 LOC each). First form acceptsbytes32/address/bool/uintN<=64; arbitraryuint256fields deferred to a follow-up gated on bigint or callers supplying pre-encoded[32]u8. Signing is a separatesecp256k1Signcall (PR 5) — splits cleanly so a digest can be relayed to a hardware wallet.EIP712.t.sol; cross-check digests against ethers.jsTypedDataEncoderstd/eip712.0(new, source-backed — pure structural encoding using PR 4 hashes + newwriteBEintrinsics), updates tozero_runtime.cfor the BE-writes, doc, conformancestd.crypto.macaroonstd.crypto.x25519+ed25519std.codec.bech32+bech32mstd/math.0operator set). Naturally source-backed; lives instd.codecnotstd.crypto.std/bech32.0(new, source-backed),docs/articles/modules/codec.md, conformancelnbc...), Lightning Address local-part, modern Bitcoin addresses (bc1...), NIP-19 Nostr entitiesstd.bolt11.decode(+ optionalencode)std/bolt11.0(new, source-backed; pure structural decode + intrinsic hash/recover calls),docs/articles/modules/bolt11.md(new), conformanceTotal mainline (PR 1–6): ~1500–1700 LOC of Zero + C glue + one vendored library. PR 1, PR 2, PR 4 are independently mergeable from each other; the rest form a partial order.
Ecosystem coverage
Four shipped agent-payment toolkits, sampled across LN / Cashu / Ethereum surfaces, show what this primitive set actually unblocks beyond
satring:X-Cashu.PRs 1 + 5 + 7c + 7d form the minimum set that touches every project in the table. PRs 6 + 7a together cover the LN↔Ethereum payment-rail bridge (lightning-wallet-mcp ships both today). A single primitive landing usually unblocks 2–4 projects, not one — the agent-payment ecosystem has converged on a small shared substrate.
Open questions
extern fun(#70 / #194) and bind via FFI, or add bitwise ops first and ship source-backed. A one-line direction-of-travel signal lets PRs be reshaped before the first one lands.secret<T>marker type the compiler refuses to use in data-dependent branches / table lookups, or are crypto authors expected to write constant-time helpers by hand in C and trust the compiler not to undo them? Note: Keccak's permutation is naturally data-independent in any sane implementation; the practical concern is SHA-2's S-box / table-driven implementations (and AES, which isn't in this roadmap). See Almeida et al., USENIX Security 2016.zero_http_curl.clinks against the system libcurl. libsecp256k1 is typically vendored. Isnative/zero-c/runtime/vendor/secp256k1/acceptable, or should the roadmap assume system linkage? (Vendoring is the BoringSSL / Node.js precedent; system linkage is the Python / Ruby precedent.)Notes & caveats
crypto/, libsodium, HACL*, or libsecp256k1. This isn't conservatism — lone-maintainer crypto implementations have produced Lucky 13 (CVE-2013-0169), Schannel CurveBall (CVE-2020-0601), and many more.SecureRandombug). PR 5's explicitsecp256k1_nonce_function_rfc6979requirement is the mitigation; documenting it in the PR body and conformance test names it directly.X-Cashupayment flow — additionally needssecp256k1_ec_pubkey_tweak_mul/_tweak_add/_combineexposed. ~50 LOC of glue against the same vendored library; scope-extension decision when PR 5 lands.Related issues / PRs
extern fun(would unlock an alternative implementation path)std.math(source-backed stdlib precedent; doesn't apply here due to bitwise gap)std.decimal(adjacent: same bigint-shaped gap surfaces for EIP-712uint256support)Maybe<primitive>construction (gates fallible API shapes likeMaybe<Signature>)KeyPair/Signaturerecords from constructors)Filing as
enhancement. Happy to keep open indefinitely as a parking spot, or to start with PR 1 in draft form if seeing the intrinsic-registration shape concretely would help triage.