From 510c4513b78ae119fdcdb4e133ce423692f54378 Mon Sep 17 00:00:00 2001 From: SaMullinsJr Date: Wed, 15 Jul 2026 18:36:02 -0400 Subject: [PATCH] v0.2.1: fix C UB on admitted 1e20-class inputs; sanitizer CI gate; allocation + supply-chain hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The C write_number fast-path cast double->long long BEFORE its range guard — undefined behavior (C11 6.3.1.4p1) for integer-valued fraction tokens >= 2^63 inside the supported domain, caught by UBSan on {"x":100000000000000000000.0}. Guard now precedes any conversion (same order as the C++ lineage). No known wrong hash was produced (x86-64 saturation fell through to the correct branch; 7/7 agreement on the affected class verified before and after). - corpus: pin huge_integer_valued_float regression vector (25 accept + 31 reject, 7/7 green) - CI: sanitize job (ASan+UBSan+float-cast-overflow, -fno-sanitize-recover=all) for C and C++ running full corpus via conformance/run_corpus_cli.py - C strbuf: OOM latch, checked malloc/realloc, size_t-overflow-guarded growth; baion_canonicalize_json returns NULL on allocation failure (never a partial string) - workflow: permissions contents:read, job timeouts, exact toolchain pins (Rust 1.94.0, DMD 2.112.0, GHC 9.6.7, cabal 3.16.1.0, opam package pins) - supply chain: cJSON pinned to commit acc76239 (v1.7.18); Cargo.lock and cabal.project.freeze committed - CHANGELOG.md added; v0.2.0 preserved, superseded not replaced Receipts: verify_all_lineages.sh PASS 7/7; differential_probe 234 cases 0 divergent; fuzz_agreement seed=20260715 8000 cases 0 failures; sanitized C and C++ CLIs green on full corpus. Co-Authored-By: Claude Fable 5 --- .github/workflows/verify.yml | 50 +++++++- CHANGELOG.md | 81 +++++++++++++ README.md | 2 +- c/CMakeLists.txt | 4 +- c/include/baion/canonical_json.h | 3 +- c/src/canonical_json.c | 68 +++++++++-- c/tools/baion_canon_hash.c | 5 + conformance/accept.jsonl | 1 + conformance/gen_corpus.py | 5 + conformance/run_corpus_cli.py | 69 +++++++++++ haskell/baionstd-public.cabal | 2 +- haskell/cabal.project.freeze | 106 +++++++++++++++++ rust/.gitignore | 1 - rust/Cargo.lock | 190 +++++++++++++++++++++++++++++++ rust/Cargo.toml | 2 +- 15 files changed, 572 insertions(+), 17 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 conformance/run_corpus_cli.py create mode 100644 haskell/cabal.project.freeze create mode 100644 rust/Cargo.lock diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 56f5885..1699df2 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -11,10 +11,16 @@ on: pull_request: workflow_dispatch: +# Least privilege: every job only reads the repo. Nothing here writes +# contents, issues, or packages (GitHub hardening guidance). +permissions: + contents: read + jobs: build: name: build (${{ matrix.lineage }}) runs-on: ubuntu-latest + timeout-minutes: 30 strategy: fail-fast: false matrix: @@ -29,22 +35,27 @@ jobs: go-version: '1.22' cache-dependency-path: go/go.mod + # Toolchains pinned to exact versions (not floating "latest"/"stable") + # so a moved toolchain cannot silently change what a release build + # means. Versions match the locally verified v0.2.1 build environment. - name: Set up Rust if: matrix.lineage == 'rust' uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable (2026-07-15) + with: + toolchain: '1.94.0' - name: Set up D if: matrix.lineage == 'd' uses: dlang-community/setup-dlang@d7d85fcde7c4cd5f9a6618fce1bccc316e1e910b # v2 with: - compiler: dmd-latest + compiler: dmd-2.112.0 - name: Set up Haskell if: matrix.lineage == 'haskell' uses: haskell-actions/setup@cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553 # v2.11.0 with: - ghc-version: '9.6' - cabal-version: 'latest' + ghc-version: '9.6.7' + cabal-version: '3.16.1.0' - name: Set up OCaml if: matrix.lineage == 'ocaml' @@ -100,7 +111,7 @@ jobs: if: matrix.lineage == 'ocaml' working-directory: ocaml run: | - opam install --yes dune yojson digestif alcotest + opam install --yes dune.3.21.1 yojson.3.0.0 digestif.1.3.0 alcotest.1.9.1 make build test # Sanity: the CLI the verify job will receive must exist and answer. @@ -115,10 +126,41 @@ jobs: path: ${{ matrix.lineage }}/bin/baion_canon_hash if-no-files-found: error + # Sanitizer gate for the two memory-unsafe lineages: same corpus contract + # as the verifier, but the CLI is built with ASan+UBSan and aborts on any + # undefined behavior. Added in v0.2.1 after UBSan caught an out-of-range + # double→long long conversion on an admitted input (1e20) that the plain + # build masked by saturating. + sanitize: + name: sanitize (${{ matrix.lineage }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + lineage: [c, cpp] + env: + SAN_FLAGS: -fsanitize=address,undefined,float-cast-overflow -fno-sanitize-recover=all -fno-omit-frame-pointer + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Build and test with ASan+UBSan + run: | + cmake -S ${{ matrix.lineage }} -B ${{ matrix.lineage }}/build-san \ + -DCMAKE_C_FLAGS="$SAN_FLAGS" \ + -DCMAKE_CXX_FLAGS="$SAN_FLAGS" \ + -DCMAKE_EXE_LINKER_FLAGS="$SAN_FLAGS" + cmake --build ${{ matrix.lineage }}/build-san + ctest --test-dir ${{ matrix.lineage }}/build-san --output-on-failure + + - name: Run full corpus through sanitized CLI + run: python3 conformance/run_corpus_cli.py ${{ matrix.lineage }}/bin/baion_canon_hash + verify: name: verify 7/7 byte-identity needs: build runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3bdae38 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,81 @@ +# Changelog + +## v0.2.1 — 2026-07-15 + +Corrective release. v0.2.0 remains published and tagged — this release +supersedes it rather than replacing it (found → preserved → corrected → +tested → superseded). + +### Fixed + +- **C lineage undefined behavior on admitted input** (`c/src/canonical_json.c`, + `write_number`): the integer fast-path evaluated `(double)(long long)d` + *before* its `fabs(d) < 1e15` range guard. For integer-valued fraction + tokens ≥ 2⁶³ inside the supported domain (e.g. `100000000000000000000.0`, + |v| < 10²¹), the double→`long long` conversion is undefined behavior + (C11 §6.3.1.4p1), caught by UBSan + (`runtime error: 1e+20 is outside the range of representable values of + type 'long long int'`). On tested x86-64 builds the conversion saturated + and the code fell through to the correct big-integer branch, so all seven + lineages agreed on the affected inputs — no known wrong hash was ever + produced — but undefined behavior is not a cross-platform contract. The + guard now runs before any conversion (`fabs(d) < 1e15 && trunc(d) == d`), + the same order the C++ lineage already used. + +### Added + +- Regression vector `huge_integer_valued_float` + (`{"x":100000000000000000000.0}`) pinned in the conformance corpus; + all seven lineages agree (25 accept + 31 reject vectors green). +- Sanitizer CI gate for the two memory-unsafe lineages (C, C++): + ASan + UBSan + float-cast-overflow with `-fno-sanitize-recover=all`, + running each lineage's tests and the full corpus through the + instrumented CLI (`conformance/run_corpus_cli.py`). + +### Hardened + +- C string buffer: allocation failures now latch an OOM flag and + `baion_canonicalize_json` returns NULL (defined failure — never a + partial canonical string, never a write through a failed allocation); + `realloc` no longer assigned directly to the live pointer; capacity + growth guarded against `size_t` overflow; the object-key sort array + allocation is checked. +- Supply chain: workflow declares least-privilege + `permissions: contents: read`; every job has a timeout; cJSON is + fetched by commit SHA (`acc76239…`, the commit behind v1.7.18) instead + of a movable tag; CI toolchains pinned to exact versions (Rust 1.94.0, + DMD 2.112.0, GHC 9.6.7, cabal 3.16.1.0, pinned opam package versions); + `rust/Cargo.lock` and `haskell/cabal.project.freeze` now committed. + +### Known remaining limits (documented, not yet enforced) + +- No enforced caps on input size, nesting depth, or member counts. + Resource limits change which documents are *rejected*, so they are a + cross-lineage contract change — all seven lineages must adopt identical + limits in the same release. Planned for a future minor version; until + then, treat the CLIs as trusted-input tools. +- The C number writer assumes the process stays in the default `C` + locale (documented in-source). + +## v0.2.0 — 2026-07-15 + +Cross-lineage hardening release: uniform rejection contract (invalid +UTF-8, raw controls, malformed escapes/tokens, duplicate keys, U+0000, +BOM, number domain), corpus-as-data conformance, seeded fuzz agreement +layer, ES-262 shortest-digits fix. Breaking: previously-accepted lax +inputs are now uniformly rejected. See the GitHub release notes. + +## v0.1.3 — 2026-07-15 + +Uniform rejection of duplicate object keys across all seven lineages. + +## v0.1.2 — 2026-07-15 + +Canonicalization contract round: uniform U+0000 rejection, Go number +normalization, `build_all.sh`, verifier reject-vectors (external review +round 2). + +## v0.1.1 — 2026-07-15 + +Initial public release: seven-lineage canonical JSON + SHA-256, +byte-identity verified. diff --git a/README.md b/README.md index 0e36ab2..8c8c111 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Seven independent implementations of the same canonicalization contract — C, C **Prerequisites** (one toolchain per lineage): a C compiler + `make`; CMake ≥ 3.20 + a C++17 compiler; Rust (`cargo`); Go ≥ 1.22; D (`dmd` + `dub`); GHC ≥ 9.6 + `cabal` (aeson ≥ 2.2 is fetched by cabal); OCaml ≥ 5.x + `dune` with `yojson`, `digestif`, `alcotest` (via opam); `python3` for the conformance tooling. A successful run ends with a 7/7 PASS table from `build_all.sh` and `PASS: 7/7 lineages agree ...` from the verifier, exit 0. -`build_all.sh` builds each lineage with its native toolchain, runs its test suite, and places the CLI at `/bin/baion_canon_hash` — the layout the verifier requires. `verify_all_lineages.sh` then feeds every vector in the conformance corpus (`conformance/accept.jsonl` + `conformance/reject.jsonl`) to every CLI: accept vectors must hash to the corpus-pinned SHA-256 in all seven lineages, and reject vectors must be **uniformly refused** (see below). `conformance/differential_probe.py` additionally sweeps generated danger-zone cases (number bands, escape forms, document framing) and fails on any disagreement; both run in CI. All seven lineages must be present — a missing binary fails the run, and one byte of disagreement anywhere fails the run. `conformance/fuzz_agreement.py` is the third layer: a seeded randomized fuzzer (structured boundary-biased documents, byte mutations, raw garbage) that asserts seven-way agreement on every generated input. +`build_all.sh` builds each lineage with its native toolchain, runs its test suite, and places the CLI at `/bin/baion_canon_hash` — the layout the verifier requires. `verify_all_lineages.sh` then feeds every vector in the conformance corpus (`conformance/accept.jsonl` + `conformance/reject.jsonl`) to every CLI: accept vectors must hash to the corpus-pinned SHA-256 in all seven lineages, and reject vectors must be **uniformly refused** (see below). `conformance/differential_probe.py` additionally sweeps generated danger-zone cases (number bands, escape forms, document framing) and fails on any disagreement; both run in CI. All seven lineages must be present — a missing binary fails the run, and one byte of disagreement anywhere fails the run. `conformance/fuzz_agreement.py` is the third layer: a seeded randomized fuzzer (structured boundary-biased documents, byte mutations, raw garbage) that asserts seven-way agreement on every generated input. A fourth CI gate builds the two memory-unsafe lineages (C, C++) under ASan + UBSan (`-fno-sanitize-recover=all`) and runs the full corpus through the instrumented CLIs via `conformance/run_corpus_cli.py` — any undefined behavior on an admitted input fails the build. Release history: [CHANGELOG.md](CHANGELOG.md). ## Why this exists diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt index 65624b6..1bacda8 100644 --- a/c/CMakeLists.txt +++ b/c/CMakeLists.txt @@ -12,10 +12,12 @@ include(FetchContent) # Fetch cJSON (single .c/.h JSON parser). # Offline builds: pre-populate with -DFETCHCONTENT_SOURCE_DIR_CJSON=/path/to/cJSON +# Pinned to the commit behind tag v1.7.18: tags are movable refs, so a +# reviewed commit SHA is the only immutable identity for the vendored source. FetchContent_Declare( cjson GIT_REPOSITORY https://github.com/DaveGamble/cJSON.git - GIT_TAG v1.7.18 + GIT_TAG acc76239bee01d8e9c858ae2cab296704e52d916 # v1.7.18 ) FetchContent_GetProperties(cjson) if(NOT cjson_POPULATED) diff --git a/c/include/baion/canonical_json.h b/c/include/baion/canonical_json.h index 4fff8a7..2ee038d 100644 --- a/c/include/baion/canonical_json.h +++ b/c/include/baion/canonical_json.h @@ -4,7 +4,8 @@ #include #include -/* Canonicalize any cJSON value. Returns heap-allocated string. Caller must free(). */ +/* Canonicalize any cJSON value. Returns heap-allocated string; caller must + * free(). Returns NULL on allocation failure — never a partial string. */ char* baion_canonicalize_json(const cJSON* value); /* Pre-parse scan of raw JSON input bytes: returns BAION_OK if the input is diff --git a/c/src/canonical_json.c b/c/src/canonical_json.c index 1ce97b6..5d55063 100644 --- a/c/src/canonical_json.c +++ b/c/src/canonical_json.c @@ -10,16 +10,21 @@ #include #include +#include #include #include #include -/* Dynamic string buffer */ +/* Dynamic string buffer. Allocation failure latches `oom`; every append + * becomes a no-op once set, so the walk finishes cheaply and the public + * entry point reports one defined failure (NULL) instead of any internal + * path dereferencing a failed allocation. */ typedef struct { char* data; size_t len; size_t cap; + int oom; } strbuf_t; static void strbuf_init(strbuf_t* sb) @@ -27,22 +32,53 @@ static void strbuf_init(strbuf_t* sb) sb->cap = 512; sb->data = (char*)malloc(sb->cap); sb->len = 0; - sb->data[0] = '\0'; + sb->oom = (sb->data == NULL); + if (!sb->oom) + sb->data[0] = '\0'; } static void strbuf_ensure(strbuf_t* sb, size_t extra) { - if (sb->len + extra + 1 > sb->cap) + if (sb->oom) + return; + /* len + extra + 1 must not wrap: a wrapped "needed" would pass the + * capacity test and let the memcpy in strbuf_append run off the end. */ + if (extra > SIZE_MAX - sb->len - 1) { - while (sb->len + extra + 1 > sb->cap) - sb->cap *= 2; - sb->data = (char*)realloc(sb->data, sb->cap); + sb->oom = 1; + return; + } + size_t needed = sb->len + extra + 1; + if (needed > sb->cap) + { + size_t cap = sb->cap; + while (cap < needed) + { + if (cap > SIZE_MAX / 2) + { + cap = needed; /* doubling would wrap; exact size is enough */ + break; + } + cap *= 2; + } + /* realloc into a temporary: assigning a NULL return straight to + * sb->data would leak the original block and lose the buffer. */ + char* grown = (char*)realloc(sb->data, cap); + if (!grown) + { + sb->oom = 1; + return; + } + sb->data = grown; + sb->cap = cap; } } static void strbuf_append(strbuf_t* sb, const char* s, size_t n) { strbuf_ensure(sb, n); + if (sb->oom) + return; memcpy(sb->data + sb->len, s, n); sb->len += n; sb->data[sb->len] = '\0'; @@ -51,6 +87,8 @@ static void strbuf_append(strbuf_t* sb, const char* s, size_t n) static void strbuf_appendc(strbuf_t* sb, char c) { strbuf_ensure(sb, 1); + if (sb->oom) + return; sb->data[sb->len++] = c; sb->data[sb->len] = '\0'; } @@ -134,6 +172,11 @@ static void canonicalize_object(strbuf_t* sb, const cJSON* obj) /* Collect pointers and sort by key */ const cJSON** keys = (const cJSON**)malloc((size_t)count * sizeof(cJSON*)); + if (!keys) + { + sb->oom = 1; + return; + } int i = 0; for (child = obj->child; child; child = child->next) { @@ -186,7 +229,11 @@ static void write_number(strbuf_t* sb, const cJSON* item) * trailing decimal (RFC 8785 §3.2.2.3 / ECMA-262 §7.1.12.1). All * language implementations emit 1.0 → "1", -3.0 → "-3", 1.5 → "1.5"; * disagreement on this branch breaks SHA-256 digest parity. */ - if (d == (double)(long long)d && fabs(d) < 1e15) + /* Range check MUST precede the integer conversion: for |d| >= 2^63 + * (e.g. 1e20, inside the admitted domain |v| < 1e21) the cast to + * long long is undefined behavior (C11 6.3.1.4p1). Same guard order + * as the C++ lineage. */ + if (fabs(d) < 1e15 && trunc(d) == d) { char buf[32]; snprintf(buf, sizeof(buf), "%lld", (long long)d); @@ -792,5 +839,12 @@ char* baion_canonicalize_json(const cJSON* value) strbuf_t sb; strbuf_init(&sb); canonicalize_value(&sb, value); + if (sb.oom) + { + /* Defined failure: a partial canonical string must never reach a + * hash — free it and report NULL rather than a truncated form. */ + free(sb.data); + return NULL; + } return sb.data; } diff --git a/c/tools/baion_canon_hash.c b/c/tools/baion_canon_hash.c index 5aa567a..7751ac4 100644 --- a/c/tools/baion_canon_hash.c +++ b/c/tools/baion_canon_hash.c @@ -154,6 +154,11 @@ int main(void) char* canonical = baion_canonicalize_json(root); cJSON_Delete(root); + if (!canonical) + { + fprintf(stderr, "baion_canon_hash: out of memory during canonicalization\n"); + return -BAION_ERR_PARSE; + } uint8_t hash[32]; baion_sha256((const uint8_t*)canonical, strlen(canonical), hash); diff --git a/conformance/accept.jsonl b/conformance/accept.jsonl index 74308c3..62a83c2 100644 --- a/conformance/accept.jsonl +++ b/conformance/accept.jsonl @@ -22,3 +22,4 @@ {"name": "same_key_sibling_objects", "input": "[{\"k\":1},{\"k\":2}]", "sha256": "98fcf287e1991c1602a189793606501715f8ae194db5dcaaf6515ed29937c20d"} {"name": "micro_boundary", "input": "{\"x\":0.000001}", "sha256": "2d6412ab0155bb89d63b73dbf83334b26b39d03e8c832cc253c6a6caceba9733"} {"name": "big_float_shortest_digits", "input": "{\"x\":65219416364867774.9377591}", "sha256": "96df26a780f11e7a70c701e23ff9f79ab115118d77c70945885996a1014a2d84"} +{"name": "huge_integer_valued_float", "input": "{\"x\":100000000000000000000.0}", "sha256": "356acd219b8c369fc389513fb5c3f9fc2977fff3c432bab9df5b1e3a2800b072"} diff --git a/conformance/gen_corpus.py b/conformance/gen_corpus.py index 38e83d5..7559c44 100644 --- a/conformance/gen_corpus.py +++ b/conformance/gen_corpus.py @@ -53,6 +53,11 @@ # Pins ES-262 shortest-digits for an integer-valued double beyond 2^53: # exact value is ...776 but the canonical spelling is the 16-digit ...780. ("big_float_shortest_digits", '{"x":65219416364867774.9377591}'), + # Regression for the v0.2.0 C-lineage UB (fixed v0.2.1): a fractional + # token whose value is integer-valued and >= 2^63 but inside the admitted + # domain (|v| < 1e21). The old C writer cast to long long BEFORE its + # range check — undefined behavior per C11 6.3.1.4p1, caught by UBSan. + ("huge_integer_valued_float", '{"x":100000000000000000000.0}'), ] # Uniform-rejection contract. reason is documentation for humans + remediation maps. diff --git a/conformance/run_corpus_cli.py b/conformance/run_corpus_cli.py new file mode 100644 index 0000000..b708ac4 --- /dev/null +++ b/conformance/run_corpus_cli.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# BAION STD conformance runner — Observer for ONE CLI against the pinned corpus. +# Spec: repo README "Conformance corpus"; companion to verify_all_lineages.sh. +# +# WHY this exists: verify_all_lineages.sh requires all seven CLIs at once, so +# it cannot exercise a single instrumented build (sanitizer CI builds only the +# C or C++ lineage). This runner holds one CLI to the same corpus contract: +# every accept vector must hash to its pinned SHA-256, every reject vector +# must exit nonzero. Under ASan/UBSan with -fno-sanitize-recover, any +# undefined behavior on an admitted input aborts the CLI and fails the run. +# +# Usage: run_corpus_cli.py +import json +import subprocess +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent + + +def main() -> int: + if len(sys.argv) != 2: + print(f"usage: {sys.argv[0]} ", file=sys.stderr) + return 2 + cli = Path(sys.argv[1]) + if not cli.exists(): + print(f"FAIL: CLI not found at {cli}", file=sys.stderr) + return 1 + + failures = 0 + + for line in (HERE / "accept.jsonl").read_text().splitlines(): + case = json.loads(line) + proc = subprocess.run( + [str(cli)], input=case["input"].encode("utf-8"), + capture_output=True, timeout=30, + ) + got = proc.stdout.decode().strip() + if proc.returncode != 0 or got != case["sha256"]: + print(f"ACCEPT {case['name']}: FAIL " + f"(exit={proc.returncode}, got={got or ''}, " + f"want={case['sha256']}) stderr={proc.stderr.decode().strip()}") + failures += 1 + else: + print(f"ACCEPT {case['name']}: ok") + + for line in (HERE / "reject.jsonl").read_text().splitlines(): + case = json.loads(line) + proc = subprocess.run( + [str(cli)], input=case["input"].encode("utf-8"), + capture_output=True, timeout=30, + ) + # Reject contract is a clean nonzero exit — a sanitizer abort + # (SIGABRT, negative returncode) is a crash, not a rejection. + if proc.returncode <= 0: + print(f"REJECT {case['name']}: FAIL (exit={proc.returncode})") + failures += 1 + else: + print(f"REJECT {case['name']}: ok") + + if failures: + print(f"FAIL: {failures} corpus case(s) failed for {cli}") + return 1 + print(f"PASS: corpus green for {cli}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/haskell/baionstd-public.cabal b/haskell/baionstd-public.cabal index 98c068b..3bc0a37 100644 --- a/haskell/baionstd-public.cabal +++ b/haskell/baionstd-public.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: baionstd-public -version: 0.2.0 +version: 0.2.1 synopsis: BAION canonical JSON + SHA-256 (Haskell) license: MIT license-file: LICENSE diff --git a/haskell/cabal.project.freeze b/haskell/cabal.project.freeze new file mode 100644 index 0000000..72f112c --- /dev/null +++ b/haskell/cabal.project.freeze @@ -0,0 +1,106 @@ +active-repositories: hackage.haskell.org:merge +constraints: any.OneTuple ==0.4.2.1, + any.QuickCheck ==2.16.0.0, + QuickCheck -old-random +templatehaskell, + any.StateVar ==1.2.2, + any.aeson ==2.2.3.0, + aeson +ordered-keymap, + any.ansi-terminal ==1.1.5, + ansi-terminal -example, + any.ansi-terminal-types ==1.1.3, + any.array ==0.5.8.0, + any.assoc ==1.1.1, + assoc -tagged, + any.base ==4.18.3.0, + any.base-orphans ==0.9.4, + any.base16 ==1.0, + any.basement ==0.0.16, + any.bifunctors ==5.6.3, + bifunctors +tagged, + any.binary ==0.8.9.1, + any.bytestring ==0.11.5.4, + any.call-stack ==0.4.0, + any.character-ps ==0.1, + any.colour ==2.3.6, + any.comonad ==5.0.10, + comonad +containers +distributive +indexed-traversable, + any.containers ==0.6.7, + any.contravariant ==1.5.6, + contravariant +statevar, + any.crypton ==1.1.1, + crypton -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq +support_pclmuldq +support_rdrand -support_sse +use_target_attributes, + any.data-fix ==0.3.4, + any.deepseq ==1.4.8.1, + any.directory ==1.3.8.5, + any.distributive ==0.6.3, + distributive +tagged, + any.dlist ==1.0, + dlist -werror, + any.exceptions ==0.10.7, + any.filepath ==1.4.301.0, + any.generically ==0.1.1, + any.ghc-bignum ==1.3, + any.ghc-boot-th ==9.6.7, + any.ghc-prim ==0.10.0, + any.hashable ==1.5.1.0, + hashable -arch-native -random-initial-seed, + any.indexed-traversable ==0.1.4, + any.indexed-traversable-instances ==0.1.2, + any.integer-conversion ==0.1.1, + any.integer-gmp ==1.1, + any.integer-logarithms ==1.0.5, + integer-logarithms -check-bounds +integer-gmp, + any.memory ==0.18.0, + memory +support_bytestring +support_deepseq, + any.mtl ==2.3.1, + any.network-uri ==2.6.4.2, + any.optparse-applicative ==0.19.0.0, + optparse-applicative +process, + any.os-string ==2.0.10, + any.parsec ==3.1.16.1, + any.pretty ==1.1.3.6, + any.prettyprinter ==1.7.1, + prettyprinter -buildreadme +text, + any.prettyprinter-ansi-terminal ==1.1.3, + any.primitive ==0.9.1.0, + any.process ==1.6.19.0, + any.ram ==0.21.1, + any.random ==1.3.1, + any.rts ==1.0.2, + any.scientific ==0.3.8.1, + scientific -integer-simple, + any.semialign ==1.3.1, + semialign +semigroupoids, + any.semigroupoids ==6.0.2, + semigroupoids +comonad +containers +contravariant +tagged +unordered-containers, + any.splitmix ==0.1.3.2, + splitmix -optimised-mixer, + any.stm ==2.5.1.0, + any.strict ==0.5.1, + any.tagged ==0.8.10, + tagged +deepseq +template-haskell, + any.tasty ==1.5.3, + tasty +unix, + any.tasty-hunit ==0.10.2, + any.template-haskell ==2.20.0.0, + any.text ==2.0.2, + any.text-iso8601 ==0.1.1, + any.text-short ==0.1.6.1, + text-short -asserts, + any.th-abstraction ==0.7.2.0, + any.th-compat ==0.1.7, + any.these ==1.2.1, + any.time ==1.12.2, + any.time-compat ==1.9.9, + any.transformers ==0.6.1.0, + any.transformers-compat ==0.8, + transformers-compat -five +five-three +generic-deriving +mtl, + any.unix ==2.8.6.0, + any.unordered-containers ==0.2.21, + unordered-containers -debug, + any.uuid-types ==1.0.6, + any.vector ==0.13.2.0, + vector +boundschecks -internalchecks -unsafechecks -wall, + any.vector-stream ==0.1.0.1, + any.witherable ==0.5 +index-state: hackage.haskell.org 2026-03-14T12:57:42Z diff --git a/rust/.gitignore b/rust/.gitignore index 2c96eb1..2f7896d 100644 --- a/rust/.gitignore +++ b/rust/.gitignore @@ -1,2 +1 @@ target/ -Cargo.lock diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 0000000..0e03757 --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,190 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "baion_std" +version = "0.2.1" +dependencies = [ + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 53be521..e2453af 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "baion_std" -version = "0.2.0" +version = "0.2.1" edition = "2021" description = "BAION canonical JSON + SHA-256 for Rust — public standalone library" license = "MIT"