sanning-proof 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sanning_proof-0.4.0/.github/workflows/ci.yml +73 -0
- sanning_proof-0.4.0/.github/workflows/release-ts.yml +71 -0
- sanning_proof-0.4.0/.github/workflows/release.yml +45 -0
- sanning_proof-0.4.0/.gitignore +11 -0
- sanning_proof-0.4.0/CHANGELOG.md +108 -0
- sanning_proof-0.4.0/CLAUDE.md +116 -0
- sanning_proof-0.4.0/LICENSE +21 -0
- sanning_proof-0.4.0/PKG-INFO +202 -0
- sanning_proof-0.4.0/README.md +154 -0
- sanning_proof-0.4.0/cross-kernel/.gitignore +5 -0
- sanning_proof-0.4.0/cross-kernel/PIN +8 -0
- sanning_proof-0.4.0/cross-kernel/README.md +108 -0
- sanning_proof-0.4.0/cross-kernel/accept-set.json +133 -0
- sanning_proof-0.4.0/cross-kernel/generate_cases.py +352 -0
- sanning_proof-0.4.0/cross-kernel/generate_export_cases.py +98 -0
- sanning_proof-0.4.0/cross-kernel/go-verifier/accept_set.go +117 -0
- sanning_proof-0.4.0/cross-kernel/go-verifier/go.mod +13 -0
- sanning_proof-0.4.0/cross-kernel/go-verifier/go.sum +12 -0
- sanning_proof-0.4.0/cross-kernel/go-verifier/main.go +140 -0
- sanning_proof-0.4.0/cross-kernel/run.sh +43 -0
- sanning_proof-0.4.0/cross-kernel/run_export.sh +33 -0
- sanning_proof-0.4.0/cross-kernel/ts_export_leg.mjs +93 -0
- sanning_proof-0.4.0/cross-kernel/ts_leg.mjs +140 -0
- sanning_proof-0.4.0/cross-kernel/vendor-agent/go.mod +8 -0
- sanning_proof-0.4.0/cross-kernel/vendor-agent/pkg/proof/LICENSE +27 -0
- sanning_proof-0.4.0/cross-kernel/vendor-agent/pkg/proof/VENDORING.md +25 -0
- sanning_proof-0.4.0/cross-kernel/vendor-agent/pkg/proof/proof.go +398 -0
- sanning_proof-0.4.0/pyproject.toml +48 -0
- sanning_proof-0.4.0/specs/architecture.md +177 -0
- sanning_proof-0.4.0/specs/envelope-spec.md +358 -0
- sanning_proof-0.4.0/specs/evidence-bundle.md +215 -0
- sanning_proof-0.4.0/specs/evidence-export.md +498 -0
- sanning_proof-0.4.0/specs/governance.md +66 -0
- sanning_proof-0.4.0/src/sanning_proof/__init__.py +96 -0
- sanning_proof-0.4.0/src/sanning_proof/bundle.py +141 -0
- sanning_proof-0.4.0/src/sanning_proof/canonicalize.py +97 -0
- sanning_proof-0.4.0/src/sanning_proof/envelope.py +325 -0
- sanning_proof-0.4.0/src/sanning_proof/evidence.py +1269 -0
- sanning_proof-0.4.0/src/sanning_proof/hash.py +15 -0
- sanning_proof-0.4.0/src/sanning_proof/merkle.py +127 -0
- sanning_proof-0.4.0/src/sanning_proof/rsa_pss.py +202 -0
- sanning_proof-0.4.0/src/sanning_proof/sign.py +36 -0
- sanning_proof-0.4.0/src/sanning_proof/verify.py +24 -0
- sanning_proof-0.4.0/test-vectors/CORPUS-v2.md +124 -0
- sanning_proof-0.4.0/test-vectors/README.md +65 -0
- sanning_proof-0.4.0/test-vectors/VENDORING.md +50 -0
- sanning_proof-0.4.0/test-vectors/envelope-asset-missing-01.json +45 -0
- sanning_proof-0.4.0/test-vectors/envelope-asset-registered-01.json +41 -0
- sanning_proof-0.4.0/test-vectors/envelope-key-retired-01.json +37 -0
- sanning_proof-0.4.0/test-vectors/envelope-policy-changed-01.json +40 -0
- sanning_proof-0.4.0/test-vectors/envelope-tamper-detected-01.json +48 -0
- sanning_proof-0.4.0/test-vectors/envelope-verification-checkpoint-01.json +50 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-bundle.golden.json +332 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-malformed-rsa-key.json +278 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-malformed-signature-alg.json +278 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-positive-01.json +300 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-source-bundle-ref.json +174 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-attestation-signature.json +300 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-body-hash.json +278 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-data-hash-binding.json +300 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-disclosed-content.json +300 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-mis-salt.json +300 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-operator-binding.json +296 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-source-linkage.json +300 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-subject-ref.json +300 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-verdict-disagreement.json +300 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/evidence-export-tamper-wrapper-signature.json +278 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/negatives/attestation-exponent-forgery.json +267 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/negatives/deep-nesting.json +415 -0
- sanning_proof-0.4.0/test-vectors/evidence-export/rsa-pss-attestation.golden.json +26 -0
- sanning_proof-0.4.0/test-vectors/merkle-tree-00-leaves.json +8 -0
- sanning_proof-0.4.0/test-vectors/merkle-tree-01-leaves.json +31 -0
- sanning_proof-0.4.0/test-vectors/merkle-tree-02-leaves.json +56 -0
- sanning_proof-0.4.0/test-vectors/merkle-tree-03-leaves.json +81 -0
- sanning_proof-0.4.0/test-vectors/merkle-tree-07-leaves.json +152 -0
- sanning_proof-0.4.0/test-vectors/merkle-tree-1024-leaves.json +17463 -0
- sanning_proof-0.4.0/test-vectors/merkle-tree-16-leaves.json +309 -0
- sanning_proof-0.4.0/test-vectors/negatives/negative-lone-surrogate-00.json +10 -0
- sanning_proof-0.4.0/test-vectors/negatives/negative-malformed-minor-00.json +10 -0
- sanning_proof-0.4.0/test-vectors/negatives/negative-malformed-minor-01.json +10 -0
- sanning_proof-0.4.0/test-vectors/negatives/negative-malformed-minor-02.json +10 -0
- sanning_proof-0.4.0/test-vectors/negatives/negative-missing-payload-hash-00.json +10 -0
- sanning_proof-0.4.0/test-vectors/sanning.events-v1/events-checkpoint-01.json +168 -0
- sanning_proof-0.4.0/test-vectors/sanning.events-v1/events-checkpoint-chain-01.json +188 -0
- sanning_proof-0.4.0/test-vectors/sanning.events-v1/events-event-01.json +50 -0
- sanning_proof-0.4.0/test-vectors/sanning.events-v1/events-event-02.json +47 -0
- sanning_proof-0.4.0/tests/test_accept_set.py +108 -0
- sanning_proof-0.4.0/tests/test_attestation_exponent_forgery.py +50 -0
- sanning_proof-0.4.0/tests/test_bundle.py +175 -0
- sanning_proof-0.4.0/tests/test_canonicalization_depth.py +71 -0
- sanning_proof-0.4.0/tests/test_canonicalize.py +45 -0
- sanning_proof-0.4.0/tests/test_conformance.py +472 -0
- sanning_proof-0.4.0/tests/test_envelope.py +393 -0
- sanning_proof-0.4.0/tests/test_evidence.py +198 -0
- sanning_proof-0.4.0/tests/test_evidence_export.py +307 -0
- sanning_proof-0.4.0/tests/test_hash.py +18 -0
- sanning_proof-0.4.0/tests/test_merkle.py +89 -0
- sanning_proof-0.4.0/tests/test_rsa_pss.py +112 -0
- sanning_proof-0.4.0/tests/test_sign_verify.py +60 -0
- sanning_proof-0.4.0/tools/gen-vectors/.gitignore +0 -0
- sanning_proof-0.4.0/tools/gen-vectors/LICENSE +28 -0
- sanning_proof-0.4.0/tools/gen-vectors/README.md +58 -0
- sanning_proof-0.4.0/tools/gen-vectors/export_vectors.py +221 -0
- sanning_proof-0.4.0/tools/gen-vectors/gen_exponent_forgery_vector.py +204 -0
- sanning_proof-0.4.0/tools/gen-vectors/gen_export_vectors.py +173 -0
- sanning_proof-0.4.0/tools/gen-vectors/gen_vectors.py +982 -0
- sanning_proof-0.4.0/tools/gen-vectors/pyproject.toml +10 -0
- sanning_proof-0.4.0/ts/CHANGELOG.md +107 -0
- sanning_proof-0.4.0/ts/LICENSE +21 -0
- sanning_proof-0.4.0/ts/README.md +168 -0
- sanning_proof-0.4.0/ts/package-lock.json +1480 -0
- sanning_proof-0.4.0/ts/package.json +59 -0
- sanning_proof-0.4.0/ts/scripts/gen-export-fixture.mjs +387 -0
- sanning_proof-0.4.0/ts/scripts/gen-rsa-attestation-fixture.mjs +88 -0
- sanning_proof-0.4.0/ts/src/agent-proof.ts +229 -0
- sanning_proof-0.4.0/ts/src/cli.ts +619 -0
- sanning_proof-0.4.0/ts/src/compose.ts +189 -0
- sanning_proof-0.4.0/ts/src/crypto.ts +243 -0
- sanning_proof-0.4.0/ts/src/evidence.ts +1491 -0
- sanning_proof-0.4.0/ts/src/index.ts +105 -0
- sanning_proof-0.4.0/ts/src/merkle.ts +125 -0
- sanning_proof-0.4.0/ts/src/types.ts +72 -0
- sanning_proof-0.4.0/ts/src/verifier.ts +340 -0
- sanning_proof-0.4.0/ts/test/accept-set.test.ts +109 -0
- sanning_proof-0.4.0/ts/test/agent-proof.test.ts +148 -0
- sanning_proof-0.4.0/ts/test/attestation-exponent-forgery.test.ts +95 -0
- sanning_proof-0.4.0/ts/test/canonicalization-depth.test.ts +65 -0
- sanning_proof-0.4.0/ts/test/cli-content-e2e.test.ts +298 -0
- sanning_proof-0.4.0/ts/test/cli-e2e.test.ts +411 -0
- sanning_proof-0.4.0/ts/test/cli.test.ts +185 -0
- sanning_proof-0.4.0/ts/test/compose-cli-e2e.test.ts +162 -0
- sanning_proof-0.4.0/ts/test/compose-export.test.ts +221 -0
- sanning_proof-0.4.0/ts/test/conformance.test.ts +480 -0
- sanning_proof-0.4.0/ts/test/crypto.test.ts +32 -0
- sanning_proof-0.4.0/ts/test/evidence-content.test.ts +421 -0
- sanning_proof-0.4.0/ts/test/evidence-export.test.ts +443 -0
- sanning_proof-0.4.0/ts/test/evidence-golden.test.ts +200 -0
- sanning_proof-0.4.0/ts/test/evidence.test.ts +323 -0
- sanning_proof-0.4.0/ts/test/external-commitment.test.ts +164 -0
- sanning_proof-0.4.0/ts/test/fixtures/anchor-trace-bundle.golden.json +110 -0
- sanning_proof-0.4.0/ts/test/fixtures/evidence-export-bundle.golden.json +332 -0
- sanning_proof-0.4.0/ts/test/fixtures/rsa-pss-attestation.golden.json +26 -0
- sanning_proof-0.4.0/ts/test/rsa-pss-attestation.test.ts +132 -0
- sanning_proof-0.4.0/ts/test/verifier.test.ts +178 -0
- sanning_proof-0.4.0/ts/tsconfig.build.json +15 -0
- sanning_proof-0.4.0/ts/tsconfig.json +17 -0
- sanning_proof-0.4.0/ts/vitest.config.ts +12 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
16
|
+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- name: Install
|
|
20
|
+
run: pip install -e .[dev]
|
|
21
|
+
- name: Format check
|
|
22
|
+
run: black --check src tests
|
|
23
|
+
- name: Conformance + unit tests
|
|
24
|
+
run: pytest -q
|
|
25
|
+
|
|
26
|
+
ts:
|
|
27
|
+
# The TypeScript kernel (ts/) — same conformance gate as the Python kernel,
|
|
28
|
+
# against the one authoritative test-vectors/ corpus. SHA-pinned actions
|
|
29
|
+
# per this repo's supply-chain discipline.
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
defaults:
|
|
32
|
+
run:
|
|
33
|
+
working-directory: ts
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
36
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
37
|
+
with:
|
|
38
|
+
node-version: "24"
|
|
39
|
+
- run: npm ci
|
|
40
|
+
- name: Build (dist ESM + declarations — the publishable shape)
|
|
41
|
+
run: npm run build
|
|
42
|
+
- name: Typecheck
|
|
43
|
+
run: npm run typecheck
|
|
44
|
+
- name: Conformance + verifier tests (vectors byte-for-byte)
|
|
45
|
+
run: npm test
|
|
46
|
+
|
|
47
|
+
cross-kernel:
|
|
48
|
+
# Python ⇄ TS ⇄ Go agreement gate over the corpus + adversarial cases
|
|
49
|
+
# (cross-kernel/). The three kernels must return identical verdicts on
|
|
50
|
+
# identical bytes. Python+TS always run; the Go leg builds sanning-io/agent
|
|
51
|
+
# pkg/proof at cross-kernel/PIN when the agent source is fetchable, and
|
|
52
|
+
# SKIPS loudly otherwise (public PRs without private-repo access).
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
56
|
+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
57
|
+
with:
|
|
58
|
+
python-version: "3.13"
|
|
59
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
60
|
+
with:
|
|
61
|
+
node-version: "24"
|
|
62
|
+
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
63
|
+
with:
|
|
64
|
+
go-version: "1.25.0"
|
|
65
|
+
- name: Install Python kernel + node deps
|
|
66
|
+
run: |
|
|
67
|
+
pip install -e .[dev]
|
|
68
|
+
(cd ts && npm ci)
|
|
69
|
+
- name: Cross-kernel agreement (Python + TS + Go)
|
|
70
|
+
# Go leg builds the VENDORED sanning-io/agent pkg/proof (cross-kernel/
|
|
71
|
+
# vendor-agent, pinned in cross-kernel/PIN) — no private-repo access,
|
|
72
|
+
# so all three legs run on public and fork PRs.
|
|
73
|
+
run: bash cross-kernel/run.sh
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Publishes ts/ (@sanning/proof) to npm via OIDC trusted publishing — no
|
|
2
|
+
# long-lived npm tokens anywhere (the npm mirror of this repo's PyPI
|
|
3
|
+
# Trusted-Publishing flow in release.yml). This repo is PUBLIC, so npm
|
|
4
|
+
# provenance works from day one: --provenance is ON.
|
|
5
|
+
#
|
|
6
|
+
# npmjs.com side (one-time, package settings for @sanning/proof →
|
|
7
|
+
# "Trusted Publisher" → GitHub Actions):
|
|
8
|
+
# organization: sanning-io
|
|
9
|
+
# repository: proof
|
|
10
|
+
# workflow: release-ts.yml (filename must match exactly)
|
|
11
|
+
# environment: (leave blank)
|
|
12
|
+
#
|
|
13
|
+
# Real publish: push a `ts-vX.Y.Z` tag whose version matches ts/package.json
|
|
14
|
+
# (the guard step enforces it).
|
|
15
|
+
# Dry-run: workflow_dispatch — runs the ENTIRE path including
|
|
16
|
+
# `npm publish --dry-run`. Lesson from the v1.2.0 cosign failure: a dry-run
|
|
17
|
+
# that skips the publish step validates nothing.
|
|
18
|
+
#
|
|
19
|
+
# Tag namespace: ts-v* (TS). Does not overlap v* (Python, release.yml) or
|
|
20
|
+
# test-vectors-v* (corpus).
|
|
21
|
+
|
|
22
|
+
name: release-ts
|
|
23
|
+
|
|
24
|
+
on:
|
|
25
|
+
push:
|
|
26
|
+
tags: ['ts-v*.*.*']
|
|
27
|
+
workflow_dispatch: # full dry-run: build, test, pack — publishes nothing
|
|
28
|
+
|
|
29
|
+
permissions:
|
|
30
|
+
contents: read
|
|
31
|
+
id-token: write # npm OIDC trusted publishing
|
|
32
|
+
|
|
33
|
+
jobs:
|
|
34
|
+
publish:
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
defaults:
|
|
37
|
+
run:
|
|
38
|
+
working-directory: ts
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
41
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
42
|
+
with:
|
|
43
|
+
node-version: '24'
|
|
44
|
+
registry-url: 'https://registry.npmjs.org'
|
|
45
|
+
- name: Pin npm to a trusted-publishing-capable version
|
|
46
|
+
# OIDC trusted publishing needs npm >= 11.5.1; Node images float their
|
|
47
|
+
# bundled npm. Floor it explicitly (a floating tool default broke the
|
|
48
|
+
# first v1.2.0 release cut — don't repeat that with npm).
|
|
49
|
+
run: |
|
|
50
|
+
npm install -g npm@^11.5.1
|
|
51
|
+
npm --version
|
|
52
|
+
- run: npm ci
|
|
53
|
+
- name: Build (exports point at dist/)
|
|
54
|
+
run: npm run build
|
|
55
|
+
- run: npm run typecheck
|
|
56
|
+
- name: Conformance + verifier tests (vectors byte-for-byte)
|
|
57
|
+
run: npm test
|
|
58
|
+
- name: Tag ↔ package version guard
|
|
59
|
+
if: github.event_name == 'push'
|
|
60
|
+
run: |
|
|
61
|
+
PKG="$(node -p "require('./package.json').version")"
|
|
62
|
+
TAG="${GITHUB_REF_NAME#ts-v}"
|
|
63
|
+
if [ "$PKG" != "$TAG" ]; then
|
|
64
|
+
echo "::error::tag ts-v${TAG} does not match ts/package.json version ${PKG}"
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
- name: Publish to npm (dry-run on dispatch)
|
|
68
|
+
# --provenance ON: this repo is public, so npm provenance attests the
|
|
69
|
+
# build from source. On workflow_dispatch this runs --dry-run (packs +
|
|
70
|
+
# validates, publishes nothing); on a ts-v* tag push it publishes.
|
|
71
|
+
run: npm publish --provenance --access public ${{ github.event_name == 'workflow_dispatch' && '--dry-run' || '' }}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI via Trusted Publishing (OIDC) — no token secrets.
|
|
4
|
+
# Trigger: a maintainer pushes a v* tag (e.g. `git tag v0.1.0 && git push origin v0.1.0`).
|
|
5
|
+
# One-time setup on pypi.org: add a "pending publisher" for project sanning-proof
|
|
6
|
+
# with owner=sanning-io, repo=proof, workflow=release.yml, environment=pypi.
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
tags: ["v*"]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
17
|
+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
- name: Run the conformance gate before anything ships
|
|
21
|
+
run: |
|
|
22
|
+
pip install -e .[dev]
|
|
23
|
+
black --check src tests
|
|
24
|
+
pytest -q
|
|
25
|
+
- name: Build sdist + wheel
|
|
26
|
+
run: |
|
|
27
|
+
pip install build
|
|
28
|
+
python -m build
|
|
29
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
30
|
+
with:
|
|
31
|
+
name: dist
|
|
32
|
+
path: dist/
|
|
33
|
+
|
|
34
|
+
publish:
|
|
35
|
+
needs: build
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
environment: pypi
|
|
38
|
+
permissions:
|
|
39
|
+
id-token: write
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
42
|
+
with:
|
|
43
|
+
name: dist
|
|
44
|
+
path: dist/
|
|
45
|
+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Changelog — `sanning-proof` (Python kernel)
|
|
2
|
+
|
|
3
|
+
All notable changes to the Python verification kernel. Format loosely follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/); this project is pre-1.0, so
|
|
5
|
+
minor versions may include behavior changes.
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Cross-kernel accept-set agreement.** The tri-kernel gate now probes the
|
|
12
|
+
accepted-`spec_version` registry itself: `cross-kernel/accept-set.json`
|
|
13
|
+
transcribes the ratified accept-set (envelope-spec §4 registry + §5
|
|
14
|
+
accepted-majors discipline) and `cross-kernel/generate_cases.py` builds one
|
|
15
|
+
correctly signed probe per identifier, in the profile's registered binding
|
|
16
|
+
mode, plus the profile-conditional signed-scope dialects (32 → 48 cases).
|
|
17
|
+
Until now every case in the set carried `sanning.agent/v1` or
|
|
18
|
+
`sanning.events/v1`, so three kernels could hold three different registries
|
|
19
|
+
and the gate stayed green. Each of the three registries is additionally held
|
|
20
|
+
to the declaration by **set equality**, because a verdict probe can only
|
|
21
|
+
disagree about identifiers someone declared — a kernel admitting a profile
|
|
22
|
+
nobody wrote down has no probe to fail: `tests/test_accept_set.py` for the
|
|
23
|
+
Python `ACCEPTED_SPEC_VERSIONS`, `ts/test/accept-set.test.ts` for the TS
|
|
24
|
+
`ACCEPTED_SPEC_MAJORS`, and `cross-kernel/go-verifier/accept_set.go` for Go's
|
|
25
|
+
`SupportedSpecMajor` (inside the gate, since Go has no unit-test leg here).
|
|
26
|
+
No kernel behavior changes: the registry's contents are untouched.
|
|
27
|
+
|
|
28
|
+
### Known gaps
|
|
29
|
+
|
|
30
|
+
- **`sanning.mlflow/v1` admission is divergent and escalated, not fixed.** The
|
|
31
|
+
ratified accept-set is `{ sanning.agent/v1, sanning.mlflow/v1,
|
|
32
|
+
sanning.events/v1 }` (§5, amendment A5 — "in all three kernels"); the Python
|
|
33
|
+
kernel carries all three, while TS `ACCEPTED_SPEC_MAJORS` and Go
|
|
34
|
+
`SupportedSpecMajor` carry two. A second divergence sits behind it: TS and Go
|
|
35
|
+
do not implement the mlflow profile's underscore-annotation signed-scope
|
|
36
|
+
strip, so an `_tx_id`-carrying envelope would split the verdict even once the
|
|
37
|
+
identifier is admitted. Both are quarantined in `accept-set.json` and asserted
|
|
38
|
+
in both directions (the divergence must persist unchanged; resolving it fails
|
|
39
|
+
the gate until the row is deleted). Widening a kernel's accept-set changes
|
|
40
|
+
which envelopes are admitted — a founder decision, not a kernel edit.
|
|
41
|
+
|
|
42
|
+
## [0.4.0] — 2026-08-05
|
|
43
|
+
|
|
44
|
+
First release of the Python verification kernel under this name, carrying the
|
|
45
|
+
July 2026 security fixes.
|
|
46
|
+
|
|
47
|
+
### Security
|
|
48
|
+
|
|
49
|
+
Merged to `main` during the July 2026 adversarial pass; **this is the first
|
|
50
|
+
release that publishes them.** Both are normative cross-kernel invariants — the
|
|
51
|
+
TypeScript kernel carries the identical guards.
|
|
52
|
+
|
|
53
|
+
- **RSA public-exponent guard (`sanning_proof.rsa_pss`).** `verify_rsa_pss_sha256`
|
|
54
|
+
now rejects any operator key whose public exponent is not `65537`
|
|
55
|
+
(`RSA_PUBLIC_EXPONENT`), returning `False` — a **FAILED** attestation, exit 1 —
|
|
56
|
+
rather than delegating the decision to the crypto library. The operator-address
|
|
57
|
+
binding is `base64url(SHA-256(n))`, which commits to the **modulus alone**; with
|
|
58
|
+
`e = 1`, RSA verification degenerates to the identity function, so anyone holding
|
|
59
|
+
a public modulus could forge an attestation that binds to the legitimate
|
|
60
|
+
operator's address without ever holding the private key. A malformed `e`
|
|
61
|
+
(unparseable or empty) still routes to `MalformedRsaError` → exit 2, so
|
|
62
|
+
"forged" and "garbage" stay distinguishable.
|
|
63
|
+
- **Canonicalization depth bound (`sanning_proof.canonicalize`).**
|
|
64
|
+
`MAX_CANONICAL_DEPTH = 128`, enforced by an **iterative** (never recursive)
|
|
65
|
+
`exceeds_depth` walk that runs *before* canonicalization on already-parsed,
|
|
66
|
+
possibly hostile input. Container nesting past the bound raises
|
|
67
|
+
`CanonicalDepthError`, bucketed as **malformed** (exit 2), not as a failed
|
|
68
|
+
verification. Without it, an attacker-chosen nesting depth splits the verdict
|
|
69
|
+
across kernels — bytes one kernel canonicalizes on a large native stack and
|
|
70
|
+
another rejects with `RecursionError` — which is a verdict-integrity break, not
|
|
71
|
+
merely a crash.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- **Family namespace.** Every family
|
|
76
|
+
wire string moves namespace with the MAJOR unchanged at `v1` (a new namespace,
|
|
77
|
+
not a new revision): `sanning.agent/v1`, `sanning.mlflow/v1`, `sanning.events/v1`
|
|
78
|
+
(`ACCEPTED_SPEC_VERSIONS`), `sanning.evidence/v1` (`ACCEPTED_EVIDENCE_MAJORS`),
|
|
79
|
+
`sanning.anchor.trace/v1` and `sanning.evidence.export/v1` (`body_type`),
|
|
80
|
+
`sanning.evidence.verdict/v1` (`VERDICT_SCHEMA_VERSION`), and
|
|
81
|
+
`sanning.agent.proof/v1` (`BUNDLE_SPEC_VERSION`). Envelopes and bundles emitted
|
|
82
|
+
outside the accept-set do not verify.
|
|
83
|
+
- **BREAKING — conformance corpus re-cut to `test-vectors-v2.0`.** Every vector was
|
|
84
|
+
regenerated under the migrated namespace, so all signatures, Merkle leaves, roots
|
|
85
|
+
and audit paths were re-derived; the manifest moved `CORPUS-v1.md` → `CORPUS-v2.md`
|
|
86
|
+
and the profile directory is `sanning.events-v1/`. A byte change
|
|
87
|
+
to existing vectors is a **major** cut per `specs/governance.md` §4; the 30-day RFC
|
|
88
|
+
was waived by founder call (recorded in §7). **41 manifest rows, up from 39** — the
|
|
89
|
+
two `evidence-export/negatives/` files (the RSA `e=1` exponent forgery and the
|
|
90
|
+
canonicalization-depth bundle) were never pinned in any manifest and are now listed.
|
|
91
|
+
A `v1.x` pin cannot be upgraded in place; downstreams re-pin deliberately.
|
|
92
|
+
- The `e=1` attestation-forgery negative gained a generator
|
|
93
|
+
(`tools/gen-vectors/gen_exponent_forgery_vector.py`), closing the CORP1 violation it
|
|
94
|
+
carried since the July security pass. It asserts on every run that raw `e=1` RSA
|
|
95
|
+
verification *accepts* the forged signature, so the vector cannot decay into one that
|
|
96
|
+
fails for an unrelated reason while the exponent guard stops being exercised.
|
|
97
|
+
|
|
98
|
+
### Known gaps
|
|
99
|
+
|
|
100
|
+
- The vendored Go leg (`cross-kernel/vendor-agent/` @ `cross-kernel/PIN`) still carries
|
|
101
|
+
the pre-migration accept-set, so `cross-kernel/run.sh`'s Go leg is expected-red until
|
|
102
|
+
the `agent` repo migrates. The Python⇄TS legs (including the byte-identical export
|
|
103
|
+
verdict gate) are green.
|
|
104
|
+
- Both gaps clear **after** this release, not before it: `sanning-io/anchor` can only
|
|
105
|
+
re-cut the golden bundle once it depends on a published `@sanning/proof`, and the Go
|
|
106
|
+
leg needs the `agent` migration. Neither blocks 0.4.0 — re-cutting the fixture locally
|
|
107
|
+
to make the skips green would destroy the cross-repo emit↔verify guarantee the fixture
|
|
108
|
+
exists to hold.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
`sanning-io/proof` — the **polyglot verification home** of the Sanning evidence plane: the family
|
|
8
|
+
specs (`specs/`), the one authoritative conformance corpus (`test-vectors/`), and two conformant
|
|
9
|
+
kernels that reproduce it byte-for-byte:
|
|
10
|
+
|
|
11
|
+
- **Python** (`sanning_proof`, repo root) — extracted from the MLflow plugin's proof engine; this is
|
|
12
|
+
the kernel most of this CLAUDE.md is about.
|
|
13
|
+
- **TypeScript** (`@sanning/proof`, [`ts/`](ts/)) — moved here 2026-06-11 from the browser verifier
|
|
14
|
+
app (an app never owns a kernel — same principle that extracted the Python kernel from mlflow).
|
|
15
|
+
|
|
16
|
+
Both implement exactly the five kernel primitives from
|
|
17
|
+
[`specs/architecture.md`](specs/architecture.md) §3: RFC 8785 (JCS) canonicalization, SHA-256, Ed25519
|
|
18
|
+
sign/verify, RFC 9162 binary Merkle (incl. inclusion-proof bundles), and the
|
|
19
|
+
accepted-`spec_version` registry. **No I/O, no networking, no key lifecycle** — those belong to
|
|
20
|
+
producers (`agent`, `mlflow`), not the kernel. The Go reference stays MIT-carved in
|
|
21
|
+
`agent/pkg/proof` (sanning-io).
|
|
22
|
+
|
|
23
|
+
### Working in the TS kernel (`ts/`)
|
|
24
|
+
|
|
25
|
+
Standalone npm package, separate from the Python tooling:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd ts && npm ci
|
|
29
|
+
npm run build # tsc -p tsconfig.build.json → dist/ (ESM + .d.ts; the published shape)
|
|
30
|
+
npm run typecheck # tsc --noEmit
|
|
31
|
+
npm test # vitest — conformance vs ../test-vectors (the same corpus), byte-for-byte
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
CI runs a dedicated `ts` job (`ci.yml`). Releases go through `release-ts.yml` (npm OIDC trusted
|
|
35
|
+
publishing, provenance on, tags `ts-v*` — distinct from Python's `v*` / `release.yml`). Source
|
|
36
|
+
relative imports MUST carry explicit `.js` extensions (Node-ESM requirement; `moduleResolution:
|
|
37
|
+
bundler` won't add them — this bit `0.1.1`, fixed in `0.1.2`). Kernel changes are spec-layer
|
|
38
|
+
changes: byte-for-byte conformance is the gate, and the Python and Go kernels must stay in
|
|
39
|
+
lockstep (escalate divergences per `specs/governance.md` §6).
|
|
40
|
+
|
|
41
|
+
**Bundle verify + the CLI live HERE, not in `@ar.io/anchor`** (the kernel is the producer-neutral
|
|
42
|
+
verifier — an auditor must not need the write SDK). `src/evidence.ts` = `verifyEvidenceBundle`
|
|
43
|
+
(signed `sanning.evidence/v1` / `sanning.anchor.trace/v1` bundles); `src/agent-proof.ts` =
|
|
44
|
+
`verifyAgentProofBundle` (`sanning.agent.proof/v1`); `src/cli.ts` = the `bin: proof` →
|
|
45
|
+
`npx @sanning/proof verify <bundle> [gateways]` (sniffs `spec_version`, so one verb covers anchor +
|
|
46
|
+
agent; pinned exit codes `0/1/2/3`). Bundle *emit* (`toEvidenceBundle`) lives in `@ar.io/anchor`.
|
|
47
|
+
Implemented on `main`; the `bin` ships on the next `ts-v*` tag. Cross-repo round-trip is pinned by
|
|
48
|
+
`ts/test/fixtures/anchor-trace-bundle.golden.json` (a real @ar.io/anchor bundle, frozen).
|
|
49
|
+
|
|
50
|
+
## Commands
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
python3 -m venv .venv && .venv/bin/pip install -e .[dev] # one-time setup
|
|
54
|
+
.venv/bin/pytest -q # all tests (conformance is the contract)
|
|
55
|
+
.venv/bin/pytest tests/test_conformance.py -q # corpus gate only
|
|
56
|
+
.venv/bin/black src tests # format (CI runs black --check)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## The conformance contract
|
|
60
|
+
|
|
61
|
+
`test-vectors/` is the authoritative home of the family conformance corpus (the current
|
|
62
|
+
cut is the DL-22 `sanning.*` re-cut declared in `CORPUS-v2.md` — a MAJOR cut per
|
|
63
|
+
`specs/governance.md` §4, tagged **`test-vectors-v2.0`** → commit `3a9ccf5`.
|
|
64
|
+
Regeneration is an ordered ceremony: see `test-vectors/README.md`. Provenance:
|
|
65
|
+
`test-vectors/VENDORING.md`; downstreams like `agent` (sanning-io) vendor byte-for-byte
|
|
66
|
+
from here). `tests/test_conformance.py`
|
|
67
|
+
asserts, for every vector: SHA-256 corpus integrity against the CORPUS-v2.md table, JCS-canonical
|
|
68
|
+
payload bytes, `payload_hash`, envelope-for-signature bytes, deterministic Ed25519 signatures, and
|
|
69
|
+
Merkle roots / audit paths. **If the kernel disagrees with a vector, the kernel is wrong — never
|
|
70
|
+
the vector.** Never edit vector files; re-sync only at a published corpus tag per
|
|
71
|
+
`VENDORING.md`.
|
|
72
|
+
|
|
73
|
+
## Spec pins (do not drift)
|
|
74
|
+
|
|
75
|
+
- `envelope-spec.md` **v1.3 (ratified v1.0 2026-06-10, amended through 2026-06-15 — additive,
|
|
76
|
+
no wire change)** — the family contract. Ratified accept-set (§4 registry + §5): `sanning.agent/v1`
|
|
77
|
+
(inline payload), `sanning.mlflow/v1` (external commitment), `sanning.events/v1` (external
|
|
78
|
+
commitment, Minimal disclosure). Fail-closed on unknown majors. **The kernels do not all
|
|
79
|
+
implement that set today:** `sanning.mlflow/v1` is carried by the Python kernel only — the TS
|
|
80
|
+
(`ACCEPTED_SPEC_MAJORS`) and Go (`SupportedSpecMajor`) registries omit it, as does their
|
|
81
|
+
signed-scope handling of the mlflow `_*` annotation dialect. That gap is an **escalated
|
|
82
|
+
divergence**, pinned by the cross-kernel gate and awaiting a founder admission decision — see
|
|
83
|
+
[`cross-kernel/README.md`](cross-kernel/README.md) § Known divergences and
|
|
84
|
+
[`cross-kernel/accept-set.json`](cross-kernel/accept-set.json). Do not close it with a one-line
|
|
85
|
+
registry edit: widening an accept-set changes which envelopes verify.
|
|
86
|
+
- Signed scope = the envelope minus **`signature`**, minus the reserved **`co_signatures`**
|
|
87
|
+
(envelope-spec §7.1), and — **profile-conditional** — minus underscore-prefixed annotation
|
|
88
|
+
keys for `sanning.mlflow/v1` + legacy envelopes ONLY (mlflow convention, e.g. `_tx_id`). The
|
|
89
|
+
`sanning.agent/v1` and `sanning.events/v1` scopes are minus-signature/co_signatures only, matching
|
|
90
|
+
Go: an injected `_*` key on an agent or events envelope MUST fail verification. The corpus can
|
|
91
|
+
catch neither the co_signatures strip nor the profile-conditionality — unit tests in
|
|
92
|
+
`tests/test_envelope.py`
|
|
93
|
+
are the only guard; do not remove them because conformance still passes without them.
|
|
94
|
+
- Merkle: RFC 9162 §2.1 domain separation (`0x00` leaf / `0x01` node), empty-tree root =
|
|
95
|
+
`SHA-256("")`. Never the Bitcoin duplicate-last-leaf variant.
|
|
96
|
+
- Ed25519 is strict RFC 8032 (libsodium via PyNaCl) — matches Go `crypto/ed25519` and the JS
|
|
97
|
+
sibling's `zip215: false`.
|
|
98
|
+
|
|
99
|
+
## Working rules
|
|
100
|
+
|
|
101
|
+
- Verifiers must never raise on adversarial input — malformed envelopes return a failed
|
|
102
|
+
`VerificationResult`, they don't throw.
|
|
103
|
+
- Each commit lands one primitive, CI-green, with tests in the same commit.
|
|
104
|
+
- Dependencies are pinned to two load-bearing packages: `PyNaCl` and `jcs`. Adding a dependency
|
|
105
|
+
is a design decision, not a convenience.
|
|
106
|
+
- Source siblings for cross-checks (read-only): Go reference `agent/pkg/{proof,merkle}` (sanning-io),
|
|
107
|
+
origin kernel `mlflow` (sanning-io).
|
|
108
|
+
|
|
109
|
+
## Release
|
|
110
|
+
|
|
111
|
+
`release.yml` publishes to PyPI via Trusted Publishing (OIDC) on a `v*` tag pushed by a
|
|
112
|
+
maintainer. Version lives in `pyproject.toml` + `src/sanning_proof/__init__.py`; bump both together.
|
|
113
|
+
The TS kernel is a **third** version site — `ts/package.json`, released separately by a `ts-v*`
|
|
114
|
+
tag through `release-ts.yml`. All three move in lockstep; a bump in one and not the others is the
|
|
115
|
+
classic release-day failure, and neither Python workflow guards it (`release.yml` has no
|
|
116
|
+
tag↔version check — PyPI publishes whatever `pyproject.toml` says).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sanning Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: sanning-proof
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Verification kernel for the Sanning verification stack: RFC 8785 canonicalization, SHA-256, Ed25519 envelope sign/verify, RFC 9162 Merkle inclusion proofs, and RSA-PSS attested-evidence-export verification.
|
|
5
|
+
Project-URL: Repository, https://github.com/sanning-io/proof
|
|
6
|
+
Project-URL: Issues, https://github.com/sanning-io/proof/issues
|
|
7
|
+
Author: Sanning Inc.
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Sanning Inc.
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: arweave,ed25519,jcs,merkle,provenance,rfc8785,rfc9162,sanning,verification
|
|
31
|
+
Classifier: Development Status :: 4 - Beta
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
39
|
+
Classifier: Topic :: Security :: Cryptography
|
|
40
|
+
Requires-Python: >=3.10
|
|
41
|
+
Requires-Dist: cryptography>=41.0.0
|
|
42
|
+
Requires-Dist: jcs>=0.2.1
|
|
43
|
+
Requires-Dist: pynacl>=1.5.0
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: black>=24.0; extra == 'dev'
|
|
46
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
|
|
49
|
+
# Sanning Proof
|
|
50
|
+
|
|
51
|
+
Offline verification for the [Sanning](https://console.sanning.io) evidence plane: standalone, dependency-light verification kernels in **Python** and **TypeScript**, plus the ratified specs and the conformance corpus both kernels are gated against, byte for byte. No network, no accounts, no vendor code in the trust path — anyone can check a piece of anchored evidence with nothing but this package.
|
|
52
|
+
|
|
53
|
+
- **Python** — `sanning_proof` (PyPI: [`sanning-proof`](https://pypi.org/project/sanning-proof/)), at the repo root.
|
|
54
|
+
- **TypeScript** — [`@sanning/proof`](https://www.npmjs.com/package/@sanning/proof) (npm), in [`ts/`](ts/) — browser + Node ≥ 20, ESM.
|
|
55
|
+
|
|
56
|
+
Verify a signed envelope fetched from any Arweave gateway:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import json
|
|
60
|
+
import urllib.request
|
|
61
|
+
|
|
62
|
+
from sanning_proof import verify_envelope
|
|
63
|
+
|
|
64
|
+
raw = urllib.request.urlopen("https://arweave.net/raw/<tx_id>").read()
|
|
65
|
+
result = verify_envelope(json.loads(raw))
|
|
66
|
+
|
|
67
|
+
assert result.ok # spec_version + payload binding + Ed25519 signature
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { verifyEnvelope } from "@sanning/proof";
|
|
72
|
+
|
|
73
|
+
const env = await (await fetch("https://arweave.net/raw/<tx_id>")).json();
|
|
74
|
+
const result = await verifyEnvelope(env);
|
|
75
|
+
result.ok; // spec_version + payload binding + Ed25519 signature
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
> [!NOTE]
|
|
79
|
+
> Verification never needs an account or a server. If you are *producing* evidence — anchoring agent steps, running a fleet under continuous tamper-evidence — the hosted plane is [console.sanning.io](https://console.sanning.io) and the write SDK is [`@sanning/anchor`](https://github.com/sanning-io/anchor).
|
|
80
|
+
|
|
81
|
+
## Install
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install sanning-proof # Python
|
|
85
|
+
npm install @sanning/proof # TypeScript
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## What a verdict proves — and what it doesn't
|
|
89
|
+
|
|
90
|
+
`result.ok` proves exactly this: *the holder of the private key matching the envelope's `public_key` signed exactly these bytes, and the payload binding holds.*
|
|
91
|
+
|
|
92
|
+
It deliberately does **not** prove:
|
|
93
|
+
|
|
94
|
+
- **Whose key that is.** The key ↔ identity binding comes from out of band — for example an enrollment roster. The kernel checks cryptography, not identity.
|
|
95
|
+
- **That the envelope is on-chain.** Fetch the transaction from a gateway yourself and re-verify — which is exactly what the example above does.
|
|
96
|
+
- **When it happened.** `signed_at` is the signer's claim; witnessed time comes from the Arweave block the envelope landed in.
|
|
97
|
+
- **What the raw data was.** Envelopes are content-blind: they commit to hashes. Raw bytes never pass through this kernel unless the producer explicitly disclosed them alongside the proof.
|
|
98
|
+
|
|
99
|
+
For a verification tool, the guarantee boundary is the product — a verifier that overclaims is worse than no verifier at all.
|
|
100
|
+
|
|
101
|
+
## Verify more things
|
|
102
|
+
|
|
103
|
+
**Bind an artifact you hold** to the provenance an envelope commits to (reverse lookup):
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import hashlib
|
|
107
|
+
|
|
108
|
+
artifact_hash = hashlib.sha256(open("model.pkl", "rb").read()).hexdigest()
|
|
109
|
+
result = verify_envelope(envelope, expected_content_hash=artifact_hash)
|
|
110
|
+
print(result.content_hash_ok, result.content_role) # True, "asset"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Verify an external-commitment envelope** (`sanning.mlflow/v1`) against the committed bytes:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
result = verify_envelope(envelope, payload_bytes=canonical_bytes)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Verify an inclusion-proof bundle** — proves a leaf event was in a signed checkpoint:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from sanning_proof import verify_proof_bundle
|
|
123
|
+
|
|
124
|
+
bundle = json.load(open("proof-bundle.json"))
|
|
125
|
+
result = verify_proof_bundle(bundle)
|
|
126
|
+
assert result.ok and result.inclusion_ok
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The TypeScript kernel has full parity, including the RFC 9162 Merkle primitives (`leafHash` / `merkleRoot` / `auditPath` / `verifyInclusion`) — see [`ts/README.md`](ts/README.md).
|
|
130
|
+
|
|
131
|
+
## The CLI
|
|
132
|
+
|
|
133
|
+
The TypeScript package ships a turnkey CLI. Pinned exit codes — `0` verified · `1` failed · `2` malformed · `3` gateway-unavailable — safe to gate CI on.
|
|
134
|
+
|
|
135
|
+
**Verify** any evidence or agent-proof bundle, fully offline; optionally re-fetch checkpoints on-chain by passing gateways:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npx @sanning/proof verify <bundle.json> [gateway1,gateway2,...] [--logs <logs.json>]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`--logs` binds disclosed raw logs to their committed hashes (evidence bundles only).
|
|
142
|
+
|
|
143
|
+
**Compose an attested export** — turn a source trace bundle plus operator attestation records into one signed, offline-verifiable `sanning.evidence/v1` bundle that verifies like any other:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npx @sanning/proof export <source-bundle.json> --attestations <att.json> --key <exporter.hex> -o export.json
|
|
147
|
+
npx @sanning/proof verify export.json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Programmatic equivalent: `composeExport(sourceBundle, attestations, { privateKey })`. Full format: [`specs/evidence-export.md`](specs/evidence-export.md).
|
|
151
|
+
|
|
152
|
+
## Signing (producers)
|
|
153
|
+
|
|
154
|
+
Most producers never call the kernel directly — the write SDK ([`@sanning/anchor`](https://github.com/sanning-io/anchor)), the agent daemon, and the MLflow plugin all sign through it. For a custom producer:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from sanning_proof import sign_envelope, signing_key_from_seed_hex
|
|
158
|
+
|
|
159
|
+
key = signing_key_from_seed_hex("<32-byte seed hex>")
|
|
160
|
+
envelope = sign_envelope({...}, key) # the envelope minus `signature`, per the spec
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## The standard
|
|
164
|
+
|
|
165
|
+
This repo is the authoritative home of the evidence plane's **standards layer** — the contract is public alongside the reference verifiers:
|
|
166
|
+
|
|
167
|
+
- [`specs/envelope-spec.md`](specs/envelope-spec.md) — the producer-neutral **Verifiable Event Envelope** family contract (ratified v1.0, amended through **v1.4**, 2026-07-15).
|
|
168
|
+
- [`specs/evidence-bundle.md`](specs/evidence-bundle.md) — the `sanning.evidence/v1` report wrapper.
|
|
169
|
+
- [`specs/evidence-export.md`](specs/evidence-export.md) — the attested-export format behind `proof export`.
|
|
170
|
+
- [`specs/architecture.md`](specs/architecture.md) — the kernel / producer / connector / transport factoring standard.
|
|
171
|
+
- [`specs/governance.md`](specs/governance.md) — who decides, and how.
|
|
172
|
+
- [`test-vectors/`](test-vectors/) — the conformance corpus (current cut: tag **`test-vectors-v2.0`**, per-file SHA-256 in [`CORPUS-v2.md`](test-vectors/CORPUS-v2.md)); generated by [`tools/gen-vectors/`](tools/gen-vectors/), never hand-edited.
|
|
173
|
+
|
|
174
|
+
Three envelope **profiles** are registered against the family contract: `sanning.agent/v1` (the agent daemon's inline-payload profile), `sanning.mlflow/v1` (the MLflow plugin's external-commitment profile), and `sanning.events/v1` (the anchor SDK's minimal-disclosure profile). The Python kernel accepts all three; the TypeScript kernel accepts `sanning.agent/v1` and `sanning.events/v1` (the mlflow dialect is Python-only). Additive minors are accepted within a major; unknown majors and malformed versions fail closed.
|
|
175
|
+
|
|
176
|
+
**Conformance discipline:** both kernels reproduce the corpus byte for byte — JCS-canonical bytes, payload hashes, envelope-for-signature bytes, deterministic signatures, Merkle roots, audit paths. If this package disagrees with a vector, the package is wrong — never the vector.
|
|
177
|
+
|
|
178
|
+
## Kernel scope
|
|
179
|
+
|
|
180
|
+
The kernel is deliberately small: canonicalization (RFC 8785), SHA-256 hashing, Ed25519 sign/verify, RFC 9162 binary Merkle inclusion proofs, and the profile registry — **no I/O, no networking, no key lifecycle**. Gateway fetching, attestation polling, and key storage belong to the products that import it. Dependencies are deliberately minimal: Python has exactly two ([`PyNaCl`](https://pypi.org/project/PyNaCl/), [`jcs`](https://pypi.org/project/jcs/)); TypeScript has exactly two ([`@noble/ed25519`](https://www.npmjs.com/package/@noble/ed25519), [`canonicalize`](https://www.npmjs.com/package/canonicalize)).
|
|
181
|
+
|
|
182
|
+
## Development
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
python3 -m venv .venv && .venv/bin/pip install -e .[dev]
|
|
186
|
+
.venv/bin/pytest -q # the conformance gate is the contract
|
|
187
|
+
.venv/bin/black src tests
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The TypeScript kernel lives in [`ts/`](ts/) and is conformance-gated in CI against the same `test-vectors/` directory.
|
|
191
|
+
|
|
192
|
+
## Contributing
|
|
193
|
+
|
|
194
|
+
Issues and pull requests are welcome. If you believe a kernel disagrees with a spec or a corpus vector, open an issue titled `contract conflict: <spec> §<section>` with the smallest reproduction — see [`specs/governance.md`](specs/governance.md) §6.
|
|
195
|
+
|
|
196
|
+
## Security
|
|
197
|
+
|
|
198
|
+
Please report vulnerabilities privately via GitHub Security Advisories ("Report a vulnerability" under this repo's Security tab) and do **not** open a public issue.
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
[MIT](LICENSE). The verifier is deliberately open-licensed so anyone can audit it and verify evidence independently of Sanning.
|