crypto

crypto

zig 0.15 crypto paths and patterns.

ecdsa

the ecdsa types are under std.crypto.sign.ecdsa, not std.crypto.ecdsa:

const crypto = std.crypto;

// correct (0.15)
const Secp256k1 = crypto.sign.ecdsa.EcdsaSecp256k1Sha256;
const P256 = crypto.sign.ecdsa.EcdsaP256Sha256;

// wrong - will error "has no member named 'ecdsa'"
// const Secp256k1 = crypto.ecdsa.EcdsaSecp256k1Sha256;

verifying signatures

const Scheme = std.crypto.sign.ecdsa.EcdsaSecp256k1Sha256;

// signature is r || s (64 bytes for 256-bit curves)
const sig = Scheme.Signature.fromBytes(sig_bytes[0..64].*);

// public key from SEC1 compressed format (33 bytes)
const public_key = try Scheme.PublicKey.fromSec1(key_bytes);

// verify - returns error.SignatureVerificationFailed on mismatch
try sig.verify(message, public_key);

key sizes

curve compressed pubkey uncompressed pubkey signature (r||s)
P-256 33 bytes 65 bytes 64 bytes
secp256k1 33 bytes 65 bytes 64 bytes

compressed keys start with 0x02 or 0x03. uncompressed start with 0x04.

see: zat/jwt.zig

signing

const Scheme = std.crypto.sign.ecdsa.EcdsaSecp256k1Sha256;

// secret key from raw 32 bytes
const secret_key = try Scheme.SecretKey.fromBytes(key_bytes[0..32].*);

// sign with RFC 6979 deterministic nonce (null = no additional randomness)
const sig = secret_key.sign(message, null);

// low-S normalization (required by AT Protocol / Bitcoin)
const Curve = std.crypto.ecc.Secp256k1;
const s_order = Curve.scalar.neg(sig.s, .big);
// if s > half_order, replace with order - s

the null second arg means RFC 6979 deterministic nonce — no randomness source needed, signature is reproducible from the same key+message.

low-S normalization ensures s <= half_order. some protocols (AT Protocol, Bitcoin) reject high-S signatures. check by comparing the s component against the curve half-order.

see: zat/jwt.zig, k256 tests

sha-256 hashing

for content addressing (CIDs, integrity checks). hash data into a fixed-size digest:

const Sha256 = std.crypto.hash.sha2.Sha256;

var hash: [Sha256.digest_length]u8 = undefined; // 32 bytes
Sha256.hash(data, &hash, .{});

no allocation needed — the digest is a fixed 32-byte array on the stack. the .{} is options (empty = defaults).

use case: creating CIDs (content identifiers) for IPLD/AT Protocol — hash DAG-CBOR encoded data, then wrap the digest in a CID with version + codec + multihash framing.

see: zat/cbor.zig Cid.forDagCbor