Giulio2002/bend-collections

★ 40Forks 4CGitHub ↗Compare

README

bend-collections

Collections for Bend 2, written in stock Bend and benchmarked against optimized C implementations of the same algorithms.

Container Module Notes
Dynamic array src/containers/dynamic_array.bend packed storage, amortized push
Deque src/containers/deque.bend two-list deque
FIFO queue src/containers/queue.bend two-list queue
Stack src/containers/stack.bend
Simple / priority queue src/containers/{simple,priority}_queue.bend facades over queue and heap
Binary heap src/containers/binary_heap.bend packed-array min-heap, static comparator
Doubly linked list src/containers/doubly_linked_list.bend arena-backed, stable handles
Intrusive doubly linked list src/containers/intrusive_doubly_linked_list.bend application-owned nodes, O(1) membership edits; guide; contributed by Ryan Berckmans in #5
List iterator src/containers/dlist_iterator.bend owning bidirectional iterator
Tree map src/containers/balanced_search_tree.bend indexed red-black tree
Bitset src/containers/bitset.bend packed words
Bit list src/containers/bitlist.bend growable packed bits, optional limit (SSZ Bitlist[N])
Hash map src/containers/hash_table.bend String keys, Base.Map-style API
LRU cache src/containers/lru.bend String keys, lifetimes, 64-bit metrics
SHA-256 src/crypto/sha/sha256.bend FIPS 180-4, from bend-sha256
SHA-256 (packed) src/crypto/sha/packed/sha256.bend the same hash over a packed Array<U32> and a byte length, no list on the path; laws in proofs/crypto/sha/packed/laws.bend
Keccak-256 src/crypto/keccak/keccak.bend Ethereum Keccak-256 (MIT), from bend-keccak
BLAKE2s src/crypto/blake/blake2s/blake2s.bend RFC 7693, 32-byte digest
BLAKE2b src/crypto/blake/blake2b/blake2b.bend RFC 7693, 64-byte digest
BLAKE3 src/crypto/blake/blake3/blake3.bend hash mode, 32-byte digest
SHA-512 src/crypto/sha512/sha512.bend FIPS 180-4, 64-byte digest; proved equal to spec/crypto/sha512.bend for every input
SHA3-256 src/crypto/sha3/sha3_256.bend FIPS 202 on the Keccak-f[1600] of keccak/; proved equal to spec/crypto/sha3.bend
Hashing facade src/crypto/hash.bend one-shot sha256/sha512/sha3_256 and an incremental Hasher (new_*, update, digest); see below
Constant-time compare src/crypto/subtle.bend eq(a, b) on byte lists (or lists of packed 32-bit words), one pass, proved True exactly when a == b
HMAC-SHA256 src/crypto/mac.bend RFC 2104: sign(key, msg), verify(key, msg, tag) (constant-time subtle.eq); proved equal to the RFC/FIPS 198-1 spec spec/crypto/hmac.bend for every input, verify(k, m, sign(k, m)) and rejection of every other tag; RFC 4231 vectors; contracts
HKDF-SHA256 src/crypto/kdf.bend RFC 5869: extract, expand, hkdf, lengths above 255 * 32 are the value LengthTooLarge; proved equal to spec/crypto/hkdf.bend for every input, output length and prefix laws; RFC 5869 vectors
AES src/crypto/aes/aes.bend FIPS 197 block cipher, 128/192/256-bit keys; S-box as a constant-time Boyar-Peralta circuit (no table); proved equal to the FIPS 197 specification for every key and block, see docs/CRYPTO_CONTRACTS.md
AES-GCM src/crypto/aesgcm.bend SP 800-38D AES-128-GCM and AES-256-GCM, 12-byte nonces, 16-byte tags appended, tag checked with subtle.eq; runs a constant-time bitsliced AES (BearSSL aes_ct layout) with a one-pass GHASH, about 7x BearSSL's constant-time C; proved equal to the SP 800-38D specification, decrypt(encrypt(x)) == Some(x), a wrong tag gives None
Argon2id src/crypto/argon2/argon2.bend RFC 9106 (v0x13), with secret and associated data; proved equal to spec/crypto/argon2/ for every input that fits (m' <= 2^23 blocks)
Password hashing src/crypto/password.bend Argon2id PHC strings: hash_password, hash_password_os, verify_password, needs_rehash; see below
ChaCha20 src/crypto/chacha/chacha20.bend RFC 8439: chacha20_block, chacha20(key, counter, nonce, bytes), HChaCha20 and XChaCha20 (draft-irtf-cfrg-xchacha-03); the core src/crypto/chacha/core.bend takes the number of double rounds (ChaCha8/12 reuse it); proved equal to the RFC spec spec/crypto/chacha.bend for every input and round count, decryption is encryption; RFC 8439 / XChaCha vectors; contracts
Poly1305 src/crypto/poly1305/poly1305.bend RFC 8439: poly1305(key, msg), verify (constant-time subtle.eq); arithmetic mod 2^130-5 on 5 radix-2^26 natural-number limbs (poly1305-donna / HACL* Field32xN layout), proved equal to the Nat spec spec/crypto/poly1305.bend for every input, and mac_aead equal to the RFC 8439 2.8 tag of mac_data; RFC 8439 vectors
AEAD src/crypto/aead.bend encrypt(alg, key, nonce, aad, pt), decrypt(alg, key, nonce, aad, ct) for CHACHA20_POLY1305 (RFC 8439), XCHACHA20_POLY1305, AES_128_GCM and AES_256_GCM (SP 800-38D); proved: each algorithm equals its spec, decrypt(encrypt(x)) == Some(x), every tag other than the expected one is rejected; RFC 8439 / XChaCha / GCM vectors, Wycheproof, differential test against cryptography
X25519 key exchange src/crypto/kex.bend RFC 7748: generate_keypair(seed), generate_keypair_os(), shared_secret(sk, pk) (all-zero result rejected); field arithmetic mod 2^255 - 19 in src/crypto/curve25519/; proved equal to spec/crypto/curve25519/x25519.bend
Ed25519 signatures src/crypto/sign.bend RFC 8032 section 5.1: generate_keypair, sign, verify (cofactorless, S < L enforced); proved equal to spec/crypto/ed25519.bend
secp256k1 src/crypto/secp256k1.bend SEC 1/SEC 2 ECDSA with RFC 6979 deterministic nonces and low-S output (sign, sign_compact, verify, verify_strict), public key recovery (recover), Ethereum eth_address and the ECRECOVER precompile (ecrecover), BIP-340 Schnorr (schnorr_pubkey, schnorr_sign, schnorr_verify), SEC 1 key encodings, generate_keypair(seed) / generate_keypair_os(); complete (Renes-Costello-Batina) point formulas and masked double-and-add; every function proved equal to its specification spec/crypto/secp256k1/ (over the natural numbers) for every input; RFC 6979, BIP-340 (19), Wycheproof and ecrecover vectors plus a differential test against cryptography and the BIP-340 reference code; contracts
Integer math src/math/natural.bend Python-style math integer functions, see below
Math per type src/math/generic.bend, src/math/f64.bend the same functions for U32, U64, F32 and a software F64, see below
Fixed-width integers src/math/fixed.bend, src/math/number.bend Rust's checked_/wrapping_/saturating_/overflowing_ families for U32 and U64, bit counts, primality, bytes, extended gcd, see below
Random numbers src/math/random.bend (src/math/random/) Go's math/rand/v2 bit for bit: a Source interface for any seeded generator, ChaCha8 (C2SP chacha8rand) and PCG-DXSM sources, unbiased bounded integers (Lemire), floats, Fisher-Yates shuffles; see below
Secure random src/crypto/random.bend ChaCha8Rand generator: seeded or OS-seeded (IO.random_u32), bytes, read_words (bytes into a packed Array<U32>), uint_below, shuffle; contract and caveats

The hash map and the LRU follow Base's conventions: signatures are quantity-polymorphic (a, -V: Kind(a), as Base.Map uses), and reads that copy a value out (get, peek) take -V: Data on the &2 instance, like Map.get.

Integer math

src/math/natural.bend is the exact-integer part of a Python-style math library, after the design reference's roadmap (numeric built-ins and the math module's integer functions first), on Bend's natural numbers:

Function Python Result
gcd(a, b), gcd_all(xs) math.gcd greatest common divisor; gcd_all([]) == 0
lcm(a, b), lcm_all(xs) math.lcm least common multiple; lcm_all([]) == 1
isqrt(n) math.isqrt r*r <= n < (r+1)*(r+1) (Heron's iteration)
iroot(n, k) — r^k <= n < (r+1)^k, Domain for k == 0
ilog(n, b) — b^r <= n < b^(r+1), Domain for n == 0 or b < 2
factorial(n), perm(n, k), comb(n, k) math.factorial/perm/comb n!, n!/(n-k)!, C(n, k); 0 when k > n
prod(xs), sum(xs) math.prod, sum prod([]) == 1
pow_mod(b, e, m) pow(b, e, m) b^e mod m, ZeroDivision for m == 0
mod_inverse(a, m) pow(a, -1, m) a*x == 1 (mod m), NotInvertible when not coprime
divmod(a, b) divmod QR{a // b, a % b}, ZeroDivision for b == 0
bit_length(n) int.bit_length 2^(L-1) <= n < 2^L
clamp(x, lo, hi) — x limited to [lo, hi], Domain for hi < lo

Errors are values (MathError: ZeroDivision, Domain, NotInvertible), following the reference's error model. The contract is spec/math/natural.bend: one <Function>.<clause> proposition per guarantee (Gcd.greatest, Isqrt.lt_succ, PowMod.zero_modulus, ...; divisibility is dvd(d, a), a quotient with its equation), and proofs/math/natural/proof.bend proves every clause under its name (statement shapes follow Lean 4 Mathlib, the Why3 gallery and HACL*; each file cites its source; coverage in docs/MATH_CONTRACTS.md), and tools/check_math.py compares ~5000 random and edge-case calls with CPython. Elementary float functions (exp, sin, ...), cmath, fractions, decimal, statistics and random are not included: exact rationals need unbounded integers, and Bend's native backend stops at 2^48 - 1 per Nat, so results and intermediates of these functions must stay below that at run time (the proofs are over all naturals). The same functions for U32, U64, F32 and a software binary64 follow below.

Math per type: U32, U64, F32, F64

src/math/generic.bend writes the functions above once, as templates over a numeric interface (src/math/num.bend): a type takes part through two functions passed as templates, ~op: Op<T> -> T (its arithmetic, one constructor per operation) and ~test: Test<T> -> Bool (comparisons and overflow tests). Templates are substituted at compile time and the match on the operation's constructor is resolved there, so each call compiles to the instance's native code:

import ./src/math/generic.bend as G
import ./src/math/instances.bend as I
import ./src/math/f64.bend as F64
import ./src/math/u64.bend as W

G.gcd(~U32, ~I.u32_op, ~I.u32_is, 12, 18)          # 6
G.comb(~W.U64, ~I.u64_op, ~I.u64_is, n, k)         # Done{C(n, k)} or Fail{Overflow}
G.clamp(~F32, ~I.f32_op, ~I.f32_is, x, lo, hi)
G.pow(~F64.F64, ~F64.f64_op, ~F64.f64_is, x, 10n)
Instance Type Functions
I.u32_op, I.u32_is Base U32 all of them
I.u64_op, I.u64_is src/math/u64.bend's two-word U64 (fast arithmetic in src/math/w64.bend) all of them
I.f32_op, I.f32_is Base F32 min max clamp abs sign sum prod pow
F64.f64_op, F64.f64_is src/math/f64.bend's software binary64 min max clamp abs sign sum prod pow

The integer functions (gcd lcm gcd_all lcm_all isqrt iroot ilog factorial perm comb pow_mod mod_inverse divmod bit_length) need an unsigned integer instance; the ordered ones (min max clamp abs sign sum prod pow) any instance. Fixed widths are checked: a result (or a partial product of sum, prod, lcm_all) that does not fit is Fail{Overflow}, never a wrapped value (the design reference's rule for fixed-width integers). comb reduces by a gcd at every step and pow_mod / mod_inverse multiply mod m without overflow, so they fail only when the true result does not fit. isqrt is the instance's own: an F32 square-root estimate corrected exactly (U32), plus one Newton step on 64 bits (U64).

src/math/w64.bend is the U64 arithmetic: only native U32 operations and Nat values below 2^48 (the runtime's bound), with 64/64 division by a normalized 32-bit quotient estimate (at most two over, Knuth's Theorem B) and a 128-bit product for mulmod.

src/math/f64.bend is IEEE-754 binary64 in software on two U32 words (Bend 2 has only F32): Berkeley SoftFloat 3e's algorithms (addMags, subMags, f64_mul, roundPackToF64, normRoundPackToF64) on 64-bit significands, exact long division by 32-bit quotient digits and an integer square root with one Newton step, each rounded once to nearest-even, with subnormals, signed zeros, infinities and NaN as IEEE 754; lt le eq, neg abs copysign, the classification predicates and of_nat; and the rest of the design reference's float foundation, each result an exact integer at a scale rounded once: trunc floor ceil round (ties to even), to_u64 to_u32 floor_u64 ceil_u64 round_u64 of_u64 of_u32 (errors as values), frexp ldexp ulp, nextafter fmin fmax, is_normal is_subnormal is_integer to_bits of_bits64, modf, exact fmod and IEEE remainder, isclose and as_integer_ratio. Every clause of spec/math/f64.bend is proved (proofs/math/typed/f64*.bend).

These instances are stated and tested, and mostly not proved (the Nat functions above are the proved reference; at U32, abs, min, max, sign and clamp are proved in proofs/math/typed/u32.bend). Their contracts are in spec/math/: generic.bend relates every typed function to the proved Nat one (Gcd.agrees, Comb.checked: the Nat result, or Overflow exactly when it needs more than the width) and states the float ones through the type's IEEE operations (FSum.fold, FPow.binary); instances.bend states what each instance operation computes; w64.bend the 64-bit arithmetic; f64.bend is an executable binary64 reference in Flocq's style (the exact result of each operation, rounded once to nearest-even). The evidence: tools/check_generic.py compares every function on every type with Python (U32/U64 with overflow detection, F32 through numpy float32, F64 through Python floats), naming the clause of each case; tools/check_f64.py compares the software binary64 with the machine's doubles on random bit patterns of every class (zeros, subnormals, normals, infinities, NaN, cancellations, ties), and tools/check_f64x.py the rounding, conversion, exponent, neighbour, remainder and ratio functions with CPython's math (the design reference's appendix A special cases included); tools/check_f64_spec.py tests spec/math/f64.bend itself against the machine's doubles through a line-by-line mirror; and proofs/math/typed/examples.bend has the proof checker evaluate the integer clauses and instance laws at concrete U32 inputs. All run in tools/validate.py; docs/MATH_CONTRACTS.md has the function-by-type matrix.

Fixed-width integers and number theory

src/math/fixed.bend gives U32 and U64 (src/math/u64.bend's two-word U64) the four families Rust defines for u32/u64 (design reference section 2.6), plus the integer extras of sections 3.2 and 10; src/math/number.bend has the Nat versions of the extras:

Functions (u32_ and u64_ prefixed) Result
checked_add sub mul div rem pow shl shr Some exact result, None on overflow, a zero divisor, or a shift >= the width
wrapping_add sub mul pow shl shr the result modulo 2^w; shifts by the amount mod w
saturating_add sub mul pow clamped to [0, 2^w - 1]
overflowing_add sub mul pow shl shr OV{wrapping value, overflowed}
bit_count number of 1 bits (int.bit_count, count_ones)
to_bytes_le/be, from_bytes_le/be w/8 bytes as U32s in [0, 256); from_bytes is None for any other length or a value >= 256
u32_is_prime, u32_next_prime trial division (exact); the least prime above n below 2^32, or None
NB.bit_count, NB.egcd, NB.is_prime (Nat) 1 bits; EG{g, x, y, neg} with a x - b y == g (b y - a x when neg), g == gcd(a, b); primality

Unsigned division never overflows, so Rust's wrapping_, saturating_ and overflowing_ div/rem are plain division, and div_euclid/rem_euclid equal div/rem for unsigned types (the remainder is already non-negative); only checked_div/checked_rem add something (the zero divisor). is_prime/next_prime at U64 are not provided: the fast deterministic Miller-Rabin needs the base-set theorem ("bases 2..37 decide every n < 2^64"), which is a finite computation over all 64-bit composites and not provable here without an axiom, and exact trial division is too slow at 64 bits.

Every function is proved against spec/math/fixed.bend and spec/math/number.bend (77 clauses, gate proofs/math/number/proof.bend: the checked results are the Nat operation on the values when it fits, the wrapping ones that result modulo 2^w, a wrapping difference plus b is a plus 2^w exactly when a < b, next_prime returns the least prime above n or None only when none is left below 2^32, the bytes are the base-256 digits of the value). tools/check_fixed.py compares every function with Python integers under Rust's semantics, naming the clause of each case.

Hashing

src/crypto/hash.bend is the entry point for hashing byte lists (List<&2, U32>, each value < 256):

import ./src/crypto/hash.bend as Hash

Hash.sha256(bytes)                       # 32 bytes (FIPS 180-4)
Hash.sha512(bytes)                       # 64 bytes (FIPS 180-4)
Hash.sha3_256(bytes)                     # 32 bytes (FIPS 202)
h = Hash.update(Hash.update(Hash.new_sha512(), part1), part2)
Hash.digest(h)                           # == Hash.sha512(part1 ++ part2)

Every function is proved against its standard's executable specification (spec/crypto/sha.bend, sha512.bend, sha3.bend), and the incremental Hasher is proved equal to the one-shot hash for every split of the input (proofs/crypto/hash/laws.bend); src/crypto/subtle.bend's eq compares digests and tags without an early exit, proved to return True exactly on equal lists. docs/CRYPTO_CONTRACTS.md lists the clauses and their evidence; tools/check_crypto_hash.py tests all of it against Python's hashlib.

Password hashing

src/crypto/password.bend stores Argon2id hashes as PHC strings ($argon2id$v=19$m=..,t=..,p=..$salt$hash, unpadded base64), as the reference implementation and argon2-cffi write them:

import ./src/crypto/password.bend as PW

PW.hash_password(pw, salt, PW.owasp())   # Some{"$argon2id$v=19$m=19456,t=2,p=1$..."}, None if out of range
PW.hash_password_os(pw, PW.rfc9106())    # IO: the same with a fresh 16-byte salt (IO.random_u32)
PW.verify_password(pw, encoded)          # recompute and compare with subtle.eq
PW.needs_rehash(encoded, PW.owasp())     # not Argon2id v=19 with exactly these parameters

owasp() is m = 19 MiB, t = 2, p = 1; rfc9106() is RFC 9106 section 4's second recommendation (m = 64 MiB, t = 3, p = 4); both use 16-byte salts and 32-byte tags, and any PW.Params{m, t, p, tag, salt} works. The core, src/crypto/argon2/argon2.bend's argon2id(pw, salt, key, ad, t, m, p, T), also takes a secret and associated data. Proved: the implementation equals the RFC 9106 specification (spec/crypto/argon2/, on the BLAKE2b specification) for every input whose memory fits (m' <= 2^23 KiB), a hash returned by hash_password verifies with its password and needs no rehash with its parameters, and parse(format(x)) == Some{x} for PHC strings (docs/CRYPTO_CONTRACTS.md). tools/check_argon2.py tests the RFC 9106 section 5.3 vector and compares tags and PHC strings with argon2-cffi.

Random numbers

src/math/random.bend follows Go's math/rand/v2: a generator is a value built from a seed the caller chooses, and every call returns its value next to the advanced generator (Bend is pure). The same seed gives Go's exact sequence (Go's test vectors pass).

import ./src/math/random.bend as R
import ./src/math/random/pcg.bend as PCG
import ./src/math/u64.bend as W

def gen() -> PCG.PCG:
  R.pcg(W.U64{1, 0}, W.U64{2, 0})                     # Go: rand.New(rand.NewPCG(1, 2))

# a die roll and the advanced generator              # Go: r.Uint64N(6)
def roll(g: PCG.PCG) -> W.U64 & PCG.PCG:
  R.uint64n(~PCG.PCG, ~R.pcg_next, g, W.U64{6, 0})

def deal(g: PCG.PCG, +cards: List<&2, U32>) -> List<&2, U32> & PCG.PCG:
  R.shuffle(~U32, ~PCG.PCG, ~R.pcg_next, g, cards)    # Go: r.Shuffle

# shuffle the first n slots of an array in place    # Go: r.Shuffle on a slice
def deal_array(g: PCG.PCG, a: Array<U32>, +n: Nat) -> Array<U32> & PCG.PCG:
  R.shuffle_array(~U32, ~PCG.PCG, ~R.pcg_next, g, a, n)

A source is any state type S with ~next: S -> U64 & S, passed as templates like src/math/num.bend's ~op: every function of src/math/random/rand.bend (uint64 uint32 int64 int32 uint64n/uint_below uint32n intn int_range float64 shuffle shuffle_array perm) is written once for all sources, and so will math/statistics be (a normal(~S, ~next, s) on top of float64). Sources: R.chacha8(seed) (Go's ChaCha8, C2SP chacha8rand, a 32-byte seed) and R.pcg(seed1, seed2) (Go's PCG, 128-bit LCG with the DXSM output). uint64n is Lemire's nearly divisionless method exactly as Go's (a power of two masks, otherwise multiply and reject while the low half is below 2^64 mod n), with at most 128 draws where Go loops forever.

Proved for every input (proofs/math/random/proof.bend, proof_draws.bend, proof_pcg.bend, proof_float.bend; docs/MATH_CONTRACTS.md): the ChaCha8 generator outputs C2SP's stream for every key and seed; a PCG step is the 128-bit LCG and the output DXSM on naturals; uint64n computes the specification's draw and is below n for every source; Lemire's rejection is exactly unbiased (for every width, bound and k < n, exactly floor(2^w / n) source outputs draw k); shuffle and perm return permutations for every source (Mathlib's List.Perm, by counts), and shuffle_array permutes the array's slots (shuffle itself goes through an array: linear time, not quadratic); float64 is m 2^-53 exactly and below 1; the word slices and the bounded wrappers (uint32n, intn, int_range) are what they say. Tested: Go's vectors and a Python mirror of Go on random seeds and call sequences, plus a chi-square smoke test (tools/check_random.py).

src/crypto/random.bend is the secure generator: ChaCha8Rand keyed from the OS (from_os(), eight IO.random_u32) or from a secret seed (new), with bytes (Go's ChaCha8.Read), uint_below and shuffle; proofs and security caveats in docs/CRYPTO_CONTRACTS.md.

Key exchange and signatures

import ./src/crypto/kex.bend as Kex
import ./src/crypto/sign.bend as Sign

Kex.generate_keypair(seed)               # Some{Keypair{secret, public}} for 32 bytes
Kex.shared_secret(sk, pk)                # X25519(sk, pk), None if all zero or malformed
Sign.generate_keypair(seed)              # Ed25519 key pair from a 32-byte seed
Sign.sign(sk, msg)                       # Some{64-byte signature}
Sign.verify(pk, msg, sig)                # True iff valid (S >= L rejected)
Sign.signing_key(seed)                   # Some{expanded key}: sign_with_key(k, msg) per message
Sign.context(xs)                         # curve constants and base point, computed once;
                                         # generate_keypair_ctx, signing_key_ctx, verify_ctx take it

X25519 (RFC 7748) and Ed25519 (RFC 8032) run on one field implementation (src/crypto/curve25519/fe.bend, 15 limbs of 17 bits with products on the 32-bit multiplier, every operation proved to compute its value mod p with bounded limbs). Each facade is proved equal to a transcription of its RFC (spec/crypto/curve25519/x25519.bend, spec/crypto/ed25519.bend) for every input, with SHA-512 proved equal to FIPS 180-4; tools/check_curve25519.py runs the RFC vectors and a differential test against Python's cryptography. Secret-dependent steps are branch-free (selection by arithmetic, fixed bit counts); Bend has no timing model, so constant time is a property of the code's shape, not a theorem.

Install

The library and its laws are published on BendHub (MIT):

Package Hash Contents
[email protected] 0xd9a2fae439ac7ff9e21e0853948f94fe the library (main.bend: every public module)
[email protected] 0x993b989cb899a5e5c6facb3d6fbccf8f every law: imports the three parts below by name
[email protected] 0x5c489f5d9646d7cc9aa3dd8137e9dc07 containers, the shared proof library, END_TO_END, PROOF
[email protected] 0xf86f5f1d9a594d5a5cff999100e01d03 math
[email protected] 0xa7e654f9780078ca65bf9e187da99d3e crypto and random

Import any module by its path in the package:

import bend-collections@1.0.0.0/src/containers/hash_table.bend as HashMap
import bend-collections@1.0.0.0/src/crypto/aead.bend as AEAD
import bend-collections@1.0.0.0/src/math/random.bend as Rand

and the laws the same way, one package's proof root at a time or all at once:

import bend-collections-laws-crypto@1.0.0.0/proofs/crypto/aead/proof.bend as AeadLaws
import bend-collections-laws@1.0.0.0/laws.bend as Laws

BendHub caps a package at 16 MiB, so the laws are published in three parts (laws_containers.bend, laws_math.bend, laws_crypto.bend, one proof root each, 64 in total) and laws.bend imports the three by name. A name resolves to a content hash, and every fetched file is checked against it, so an import never changes under you. Checking all the laws at once takes about 12 GB of memory and a large stack (ulimit -s unlimited plus BUN_JSC_maxPerThreadStackUsage=1073741824); checking one part or one root does not. Version 1.0.0.0 was published from commit 8e660ee.

Layout

src/containers/   the collections, their internals (internal/) and API types (types/)
src/math/         integer math (natural.bend), the same per type (num, generic, instances),
                  software binary64 (f64), 64-bit words (u64, w64), fixed-width U32/U64
                  families and number theory (fixed, number), hashing, powers of two,
                  random numbers (random.bend, random/: Go's math/rand/v2)
src/crypto/       SHA-256 (sha/), SHA-512 (sha512/), Keccak-256 (keccak/), SHA3-256 (sha3/),
                  BLAKE2s, BLAKE2b and BLAKE3 (blake/), the hashing facade (hash.bend),
                  constant-time comparison (subtle.bend), HMAC and HKDF (mac.bend, kdf.bend),
                  AES and GCM (aes/, aesgcm.bend), ChaCha20 (chacha/), Poly1305 (poly1305/)
                  and the AEAD facade (aead.bend, aead/), Argon2id (argon2/) and password
                  hashing (password.bend), X25519 and Ed25519 (curve25519/, ed25519/,
                  kex.bend, sign.bend), the secure random generator (random.bend)
spec/             the specifications, mirroring src/: what each module does,
                  independent of how
  containers/<pkg>.bend  the abstract model and its contract: every SPARK
                      formal-container Post clause as a `<Subprogram>.<clause>`
                      proposition (docs/SPARK_CONTRACTS.md); a spec spanning
                      several files is a directory with a main.bend
  crypto/             FIPS 180-4 SHA-256 and SHA-512, the Keccak sponge, FIPS 202
                      SHA3-256, RFC 7693 BLAKE2, BLAKE3, list equality (subtle),
                      FIPS 198-1 HMAC and RFC 5869 HKDF, FIPS 197 AES and SP 800-38D
                      GCM (aes/: GF(2) polynomials, the cipher, GHASH/GCTR/GCM),
                      RFC 8439 ChaCha20, Poly1305 and ChaCha20-Poly1305,
                      XChaCha20(-Poly1305), RFC 9106 Argon2id (argon2/), RFC 7748
                      X25519 and RFC 8032 Ed25519 (curve25519/, ed25519.bend)
  math/<module>.bend  each src/math module's contract as `<Function>.<clause>`
                      propositions (docs/MATH_CONTRACTS.md): proved for
                      natural, u64, hash and pow2; stated and tested for the
                      typed versions (generic, instances, w64, f64)
  lib/                shared model definitions (lists, the SPARK sequence
                      predicates, order laws, U32 sequences, the 64-bit word
                      model)
proofs/           only proofs: one package per src package, mirroring src/,
                  each importing its spec from spec/:
  containers/<pkg>/   the lemmas, the .src sources they expand from, and
                      proof.bend (the package's entry point: the refinement
                      theorems and the proof of every contract clause)
  math/<pkg>/         the same for src/math: natural/proof.bend proves every
                      clause of spec/math/natural.bend, math/proof.bend those
                      of u64, hash and pow2, number/proof.bend those of
                      number and fixed; typed/examples.bend checks the
                      typed clauses at concrete U32 inputs
  crypto/<pkg>/       the same for src/crypto: sha/ proves SHA-256 equal to its
                      executable FIPS 180-4 specification for every input,
                      keccak/ the packed API equal to the independent sponge
                      specification (padding, absorption, rejection, all words),
                      sha512/ and sha3/ likewise, hash/ the facade and the
                      incremental hasher, subtle/ eq, mac/ and kdf/ HMAC and
                      HKDF, aes/ AES and AES-GCM, chacha/, poly1305/, aead/
                      equal to RFC 8439 and the AEAD laws, argon2/ Argon2id and
                      the password facade, curve25519/ and ed25519/ X25519 and
                      Ed25519, random/ the secure generator; docs/CRYPTO_CONTRACTS.md
  lib/                proof library shared by the packages (logic, Nat, lists,
                      U32 words, arrays, order laws)
  PROOF.bend          the whole library; END_TO_END.bend the public laws
  prove.py            checks every proof and every spec
tests/            native test drivers, one per container; oracles in tests/support/
benchmarks/       bend/ and native/ (C) drivers, workload table, runner
tools/            proof generators (mac.py expands the .src proof sources),
                  validation and differential tests: see tools/README.md

Requirements

Bend 2.0.34 (the release; pinned in tools/toolchain.json), clang and Python 3.

Test

bend tests/<container>/main.bend    # each container's test driver
bend tests/math/natural.bend -o build/math/natural && python3 tools/check_math.py   # math vs CPython
python3 tools/check_crypto_hash.py      # subtle, SHA-512, SHA3-256, the hash facade vs hashlib
python3 tools/check_mac.py              # HMAC-SHA256 / HKDF-SHA256: RFC 4231 / RFC 5869, hmac, `cryptography`
python3 tools/check_aes.py           # AES / AES-GCM: FIPS 197, NIST CAVP GCM vectors, `cryptography`
python3 tools/check_argon2.py           # Argon2id (RFC 9106 vector) and PHC strings vs argon2-cffi
python3 tools/check_chacha.py        # ChaCha20/XChaCha20/AEADs vs `cryptography`, Wycheproof
python3 tools/check_poly1305.py      # Poly1305 and its spec mirror vs `cryptography`
python3 tools/check_curve25519.py    # X25519 / Ed25519: RFC 7748 / RFC 8032 vectors, `cryptography`
bend tests/math/random.bend -o build/math/random && python3 tools/check_random.py   # random vs Go
python3 tools/backend_diff.py --quick   # every module: `bend file.bend` vs the C build vs the JS build

The proofs are about the Bend source; the compiler underneath them is not verified. tools/backend_diff.py runs every public module on the same seeded random and edge-case inputs through the three execution paths of the toolchain (bend file.bend args, the native C backend, the JavaScript backend) and fails on any differing line. What it has found is in docs/BACKEND_BUGS.md; the coverage is in tools/README.md.

Benchmark

python3 benchmarks/bench.py --build
python3 benchmarks/full_sweep.py           # calibrated, every size
python3 benchmarks/natural.py --report build/bench/math.json   # src/math/natural.bend

Every container and both hashes run the same algorithm in Bend (native C backend) and in C, with identical inputs and a checksum that must match. Results and the differences to C: BENCHMARK.md.

Contributors

Giulio2002MattCozendeyryanberckmans

Issues