twzrd-receipt-verifier 1.0.3 → 1.0.5
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.
- package/README.md +13 -4
- package/package.json +9 -1
- package/verify_twzrd_receipt.js +26 -2
package/README.md
CHANGED
|
@@ -27,6 +27,9 @@ after it spends:
|
|
|
27
27
|
```bash
|
|
28
28
|
# zero-install: verify a receipt straight from the published package
|
|
29
29
|
npx twzrd-receipt-verifier receipt.json --pubkey 9V6Pn19kiUA5Rn6JpQfNduanvGt2aXGwsarosNfa2Ldf
|
|
30
|
+
|
|
31
|
+
# replay-resistance (opt-in): reject receipts older than 60s — and reject any with no timestamp
|
|
32
|
+
npx twzrd-receipt-verifier receipt.json --pubkey 9V6Pn19kiUA5Rn6JpQfNduanvGt2aXGwsarosNfa2Ldf --max-age 60
|
|
30
33
|
```
|
|
31
34
|
|
|
32
35
|
## The published signing key
|
|
@@ -72,21 +75,27 @@ The receipt object looks like:
|
|
|
72
75
|
## Python
|
|
73
76
|
|
|
74
77
|
```bash
|
|
75
|
-
pip install
|
|
78
|
+
pip install twzrd-receipt-verifier # PyPI; or: pip install pynacl pycryptodome for script-only use
|
|
76
79
|
|
|
77
80
|
# fetch the published key and verify:
|
|
78
|
-
|
|
81
|
+
twzrd-verify-receipt receipt.json
|
|
82
|
+
# or: python verify_twzrd_receipt.py receipt.json
|
|
79
83
|
|
|
80
84
|
# pin the key out-of-band (recommended):
|
|
81
85
|
python verify_twzrd_receipt.py receipt.json --pubkey 9V6Pn19kiUA5Rn6JpQfNduanvGt2aXGwsarosNfa2Ldf
|
|
82
86
|
|
|
83
87
|
# also confirm a tampered copy FAILS:
|
|
84
|
-
|
|
88
|
+
twzrd-verify-receipt receipt.json --self-test
|
|
89
|
+
|
|
90
|
+
# replay-resistance (opt-in; same semantics as the npm CLI --max-age):
|
|
91
|
+
twzrd-verify-receipt receipt.json --max-age 300
|
|
85
92
|
|
|
86
93
|
# from stdin:
|
|
87
|
-
cat receipt.json |
|
|
94
|
+
cat receipt.json | twzrd-verify-receipt -
|
|
88
95
|
```
|
|
89
96
|
|
|
97
|
+
Source: [packages/twzrd-agent-intel/verifier](https://github.com/twzrd-sol/wzrd-final/tree/main/packages/twzrd-agent-intel/verifier)
|
|
98
|
+
|
|
90
99
|
## Node
|
|
91
100
|
|
|
92
101
|
```bash
|
package/package.json
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "twzrd-receipt-verifier",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Standalone offline verifier for TWZRD AO-Receipt V5 (Ed25519-signed keccak256 leaf). No trust in TWZRD servers or code.",
|
|
5
5
|
"keywords": ["twzrd", "x402", "solana", "ed25519", "keccak256", "receipt", "verifier", "agent", "attestation"],
|
|
6
6
|
"homepage": "https://intel.twzrd.xyz",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/twzrd-sol/wzrd-final/issues"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/twzrd-sol/wzrd-final.git",
|
|
13
|
+
"directory": "packages/twzrd-agent-intel/verifier"
|
|
14
|
+
},
|
|
7
15
|
"license": "MIT",
|
|
8
16
|
"author": "TWZRD",
|
|
9
17
|
"bin": {
|
package/verify_twzrd_receipt.js
CHANGED
|
@@ -143,12 +143,15 @@ Verifies, with NO trust in TWZRD's servers or code, that a receipt was authored
|
|
|
143
143
|
TWZRD's published Ed25519 key and was not tampered with.
|
|
144
144
|
|
|
145
145
|
usage:
|
|
146
|
-
twzrd-receipt-verifier <receipt.json|-> [--pubkey KEY] [--base-url URL] [--self-test]
|
|
146
|
+
twzrd-receipt-verifier <receipt.json|-> [--pubkey KEY] [--base-url URL] [--max-age SECS] [--self-test]
|
|
147
147
|
|
|
148
148
|
arguments:
|
|
149
149
|
<receipt.json> path to the receipt JSON, or "-" to read from stdin
|
|
150
150
|
--pubkey KEY trust this base58 Ed25519 pubkey (out-of-band) instead of fetching it
|
|
151
151
|
--base-url URL where to fetch the published key (default: ${DEFAULT_BASE_URL})
|
|
152
|
+
--max-age SECS replay-resistance policy: reject if preimage.timestamp_unix is older than
|
|
153
|
+
SECS, OR if the receipt carries no valid timestamp. Crypto (leaf+sig) is
|
|
154
|
+
time-independent; this is opt-in relying-party policy.
|
|
152
155
|
--self-test additionally confirm a tampered copy FAILS (proves the check works)
|
|
153
156
|
-h, --help show this help
|
|
154
157
|
|
|
@@ -156,7 +159,7 @@ exit code: 0 = VALID, 1 = INVALID / error
|
|
|
156
159
|
key source: ${DEFAULT_BASE_URL}/.well-known/x402`;
|
|
157
160
|
if (args.includes('-h') || args.includes('--help')) { console.log(HELP); process.exit(0); }
|
|
158
161
|
if (args.length === 0) {
|
|
159
|
-
console.error('usage: twzrd-receipt-verifier <receipt.json|-> [--pubkey KEY] [--base-url URL] [--self-test]');
|
|
162
|
+
console.error('usage: twzrd-receipt-verifier <receipt.json|-> [--pubkey KEY] [--base-url URL] [--max-age SECS] [--self-test]');
|
|
160
163
|
console.error(' twzrd-receipt-verifier --help');
|
|
161
164
|
process.exit(1);
|
|
162
165
|
}
|
|
@@ -164,6 +167,8 @@ key source: ${DEFAULT_BASE_URL}/.well-known/x402`;
|
|
|
164
167
|
const getOpt = (name) => { const i = args.indexOf(name); return i >= 0 ? args[i + 1] : undefined; };
|
|
165
168
|
const selfTest = args.includes('--self-test');
|
|
166
169
|
const baseUrl = getOpt('--base-url') || DEFAULT_BASE_URL;
|
|
170
|
+
const maxAgeArg = getOpt('--max-age');
|
|
171
|
+
const maxAge = maxAgeArg ? parseInt(maxAgeArg, 10) : 0; // 0 = freshness check off (opt-in policy)
|
|
167
172
|
|
|
168
173
|
// keccak self-test: refuse to run with a broken hash backend
|
|
169
174
|
if (keccak256('') !== KECCAK_EMPTY) { console.error('FATAL: keccak256 backend is wrong'); process.exit(1); }
|
|
@@ -177,6 +182,25 @@ key source: ${DEFAULT_BASE_URL}/.well-known/x402`;
|
|
|
177
182
|
console.log(`trusted pubkey: ${trusted} [source: ${src}]`);
|
|
178
183
|
|
|
179
184
|
const res = verify(receipt, trusted);
|
|
185
|
+
|
|
186
|
+
// Opt-in replay-resistance freshness gate. Crypto (leaf+sig) is time-independent; this is
|
|
187
|
+
// relying-party policy. A receipt with missing/zero timestamp_unix is REJECTED when
|
|
188
|
+
// --max-age is set (no silent bypass) — mirrors the Python verifier (#720).
|
|
189
|
+
if (maxAge > 0) {
|
|
190
|
+
res.errors = res.errors || [];
|
|
191
|
+
const ts = receipt && receipt.preimage ? Number(receipt.preimage.timestamp_unix) : NaN;
|
|
192
|
+
if (!Number.isFinite(ts) || ts <= 0) {
|
|
193
|
+
res.errors.push(`--max-age ${maxAge}s set but receipt has no valid timestamp_unix`);
|
|
194
|
+
res.valid = false;
|
|
195
|
+
} else {
|
|
196
|
+
const age = Math.abs(Math.floor(Date.now() / 1000) - ts);
|
|
197
|
+
if (age > maxAge) {
|
|
198
|
+
res.errors.push(`receipt too old (age ${age}s > --max-age ${maxAge}s)`);
|
|
199
|
+
res.valid = false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
180
204
|
console.log(`leaf_valid : ${res.leaf_valid}`);
|
|
181
205
|
console.log(`signature_valid : ${res.signature_valid}`);
|
|
182
206
|
res.errors.forEach((e) => console.log(' - ' + e));
|