sia-verifier 1.3.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.
- sia_verifier-1.3.0/LICENSE +21 -0
- sia_verifier-1.3.0/PKG-INFO +126 -0
- sia_verifier-1.3.0/README.md +86 -0
- sia_verifier-1.3.0/pyproject.toml +37 -0
- sia_verifier-1.3.0/setup.cfg +4 -0
- sia_verifier-1.3.0/sia_verifier/__init__.py +77 -0
- sia_verifier-1.3.0/sia_verifier/__main__.py +136 -0
- sia_verifier-1.3.0/sia_verifier/core.py +494 -0
- sia_verifier-1.3.0/sia_verifier/holdout.py +291 -0
- sia_verifier-1.3.0/sia_verifier/rederive.py +461 -0
- sia_verifier-1.3.0/sia_verifier/replay.py +312 -0
- sia_verifier-1.3.0/sia_verifier.egg-info/PKG-INFO +126 -0
- sia_verifier-1.3.0/sia_verifier.egg-info/SOURCES.txt +15 -0
- sia_verifier-1.3.0/sia_verifier.egg-info/dependency_links.txt +1 -0
- sia_verifier-1.3.0/sia_verifier.egg-info/entry_points.txt +5 -0
- sia_verifier-1.3.0/sia_verifier.egg-info/requires.txt +1 -0
- sia_verifier-1.3.0/sia_verifier.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SIA Sentinel contributors
|
|
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,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sia-verifier
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: Independent verifier for sia-attestation/1 Proof-of-Savings attestations — no trust in the auditor required
|
|
5
|
+
Author: SIA Sentinel contributors
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 SIA Sentinel contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/sia-sentinel/sia-sentinel
|
|
29
|
+
Keywords: ai,finops,attestation,ed25519,verification
|
|
30
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Topic :: Security :: Cryptography
|
|
35
|
+
Requires-Python: >=3.9
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
License-File: LICENSE
|
|
38
|
+
Requires-Dist: cryptography>=41.0
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# sia-verifier
|
|
42
|
+
|
|
43
|
+
**Independent verifier for `sia-attestation/1` Proof-of-Savings attestations.**
|
|
44
|
+
|
|
45
|
+
This package lets *anyone* verify an AI cost-savings attestation **without
|
|
46
|
+
trusting the auditor**. It needs only the attestation document and the
|
|
47
|
+
issuer's public key (carried inside the document). No network calls, no
|
|
48
|
+
Sentinel server, no SDK.
|
|
49
|
+
|
|
50
|
+
## Why this exists
|
|
51
|
+
|
|
52
|
+
A Proof-of-Savings attestation claims: *"this AI workload was audited, the
|
|
53
|
+
savings are real, and here is cryptographic proof."* The whole point of the
|
|
54
|
+
claim is that you should not have to take the auditor's word for it. This
|
|
55
|
+
verifier is the reference implementation of that promise:
|
|
56
|
+
|
|
57
|
+
- **Ed25519 receipt signature** — the receipt (including `receipt_id` and the
|
|
58
|
+
reproducibility manifest) is signed; any tampering breaks the signature.
|
|
59
|
+
- **Claim consistency** — the public claim must match the signed
|
|
60
|
+
`safety_approved` field; a forged claim fails verification.
|
|
61
|
+
- **TrustChain hash chain** (optional) — given a ledger export, every entry is
|
|
62
|
+
recomputed; removal, insertion, reorder or content tampering breaks it.
|
|
63
|
+
- **Checkpoints** (optional) — signed commitments pinning the chain head.
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install sia-verifier
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Single dependency: `cryptography`. Python 3.9+.
|
|
72
|
+
|
|
73
|
+
## CLI
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Verify an attestation document (the JSON served at GET /v1/attestations/{id})
|
|
77
|
+
sia-verifier attestation.json
|
|
78
|
+
|
|
79
|
+
# Also verify the hash chain from a ledger export
|
|
80
|
+
sia-verifier attestation.json --chain registry.jsonl
|
|
81
|
+
|
|
82
|
+
# Also verify a checkpoint signature
|
|
83
|
+
sia-verifier attestation.json --checkpoint checkpoint.json
|
|
84
|
+
|
|
85
|
+
# Machine-readable verdict
|
|
86
|
+
sia-verifier attestation.json --json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Exit code `0` = valid, `1` = invalid, `2` = input error — safe to wire into CI.
|
|
90
|
+
|
|
91
|
+
## Python API
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import json
|
|
95
|
+
from sia_verifier import verify_attestation, verify_chain
|
|
96
|
+
|
|
97
|
+
attestation = json.load(open("attestation.json"))
|
|
98
|
+
verdict = verify_attestation(attestation)
|
|
99
|
+
|
|
100
|
+
assert verdict.valid, verdict.reasons
|
|
101
|
+
assert verdict.receipt_signature_valid
|
|
102
|
+
assert verdict.claim_consistent
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## What is NOT verified (by design)
|
|
106
|
+
|
|
107
|
+
- The `verification.*` fields inside the attestation are the *server's* live
|
|
108
|
+
opinion and are deliberately ignored — they are not part of the signed
|
|
109
|
+
commitment.
|
|
110
|
+
- The issuer's public key is taken from the document itself. For a
|
|
111
|
+
higher-assurance check, pin the issuer's key out-of-band (e.g. from the
|
|
112
|
+
issuer's published key registry) and compare it to `issuer.public_key`
|
|
113
|
+
before trusting the verdict.
|
|
114
|
+
- This verifier checks cryptographic integrity, not whether the audit
|
|
115
|
+
methodology was sound. Methodology is documented in the attestation's
|
|
116
|
+
reproducibility manifest.
|
|
117
|
+
|
|
118
|
+
## Specification
|
|
119
|
+
|
|
120
|
+
The full format is defined by the `sia-attestation/1` specification
|
|
121
|
+
(commitment construction, hash chain, checkpoints). See the SIA Sentinel
|
|
122
|
+
repository, `docs/attestation-spec.md`.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# sia-verifier
|
|
2
|
+
|
|
3
|
+
**Independent verifier for `sia-attestation/1` Proof-of-Savings attestations.**
|
|
4
|
+
|
|
5
|
+
This package lets *anyone* verify an AI cost-savings attestation **without
|
|
6
|
+
trusting the auditor**. It needs only the attestation document and the
|
|
7
|
+
issuer's public key (carried inside the document). No network calls, no
|
|
8
|
+
Sentinel server, no SDK.
|
|
9
|
+
|
|
10
|
+
## Why this exists
|
|
11
|
+
|
|
12
|
+
A Proof-of-Savings attestation claims: *"this AI workload was audited, the
|
|
13
|
+
savings are real, and here is cryptographic proof."* The whole point of the
|
|
14
|
+
claim is that you should not have to take the auditor's word for it. This
|
|
15
|
+
verifier is the reference implementation of that promise:
|
|
16
|
+
|
|
17
|
+
- **Ed25519 receipt signature** — the receipt (including `receipt_id` and the
|
|
18
|
+
reproducibility manifest) is signed; any tampering breaks the signature.
|
|
19
|
+
- **Claim consistency** — the public claim must match the signed
|
|
20
|
+
`safety_approved` field; a forged claim fails verification.
|
|
21
|
+
- **TrustChain hash chain** (optional) — given a ledger export, every entry is
|
|
22
|
+
recomputed; removal, insertion, reorder or content tampering breaks it.
|
|
23
|
+
- **Checkpoints** (optional) — signed commitments pinning the chain head.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install sia-verifier
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Single dependency: `cryptography`. Python 3.9+.
|
|
32
|
+
|
|
33
|
+
## CLI
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Verify an attestation document (the JSON served at GET /v1/attestations/{id})
|
|
37
|
+
sia-verifier attestation.json
|
|
38
|
+
|
|
39
|
+
# Also verify the hash chain from a ledger export
|
|
40
|
+
sia-verifier attestation.json --chain registry.jsonl
|
|
41
|
+
|
|
42
|
+
# Also verify a checkpoint signature
|
|
43
|
+
sia-verifier attestation.json --checkpoint checkpoint.json
|
|
44
|
+
|
|
45
|
+
# Machine-readable verdict
|
|
46
|
+
sia-verifier attestation.json --json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Exit code `0` = valid, `1` = invalid, `2` = input error — safe to wire into CI.
|
|
50
|
+
|
|
51
|
+
## Python API
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import json
|
|
55
|
+
from sia_verifier import verify_attestation, verify_chain
|
|
56
|
+
|
|
57
|
+
attestation = json.load(open("attestation.json"))
|
|
58
|
+
verdict = verify_attestation(attestation)
|
|
59
|
+
|
|
60
|
+
assert verdict.valid, verdict.reasons
|
|
61
|
+
assert verdict.receipt_signature_valid
|
|
62
|
+
assert verdict.claim_consistent
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## What is NOT verified (by design)
|
|
66
|
+
|
|
67
|
+
- The `verification.*` fields inside the attestation are the *server's* live
|
|
68
|
+
opinion and are deliberately ignored — they are not part of the signed
|
|
69
|
+
commitment.
|
|
70
|
+
- The issuer's public key is taken from the document itself. For a
|
|
71
|
+
higher-assurance check, pin the issuer's key out-of-band (e.g. from the
|
|
72
|
+
issuer's published key registry) and compare it to `issuer.public_key`
|
|
73
|
+
before trusting the verdict.
|
|
74
|
+
- This verifier checks cryptographic integrity, not whether the audit
|
|
75
|
+
methodology was sound. Methodology is documented in the attestation's
|
|
76
|
+
reproducibility manifest.
|
|
77
|
+
|
|
78
|
+
## Specification
|
|
79
|
+
|
|
80
|
+
The full format is defined by the `sia-attestation/1` specification
|
|
81
|
+
(commitment construction, hash chain, checkpoints). See the SIA Sentinel
|
|
82
|
+
repository, `docs/attestation-spec.md`.
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sia-verifier"
|
|
7
|
+
version = "1.3.0"
|
|
8
|
+
description = "Independent verifier for sia-attestation/1 Proof-of-Savings attestations — no trust in the auditor required"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "SIA Sentinel contributors" }]
|
|
13
|
+
keywords = ["ai", "finops", "attestation", "ed25519", "verification"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Security :: Cryptography",
|
|
20
|
+
]
|
|
21
|
+
# Единственная зависимость — криптография. Никаких сетевых вызовов,
|
|
22
|
+
# никакого SDK Sentinel: верификатор должен работать у любой третьей
|
|
23
|
+
# стороны, которая не доверяет аудитору. (rederive делает один
|
|
24
|
+
# опциональный живой запрос к публичному логу Rekor — по флагу.)
|
|
25
|
+
dependencies = ["cryptography>=41.0"]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
sia-verifier = "sia_verifier.__main__:main"
|
|
29
|
+
sia-rederive = "sia_verifier.rederive:main"
|
|
30
|
+
sia-holdout = "sia_verifier.holdout:main"
|
|
31
|
+
sia-replay = "sia_verifier.replay:main"
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/sia-sentinel/sia-sentinel"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
include = ["sia_verifier*"]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""sia-verifier — независимый верификатор аттестаций Proof-of-Savings.
|
|
2
|
+
|
|
3
|
+
Минимальный пакет (только `cryptography` + stdlib), который проверяет
|
|
4
|
+
аттестации стандарта ``sia-attestation/1`` **без доверия к аудитору**:
|
|
5
|
+
нужен только сам документ аттестации и (опционально) выгрузка журнала
|
|
6
|
+
TrustChain. Никаких сетевых вызовов, никакого SDK Sentinel.
|
|
7
|
+
|
|
8
|
+
Что проверяется:
|
|
9
|
+
|
|
10
|
+
1. **Подпись квитанции** — Ed25519 (RFC 8032) над каноническим JSON
|
|
11
|
+
коммитмента, восстановленного из полей квитанции (включая
|
|
12
|
+
``receipt_id``). Публичный ключ берётся из ``issuer.public_key``
|
|
13
|
+
аттестации и принимается только в виде base64 raw 32 байт.
|
|
14
|
+
2. **Согласованность заявления** — ``receipt.safety_approved`` должен
|
|
15
|
+
совпадать с ``claim.savings_verified``: подпись покрывает именно
|
|
16
|
+
``safety_approved``, поэтому расхождение означает подделку заявления.
|
|
17
|
+
3. **Хеш-цепочка TrustChain** (опционально, по выгрузке журнала) —
|
|
18
|
+
каждая запись пересчитывается из ``seq``, ``prev_hash`` и содержимого;
|
|
19
|
+
удаление, вставка, перестановка или подмена записи ломают цепочку.
|
|
20
|
+
4. **Чекпоинты** — подписанные коммитменты на голову цепочки (v1) и на
|
|
21
|
+
голову цепочки + Merkle tree head (v2).
|
|
22
|
+
5. **Merkle-доказательства** (RFC 6962) — ``verify_inclusion`` проверяет
|
|
23
|
+
включение записи в tree head, ``verify_consistency`` — что дерево
|
|
24
|
+
размера N является продолжением дерева размера M.
|
|
25
|
+
|
|
26
|
+
CLI (v1.3.0)::
|
|
27
|
+
|
|
28
|
+
sia-verifier attestation.json [--chain registry.jsonl] [--checkpoint cp.json]
|
|
29
|
+
sia-rederive [--artifacts DIR] [--flow F] [--chain C] [--no-rekor]
|
|
30
|
+
sia-replay --flow F --record report1.json --replay report2.json
|
|
31
|
+
sia-holdout make|reveal|verify ...
|
|
32
|
+
|
|
33
|
+
Первая команда проверяет подпись/цепь/чекпоинт; вторая перевыводит сам
|
|
34
|
+
вердикт записи (формулы считаются локально, входы пришиты к подписи через
|
|
35
|
+
receipt.code_hash); третья исполняет задекларированный допуск
|
|
36
|
+
replay_tolerance — сравнение записи с независимым повторным прогоном
|
|
37
|
+
(Б2: односторонний допуск «к заявлению»); четвёртая — инструмент
|
|
38
|
+
внешнего аудитора holdout (п.8).
|
|
39
|
+
Все три доступны и как ``python -m sia_verifier.<module>``.
|
|
40
|
+
|
|
41
|
+
Программа и API возвращают вердикт; ненулевой код выхода при
|
|
42
|
+
невалидной аттестации позволяет встраивать проверку в CI.
|
|
43
|
+
"""
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
from .core import (
|
|
47
|
+
ATTESTATION_SPEC,
|
|
48
|
+
AttestationVerdict,
|
|
49
|
+
ChainVerdict,
|
|
50
|
+
leaf_hash,
|
|
51
|
+
resolve_receipt_key,
|
|
52
|
+
verify_attestation,
|
|
53
|
+
verify_chain,
|
|
54
|
+
verify_checkpoint,
|
|
55
|
+
verify_consistency,
|
|
56
|
+
verify_inclusion,
|
|
57
|
+
verify_key_declarations,
|
|
58
|
+
verify_receipt,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
__version__ = "1.3.0"
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
"ATTESTATION_SPEC",
|
|
65
|
+
"AttestationVerdict",
|
|
66
|
+
"ChainVerdict",
|
|
67
|
+
"leaf_hash",
|
|
68
|
+
"resolve_receipt_key",
|
|
69
|
+
"verify_attestation",
|
|
70
|
+
"verify_chain",
|
|
71
|
+
"verify_checkpoint",
|
|
72
|
+
"verify_consistency",
|
|
73
|
+
"verify_inclusion",
|
|
74
|
+
"verify_key_declarations",
|
|
75
|
+
"verify_receipt",
|
|
76
|
+
"__version__",
|
|
77
|
+
]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""CLI независимого верификатора аттестаций Proof-of-Savings.
|
|
2
|
+
|
|
3
|
+
Примеры::
|
|
4
|
+
|
|
5
|
+
# Проверить аттестацию из файла (документ GET /v1/attestations/{id})
|
|
6
|
+
python -m sia_verifier attestation.json
|
|
7
|
+
|
|
8
|
+
# Проверить аттестацию + хеш-цепочку журнала
|
|
9
|
+
python -m sia_verifier attestation.json --chain registry.jsonl
|
|
10
|
+
|
|
11
|
+
# Проверить подпись чекпоинта
|
|
12
|
+
python -m sia_verifier attestation.json --checkpoint checkpoint.json
|
|
13
|
+
|
|
14
|
+
Код выхода: 0 — аттестация валидна, 1 — невалидна, 2 — ошибка ввода.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from .core import verify_attestation, verify_chain, verify_checkpoint
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _load_json(path: Path) -> Any:
|
|
28
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _load_jsonl(path: Path) -> list[dict[str, Any]]:
|
|
32
|
+
entries = []
|
|
33
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
34
|
+
line = line.strip()
|
|
35
|
+
if line:
|
|
36
|
+
entries.append(json.loads(line))
|
|
37
|
+
return entries
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def main(argv: list[str] | None = None) -> int:
|
|
41
|
+
parser = argparse.ArgumentParser(
|
|
42
|
+
prog="sia-verifier",
|
|
43
|
+
description="Independent verifier for sia-attestation/1 Proof-of-Savings attestations.",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"attestation",
|
|
47
|
+
type=Path,
|
|
48
|
+
help="Path to the attestation document JSON (GET /v1/attestations/{id})",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--chain",
|
|
52
|
+
type=Path,
|
|
53
|
+
default=None,
|
|
54
|
+
help="Optional TrustChain ledger export (registry.jsonl) to verify the hash chain",
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--checkpoint",
|
|
58
|
+
type=Path,
|
|
59
|
+
default=None,
|
|
60
|
+
help="Optional checkpoint JSON to verify against the issuer public key",
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"--json",
|
|
64
|
+
action="store_true",
|
|
65
|
+
help="Print the verdict as JSON instead of human-readable text",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
args = parser.parse_args(argv)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
attestation = _load_json(args.attestation)
|
|
72
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
73
|
+
print(f"error: cannot read attestation file: {exc}", file=sys.stderr)
|
|
74
|
+
return 2
|
|
75
|
+
|
|
76
|
+
verdict = verify_attestation(attestation)
|
|
77
|
+
result: dict[str, Any] = {"attestation": verdict.to_dict()}
|
|
78
|
+
|
|
79
|
+
issuer_key = (attestation.get("issuer") or {}).get("public_key", "")
|
|
80
|
+
attestation_id = attestation.get("attestation_id")
|
|
81
|
+
|
|
82
|
+
if args.chain is not None:
|
|
83
|
+
try:
|
|
84
|
+
entries = _load_jsonl(args.chain)
|
|
85
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
86
|
+
print(f"error: cannot read chain file: {exc}", file=sys.stderr)
|
|
87
|
+
return 2
|
|
88
|
+
|
|
89
|
+
chain_verdict = verify_chain(entries, attestation_id=attestation_id)
|
|
90
|
+
result["chain"] = chain_verdict.to_dict()
|
|
91
|
+
|
|
92
|
+
if args.checkpoint is not None:
|
|
93
|
+
try:
|
|
94
|
+
checkpoint = _load_json(args.checkpoint)
|
|
95
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
96
|
+
print(f"error: cannot read checkpoint file: {exc}", file=sys.stderr)
|
|
97
|
+
return 2
|
|
98
|
+
|
|
99
|
+
result["checkpoint_valid"] = verify_checkpoint(checkpoint, issuer_key)
|
|
100
|
+
|
|
101
|
+
overall = verdict.valid
|
|
102
|
+
if "chain" in result:
|
|
103
|
+
overall = overall and result["chain"]["valid"]
|
|
104
|
+
if result["chain"].get("contains_attestation") is False:
|
|
105
|
+
overall = False
|
|
106
|
+
result["chain"]["reason"] = (
|
|
107
|
+
result["chain"].get("reason")
|
|
108
|
+
or "attestation_id not found in the provided chain"
|
|
109
|
+
)
|
|
110
|
+
if "checkpoint_valid" in result:
|
|
111
|
+
overall = overall and result["checkpoint_valid"]
|
|
112
|
+
|
|
113
|
+
if args.json:
|
|
114
|
+
result["valid"] = overall
|
|
115
|
+
print(json.dumps(result, indent=2))
|
|
116
|
+
else:
|
|
117
|
+
print(f"spec: {attestation.get('spec')}")
|
|
118
|
+
print(f"attestation_id: {attestation_id}")
|
|
119
|
+
print(f"receipt signature: {'VALID' if verdict.receipt_signature_valid else 'INVALID'}")
|
|
120
|
+
print(f"claim consistent: {'yes' if verdict.claim_consistent else 'NO'}")
|
|
121
|
+
if "chain" in result:
|
|
122
|
+
chain = result["chain"]
|
|
123
|
+
print(f"chain: {'VALID' if chain['valid'] else 'INVALID'} ({chain['entries']} entries)")
|
|
124
|
+
if chain.get("contains_attestation") is False:
|
|
125
|
+
print("chain membership: attestation NOT FOUND in chain")
|
|
126
|
+
if "checkpoint_valid" in result:
|
|
127
|
+
print(f"checkpoint signature: {'VALID' if result['checkpoint_valid'] else 'INVALID'}")
|
|
128
|
+
for reason in verdict.reasons:
|
|
129
|
+
print(f" - {reason}")
|
|
130
|
+
print(f"VERDICT: {'VALID' if overall else 'INVALID'}")
|
|
131
|
+
|
|
132
|
+
return 0 if overall else 1
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
raise SystemExit(main())
|