obsign-verify 0.1.0__py3-none-any.whl
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.
- obsign_verify/__init__.py +27 -0
- obsign_verify/canonical.py +80 -0
- obsign_verify/cli.py +121 -0
- obsign_verify/data/challenge/ATTESTATION.md +48 -0
- obsign_verify/data/challenge/bundles/env_only_change_must_still_verify/receipt.json +62 -0
- obsign_verify/data/challenge/bundles/forged_input_hash/receipt.json +62 -0
- obsign_verify/data/challenge/bundles/forged_output_hash/receipt.json +62 -0
- obsign_verify/data/challenge/bundles/resealed_tampered_claim/receipt.json +62 -0
- obsign_verify/data/challenge/bundles/swapped_dtype/receipt.json +62 -0
- obsign_verify/data/challenge/bundles/swapped_kernel/receipt.json +62 -0
- obsign_verify/data/challenge/bundles/tampered_params/receipt.json +62 -0
- obsign_verify/data/challenge/bundles/truncated_receipt/receipt.json +32 -0
- obsign_verify/data/challenge/bundles/valid/receipt.json +62 -0
- obsign_verify/data/conformance_vectors.json +100 -0
- obsign_verify/kernel.py +113 -0
- obsign_verify/py.typed +0 -0
- obsign_verify/signature.py +114 -0
- obsign_verify/verify.py +107 -0
- obsign_verify-0.1.0.dist-info/METADATA +144 -0
- obsign_verify-0.1.0.dist-info/RECORD +24 -0
- obsign_verify-0.1.0.dist-info/WHEEL +5 -0
- obsign_verify-0.1.0.dist-info/entry_points.txt +2 -0
- obsign_verify-0.1.0.dist-info/licenses/LICENSE +206 -0
- obsign_verify-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Verify an Obsign receipt: re-derive the number yourself, offline.
|
|
2
|
+
|
|
3
|
+
from obsign_verify import verify, load_receipt
|
|
4
|
+
result = verify(load_receipt(open("receipt.json", encoding="utf-8").read()))
|
|
5
|
+
|
|
6
|
+
Imports NOTHING from the engine that produced the receipt. That independence is
|
|
7
|
+
the point -- if this package imported the producer, "it verifies" would mean "the
|
|
8
|
+
producer agrees with itself".
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .canonical import canonical_sha256, claim_of, load_receipt
|
|
12
|
+
from .verify import verify
|
|
13
|
+
|
|
14
|
+
__all__ = ["verify", "load_receipt", "claim_of", "canonical_sha256", "__version__"]
|
|
15
|
+
__version__ = "0.1.0"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def data_path(*parts: str):
|
|
19
|
+
"""Absolute path to a file shipped INSIDE the package.
|
|
20
|
+
|
|
21
|
+
The conformance vectors and the challenge bundles are package data, not repo
|
|
22
|
+
files. A `pip install` has to give a stranger everything the README tells them
|
|
23
|
+
to run -- the first cut shipped neither, so `obsign-verify challenge/...` and
|
|
24
|
+
`pytest` both failed for anyone who installed rather than cloned.
|
|
25
|
+
"""
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
return Path(__file__).resolve().parent.joinpath("data", *parts)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Canonical form and the claim hash.
|
|
2
|
+
|
|
3
|
+
The whole verifier stands on this file. If canonicalisation is off by one byte, a
|
|
4
|
+
genuine receipt reports as tampered and the product's central promise inverts.
|
|
5
|
+
|
|
6
|
+
THE INT/FLOAT TRAP, which the spec calls out by name
|
|
7
|
+
|
|
8
|
+
Python writes `1.0` for a float and `1` for an int, and the two canonicalise to
|
|
9
|
+
DIFFERENT bytes and therefore different hashes. A reader that erases the
|
|
10
|
+
distinction -- notably JavaScript's `JSON.parse`, where every number is a double --
|
|
11
|
+
will canonicalise `gamma: 0.0` as `0` and report an honest receipt as tampered.
|
|
12
|
+
|
|
13
|
+
Python's `json` preserves it, so this implementation can parse normally. That is
|
|
14
|
+
luck, not design, and `tests/` pins it so a future "cleanup" to a float-normalising
|
|
15
|
+
parser fails loudly instead of silently rejecting every receipt in the field.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import hashlib
|
|
21
|
+
import json
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
#: Fields the spec excludes from the claim. `env` is informational, `signature`
|
|
25
|
+
#: and `case` are post-hoc, `receipt_sha256` is the hash itself, `_`-prefixed keys
|
|
26
|
+
#: are helpers. A verifier that hashed these would report a genuine receipt as
|
|
27
|
+
#: tampered the moment anyone recorded a different platform.
|
|
28
|
+
NON_CLAIM = ("receipt_sha256", "env", "signature", "case")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def claim_of(receipt: dict) -> dict:
|
|
32
|
+
"""The subset of a receipt that its `receipt_sha256` covers."""
|
|
33
|
+
return {k: v for k, v in receipt.items()
|
|
34
|
+
if k not in NON_CLAIM and not k.startswith("_")}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def canonical_bytes(obj: Any) -> bytes:
|
|
38
|
+
"""Canonical JSON exactly as docs/SPEC.md defines it.
|
|
39
|
+
|
|
40
|
+
`allow_nan=False` is load-bearing, not tidiness: NaN and Infinity have no JSON
|
|
41
|
+
representation, so permitting them would let two different receipts share a
|
|
42
|
+
canonical form.
|
|
43
|
+
"""
|
|
44
|
+
return json.dumps(obj, sort_keys=True, separators=(",", ":"),
|
|
45
|
+
ensure_ascii=True, allow_nan=False).encode("utf-8")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def canonical_sha256(obj: Any) -> str:
|
|
49
|
+
return hashlib.sha256(canonical_bytes(obj)).hexdigest()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def load_receipt(text: str) -> dict:
|
|
53
|
+
"""Parse receipt TEXT into a dict, preserving the int/float distinction.
|
|
54
|
+
|
|
55
|
+
Takes text rather than a path so the caller controls I/O, and so a port to
|
|
56
|
+
another language has an obvious seam at the only place the trap can bite.
|
|
57
|
+
"""
|
|
58
|
+
obj = json.loads(text)
|
|
59
|
+
if not isinstance(obj, dict):
|
|
60
|
+
raise ValueError("a receipt must be a JSON object")
|
|
61
|
+
return obj
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def integrity(receipt: dict) -> tuple[bool, str]:
|
|
65
|
+
"""Step 1 of the trust ladder: does `receipt_sha256` recompute from the claim?
|
|
66
|
+
|
|
67
|
+
Returns (ok, detail). Never raises on a hostile receipt -- a verifier that
|
|
68
|
+
crashes on malformed input has failed open in the eyes of whoever supplied it.
|
|
69
|
+
"""
|
|
70
|
+
stated = receipt.get("receipt_sha256")
|
|
71
|
+
if not isinstance(stated, str) or not stated:
|
|
72
|
+
return False, "no receipt_sha256 to check against"
|
|
73
|
+
try:
|
|
74
|
+
recomputed = canonical_sha256(claim_of(receipt))
|
|
75
|
+
except (ValueError, TypeError) as exc:
|
|
76
|
+
return False, f"claim is not canonicalisable ({type(exc).__name__}: {exc})"
|
|
77
|
+
if recomputed != stated:
|
|
78
|
+
return False, (f"INTEGRITY FAIL - states {stated[:16]}.., "
|
|
79
|
+
f"recomputes {recomputed[:16]}..")
|
|
80
|
+
return True, "integrity OK"
|
obsign_verify/cli.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""`obsign-verify receipt.json` -- the one command PG-1 is about.
|
|
2
|
+
|
|
3
|
+
Exit code is the interface: 0 means every named receipt verified, 1 means at least
|
|
4
|
+
one did not. A script wrapping this must be able to branch on that alone.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from . import __version__
|
|
15
|
+
from .canonical import load_receipt
|
|
16
|
+
from .verify import verify
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _report(path: Path, res: dict, quiet: bool) -> None:
|
|
20
|
+
if quiet:
|
|
21
|
+
return
|
|
22
|
+
mark = "VERIFIED" if res["verified"] else "REFUSED"
|
|
23
|
+
print(f" [{mark:^8}] {path.name}")
|
|
24
|
+
print(f" integrity {'ok' if res['integrity'] else 'FAIL'}")
|
|
25
|
+
print(f" re-derived {'ok' if res['reproduced'] else 'FAIL'}")
|
|
26
|
+
sig = res.get("signature") or {}
|
|
27
|
+
if not sig.get("present"):
|
|
28
|
+
print(" signature absent (integrity and re-derivation still hold)")
|
|
29
|
+
else:
|
|
30
|
+
who = sig.get("attributed_signer")
|
|
31
|
+
# Never print a signer name the signature did not cover. A v1 signature's
|
|
32
|
+
# name can be rewritten by anyone with a text editor and no key.
|
|
33
|
+
ident = (f"attributed to {who!r}" if sig.get("identity_bound")
|
|
34
|
+
else f"NOT identity-bound (file claims {sig.get('claimed_signer')!r})")
|
|
35
|
+
print(f" signature {'ok' if sig.get('valid') else 'FAIL'} - {ident}")
|
|
36
|
+
for note in res["notes"]:
|
|
37
|
+
print(f" - {note}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _self_check(quiet: bool) -> int:
|
|
41
|
+
"""Run the challenge that ships INSIDE the package.
|
|
42
|
+
|
|
43
|
+
A stranger who `pip install`ed has no `challenge/` directory to point at, so the
|
|
44
|
+
README's "run the forgeries" instruction was unfollowable for exactly the people
|
|
45
|
+
it was written for. This needs no files of their own and no arguments.
|
|
46
|
+
|
|
47
|
+
Exit 0 means every bundle got the verdict it declares -- the honest ones
|
|
48
|
+
verified, and every forgery refused. Refusing the forgeries is the job, so a
|
|
49
|
+
clean run is NOT "9 verified".
|
|
50
|
+
"""
|
|
51
|
+
from . import data_path
|
|
52
|
+
bundles = sorted(data_path("challenge", "bundles").glob("*/receipt.json"))
|
|
53
|
+
if not bundles:
|
|
54
|
+
print("no bundles shipped with this package -- the install is incomplete",
|
|
55
|
+
file=sys.stderr)
|
|
56
|
+
return 1
|
|
57
|
+
|
|
58
|
+
wrong = 0
|
|
59
|
+
for path in bundles:
|
|
60
|
+
receipt = load_receipt(path.read_text(encoding="utf-8"))
|
|
61
|
+
declared = receipt.get("_challenge", {}).get("expect_verified")
|
|
62
|
+
got = verify(receipt)["verified"]
|
|
63
|
+
ok = got is declared
|
|
64
|
+
wrong += not ok
|
|
65
|
+
if not quiet:
|
|
66
|
+
print(f" {'ok ' if ok else '!! '}{path.parent.name:<36}"
|
|
67
|
+
f"{'VERIFIED' if got else 'REFUSED':<9}"
|
|
68
|
+
f"(expected {'VERIFIED' if declared else 'REFUSED'})")
|
|
69
|
+
if not quiet:
|
|
70
|
+
n = len(bundles)
|
|
71
|
+
print()
|
|
72
|
+
print(f"{n - wrong}/{n} bundles behaved as declared."
|
|
73
|
+
+ ("" if wrong else " Every forgery was refused, including the resealed"
|
|
74
|
+
" one whose signature is intact and whose claim is false."))
|
|
75
|
+
return 1 if wrong else 0
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def main(argv: list[str] | None = None) -> int:
|
|
79
|
+
ap = argparse.ArgumentParser(
|
|
80
|
+
prog="obsign-verify",
|
|
81
|
+
description="Re-derive an Obsign receipt's claim on your own machine.")
|
|
82
|
+
ap.add_argument("receipts", nargs="*", type=Path)
|
|
83
|
+
ap.add_argument("--self-check", action="store_true",
|
|
84
|
+
help="verify the bundled challenge: 2 honest receipts and 7 "
|
|
85
|
+
"forgeries that must be refused. Needs no files of your own.")
|
|
86
|
+
ap.add_argument("--json", action="store_true", help="machine-readable output")
|
|
87
|
+
ap.add_argument("--quiet", action="store_true")
|
|
88
|
+
ap.add_argument("--version", action="version", version=f"obsign-verify {__version__}")
|
|
89
|
+
args = ap.parse_args(argv)
|
|
90
|
+
|
|
91
|
+
if args.self_check:
|
|
92
|
+
return _self_check(args.quiet)
|
|
93
|
+
if not args.receipts:
|
|
94
|
+
ap.error("give one or more receipt files, or --self-check")
|
|
95
|
+
|
|
96
|
+
report, failures = [], 0
|
|
97
|
+
for path in args.receipts:
|
|
98
|
+
try:
|
|
99
|
+
receipt = load_receipt(path.read_text(encoding="utf-8"))
|
|
100
|
+
except Exception as exc:
|
|
101
|
+
failures += 1
|
|
102
|
+
report.append({"file": str(path), "verified": False,
|
|
103
|
+
"notes": [f"unreadable: {type(exc).__name__}: {exc}"]})
|
|
104
|
+
if not args.quiet:
|
|
105
|
+
print(f" [ REFUSED ] {path.name}: unreadable ({exc})")
|
|
106
|
+
continue
|
|
107
|
+
res = verify(receipt)
|
|
108
|
+
failures += 0 if res["verified"] else 1
|
|
109
|
+
report.append({"file": str(path), **res})
|
|
110
|
+
_report(path, res, args.quiet)
|
|
111
|
+
|
|
112
|
+
if args.json:
|
|
113
|
+
print(json.dumps(report, indent=2))
|
|
114
|
+
elif not args.quiet:
|
|
115
|
+
total = len(report)
|
|
116
|
+
print(f"\n{total - failures}/{total} receipt(s) verified on THIS machine.")
|
|
117
|
+
return 1 if failures else 0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Attestation — I ran the Receipt Challenge
|
|
2
|
+
|
|
3
|
+
Fill in, publish anywhere, link it back if you're willing. A failure to break it
|
|
4
|
+
is as useful as a break; a break is more useful.
|
|
5
|
+
|
|
6
|
+
## Who
|
|
7
|
+
- Name / handle:
|
|
8
|
+
- Affiliation (optional):
|
|
9
|
+
- Date (UTC):
|
|
10
|
+
|
|
11
|
+
## Platform
|
|
12
|
+
- OS + version:
|
|
13
|
+
- CPU (and whether ARM / x86):
|
|
14
|
+
- Python version:
|
|
15
|
+
- numpy version:
|
|
16
|
+
- Anything unusual (musl, emulation, BSD, big-endian, PyPy, WASM):
|
|
17
|
+
|
|
18
|
+
## What I ran
|
|
19
|
+
```
|
|
20
|
+
python verify_challenge.py
|
|
21
|
+
```
|
|
22
|
+
Paste the full output:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## SHA256SUMS I observed
|
|
29
|
+
Confirm the bundle you ran matches what was published:
|
|
30
|
+
```
|
|
31
|
+
sha256sum -c SHA256SUMS # or: python -c "..."
|
|
32
|
+
```
|
|
33
|
+
Result:
|
|
34
|
+
|
|
35
|
+
## Verdict
|
|
36
|
+
- [ ] Every valid receipt re-derived on my machine
|
|
37
|
+
- [ ] Every forgery was refused
|
|
38
|
+
- [ ] I found a forgery that VERIFIED ← the interesting one
|
|
39
|
+
- [ ] The valid receipt did NOT reproduce on my hardware ← also interesting
|
|
40
|
+
|
|
41
|
+
## Notes / anything you broke or tried to break
|
|
42
|
+
|
|
43
|
+
<!-- Attacks attempted, near-misses, ambiguity in the spec, anything the
|
|
44
|
+
verifier fails to check. Negative findings welcome and wanted. -->
|
|
45
|
+
|
|
46
|
+
## Independence
|
|
47
|
+
- [ ] I confirmed `verify_challenge.py` imports nothing from the producing project
|
|
48
|
+
- [ ] I read the verifier before running it
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_challenge": {
|
|
3
|
+
"expect_verified": true,
|
|
4
|
+
"note": "only `env` changed, which the spec excludes from the claim; this MUST still verify, or the format over-claims what it covers"
|
|
5
|
+
},
|
|
6
|
+
"env": {
|
|
7
|
+
"gpu": "NVIDIA GeForce RTX 5080 Laptop GPU",
|
|
8
|
+
"numpy": "2.3.5",
|
|
9
|
+
"platform": "some-other-os",
|
|
10
|
+
"python": "3.14.3",
|
|
11
|
+
"torch": "2.11.0+cu128"
|
|
12
|
+
},
|
|
13
|
+
"input": {
|
|
14
|
+
"kind": "generated",
|
|
15
|
+
"ref": "params",
|
|
16
|
+
"sha256": null
|
|
17
|
+
},
|
|
18
|
+
"kernel": "tau_field_fixed",
|
|
19
|
+
"output": {
|
|
20
|
+
"dtype": "int64",
|
|
21
|
+
"sha256": "a50bef430f745b12111225eed12cd0e4f86119ccb0fb47a878ed1f61d50b3ea3",
|
|
22
|
+
"shape": [
|
|
23
|
+
64,
|
|
24
|
+
64
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"params": {
|
|
28
|
+
"D": 0.14,
|
|
29
|
+
"dt": 0.15,
|
|
30
|
+
"frac_bits": 24,
|
|
31
|
+
"gamma": 0.02,
|
|
32
|
+
"grid": 64,
|
|
33
|
+
"sources": [
|
|
34
|
+
[
|
|
35
|
+
0.3,
|
|
36
|
+
0.3,
|
|
37
|
+
0.11,
|
|
38
|
+
0.01
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
0.7,
|
|
42
|
+
0.7,
|
|
43
|
+
0.16,
|
|
44
|
+
0.008
|
|
45
|
+
]
|
|
46
|
+
],
|
|
47
|
+
"steps": 100
|
|
48
|
+
},
|
|
49
|
+
"producer": {
|
|
50
|
+
"name": "obsign",
|
|
51
|
+
"spec": "obsign/spec/1",
|
|
52
|
+
"version": "2.0.0"
|
|
53
|
+
},
|
|
54
|
+
"receipt_sha256": "b8b4f656b10646186ca9fe16d1e34eb106f4f2336d2db8f8f4cbb4a0660a5527",
|
|
55
|
+
"run": {
|
|
56
|
+
"backend": "cpu-fixed",
|
|
57
|
+
"deterministic": true,
|
|
58
|
+
"device": "numpy",
|
|
59
|
+
"dtype": "int64"
|
|
60
|
+
},
|
|
61
|
+
"spec": "obsign/receipt/v1"
|
|
62
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_challenge": {
|
|
3
|
+
"expect_verified": false,
|
|
4
|
+
"note": "claims the computation ran on data it did not run on"
|
|
5
|
+
},
|
|
6
|
+
"env": {
|
|
7
|
+
"gpu": "NVIDIA GeForce RTX 5080 Laptop GPU",
|
|
8
|
+
"numpy": "2.3.5",
|
|
9
|
+
"platform": "Windows-11-10.0.26200-SP0",
|
|
10
|
+
"python": "3.14.3",
|
|
11
|
+
"torch": "2.11.0+cu128"
|
|
12
|
+
},
|
|
13
|
+
"input": {
|
|
14
|
+
"kind": "generated",
|
|
15
|
+
"ref": "params",
|
|
16
|
+
"sha256": "1111111111111111111111111111111111111111111111111111111111111111"
|
|
17
|
+
},
|
|
18
|
+
"kernel": "tau_field_fixed",
|
|
19
|
+
"output": {
|
|
20
|
+
"dtype": "int64",
|
|
21
|
+
"sha256": "a50bef430f745b12111225eed12cd0e4f86119ccb0fb47a878ed1f61d50b3ea3",
|
|
22
|
+
"shape": [
|
|
23
|
+
64,
|
|
24
|
+
64
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"params": {
|
|
28
|
+
"D": 0.14,
|
|
29
|
+
"dt": 0.15,
|
|
30
|
+
"frac_bits": 24,
|
|
31
|
+
"gamma": 0.02,
|
|
32
|
+
"grid": 64,
|
|
33
|
+
"sources": [
|
|
34
|
+
[
|
|
35
|
+
0.3,
|
|
36
|
+
0.3,
|
|
37
|
+
0.11,
|
|
38
|
+
0.01
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
0.7,
|
|
42
|
+
0.7,
|
|
43
|
+
0.16,
|
|
44
|
+
0.008
|
|
45
|
+
]
|
|
46
|
+
],
|
|
47
|
+
"steps": 100
|
|
48
|
+
},
|
|
49
|
+
"producer": {
|
|
50
|
+
"name": "obsign",
|
|
51
|
+
"spec": "obsign/spec/1",
|
|
52
|
+
"version": "2.0.0"
|
|
53
|
+
},
|
|
54
|
+
"receipt_sha256": "b8b4f656b10646186ca9fe16d1e34eb106f4f2336d2db8f8f4cbb4a0660a5527",
|
|
55
|
+
"run": {
|
|
56
|
+
"backend": "cpu-fixed",
|
|
57
|
+
"deterministic": true,
|
|
58
|
+
"device": "numpy",
|
|
59
|
+
"dtype": "int64"
|
|
60
|
+
},
|
|
61
|
+
"spec": "obsign/receipt/v1"
|
|
62
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_challenge": {
|
|
3
|
+
"expect_verified": false,
|
|
4
|
+
"note": "claims a different result than the computation produces"
|
|
5
|
+
},
|
|
6
|
+
"env": {
|
|
7
|
+
"gpu": "NVIDIA GeForce RTX 5080 Laptop GPU",
|
|
8
|
+
"numpy": "2.3.5",
|
|
9
|
+
"platform": "Windows-11-10.0.26200-SP0",
|
|
10
|
+
"python": "3.14.3",
|
|
11
|
+
"torch": "2.11.0+cu128"
|
|
12
|
+
},
|
|
13
|
+
"input": {
|
|
14
|
+
"kind": "generated",
|
|
15
|
+
"ref": "params",
|
|
16
|
+
"sha256": null
|
|
17
|
+
},
|
|
18
|
+
"kernel": "tau_field_fixed",
|
|
19
|
+
"output": {
|
|
20
|
+
"dtype": "int64",
|
|
21
|
+
"sha256": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
22
|
+
"shape": [
|
|
23
|
+
64,
|
|
24
|
+
64
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"params": {
|
|
28
|
+
"D": 0.14,
|
|
29
|
+
"dt": 0.15,
|
|
30
|
+
"frac_bits": 24,
|
|
31
|
+
"gamma": 0.02,
|
|
32
|
+
"grid": 64,
|
|
33
|
+
"sources": [
|
|
34
|
+
[
|
|
35
|
+
0.3,
|
|
36
|
+
0.3,
|
|
37
|
+
0.11,
|
|
38
|
+
0.01
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
0.7,
|
|
42
|
+
0.7,
|
|
43
|
+
0.16,
|
|
44
|
+
0.008
|
|
45
|
+
]
|
|
46
|
+
],
|
|
47
|
+
"steps": 100
|
|
48
|
+
},
|
|
49
|
+
"producer": {
|
|
50
|
+
"name": "obsign",
|
|
51
|
+
"spec": "obsign/spec/1",
|
|
52
|
+
"version": "2.0.0"
|
|
53
|
+
},
|
|
54
|
+
"receipt_sha256": "b8b4f656b10646186ca9fe16d1e34eb106f4f2336d2db8f8f4cbb4a0660a5527",
|
|
55
|
+
"run": {
|
|
56
|
+
"backend": "cpu-fixed",
|
|
57
|
+
"deterministic": true,
|
|
58
|
+
"device": "numpy",
|
|
59
|
+
"dtype": "int64"
|
|
60
|
+
},
|
|
61
|
+
"spec": "obsign/receipt/v1"
|
|
62
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_challenge": {
|
|
3
|
+
"expect_verified": false,
|
|
4
|
+
"note": "claim tampered AND re-sealed, so integrity is INTACT; only re-derivation catches it"
|
|
5
|
+
},
|
|
6
|
+
"env": {
|
|
7
|
+
"gpu": "NVIDIA GeForce RTX 5080 Laptop GPU",
|
|
8
|
+
"numpy": "2.3.5",
|
|
9
|
+
"platform": "Windows-11-10.0.26200-SP0",
|
|
10
|
+
"python": "3.14.3",
|
|
11
|
+
"torch": "2.11.0+cu128"
|
|
12
|
+
},
|
|
13
|
+
"input": {
|
|
14
|
+
"kind": "generated",
|
|
15
|
+
"ref": "params",
|
|
16
|
+
"sha256": null
|
|
17
|
+
},
|
|
18
|
+
"kernel": "tau_field_fixed",
|
|
19
|
+
"output": {
|
|
20
|
+
"dtype": "int64",
|
|
21
|
+
"sha256": "a50bef430f745b12111225eed12cd0e4f86119ccb0fb47a878ed1f61d50b3ea3",
|
|
22
|
+
"shape": [
|
|
23
|
+
64,
|
|
24
|
+
64
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"params": {
|
|
28
|
+
"D": 0.14,
|
|
29
|
+
"dt": 0.15,
|
|
30
|
+
"frac_bits": 24,
|
|
31
|
+
"gamma": 0.02,
|
|
32
|
+
"grid": 64,
|
|
33
|
+
"sources": [
|
|
34
|
+
[
|
|
35
|
+
0.3,
|
|
36
|
+
0.3,
|
|
37
|
+
0.11,
|
|
38
|
+
0.01
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
0.7,
|
|
42
|
+
0.7,
|
|
43
|
+
0.16,
|
|
44
|
+
0.008
|
|
45
|
+
]
|
|
46
|
+
],
|
|
47
|
+
"steps": 120
|
|
48
|
+
},
|
|
49
|
+
"producer": {
|
|
50
|
+
"name": "obsign",
|
|
51
|
+
"spec": "obsign/spec/1",
|
|
52
|
+
"version": "2.0.0"
|
|
53
|
+
},
|
|
54
|
+
"receipt_sha256": "e03f51663e632151341b77a29b32b3967565167ea767517649930c76392ca775",
|
|
55
|
+
"run": {
|
|
56
|
+
"backend": "cpu-fixed",
|
|
57
|
+
"deterministic": true,
|
|
58
|
+
"device": "numpy",
|
|
59
|
+
"dtype": "int64"
|
|
60
|
+
},
|
|
61
|
+
"spec": "obsign/receipt/v1"
|
|
62
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_challenge": {
|
|
3
|
+
"expect_verified": false,
|
|
4
|
+
"note": "output metadata rewritten under an unchanged hash"
|
|
5
|
+
},
|
|
6
|
+
"env": {
|
|
7
|
+
"gpu": "NVIDIA GeForce RTX 5080 Laptop GPU",
|
|
8
|
+
"numpy": "2.3.5",
|
|
9
|
+
"platform": "Windows-11-10.0.26200-SP0",
|
|
10
|
+
"python": "3.14.3",
|
|
11
|
+
"torch": "2.11.0+cu128"
|
|
12
|
+
},
|
|
13
|
+
"input": {
|
|
14
|
+
"kind": "generated",
|
|
15
|
+
"ref": "params",
|
|
16
|
+
"sha256": null
|
|
17
|
+
},
|
|
18
|
+
"kernel": "tau_field_fixed",
|
|
19
|
+
"output": {
|
|
20
|
+
"dtype": "int32",
|
|
21
|
+
"sha256": "a50bef430f745b12111225eed12cd0e4f86119ccb0fb47a878ed1f61d50b3ea3",
|
|
22
|
+
"shape": [
|
|
23
|
+
64,
|
|
24
|
+
64
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"params": {
|
|
28
|
+
"D": 0.14,
|
|
29
|
+
"dt": 0.15,
|
|
30
|
+
"frac_bits": 24,
|
|
31
|
+
"gamma": 0.02,
|
|
32
|
+
"grid": 64,
|
|
33
|
+
"sources": [
|
|
34
|
+
[
|
|
35
|
+
0.3,
|
|
36
|
+
0.3,
|
|
37
|
+
0.11,
|
|
38
|
+
0.01
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
0.7,
|
|
42
|
+
0.7,
|
|
43
|
+
0.16,
|
|
44
|
+
0.008
|
|
45
|
+
]
|
|
46
|
+
],
|
|
47
|
+
"steps": 100
|
|
48
|
+
},
|
|
49
|
+
"producer": {
|
|
50
|
+
"name": "obsign",
|
|
51
|
+
"spec": "obsign/spec/1",
|
|
52
|
+
"version": "2.0.0"
|
|
53
|
+
},
|
|
54
|
+
"receipt_sha256": "b8b4f656b10646186ca9fe16d1e34eb106f4f2336d2db8f8f4cbb4a0660a5527",
|
|
55
|
+
"run": {
|
|
56
|
+
"backend": "cpu-fixed",
|
|
57
|
+
"deterministic": true,
|
|
58
|
+
"device": "numpy",
|
|
59
|
+
"dtype": "int64"
|
|
60
|
+
},
|
|
61
|
+
"spec": "obsign/receipt/v1"
|
|
62
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_challenge": {
|
|
3
|
+
"expect_verified": false,
|
|
4
|
+
"note": "same numbers, different operation"
|
|
5
|
+
},
|
|
6
|
+
"env": {
|
|
7
|
+
"gpu": "NVIDIA GeForce RTX 5080 Laptop GPU",
|
|
8
|
+
"numpy": "2.3.5",
|
|
9
|
+
"platform": "Windows-11-10.0.26200-SP0",
|
|
10
|
+
"python": "3.14.3",
|
|
11
|
+
"torch": "2.11.0+cu128"
|
|
12
|
+
},
|
|
13
|
+
"input": {
|
|
14
|
+
"kind": "generated",
|
|
15
|
+
"ref": "params",
|
|
16
|
+
"sha256": null
|
|
17
|
+
},
|
|
18
|
+
"kernel": "tau_field",
|
|
19
|
+
"output": {
|
|
20
|
+
"dtype": "int64",
|
|
21
|
+
"sha256": "a50bef430f745b12111225eed12cd0e4f86119ccb0fb47a878ed1f61d50b3ea3",
|
|
22
|
+
"shape": [
|
|
23
|
+
64,
|
|
24
|
+
64
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"params": {
|
|
28
|
+
"D": 0.14,
|
|
29
|
+
"dt": 0.15,
|
|
30
|
+
"frac_bits": 24,
|
|
31
|
+
"gamma": 0.02,
|
|
32
|
+
"grid": 64,
|
|
33
|
+
"sources": [
|
|
34
|
+
[
|
|
35
|
+
0.3,
|
|
36
|
+
0.3,
|
|
37
|
+
0.11,
|
|
38
|
+
0.01
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
0.7,
|
|
42
|
+
0.7,
|
|
43
|
+
0.16,
|
|
44
|
+
0.008
|
|
45
|
+
]
|
|
46
|
+
],
|
|
47
|
+
"steps": 100
|
|
48
|
+
},
|
|
49
|
+
"producer": {
|
|
50
|
+
"name": "obsign",
|
|
51
|
+
"spec": "obsign/spec/1",
|
|
52
|
+
"version": "2.0.0"
|
|
53
|
+
},
|
|
54
|
+
"receipt_sha256": "b8b4f656b10646186ca9fe16d1e34eb106f4f2336d2db8f8f4cbb4a0660a5527",
|
|
55
|
+
"run": {
|
|
56
|
+
"backend": "cpu-fixed",
|
|
57
|
+
"deterministic": true,
|
|
58
|
+
"device": "numpy",
|
|
59
|
+
"dtype": "int64"
|
|
60
|
+
},
|
|
61
|
+
"spec": "obsign/receipt/v1"
|
|
62
|
+
}
|