{"id":49872533,"url":"https://github.com/valkyoth/base64-ng","last_synced_at":"2026-06-09T22:00:34.186Z","repository":{"id":357567637,"uuid":"1237471662","full_name":"valkyoth/base64-ng","owner":"valkyoth","description":"A next-generation Base64 codec for Rust.","archived":false,"fork":false,"pushed_at":"2026-06-07T13:47:43.000Z","size":2453,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-07T15:06:53.022Z","etag":null,"topics":["base64","base64-decoding","base64-encoding","hacktoberfest","no-std","rfc4648","rust","rust-crate","rust-lang","rustlang","simd"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/base64-ng","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/valkyoth.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["eldryoth"],"thanks_dev":"u/gh/eldryoth"}},"created_at":"2026-05-13T08:03:26.000Z","updated_at":"2026-06-07T13:19:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/valkyoth/base64-ng","commit_stats":null,"previous_names":["valkyoth/base64-ng"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/valkyoth/base64-ng","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fbase64-ng","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fbase64-ng/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fbase64-ng/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fbase64-ng/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/valkyoth","download_url":"https://codeload.github.com/valkyoth/base64-ng/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/valkyoth%2Fbase64-ng/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34127345,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["base64","base64-decoding","base64-encoding","hacktoberfest","no-std","rfc4648","rust","rust-crate","rust-lang","rustlang","simd"],"created_at":"2026-05-15T10:01:01.951Z","updated_at":"2026-06-09T22:00:34.178Z","avatar_url":"https://github.com/valkyoth.png","language":"Rust","funding_links":["https://github.com/sponsors/eldryoth","https://thanks.dev/u/gh/eldryoth"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cb\u003eSecure, no_std-first Base64 for Rust.\u003c/b\u003e\u003cbr\u003e\n  Strict decoding, caller-owned buffers, zero runtime dependencies, and release-gated security evidence.\n\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://docs.rs/base64-ng\"\u003eDocs.rs\u003c/a\u003e\n  |\n  \u003ca href=\"docs/TRUST.md\"\u003eTrust Dashboard\u003c/a\u003e\n  |\n  \u003ca href=\"docs/SECURITY_CONTROLS.md\"\u003eSecurity Controls\u003c/a\u003e\n  |\n  \u003ca href=\"docs/PLAN.md\"\u003eRoadmap\u003c/a\u003e\n  |\n  \u003ca href=\"SECURITY.md\"\u003eSecurity\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cbr\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/valkyoth/base64-ng/main/.github/images/base64-ng.webp\" alt=\"base64-ng Rust crate overview\"\u003e\n\u003c/p\u003e\n\n# base64-ng\n\n`base64-ng` is a `no_std`-first Base64 crate focused on correctness, strict decoding, caller-owned buffers, and a security-heavy release process. The long-term goal is to provide modern hardware acceleration without making unsafe SIMD the foundation of trust.\n\nThe crate starts conservative: a small scalar implementation, strict RFC 4648 behavior, and a test/release system modeled after hardened Rust service projects. Streaming is available behind an explicit feature, fuzz harnesses are isolated from the published crate, and future SIMD and broader Kani work remain gated until they have evidence.\n\n## Current Status\n\nThe current public release is `1.0.8`.\n\nImplemented now:\n\n- `no_std` core with optional `alloc` and `std` features.\n- Zero external runtime or development dependencies in `Cargo.toml`.\n- Standard and URL-safe alphabets.\n- Padded and unpadded encoding into caller-provided output buffers.\n- Stable compile-time encoding into caller-sized arrays.\n- Strict decoding into caller-provided output buffers.\n- In-place encoding when the caller provides enough spare capacity.\n- Optional `alloc` vector and string helpers.\n- In-place decode API built on the same strict scalar decoder.\n- Explicit legacy decode APIs that ignore ASCII transport whitespace while\n  keeping alphabet and padding validation strict.\n- Validation-only APIs for strict and legacy profiles when callers need to\n  reject malformed input without materializing decoded bytes.\n- Line-wrapped encoding for MIME/PEM-style output and caller-selected wrapping\n  policies.\n- Strict line-wrapped validation and decoding profiles for MIME/PEM-style\n  input.\n- Custom alphabet validation helpers for user-defined 64-byte alphabets.\n- Named dependency-free profiles for MIME, PEM, bcrypt-style, and\n  `crypt(3)`-style Base64.\n- Stack-backed encoded output buffers for short values without `alloc`.\n- Redacted secret owned buffers for sensitive encoded or decoded bytes when\n  `alloc` is enabled.\n- Separate `ct` scalar validation and decode module for sensitive payloads\n  that avoids secret-indexed lookup tables during Base64 symbol mapping.\n- `std::io` streaming encoders and decoders behind the `stream` feature.\n- Focused unit and integration tests.\n- Isolated `cargo-fuzz` harnesses for decode, in-place decode, and stream\n  chunk-boundary behavior.\n- Isolated dudect-style timing harness for the constant-time-oriented scalar\n  decoder.\n- Bounded Kani proof harnesses that run on Rust `1.90.0` with\n  `cargo-kani 0.67.0`.\n- Constant-time assembly evidence generation for reviewer inspection.\n- Local check scripts, release gate, dependency policy, audit config, CI, SBOM script, and reproducible build check.\n\nPlanned behind admission evidence:\n\n- Admitted AVX2, AVX-512, SSSE3/SSE4.1, ARM NEON, and wasm `simd128`\n  fast paths after the SIMD admission evidence is complete. The `1.0.8`\n  release remains scalar-only.\n- Async streaming wrappers only after the `tokio` feature passes the\n  dependency and cancellation-safety admission bar in [docs/ASYNC.md](docs/ASYNC.md).\n- Optional `serde` or `bytes` integration only if a concrete use case clears\n  the dependency admission policy in [docs/DEPENDENCIES.md](docs/DEPENDENCIES.md).\n- Additional Kani harnesses beyond the current bounded no-default-features\n  proof set. A clean Kani run proves only the scoped harness properties, not a\n  whole-crate or cryptographic constant-time guarantee.\n- Broader benchmark evidence against the established `base64` crate.\n\n## Trust Dashboard\n\n| Area | Status |\n| --- | --- |\n| License | `MIT OR Apache-2.0` |\n| MSRV | Rust `1.90.0` |\n| Runtime dependencies | Zero external crates |\n| Unsafe policy | Scalar encode/decode remains safe Rust; audited unsafe is limited to volatile wiping, CT comparison/barrier helpers, and test-only SIMD prototypes |\n| Active backend | Scalar only |\n| Strict decoding | Default, canonical, no whitespace |\n| Legacy compatibility | Explicit opt-in APIs |\n| Constant-time posture | Constant-time-oriented scalar validation/decode with isolated dudect-style timing evidence; no formal cryptographic guarantee |\n| Cleanup posture | Best-effort initialized-byte cleanup and redacted secret wrappers |\n| Kani | 17 bounded no-default-features harnesses verified with Rust `1.90.0` and `cargo-kani 0.67.0`; not a whole-crate formal-verification claim |\n| Release evidence | fmt, clippy, tests, docs, deny, audit, license, SBOM, reproducibility |\n\nFull adoption details live in [docs/TRUST.md](docs/TRUST.md). Security-control\nand CWE mapping lives in [docs/SECURITY_CONTROLS.md](docs/SECURITY_CONTROLS.md).\n\n## Rust Version Support\n\nThe minimum supported Rust version is Rust `1.90.0`. New deployments should\nprefer the latest stable Rust; as of May 29, 2026, that is Rust `1.96.0`.\n\nCompatibility evidence for `1.0.8`:\n\n| Rust | Local Evidence |\n| --- | --- |\n| `1.90.0` | ✓ full release gate |\n| `1.91.0` | ✓ `cargo check --all-features` |\n| `1.92.0` | ✓ `cargo check --all-features` |\n| `1.93.0` | ✓ `cargo check --all-features` |\n| `1.94.0` | ✓ `cargo check --all-features` |\n| `1.95.0` | ✓ `cargo check --all-features` |\n| `1.96.0` | ✓ `cargo check --all-features` |\n\n## Install\n\n```toml\n[dependencies]\nbase64-ng = \"1.0.8\"\n```\n\nThe crate is dual-licensed:\n\n```toml\nlicense = \"MIT OR Apache-2.0\"\n```\n\n## Features\n\n| Feature | Default | Purpose |\n| --- | --- | --- |\n| `alloc` | yes | `Vec` and encoded `String` convenience APIs. |\n| `std` | yes | `std::error::Error` support and feature base for I/O. |\n| `simd` | no | Future hardware acceleration. |\n| `stream` | no | `std::io` streaming wrappers. |\n| `allow-wasm32-best-effort-wipe` | no | Explicitly allow `wasm32` builds with compiler-fence-only cleanup. |\n| `allow-compiler-fence-only-wipe` | no | Explicitly allow unsupported native architectures to build with compiler-fence-only cleanup after platform review. |\n| `tokio` | no | Reserved for future async streaming wrappers; currently inert and dependency-free. |\n| `kani` | no | Reserved for verifier harnesses; normal builds do not require Kani. |\n| `fuzzing` | no | Reserved for verifier and fuzz harness integration; published crate stays dependency-free. |\n\nDisable defaults for embedded or freestanding use:\n\n```toml\n[dependencies]\nbase64-ng = { version = \"1.0.8\", default-features = false }\n```\n\n## Convenience API\n\nFor strict standard padded Base64 on `alloc` targets:\n\n```rust\nassert_eq!(base64_ng::encode(b\"hello\").unwrap(), \"aGVsbG8=\");\nassert_eq!(base64_ng::decode(\"aGVsbG8=\").unwrap(), b\"hello\");\n```\n\nThese helpers are intended for ordinary data and migration from simpler\nBase64 APIs. For secret-bearing payloads, use the explicit engine, profile, or\n`ct` APIs below so the security contract is visible at the call site.\n\n## Example\n\n```rust\nuse base64_ng::{STANDARD, checked_encoded_len};\n\nlet input = b\"hello\";\nconst ENCODED_CAPACITY: usize = match checked_encoded_len(5, true) {\n    Some(len) =\u003e len,\n    None =\u003e panic!(\"encoded length overflow\"),\n};\nlet mut encoded = [0u8; ENCODED_CAPACITY];\nlet written = STANDARD.encode_slice(input, \u0026mut encoded).unwrap();\nassert_eq!(\u0026encoded[..written], b\"aGVsbG8=\");\n\nlet mut decoded = [0u8; 5];\nlet written = STANDARD.decode_slice(\u0026encoded, \u0026mut decoded).unwrap();\nassert_eq!(\u0026decoded[..written], input);\n```\n\nIn-place encoding:\n\n```rust\nuse base64_ng::STANDARD;\n\nlet mut buffer = [0u8; 8];\nbuffer[..5].copy_from_slice(b\"hello\");\nlet encoded = STANDARD.encode_in_place(\u0026mut buffer, 5).unwrap();\nassert_eq!(encoded, b\"aGVsbG8=\");\n```\n\nFor sensitive payloads, `encode_slice_clear_tail` and\n`encode_in_place_clear_tail` clear unused bytes after the encoded prefix and\nclear the caller-owned output buffer on encode error.\n\nCompile-time encoding:\n\n```rust\nuse base64_ng::{STANDARD, URL_SAFE_NO_PAD};\n\nconst HELLO: [u8; 8] = STANDARD.encode_array(b\"hello\");\nconst URL_BYTES: [u8; 3] = URL_SAFE_NO_PAD.encode_array(b\"\\xfb\\xff\");\n\nassert_eq!(\u0026HELLO, b\"aGVsbG8=\");\nassert_eq!(\u0026URL_BYTES, b\"-_8\");\n```\n\nStable Rust cannot yet express the encoded length as the return array length\ndirectly, so `encode_array` uses the destination array type supplied by the\ncaller. A wrong output length fails during const evaluation.\nUse `encode_array` for fixed-size static values, not for runtime data whose\nsize is controlled by an attacker.\n\nFor untrusted length metadata, use checked length calculation:\n\n```rust\nuse base64_ng::{\n    LineEnding, LineWrap, checked_encoded_len, checked_wrapped_encoded_len, decoded_len,\n};\n\nassert_eq!(checked_encoded_len(5, true), Some(8));\nassert_eq!(\n    checked_wrapped_encoded_len(5, true, LineWrap::new(4, LineEnding::Lf)),\n    Some(9)\n);\nassert_eq!(decoded_len(b\"aGVsbG8=\", true).unwrap(), 5);\n```\n\n## Validation Without Decoding\n\nUse validation-only APIs when a protocol needs to sanitize input before storing,\nrouting, or accounting for it:\n\n```rust\nuse base64_ng::{STANDARD, URL_SAFE_NO_PAD};\n\nassert!(STANDARD.validate(b\"aGVsbG8=\"));\nassert!(!STANDARD.validate(b\"aGVsbG8\"));\n\nSTANDARD.validate_result(b\"aGVsbG8=\").unwrap();\n\nassert!(URL_SAFE_NO_PAD.validate(b\"-_8\"));\nassert!(!URL_SAFE_NO_PAD.validate(b\"+/8\"));\n```\n\nFor line-wrapped or spaced legacy inputs, use the explicit legacy profile:\n\n```rust\nuse base64_ng::STANDARD;\n\nassert!(STANDARD.validate_legacy(b\" aG\\r\\nVsbG8= \"));\nassert!(!STANDARD.validate_legacy(b\" aG-V \"));\n\nlet decoded = STANDARD\n    .decode_buffer_legacy::\u003c5\u003e(b\" aG\\r\\nVs\\tbG8= \")\n    .unwrap();\nassert_eq!(decoded.as_bytes(), b\"hello\");\n```\n\n## Line-Wrapped Encoding\n\nUse `LineWrap` when a protocol needs MIME/PEM-style line lengths:\n\n```rust\nuse base64_ng::{LineEnding, LineWrap, STANDARD};\n\nlet wrap = LineWrap::new(4, LineEnding::Lf);\nlet mut output = [0u8; 9];\nlet written = STANDARD\n    .encode_slice_wrapped(b\"hello\", \u0026mut output, wrap)\n    .unwrap();\n\nassert_eq!(\u0026output[..written], b\"aGVs\\nbG8=\");\n```\n\nBuilt-in policies include `LineWrap::MIME`, `LineWrap::PEM`, and\n`LineWrap::PEM_CRLF`. Wrapping inserts line endings between encoded lines and\ndoes not append a trailing line ending after the final line. `LineEnding`\nexposes `name()`, `Display`, `as_str()`, `as_bytes()`, and `byte_len()` for\nallocation-free policy inspection. `name()` and `Display` return printable\nidentifiers such as `LF` and `CRLF`; `as_str()` returns the literal line-ending\nbytes. `LineWrap` exposes `line_len()`, `line_ending()`, and `is_valid()` for\nconst-friendly policy checks and implements `Display` as `line_len:name`, for\nexample `76:CRLF`. `LineWrap::new` rejects zero line lengths; use\n`LineWrap::checked_new` when wrapping policy comes from configuration.\n\nNamed profiles carry the wrapping policy for common protocols:\n\n```rust\nuse base64_ng::{LineEnding, MIME, PEM};\n\nassert_eq!(MIME.line_wrap().unwrap().line_len, 76);\nassert_eq!(MIME.line_len(), Some(76));\nassert_eq!(MIME.line_ending(), Some(LineEnding::CrLf));\nassert_eq!(MIME.to_string(), \"padded=true wrap=76:CRLF\");\nassert_eq!(PEM.line_wrap().unwrap().line_len, 64);\nassert_eq!(PEM.line_len(), Some(64));\n\nlet mut encoded = [0u8; 82];\nlet written = MIME.encode_slice(\u0026[0x5a; 58], \u0026mut encoded).unwrap();\nassert_eq!(\u0026encoded[76..78], b\"\\r\\n\");\nassert!(MIME.validate(\u0026encoded[..written]));\n```\n\nAn engine can also be promoted explicitly to an unwrapped profile when a common\nconfiguration path expects profile values, or to the matching\nconstant-time-oriented decoder when sensitive decode policy is required:\n\n```rust\nuse base64_ng::STANDARD;\n\nlet profile = STANDARD.profile();\nlet ct_decoder = STANDARD.ct_decoder();\n\nassert!(profile.is_padded());\nassert!(!profile.is_wrapped());\nassert_eq!(ct_decoder.decoded_len(b\"aGVsbG8=\").unwrap(), 5);\n```\n\nWhen wrapping policy comes from configuration, prefer checked construction:\n\n```rust\nuse base64_ng::{LineEnding, LineWrap, Profile, STANDARD};\n\nlet wrap = LineWrap::checked_new(76, LineEnding::CrLf).unwrap();\nlet profile = Profile::checked_new(STANDARD, Some(wrap)).unwrap();\n\nassert!(profile.is_valid());\nassert!(profile.is_wrapped());\n```\n\nThe same policy can be used for strict wrapped decoding. Unlike legacy\nwhitespace decoding, this accepts only the configured line ending and requires\nevery non-final line to have the configured encoded length:\n\n```rust\nuse base64_ng::{LineEnding, LineWrap, STANDARD};\n\nlet wrap = LineWrap::new(4, LineEnding::Lf);\nlet mut output = [0u8; 5];\nlet written = STANDARD\n    .decode_slice_wrapped(b\"aGVs\\nbG8=\", \u0026mut output, wrap)\n    .unwrap();\n\nassert_eq!(\u0026output[..written], b\"hello\");\n\nlet encoded = STANDARD.encode_wrapped_buffer::\u003c9\u003e(b\"hello\", wrap).unwrap();\nassert_eq!(encoded.as_bytes(), b\"aGVs\\nbG8=\");\n\nlet decoded = STANDARD\n    .decode_wrapped_buffer::\u003c5\u003e(encoded.as_bytes(), wrap)\n    .unwrap();\nassert_eq!(decoded.as_bytes(), b\"hello\");\n```\n\n## Custom Alphabets\n\nUser-defined alphabets can be generated and validated at compile time:\n\n```rust\nbase64_ng::define_alphabet! {\n    struct DotSlash = b\"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n}\n\nuse base64_ng::Alphabet;\n\nassert_eq!(DotSlash::decode(b'.'), Some(0));\n```\n\nThe generated alphabet uses the deliberately conservative default\n`Alphabet::encode` implementation: it performs a fixed 64-entry scan for every\nemitted Base64 byte to avoid secret-indexed table lookups. The built-in\nalphabets override this with optimized arithmetic mappers. For very large\npayloads and custom alphabets, benchmark this tradeoff before using them on\nuntrusted high-volume traffic.\n\nIf you implement `Alphabet` manually, overriding `encode` with\n`ENCODE[value as usize]` makes normal `Engine` encoding timing-sensitive with\nrespect to the 6-bit value. Similarly, a custom `decode` implementation affects\nthe normal strict decoder. The `ct` module does not call `Alphabet::decode`; it\nscans `Alphabet::ENCODE` directly with its own fixed 64-entry mapper.\n\nBuilt-in non-RFC alphabets are available for explicit interoperability:\n\n```rust\nuse base64_ng::{BCRYPT, CRYPT};\n\nlet mut bcrypt = [0u8; 4];\nlet written = BCRYPT.encode_slice(\u0026[0xff, 0xff, 0xff], \u0026mut bcrypt).unwrap();\nassert_eq!(\u0026bcrypt[..written], b\"9999\");\n\nlet mut crypt = [0u8; 4];\nlet written = CRYPT.encode_slice(\u0026[0xff, 0xff, 0xff], \u0026mut crypt).unwrap();\nassert_eq!(\u0026crypt[..written], b\"zzzz\");\n```\n\nThe bcrypt and `crypt(3)` profiles provide alphabets and no-padding behavior\nonly. They do not parse or verify complete password-hash strings.\n\n## Legacy Whitespace Decoding\n\nStrict decoding rejects whitespace. If an existing protocol allows line-wrapped\nor spaced Base64, use the explicit legacy APIs:\n\n```rust\nuse base64_ng::STANDARD;\n\nlet mut output = [0u8; 5];\nlet written = STANDARD\n    .decode_slice_legacy(b\" aG\\r\\nVs\\tbG8= \", \u0026mut output)\n    .unwrap();\n\nassert_eq!(\u0026output[..written], b\"hello\");\n```\n\nLegacy decoding only ignores ASCII space, tab, carriage return, and line feed.\nAlphabet selection, padding placement, trailing data after padding, and\nnon-canonical trailing bits remain strict.\n\n## Bounded Memory Use\n\nFor untrusted payloads, size buffers before decoding or encoding. The checked\nhelpers let callers reject impossible or oversized metadata before allocating:\n\n```rust\nuse base64_ng::{STANDARD, checked_encoded_len, decoded_capacity};\n\nlet input = b\"hello\";\nlet encoded_len = checked_encoded_len(input.len(), true).unwrap();\nassert_eq!(encoded_len, 8);\n\nlet mut encoded = vec![0u8; encoded_len];\nlet written = STANDARD.encode_slice(input, \u0026mut encoded).unwrap();\nencoded.truncate(written);\n\nlet max_decoded = decoded_capacity(encoded.len());\nlet mut decoded = vec![0u8; max_decoded];\nlet written = STANDARD.decode_slice(\u0026encoded, \u0026mut decoded).unwrap();\ndecoded.truncate(written);\n\nassert_eq!(decoded, input);\n```\n\n`decode_vec` validates the complete input before allocating decoded output.\nUse `decode_slice` or `decode_in_place` when the caller needs hard memory\nlimits and owns the output buffer.\n\nFor sensitive payloads, use `decode_slice_clear_tail` or\n`decode_in_place_clear_tail` to clear unused bytes after the decoded prefix. On\ndecode error these variants clear the caller-owned output buffer before\nreturning the error. The legacy whitespace profile also provides\n`decode_slice_legacy_clear_tail`, `decode_in_place_legacy_clear_tail`, and\n`decode_buffer_legacy`. Strict line-wrapped profiles provide\n`decode_in_place_wrapped`, `decode_in_place_wrapped_clear_tail`, and the same\nin-place behavior through `Profile::decode_in_place`. The `ct` module provides\nthe same clear-tail decode variants for callers using the constant-time-oriented\nscalar decoder, `ct::CtEngine::decoded_len` for sizing caller-owned buffers\nunder the same opaque malformed-input policy, plus\n`ct::CtEngine::decode_buffer` for stack-backed no-alloc decoded output.\nFor constant-time-oriented in-place decode, prefer\n`ct::CtEngine::decode_in_place_clear_tail`. The non-clear-tail CT in-place API\nwas removed before the `1.0` stable boundary because failed in-place decode can\npartially destroy the encoded input and retain decoded plaintext in the same\nbuffer. If the encoded token must be logged or retried after failure, keep a\nseparate copy before any in-place decode.\n\nThe default strict decoders are not constant-time decoders: they preserve exact\nerror indexes and may return early for malformed input, padding, length, or\noutput-size errors. Use `base64_ng::ct` for secret-bearing payloads where decode\ntiming posture matters more than localized error diagnostics.\nDo not use `STANDARD`, `STANDARD_NO_PAD`, `URL_SAFE`, `URL_SAFE_NO_PAD`,\n`MIME`, `PEM`, `BCRYPT`, or `CRYPT` as token-comparison or key-material decode\nAPIs when the encoded bytes or rejection reason are sensitive. Use\n`ct::STANDARD`, `ct::URL_SAFE_NO_PAD`, or `STANDARD.ct_decoder()` instead and\nperform any final token comparison with a constant-time-oriented comparison\nappropriate for the protocol.\nFor reusable secret output buffers, use `ct::CtEngine::decode_slice_clear_tail`\nor `ct::CtEngine::decode_buffer`. The non-clear-tail CT slice API was removed\nbefore the `1.0` stable boundary because it can leave real decoded plaintext\nfrom valid leading quanta in `output` when later malformed input is rejected\nafter the fixed-shape decode pass.\nFor shared-memory, HSM-adjacent, sandboxed, or other multi-principal threat\nmodels where even transient writes to caller-owned output are unacceptable, use\n`ct::CtEngine::decode_slice_staged_clear_tail` with a private staging buffer.\n\nFor short values, `encode_buffer` returns a stack-backed `EncodedBuffer`\nand `decode_buffer` returns a stack-backed `DecodedBuffer` without requiring\nthe `alloc` feature:\n\n```rust\nuse base64_ng::{BCRYPT, MIME, STANDARD};\n\nlet encoded = STANDARD.encode_buffer::\u003c8\u003e(b\"hello\").unwrap();\nassert_eq!(encoded.as_str(), \"aGVsbG8=\");\nassert_eq!(encoded.as_utf8().unwrap(), \"aGVsbG8=\");\nassert_eq!(encoded.to_string(), \"aGVsbG8=\");\n\nlet decoded = STANDARD.decode_buffer::\u003c5\u003e(encoded.as_bytes()).unwrap();\nassert_eq!(decoded.as_bytes(), b\"hello\");\n\nlet bcrypt = BCRYPT.encode_buffer::\u003c4\u003e(\u0026[0xff, 0xff, 0xff]).unwrap();\nassert_eq!(bcrypt.as_bytes(), b\"9999\");\n\nlet wrapped = MIME.encode_buffer::\u003c82\u003e(\u0026[0x5a; 58]).unwrap();\nlet decoded = MIME.decode_buffer::\u003c58\u003e(wrapped.as_bytes()).unwrap();\nassert_eq!(decoded.as_bytes(), \u0026[0x5a; 58]);\n```\n\n`EncodedBuffer` exposes bytes only through `as_bytes`, fallible `as_utf8`, and\n`as_str`, and implements `Display` for allocation-free formatting of encoded\nBase64 text. That `Display` implementation emits the full Base64 payload; do\nnot use `EncodedBuffer` for encoded secrets that may reach logs or error\nmessages.\n`DecodedBuffer` exposes bytes through `as_bytes` and provides a fallible\n`as_utf8` view for decoded text. Both expose `is_full()` and\n`remaining_capacity()` for no-alloc sizing checks, redact the payload from\n`Debug`, clear their backing arrays when dropped as best-effort data-retention\nreduction, and provide explicit `constant_time_eq` helpers for equal-length\nreduction, and provide explicit equal-length comparison through\n`constant_time_eq_public_len`. They intentionally do not\nimplement `PartialEq`/`==`: the helper is a dependency-free best-effort\ncomparison, not a formal cryptographic token/MAC comparison primitive. Length\nmismatch returns immediately and must be treated as public protocol\ninformation. Applications that require a formally audited comparison should\nadmit that dependency at the application boundary, for example by comparing\nexposed bytes with `subtle`. Do not use these helpers as the sole MAC,\nbearer-token, password-hash, or authentication-secret comparison primitive in\nhigh-assurance systems.\n\n`into_exposed_array` is the explicit no-alloc ownership escape hatch for both\nstack-backed buffers. It returns `ExposedEncodedArray` or\n`ExposedDecodedArray`, keeping redacted formatting and best-effort drop-time\ncleanup after ownership transfer. If a bare array is unavoidable, call\n`into_exposed_unprotected_array_caller_must_zeroize`; cleanup then becomes the\ncaller responsibility.\n\nStack-backed buffers clear their backing arrays when dropped, but they cannot\nclear historical stack-frame copies made by the compiler, caller code, panic\nmachinery, or operating system crash capture. For highly sensitive payloads,\nprefer the clear-tail APIs as soon as the value is no longer needed, keep\nsecret lifetimes short, and combine crate-level cleanup with process policies\nfor locked memory, encrypted or disabled swap and hibernation, core dumps,\ncrash reporting, and allocator isolation for secret regions.\nCloning `EncodedBuffer` or `DecodedBuffer` creates a second live copy; avoid\ncloning secret material unless the duplicate lifetime is explicitly accounted\nfor.\nOn `wasm32`, the wipe barrier uses only a compiler fence; the wasm runtime JIT\nmay still optimize or retain cleared bytes outside the crate's control.\nFor that reason, `wasm32` builds fail closed by default. Enable\n`allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts the\nlimitation and applies its own approved memory strategy around stack-backed\nbuffers.\nOther native architectures without an implemented hardware wipe barrier also\nfail closed by default. Enable `allow-compiler-fence-only-wipe` only after\nreviewing `docs/UNSAFE.md` and applying platform memory controls appropriate\nfor that deployment.\n\nWhen an owned heap buffer is acceptable but accidental logging is not, use\n`encode_secret` and `decode_secret`:\n\n```rust\nuse base64_ng::STANDARD;\n\nlet encoded = STANDARD.encode_secret(b\"hello\").unwrap();\nassert_eq!(encoded.expose_secret(), b\"aGVsbG8=\");\nassert_eq!(format!(\"{encoded:?}\"), r#\"SecretBuffer { bytes: \"\u003credacted\u003e\", len: 8 }\"#);\n\nlet decoded = STANDARD.decode_secret(encoded.expose_secret()).unwrap();\nassert_eq!(decoded.expose_secret(), b\"hello\");\nassert!(decoded.constant_time_eq_public_len(b\"hello\"));\nassert_eq!(format!(\"{decoded}\"), \"\u003credacted\u003e\");\n\nlet wrapped = STANDARD\n    .encode_wrapped_secret(b\"hello\", base64_ng::LineWrap::PEM)\n    .unwrap();\nlet unwrapped = STANDARD\n    .decode_wrapped_secret(wrapped.expose_secret(), base64_ng::LineWrap::PEM)\n    .unwrap();\nassert_eq!(unwrapped.expose_secret(), b\"hello\");\n\nlet legacy = STANDARD\n    .decode_secret_legacy(b\" aG\\r\\nVs\\tbG8= \")\n    .unwrap();\nassert_eq!(legacy.expose_secret(), b\"hello\");\n\nlet decoded = base64_ng::SecretBuffer::try_from(\"aGVsbG8=\").unwrap();\nassert_eq!(decoded.expose_secret(), b\"hello\");\n```\n\nFor malformed-input timing-sensitive payloads, prefer the `ct` owned secret\nhelper:\n\n```rust\nuse base64_ng::ct;\n\nlet decoded = ct::STANDARD.decode_secret(b\"aGVsbG8=\").unwrap();\nassert!(decoded.constant_time_eq_public_len(b\"hello\"));\n```\n\nFor shared-memory, enclave-adjacent, HSM-style, or multi-principal deployments\nwhere even transient writes into the final heap allocation are unacceptable,\nuse stack-staged owned decode:\n\n```rust\nuse base64_ng::ct;\n\nlet decoded = ct::STANDARD\n    .decode_secret_staged::\u003c5\u003e(b\"aGVsbG8=\")\n    .unwrap();\nassert!(decoded.constant_time_eq_public_len(b\"hello\"));\n```\n\n`SecretBuffer` clears vector spare capacity when a vector is wrapped, and clears\ninitialized bytes plus spare capacity when dropped. It does not claim formal\nzeroization and cannot clean historical copies outside the wrapper or make\nguarantees about allocator behavior. `SecretBuffer` intentionally does not\nimplement `PartialEq`/`==`; use the explicit\n`constant_time_eq_public_len` helper only when its best-effort, public-length\nsecurity contract is sufficient. Length mismatch returns immediately and must\nbe treated as public protocol information. Applications that require a\nformally audited comparison should admit that dependency at the application\nboundary, for example by comparing exposed bytes with `subtle`.\n`SecretBuffer` does not lock memory; high-assurance deployments should pair it\nwith OS memory-locking, encrypted or disabled swap, crash-dump suppression, and\nallocator isolation where those controls are required.\nOn `wasm32`, the same compiler-fence-only wipe-barrier caveat applies to owned\nsecret buffers. `wasm32` builds fail closed by default; enable\n`allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts the\nlimitation and applies its own approved cleanup strategy.\n`expose_secret_utf8` provides an explicit borrowed text view when the secret\nbytes are valid UTF-8.\n\n`into_exposed_vec` consumes the wrapper and returns an `ExposedSecretVec`, which\nkeeps redacted formatting and best-effort drop-time cleanup. If a raw `Vec\u003cu8\u003e`\nis unavoidable, call\n`into_exposed_unprotected_vec_caller_must_zeroize`; that method name is\nintentionally loud because cleanup becomes the caller's responsibility.\n`try_into_exposed_string` provides an explicit escape hatch for UTF-8 text and\nreturns an `ExposedSecretString`, which keeps redacted formatting and\nbest-effort drop-time cleanup. If a raw `String` is unavoidable, call\n`into_exposed_unprotected_string_caller_must_zeroize`; cleanup then becomes the\ncaller responsibility. Invalid UTF-8 returns the redacted wrapper unchanged.\n\n`SecretBuffer` also implements `From\u003cVec\u003cu8\u003e\u003e` and `From\u003cString\u003e` for callers\nthat already own sensitive bytes or text and want to move them into the\nredacted wrapper without copying initialized bytes. With `alloc` enabled,\nstack-backed `EncodedBuffer` and `DecodedBuffer` values can also be consumed\ninto `SecretBuffer`; the stack backing array is cleared when the consumed\nbuffer drops at the end of the conversion.\n\n`TryFrom\u003c\u0026str\u003e`, `TryFrom\u003c\u0026[u8]\u003e`, and `TryFrom\u003c\u0026[u8; N]\u003e` for\n`EncodedBuffer\u003cCAP\u003e` encode raw input bytes with strict standard padded Base64.\nThe same byte and text conversions for `DecodedBuffer\u003cCAP\u003e` and `SecretBuffer`\ndecode strict standard padded Base64.\n`DecodedBuffer\u003cCAP\u003e` and `SecretBuffer` also implement `FromStr` with the same\nstrict standard padded decode policy. Use explicit engine or profile methods\nfor URL-safe, no-padding, MIME/PEM, bcrypt-style, or custom alphabets.\n\nWith the default `alloc` feature, vector and string helpers are available:\n\n```rust\nuse base64_ng::STANDARD;\n\nlet encoded = STANDARD.encode_vec(b\"hello\").unwrap();\nassert_eq!(encoded, b\"aGVsbG8=\");\n\nlet encoded_string = STANDARD.encode_string(b\"hello\").unwrap();\nassert_eq!(encoded_string, \"aGVsbG8=\");\n\nlet decoded = STANDARD.decode_vec(\u0026encoded).unwrap();\nassert_eq!(decoded, b\"hello\");\n```\n\nWith the `stream` feature, `std::io` encoders are available:\n\n```rust\nuse std::io::{Read, Write};\nuse base64_ng::STANDARD;\n\nlet mut encoder = STANDARD.encoder_writer(Vec::new());\nencoder.write_all(b\"he\").unwrap();\nencoder.write_all(b\"llo\").unwrap();\nassert!(encoder.has_pending_input());\nencoder.try_finish().unwrap();\nassert_eq!(encoder.get_ref(), b\"aGVsbG8=\");\nlet encoded = encoder.finish().unwrap();\nassert_eq!(encoded, b\"aGVsbG8=\");\n\nlet mut reader = STANDARD.encoder_reader(\u0026b\"hello\"[..]);\nlet mut encoded = String::new();\nreader.read_to_string(\u0026mut encoded).unwrap();\nassert_eq!(encoded, \"aGVsbG8=\");\n\nlet mut decoder = STANDARD.decoder_writer(Vec::new());\ndecoder.write_all(b\"aGVs\").unwrap();\ndecoder.write_all(b\"bG8=\").unwrap();\nassert!(decoder.has_terminal_padding());\nlet decoded = decoder.finish().unwrap();\nassert_eq!(decoded, b\"hello\");\n\nlet mut reader = STANDARD.decoder_reader(\u0026b\"aGVsbG8=\"[..]);\nlet mut decoded = Vec::new();\nreader.read_to_end(\u0026mut decoded).unwrap();\nassert_eq!(decoded, b\"hello\");\nassert!(reader.has_terminal_padding());\nassert!(reader.is_finished());\n```\n\nThe explicit adapter constructors remain available when the engine should be\npassed separately:\n\n```rust\nuse base64_ng::{STANDARD, stream::Encoder};\n\nlet encoder = Encoder::new(Vec::new(), STANDARD);\nassert_eq!(encoder.engine(), STANDARD);\n```\n\nThe stream adapters expose `engine()` and `is_padded()` for policy inspection,\nplus `pending_len()` and `has_pending_input()` for partial Base64 quantum\nvisibility, plus `pending_input_needed_len()` for the number of bytes needed to\ncomplete the partial quantum. Reader adapters also expose\n`buffered_output_len()`, `buffered_output_capacity()`,\n`buffered_output_remaining_capacity()`, and `has_buffered_output()` for bytes\nalready decoded or encoded but not yet returned to the caller. Decoders\nadditionally expose `has_terminal_padding()` so framed protocols can tell when\na padded payload has ended and leave adjacent bytes for the next protocol\nlayer. Reader adapters also expose `is_finished()` once EOF or terminal padding\nhas been reached and all buffered output has been drained, and\n`has_finished_input()` when the wrapped reader has reached EOF or terminal\npadding but buffered output may still remain. Writer adapters expose\n`try_finish()` to finalize pending input and flush the wrapped writer without\nconsuming the adapter, plus `is_finalized()` for explicit state inspection;\nafter successful finalization, later writes are rejected. Writer adapters also\nexpose `buffered_output_len()`, `buffered_output_capacity()`,\n`buffered_output_remaining_capacity()`, and `has_buffered_output()` for encoded\nor decoded bytes accepted by the adapter but not yet drained into the wrapped\nwriter. If a wrapped writer fails, retrying `flush()` or `try_finish()` drains\nthe buffered output without re-encoding or re-decoding the accepted input. All\nstream adapters also expose `can_into_inner()` and `try_into_inner()` as\nchecked recovery paths that refuse to return the wrapped reader or writer while\ndoing so would discard pending input or buffered output. Their `Debug` output\nreports adapter state without formatting the wrapped reader or writer,\nincluding recovery readiness, pending quantum state, and fixed output queue\ncapacity. As with other `std::io::Write` implementations, direct `write()`\ncalls may accept only part of the provided input while buffering encoded or\ndecoded output; use `write_all()` when the whole input slice must be consumed.\nDecoder writer and reader adapters fail closed after malformed Base64 input;\n`is_failed()` exposes that state, while unchecked `into_inner()` remains\navailable for explicit recovery of the wrapped object.\n\nURL-safe, no-padding encoding:\n\n```rust\nuse base64_ng::URL_SAFE_NO_PAD;\n\nlet mut encoded = [0u8; 7];\nlet written = URL_SAFE_NO_PAD.encode_slice(b\"hello\", \u0026mut encoded).unwrap();\nassert_eq!(\u0026encoded[..written], b\"aGVsbG8\");\n```\n\n## Security Model\n\n`base64-ng` treats Base64 as infrastructure code. Fast paths are never allowed to outrun evidence.\n\nSecurity commitments:\n\n- Stable Rust first. Current MSRV toolchain pin: Rust `1.90.0`. New deployments\n  should prefer the latest stable Rust, currently Rust `1.96.0`.\n- `no_std` core by default.\n- Scalar encode/decode remains safe Rust.\n- Audited unsafe helpers in `src/cleanup.rs` perform volatile best-effort\n  wiping plus architecture-gated inline assembly and hardware store-ordering\n  fences where stable Rust supports them, so cleanup writes resist common\n  dead-store elimination and are ordered before the cleanup boundary on\n  supported native architectures. Constant-time comparison, byte accumulation,\n  CT scan, and CT result-gate hardening remain audited in `src/ct.rs`.\n- Future unsafe SIMD remains isolated under `src/simd.rs`.\n- Local checks verify that `allow(unsafe_code)` is confined to the volatile\n  wipe helpers and SIMD boundary, every unsafe function is inventoried, and\n  every unsafe block has a nearby `SAFETY:` explanation. Architecture intrinsics,\n  CPU feature detection, and target-feature gates are checked against the same\n  boundary.\n- [docs/UNSAFE.md](docs/UNSAFE.md) inventories every current unsafe site and\n  its safety invariants.\n- [docs/ASYNC.md](docs/ASYNC.md) defines the admission bar for any future\n  async/Tokio API while the `tokio` feature remains inert.\n- [docs/DEPENDENCIES.md](docs/DEPENDENCIES.md) defines the dependency\n  admission bar for any future external crate.\n- `runtime::backend_report()` exposes the active backend, detected candidate,\n  candidate detection mode, SIMD feature status, scalar-only security posture,\n  and a conservative unsafe-boundary posture flag for audit logging. The\n  unsafe-boundary flag is true only when the reserved `simd` feature is\n  disabled; SIMD-enabled builds must rely on the release evidence scripts for\n  boundary validation. On `no_std` and non-x86 targets, candidate detection is\n  compile-time target-feature reporting, not runtime CPU probing.\n- `runtime::require_backend_policy()` lets deployments assert scalar execution,\n  disabled SIMD features, or no detected SIMD candidate.\n- `BackendPolicy::HighAssuranceScalarOnly` combines the scalar/no-SIMD\n  deployment checks into one assertion and rejects CT gate postures that are\n  ordering-only, compiler-fence-only, or hardware-barrier-unattested. AArch64\n  deployments that have platform evidence for CSDB may compile with\n  `--cfg base64_ng_aarch64_csdb_attested`; that cfg is an operator\n  attestation, not an automatic CPU probe. It reports\n  `hardware-speculation-barrier-build-asserted` so logs distinguish a\n  deployment assertion from a native target guarantee, and it is intentionally\n  not a Cargo feature so `--all-features` cannot enable it by accident.\n- Runtime backend, posture, and policy enums expose stable string identifiers\n  for CI artifacts, audit logs, and deployment evidence.\n- Runtime backend reports and policy failures use stable key/value display\n  output for log ingestion.\n- `Engine`, `ct::CtEngine`, `LineEnding`, `LineWrap`, and `Profile` implement\n  printable `Display` output for policy logging without payload\n  materialization.\n- CI runs platform tests on Linux, Windows, pinned macOS ARM images, pinned\n  Intel macOS, and `macos-latest` so the GitHub-hosted macOS migration remains\n  visible without hiding compatibility regressions behind the moving label.\n- Strict decoding rejects malformed padding and trailing data.\n- Runtime scalar APIs are expected to return `Result` or `Option` for malformed\n  input and size errors instead of panicking.\n- Public encoded-length overflow is recoverable through `Result` or `Option`;\n  untrusted length metadata should never require a panic.\n- Scalar encode avoids input-derived alphabet table indexes, and scalar decode\n  uses branch-minimized arithmetic. A separate `ct` module provides a\n  constant-time-oriented scalar validation and decode path that scans the\n  selected alphabet for every symbol so custom alphabets do not fall back to\n  standard ASCII assumptions. Its malformed-input errors are intentionally\n  non-localized, clear-tail variants clear caller-owned buffers on error, and\n  it is not documented as a formally verified cryptographic constant-time API.\n  Input length, padding length, decoded length, and final success/failure are\n  public; callers that need protocol-level success/failure timing resistance\n  should continue with fixed-shape dummy downstream work after decode failure.\n- Clear-tail encode/decode variants are available for callers that want\n  best-effort cleanup of unused caller-owned buffers without adding a runtime\n  dependency.\n- Streaming wrappers clear internal pending and queued byte buffers on drop and\n  as buffered bytes are consumed, as best-effort retention reduction.\n- Legacy compatibility must be opt-in.\n- Release gates include formatting, clippy, tests, Miri when installed, docs,\n  dependency policy, audit, license review, isolated fuzz/perf dependency\n  checks, SBOM, and reproducible build checks.\n- Kani harnesses stay in-tree and release-gated. The current\n  no-default-features harness set verifies cleanly with Rust `1.90.0` and\n  `cargo-kani 0.67.0`; this is scoped bounded evidence, not a whole-crate\n  formal-verification claim.\n\nSee [docs/PLAN.md](docs/PLAN.md), [SECURITY.md](SECURITY.md),\n[docs/RELEASE_EVIDENCE.md](docs/RELEASE_EVIDENCE.md), and\n[docs/CONSTANT_TIME.md](docs/CONSTANT_TIME.md). For the unsafe hardware\nacceleration gate, see [docs/SIMD.md](docs/SIMD.md).\nFor the trust dashboard and CWE/security-control mapping, see\n[docs/TRUST.md](docs/TRUST.md) and\n[docs/SECURITY_CONTROLS.md](docs/SECURITY_CONTROLS.md).\nFor panic-free public API policy, see\n[docs/PANIC_POLICY.md](docs/PANIC_POLICY.md).\nFor constant-time-oriented decode verification requirements, see\n[docs/CONSTANT_TIME.md](docs/CONSTANT_TIME.md).\nFor dependency admission rules, see [docs/DEPENDENCIES.md](docs/DEPENDENCIES.md).\nFor adoption guidance from the established `base64` crate, see\n[docs/MIGRATION.md](docs/MIGRATION.md).\nFor performance evidence guidance, see [docs/BENCHMARKS.md](docs/BENCHMARKS.md).\nFor fuzz target and corpus policy, see [docs/FUZZING.md](docs/FUZZING.md).\n\n## Local Checks\n\nRun the standard gate:\n\n```sh\nscripts/checks.sh\n```\n\nThe standard gate includes isolated dudect, fuzz, and performance harness\ncompile/dependency checks. It does not run fuzz campaigns or benchmarks.\n\nCheck the zero-external-crate policy directly:\n\n```sh\nscripts/validate-dependencies.sh\n```\n\nCheck release-facing documentation versions directly:\n\n```sh\nscripts/validate-doc-versions.sh\n```\n\nCheck reserved feature placeholders directly:\n\n```sh\nscripts/check_reserved_features.sh\n```\n\nCheck the wasm fail-closed cleanup policy directly:\n\n```sh\nscripts/check_wasm_wipe_policy.sh\n```\n\nRun the release gate:\n\n```sh\nscripts/stable_release_gate.sh\n```\n\nInstall cross-compilation targets used by the local and CI target checks:\n\n```sh\nrustup target add aarch64-unknown-linux-gnu x86_64-unknown-freebsd wasm32-unknown-unknown thumbv7em-none-eabihf\n```\n\nRun the dependency-free no-alloc portability smoke crate across the same\ninstalled target list:\n\n```sh\nscripts/check_no_alloc_smoke.sh\n```\n\nRun the macOS host verification on an Apple Silicon or Intel Mac:\n\n```sh\nscripts/check_macos.sh\n```\n\nOn an M2 MacBook Pro this runs the real host tests on\n`aarch64-apple-darwin`, then compile-checks both `aarch64-apple-darwin` and\n`x86_64-apple-darwin`.\n\nRequired security tools:\n\nCI and local release scripts use `scripts/ci_install_rust.sh`; that script uses rust-toolchain.toml as the single source of truth for the pinned stable Rust toolchain.\n\n```sh\ncargo install --locked cargo-audit\ncargo install --locked cargo-license\ncargo install --locked cargo-deny\ncargo install --locked cargo-sbom --version 0.10.0\n```\n\nOptional deep tools:\n\n```sh\ncargo install --locked cargo-nextest\ncargo install --locked cargo-fuzz\ncargo install --locked kani-verifier\n```\n\nVerify optional tool installation:\n\n```sh\ncargo nextest --version\ncargo fuzz --version\ncargo kani --version\n```\n\nCompile and audit fuzz targets directly while iterating on fuzz harnesses:\n\n```sh\nscripts/check_fuzz.sh\n```\n\nValidate the committed fuzz corpus policy directly:\n\n```sh\nscripts/check_fuzz_corpus.sh\n```\n\nCompile and audit the isolated performance harness directly:\n\n```sh\nscripts/check_perf.sh\n```\n\nRun the scalar comparison benchmark:\n\n```sh\ncargo run --release --manifest-path perf/Cargo.toml\n```\n\nRun a target with `cargo-fuzz`:\n\n```sh\ncargo +nightly fuzz run decode\ncargo +nightly fuzz run in_place\ncargo +nightly fuzz run stream_chunks\ncargo +nightly fuzz run differential\n```\n\nMiri is installed as a nightly Rust component, not as a Cargo package:\n\n```sh\nrustup toolchain install nightly --component miri\ncargo +nightly miri setup\nscripts/check_miri.sh\n```\n\nKani may need a one-time setup after installation:\n\n```sh\ncargo kani setup\n```\n\nOn openSUSE Tumbleweed, install `rustup` first if it is not already present:\n\n```sh\nsudo zypper install rustup\n```\n\nThe local release gate runs Miri automatically when `rustup run nightly cargo\nmiri` is available. `scripts/check_miri.sh` covers no-default-features scalar\nAPIs and all-features alloc/stream APIs. The large deterministic sweep tests are\nignored only under Miri because they are already covered by the normal release\ngate and are too slow for an interpreter.\n\n## Project Principles\n\n- Keep external crates to the absolute minimum. The current crate dependency graph is only `base64-ng`.\n- Correctness first, speed second, unsafe last.\n- The scalar implementation is the reference behavior.\n- SIMD must prove equivalence to scalar behavior across fuzzed and deterministic inputs.\n- Constant-time claims require empirical timing evidence, generated-code\n  review, and explicit documented exclusions.\n- Compatibility modes must be visible in the type/API surface.\n- Release evidence belongs in the repository and CI, not in memory.\n\n## Contributing And Releases\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for contribution rules and [docs/RELEASE.md](docs/RELEASE.md) for the maintainer release checklist.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvalkyoth%2Fbase64-ng","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvalkyoth%2Fbase64-ng","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvalkyoth%2Fbase64-ng/lists"}