Requested by nate
ECDSA signing now derives nonces with real RFC 6979, so zat's signatures match @atproto/crypto byte for byte. Before this, zat used std's draft-based deterministic nonce, which produced valid signatures with different bytes. Jwt.isExpiredAt(now_seconds) lets callers check expiry against their own clock.
Signatures from a given key now have different bytes and still verify. Commit CIDs signed by zat change too.
jwt: isExpiredAt
isExpiredAt(self, now_seconds: i64) bool: unix seconds, matchingPayload.exp. A token stays valid during the second named byexp.isExpired(io)reads the clock and calls it.- Tests cover
exp - 1,exp,exp + 1, the clock wrapper with past and future tokens, and a payload withoutexp. That payload still fails to parse withMissingExpiration.
crypto: RFC 6979 nonces
signEcdsaderives k with RFC 6979 section 3.2: HMAC-DRBG with HMAC-SHA-256 and no extra entropy. For these 256-bit orders with SHA-256, bits2int is the identity and bits2octets is one reduction. It then computes r and s withstd.crypto.eccpoint and scalar operations and normalizes to low-S.- The public API is unchanged:
signSecp256k1,signP256,Keypair.sign. - The doc comments now say exactly what the code does.
verification
- RFC 6979 A.2.5 P-256/SHA-256 "sample" and "test": k and r match exactly. s matches after low-S normalization; the RFC's "sample" s is high-S.
- 16 fixed vectors from @atproto/crypto 0.5.7 (@noble/curves 1.9.7) are hardcoded in
jwt.zig: two keys and four messages on each curve. zat's output is byte-identical, verifies with zat's strict verify, and verifies with @atproto/crypto'sverifySignature. The same test fails on origin/main. zig build check,zig build test(605/605) and CI's arm1176 compile all pass. Each commit passes on its own.- wasm32-freestanding ReleaseSmall:
src/root.zig, compiled as a module withbuild-exeand called throughKeypair.sign, builds and produces the same 16 signatures under node. The full package build cannot target freestanding wasm because websocket's bundled C zlib needs libc headers. That was already true before this change. - No zat fixture hardcodes zat-produced signatures or CIDs. Interop fixtures are untouched.
🤖 Generated with Claude Code