Lightweight cryptographic primitives for Mach.
This repository owns reusable algorithms and their assurance evidence. Protocol
state machines belong in protocol repositories such as mach-tls.
crypto.secretowns allocated secret storage, bounded views, explicit moves, allocating clones, entropy initialization, and deterministic destruction.crypto.hashprovides incremental SHA-256 and SHA-384 transcript hashing, and BLAKE2b with keyed, salted, personalized and tree modes.crypto.hmacprovides one-shot HMAC-SHA-256 and HMAC-SHA-384, with public or secret tags.crypto.hkdfprovides SHA-256 and SHA-384 extract and expand operations.crypto.aead.aes_gcmprovides AES-128-GCM and AES-256-GCM record protection.crypto.cipher.aesprovides the AES-128 and AES-256 block cipher under one expanded key.crypto.aead.chacha20_poly1305provides ChaCha20-Poly1305 record protection.crypto.group.x25519provides X25519 key agreement.crypto.group.p256provides SEC1 P-256 public keys and ECDH.crypto.signatureprovides ECDSA verification over P-256, P-384, and P-521 with any of SHA-256, SHA-384, and SHA-512, Ed25519, RSA-PSS, and strict RSA PKCS#1 v1.5 signatures.crypto.encoding.der,crypto.encoding.pem, andcrypto.encoding.keysprovide strict TLS key-container parsing and exact serialization.crypto.vectorsdefines a common test vector contract.crypto.assurancepublishes the validation state of this package.
use crypto; binds crypto.lib.crypto, which re-exports these modules for
consumers that prefer one import.
crypto.secret.Secret is move-only by contract. Initialize every owner with
secret.empty(), then allocate it with secret.init or secret.init_random.
Use secret.move to transfer the allocation and secret.clone when an
independent copy is required. Direct record copies are invalid because they
duplicate ownership.
secret.init returns fully zeroed storage, and exactly one layer zeroes it. An
allocator whose zeroes_on_allocate is set returns zeroed storage itself, and
secret.init uses it as returned. The system allocators set it, because
std.memory.secret already allocates zero-filled storage. For any other
allocator secret.init zeroes the allocation.
secret.bytes and secret.buffer return bounded borrowed views. A view must
not outlive its owner or any move or destroy operation. Exactly one layer wipes
each release. An allocator whose wipes_on_release is set wipes the whole span
before every release attempt and leaves zeroed storage when the attempt fails,
so secret.destroy hands it the storage untouched. The system allocators set
it, because std.memory.secret already wipes on release. For any other
allocator secret.destroy wipes storage immediately before every release
attempt, and if the deallocator mutates the allocation and fails while
retaining ownership, wipes it again before returning. Either way the owner
remains in a wiped cleanup-pending state, and a later destroy retries with
fully zeroed storage.
crypto.secret.SecretArray[T] owns a dynamically sized typed allocation while
preserving every field's secrecy shape. Initialize an owner with
secret.empty_array[T], construct the native allocator with
secret.system_array_allocator[T], and call secret.init_array[T] with the
element count. secret.array_data[T] returns *T only while the owner is
active. No raw pointer or declassification cast is needed for directly secret
values or records with deeply nested secret fields.
Array initialization checks count * $size_of(T), supplies $align_of(T) to
the typed allocator, and returns the complete allocation zeroed, zeroing it
itself unless the allocator sets zeroes_on_allocate. A zero-count
array is active with nil data and acquires no allocation. destroy_array[T]
splits the release wipe the same way. A wiping allocator zeroes every element
itself, and for any other allocator destroy_array[T] wipes all elements before
every release attempt and rewipes any callback mutation after a failed one. A
failed release then retains the typed pointer, count, byte size, allocator,
and cleanup-pending state for retry. Custom ArrayAllocator[T] callbacks follow
the same ownership contract. A nonnil output transfers the full requested
allocation even when allocation reports failure.
The native allocator and entropy source delegate to mach-std secret-welded OS
primitives. Storage remains secret-welded through every native boundary,
std's allocation begins zeroed, which is the only allocation zeroing on this
path, entropy fills are complete or leave a fully wiped
destination, and std's release wipes before returning storage to the operating
system, which is the only release wipe on this path. Custom allocators follow
the same ownership rule. A nonnil allocation
result transfers ownership even when allocation reports failure, allowing
partial storage to be wiped and released deterministically.
encoding.der accepts only definite, minimally encoded lengths and rejects
noncanonical BOOLEAN, INTEGER, BIT STRING, NULL, and OBJECT IDENTIFIER values.
Complete-document parsing rejects trailing bytes and validates every nested
constructed value to a maximum depth of 32, which covers CMS SignedData carrying
an RFC 3161 time-stamp token. Cursor failures are transactional.
The public writer supports overlapping content through move semantics and
validates constructed content before changing output.
encoding.pem uses standard base64 with canonical padding and 64-character
lines. Labels are bounded to 64 uppercase ASCII letters, digits, and single
interior spaces. Decoding accepts LF or CRLF and an optional final newline, but
rejects leading or trailing data, mismatched labels, nonfinal short lines,
noncanonical padding bits, URL-safe base64, and embedded whitespace. Exact-base
in-place decoding is supported. Public encoding rejects every overlap. Secret
encoding rejects exact-base overlap, and nonidentical secret views are disjoint
by contract.
encoding.keys supports the key containers needed by TLS:
- RFC 8410 Ed25519 and X25519 PKCS#8 private keys and SPKI public keys
- P-256 SEC1 and PKCS#8 private keys and uncompressed SPKI public keys
- P-384 and P-521 uncompressed SPKI public keys
- PKCS#1 RSA private keys, RSA PKCS#8 private keys, and RSA SPKI public keys
Algorithm identifiers and parameters are exact. P-256 private scalars are
range-checked. An embedded SEC1 public point must be on the curve and match the
private scalar. RSA containers require version zero, a 2048 through 4096-bit
odd modulus, a canonical odd public exponent smaller than the modulus, and all
eight PKCS#1 key integers. Encrypted PKCS#8, version-one OneAsymmetricKey,
multi-prime RSA, compressed P-256 points, and legacy RSA PUBLIC KEY PEM are
not accepted.
Initialize every private output with keys.empty_private. Successful DER
decoding copies the complete canonical document into one owned secret
allocation. Successful PEM decoding allocates the exact decoded DER size once
and transfers that allocation directly into the key. keys.private_bytes
returns a borrowed seed or scalar. keys.copy_rsa copies the public modulus and
exponent and a modulus-width private exponent into caller storage. It snapshots
the private exponent first, so private output may overlap the owning key.
Overlapping public RSA outputs are rejected.
Private keys are move-only by contract and must be released with
keys.destroy_private. A failed allocation or parse normally leaves the output
empty. If cleanup deallocation fails, the output retains wiped cleanup-pending
storage while the primary error is returned. Call destroy_private after that
failure to retry release. Serialization reproduces the exact accepted DER, not
a reconstructed variant. Public SPKI views borrow the caller's DER or PEM
scratch buffer and must not outlive it.
hash.Sha256 and hash.Sha384 are separate incremental state records for
public protocol transcripts. Initialize each record with hash.empty_sha256
or hash.empty_sha384, then activate it with the corresponding init
function. Repeated update calls accept bounded public fragments, including an
empty fragment. SHA-256 input is bounded to 2^61 - 1 bytes. SHA-384 uses its
full 128-bit encoded length field and accepts totals through 2^64 - 1 bytes.
snapshot_sha256 and snapshot_sha384 write the current digest without
changing the source state. The same state remains usable for later updates,
which is the TLS transcript operation used at handshake checkpoints. final
writes the digest and consumes the state only after successful output.
Validation, capacity, and length-overflow failures leave both state and output
unchanged.
destroy_sha256 and destroy_sha384 zeroize the complete state and are
idempotent. A destroyed state rejects update, snapshot, and final, but can be
activated again with init. This supports the TLS HelloRetryRequest transcript
rewrite without exposing the underlying state. State records are opaque and
must not be copied directly. SHA-2 runs on crypto.hash.sha256 and
crypto.hash.sha512, and the former dispatches SHA-256 to SHA-NI or ARMv8 SHA2
where the processor has it. Secret input uses those modules' secret-typed
states, and public input is lifted into them block by block. Snapshot clones, lift buffers, temporary digests, and destroyed contexts
are explicitly zeroized.
crypto.hash.blake2b is the RFC 7693 primitive. blake2b.Params is the whole
parameter block: digest length (1 to 64 bytes), 16-byte salt, 16-byte
personalization, and the tree fields with the last-node flag.
blake2b.params(n) returns sequential parameters for an n-byte digest, and
blake2b.valid checks a block. The digest length is a parameter of the hash,
not a truncation of a 64-byte digest. The byte counter is the full 128 bits of
the specification, so no input length overflows it.
hash.Blake2b hashes public input under any parameter block. It follows the
transcript contract above: empty_blake2b, init_blake2b, update_blake2b,
snapshot_blake2b, final_blake2b and destroy_blake2b. snapshot and
final write exactly the configured digest length. hash.digest_blake2b is
the one-shot form, and its output may overlap its input.
hash.Blake2bSecret runs on the secret-typed state. init_blake2b_secret
takes a secret key of 0 to 64 bytes, where an empty key is unkeyed hashing of
secret input. Longer keys are rejected with INVALID_KEY. Input arrives as
secret bytes through update_blake2b_secret or as public bytes through
update_blake2b_public, and the two may be mixed. The digest is written to a
contracts.SecretBuffer, and declassifying it is the caller's decision.
hash.mac_blake2b_secret is the one-shot keyed form with a secret tag, and
hash.mac_blake2b declassifies the tag into public output. The tag output may
overlap the message because nothing is written until the tag is complete.
The secret state, snapshot clones, lift buffers and temporary digests are explicitly zeroized, and the compression is checked constant-time at the secret instance.
hmac.sha256 and hmac.sha384 accept secret keys, public messages, and
caller-owned public output. The output capacity must be at least the full tag
size. Successful calls write the full tag. The output may overlap the public
message because no output is written until authentication is complete.
hmac.sha256_secret and hmac.sha384_secret take the same inputs and write
the tag into a caller-owned contracts.SecretBuffer, for a tag that is itself
secret derived material, such as a stateless reset token or a derived key. The
caller decides where, or whether, to declassify it. The output may overlap the
key or the message. hmac.sha256 and hmac.sha384 are these functions
followed by one declassify into the public output.
hkdf.extract_sha256 and hkdf.extract_sha384 accept secret salt and input key
material and write a fixed-size secret pseudorandom key. Empty salt and input
key material follow RFC 5869. Extract output may overlap either input because
the complete pseudorandom key is held locally before it is copied.
hkdf.expand_sha256 and hkdf.expand_sha384 treat output capacity as the
requested output length. The pseudorandom key must be exactly the hash output
size. Zero-length output is valid. Output is bounded to 255 hash blocks, which
is 8160 bytes for SHA-256 and 12240 bytes for SHA-384. Expand output may overlap
the pseudorandom key because the key is snapshotted before the first write.
Every validation failure returns written = 0 without changing output. Public
buffers cannot alias nonempty secret buffers under Mach's secret-welded pointer
types. Internal SHA-2 state, schedules, HMAC pads, intermediate tags,
pseudorandom key snapshots, and expansion blocks are explicitly zeroized.
aead.aes_gcm.Context holds one key expanded once: the AES key schedule and
the GHASH key. Construct it with aes_gcm.empty(), install a 16-byte or
32-byte secret key with aes_gcm.init, then call seal_with and open_with
for any number of records. Neither ever expands the key again. aes_gcm.dnit
zeroizes every key-derived field and returns the context to empty. It is
idempotent. init on a context that is not empty, and seal_with or
open_with on one that is, return INVALID_STATE without writing. The key
may be released or overwritten once init returns. A failed init leaves
the context empty. Every key-derived field is secret-typed, so the context is
secret-welded like the key. It is move-only by contract and must not be
copied directly.
seal_with and open_with take the arguments and keep the contracts of
seal and open below, with the context in place of the key. aes_gcm.seal
and aes_gcm.open are the one-shot form. Each call expands the key into a
temporary context, runs seal_with or open_with, and wipes it. A caller that
protects more than one record under a key, as TLS and QUIC do, holds a
Context instead.
aead.aes_gcm.seal accepts a 16-byte or 32-byte secret key, an exact 12-byte
public nonce, public additional authenticated data, secret plaintext, and
caller-owned public output. It writes ciphertext followed by the complete
16-byte tag. Output capacity must cover both. Nonce and additional data may
overlap output because both are fully consumed before the first write.
aead.aes_gcm.open accepts ciphertext followed by its complete tag and
authenticates the entire record before decrypting any byte. On authentication
failure it returns AUTH_FAILED, writes no plaintext, and zeroizes exactly the
ciphertext-length prefix of secret output. Validation and capacity failures do
not change output. Successful output may overlap the secret key because the
expanded key schedule is complete before plaintext release.
Plaintext is bounded to 68,719,476,704 bytes, or 2^32 - 2 counter blocks.
Additional authenticated data is bounded to 2^61 - 1 bytes so its bit length
is representable in GCM's 64-bit length field. The implementation uses an
algebraic AES S-box and fixed-position GHASH multiplication rather than memory
lookups indexed by secret values. Expanded keys, cipher state, hash products,
authentication tags, and stream blocks are explicitly zeroized.
AES and GHASH each run behind an internal backend seam (crypto.internal.aes
and crypto.internal.ghash). A context records the member chosen when its key
is installed, and every later call runs that member. The bitsliced software
member is portable and constant time. The hardware members are AES-NI and
PCLMULQDQ on x86_64 and the ARMv8 AES rounds and PMULL on aarch64. init
picks them where the processor has the extensions, which std.system.cpu
reports (or the build target selects, in which case nothing is probed), and
the AES and GHASH members are chosen independently. The hardware kernels are
fixed instruction streams with no branch or secret address, checked by
#[oblivious], and GHASH reduces with shifts in the bit-reflected form of
Intel's carry-less multiply white paper.
cipher.aes.Context holds one AES-128 or AES-256 key schedule. Construct it
with aes.empty(), expand the key once with aes.init, then call
aes.encrypt_block for any number of 16-byte blocks. Input must be exactly
one block, output must hold one, and they may be the same storage.
aes.dnit zeroizes the schedule. The lifecycle, the INVALID_STATE rules and
the welding are the same as for the AES-GCM context. This is the raw forward
cipher for constructions such as QUIC header protection, which derives a
five-byte mask from one encrypted packet sample. Record protection belongs to
crypto.aead.
aead.chacha20_poly1305.seal accepts an exact 32-byte secret key, an exact
12-byte public nonce, public additional authenticated data, secret plaintext,
and caller-owned public output. It writes ciphertext followed by the complete
16-byte tag. Nonce and additional data may overlap output because both are
consumed before the first output write.
aead.chacha20_poly1305.open authenticates the complete ciphertext and tag
before decrypting any byte. Authentication failure returns AUTH_FAILED and
zeroizes exactly the ciphertext-length prefix of secret output. Validation and
capacity failures do not change output. Successful output may overlap the
secret key because the ChaCha20 context snapshots it before plaintext release.
Plaintext is bounded to 274,877,906,880 bytes, or 2^32 - 1 payload blocks.
Counter zero is reserved for the Poly1305 one-time key and payload counters run
from one through 0xffffffff. Additional-data and ciphertext byte lengths are
encoded as the complete 64-bit RFC 8439 length fields. ChaCha20 uses fixed
rotations and positions. Poly1305 runs on three 44-bit limbs, nine
crypto.internal.word products and one carry chain per block, with no
secret-dependent table access. Keys, stream state, one-time keys,
accumulators, authentication tags, and stream blocks are explicitly zeroized.
This module protects TLS records and QUIC packet payloads. QUIC header
protection is a separate primitive contract because it consumes a packet
sample to generate a five-byte mask rather than an AEAD nonce and payload. For
the AES suites that primitive is cipher.aes.
group.x25519.derive_public accepts an exact 32-byte secret private value and
writes its 32-byte RFC 7748 public u-coordinate to caller-owned public output.
group.x25519.agree accepts an exact 32-byte secret private value and 32-byte
public peer encoding, then writes the shared value to caller-owned secret
output. Private values are clamped internally. Consumers can create fresh
private values directly with secret.init_random.
Peer decoding masks the high bit and accepts non-canonical values by reducing
them modulo 2^255 - 19, as required by RFC 7748. Any peer encoding that
produces the all-zero shared value is rejected with INVALID_KEY. Validation,
capacity, and low-order failures return written = 0 without changing caller
output. Successful agreement output may overlap the private key because the
complete scalar is snapshotted before output is written.
The implementation performs the same 255-step Montgomery ladder for every
private value. Conditional swaps use masks rather than secret branches. Field
operations use fixed-position radix-2^51 limbs. Each field product is 25
limb products, computed with the processor's 64 x 64 widening multiply where
mach admits it as constant time ($mach.build.ct_mul(low, 128): x86-64 on
every OS, aarch64 on linux and darwin under PSTATE.DIT, riscv64 under Zkt) and
as a fixed 255-step masked sum everywhere else. Secret division is never
used. Scalar, field, inversion, encoded result, and rejected shared-secret
state are explicitly zeroized.
group.p256.derive_public accepts an exact 32-byte big-endian private scalar
in 1..n-1 and writes the exact 65-byte uncompressed SEC1 public point.
group.p256.agree accepts that scalar and an exact uncompressed SEC1 peer
point, validates canonical coordinates and the curve equation, and writes the
32-byte big-endian shared x-coordinate to secret output. Compressed points,
the point at infinity, off-curve points, non-canonical coordinates, and invalid
private scalars are rejected. Successful agreement may overwrite its private
scalar because the complete scalar is snapshotted first.
signature.sign_ecdsa_p256_sha256 hashes the public message with SHA-256 and
uses RFC 6979 HMAC-SHA-256 nonce generation. It writes a strict minimal DER
signature of at most 72 bytes. signature.verify_ecdsa_p256_sha256 accepts an
exact uncompressed SEC1 public key and strict DER, rejects zero or out-of-range
r and s, and accepts both high- and low-s mathematical signatures. All
validation and capacity failures return written = 0 without changing output.
Signing evaluates eight nonce candidates before selecting the first valid one,
which hides the RFC rejection count and bounds all-candidate failure below the
P-256 security level.
signature.verify_ecdsa_p384_sha384 hashes the public message with SHA-384 and
accepts an exact 97-byte uncompressed secp384r1 public key and strict DER
signature of at most 104 bytes. It validates canonical coordinates, the curve
equation, and scalar ranges. P-384 private signing and ECDH are not exposed.
signature.verify_ecdsa takes the curve (CURVE_P256, CURVE_P384, or
CURVE_P521) and the hash (HASH_SHA256, HASH_SHA384, or HASH_SHA512)
independently, as CMS and PDF signers choose them, and hashes the public
message. signature.verify_ecdsa_digest takes the curve and a 32, 48, or
64-byte digest the caller computed. A digest longer than the curve order is
truncated to its leftmost bytes and a shorter one is padded on the left, per
FIPS 186-5. The public key is an exact uncompressed SEC1 point (65, 97, or 133
bytes) and the signature is strict DER, up to 72, 104, or 139 bytes. The two
fixed pairs above call verify_ecdsa. P-521 is verification only, so no
signing or ECDH is exposed for it.
Field and scalar values are four 64-bit limbs on one Montgomery ring
(crypto.internal.mont), and field coordinates stay in Montgomery form inside
a point. Each product is 16 limb products and a 16-product reduction, computed
with the processor's 64 x 64 widening multiply where mach admits it as constant
time ($mach.build.ct_mul(low, 128)) and with a fixed 64-step masked sum
everywhere else. Inversions use fixed public addition chains, scalar
multiplication uses a fixed 64-window point loop with masked selection and no
secret-indexed table, and the fixed-base comb table is public data read under
a mask. Private scalars, nonce state, hashes, field and scalar temporaries, and
projective points are explicitly zeroized.
P-384 verification uses the same ownership and constant-shape contract on six 64-bit limbs of the same ring. Verification evaluates the two ECDSA point products together in one fixed 384-step loop with masked four-way point selection and no secret-indexed table. Inversions retain fixed public exponents. Hashes, field and scalar temporaries, and projective points remain welded until explicitly zeroized.
P-521 verification (crypto.internal.p521) follows the P-384 code on nine
64-bit limbs, with a 521-step loop. A P-521 signature of 128 or more content
bytes carries the DER long-form sequence length 0x81, which the parser
requires and accepts only in minimal form.
signature.derive_ed25519_public derives an exact 32-byte RFC 8032 public key
from an exact 32-byte private seed. signature.sign_ed25519 writes an exact
64-byte deterministic pure-Ed25519 signature. Verification requires canonical
point encodings, S < L, and non-identity prime-subgroup public and nonce
points. Malformed public keys return INVALID_KEY. Malformed or
mathematically invalid signatures return AUTH_FAILED.
RSA keys use explicit RsaPublicKey and RsaPrivateKey records. A modulus is
a canonical big-endian, odd, full-width value from 256 through 512 bytes in
four-byte increments. This admits 2048, 3072, and 4096-bit TLS keys. A public
exponent is canonical big-endian, odd, at least three, and less than the
modulus. A private exponent is secret, left-zero-padded to exactly the modulus
width, and is paired with its public exponent so signing can verify its own
result before release.
The arithmetic runs on 64-bit limbs in a Montgomery ring derived from the
modulus per operation, with every limb product a crypto.internal.word
product, so a target with the hardware multiply computes one instruction per
limb pair. A modulus that does not fill its top limb, such as 2080 bits, runs
on the same ring. Exponentiation uses fixed 4-bit windows: four squarings and
one multiply per window, with the table entry read by a masked scan over all
sixteen entries so no secret indexes memory, and the same path for public and
private exponents.
signature.sign_rsa_pss_sha256 requires an exact 32-byte caller-provided
secret salt. signature.sign_rsa_pss_sha384 requires an exact 48-byte salt.
The caller owns salt generation and should use secret.init_random for every
signature. The encoded message uses emBits = modBits - 1, MGF1 with the same
hash, an exact hash-length salt, and trailer 0xbc. Signatures are exactly the
modulus width. Verification enforces the same salt and encoded-message policy.
signature.verify_rsa_pss_sha512 verifies RSA-PSS with SHA-512, MGF1 over
SHA-512 and a 64-byte salt. signature.sign_rsa_pss_sha512 is not provided.
signature.verify_rsa_pkcs1_sha256,
signature.verify_rsa_pkcs1_sha384, and signature.verify_rsa_pkcs1_sha512 accept certificate signatures encoded by
EMSA-PKCS1-v1_5. Verification requires a signature exactly as wide as the
modulus, at least eight 0xff padding bytes, the exact RFC 8017 SHA-256,
SHA-384, or SHA-512 DigestInfo with explicit NULL parameters, and no leading or trailing
bytes. Malformed encodings and digest mismatches return AUTH_FAILED without
changing caller-owned key, message, or signature storage.
Ed25519 and RSA-PSS signing consume the complete message before writing, so
public message and output storage may overlap. Validation, capacity, and key
failures write nothing and return written = 0. RSA signing performs a public
exponentiation self-check before any signature byte is released. Secret seeds,
expanded scalars, nonces, salts copied into encoded messages, private
exponentiation state, message hashes, masks, encoded messages, and unreleased
signatures are explicitly zeroized. signature.algorithm_status returns
UNSUPPORTED for an unknown TLS signature identifier, independently of
INVALID_KEY and AUTH_FAILED operation results.
The repository combines callable primitives with contracts for algorithms that are still being built. AES-128-GCM, AES-256-GCM, the AES-128 and AES-256 block cipher, ChaCha20-Poly1305, X25519, P-256 ECDH, ECDSA P-256 with SHA-256, ECDSA P-384 verification with SHA-384, ECDSA verification over P-256, P-384, and P-521 with SHA-256, SHA-384, and SHA-512, Ed25519, RSA-PSS and RSA PKCS#1 v1.5 with SHA-256 and SHA-384, and their verification with SHA-512, HMAC-SHA-256, HMAC-SHA-384, HKDF-Extract, HKDF-Expand, incremental SHA-256 and SHA-384, strict DER and PEM, and TLS key containers are callable in this revision. Their tests include NIST FIPS 197, NIST SP 800-38A, NIST SP 800-38D, other NIST, RFC 4231, RFC 5869, RFC 6979, RFC 7468, RFC 7748, RFC 8017, RFC 8032, RFC 8410, RFC 8439, SEC 1, and independent OpenSSL vectors. They cover strict-encoding and tamper cases, counter and output limits, invalid inputs, fixed-buffer failures, and supported overlap.
Mach constant-time support is functional. Its current limitation is assurance, not expressiveness. The package will use Mach secret types and oblivious code, official vectors, differential tests, generated-code inspection, leakage tests, and independent review as distinct evidence layers.
tools/assurance run is the fail-closed release evidence gate. It runs the
functional, official-vector, differential, invalid-input, leakage, and
zeroization layers independently. It then performs two clean builds of every
project for all six targets in debug and release mode with verified IR and
emitted assembly. The two artifact sets must be byte-for-byte identical.
A successful run publishes an immutable machine-readable evidence directory
under out/assurance/ and updates out/assurance/current. The report records
the exact Git commit, compiler binary hash, compiler version, dependency lock,
target, profile, algorithm, vector source, logs, generated-code samples, and
artifact hashes. tools/assurance verify rejects missing, modified, or stale
evidence. See the assurance guide for the complete contract.
The source-only floor remains assurance.SCAFFOLD because generated evidence
is deliberately not trusted merely because a source constant says it exists.
The gate advances the automated level in state.tsv only after every required
artifact passes. Callable algorithm availability and independent review status
are separate fields. Independent review is currently NOT_RECORDED.
Dependencies are pinned Git tags. Build output uses Mach's default out/
directory inside this repository.
mach test . runs only the tests the library reaches. mach test . -a tests
runs every test, through src/lib/tests.mach. A new test module goes there,
and tools/test-selection fails until it does.
mach dep pull .
mach build .
mach test . -a tests
tools/test-selection
tools/assurance run
tools/assurance verify