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 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 pynacl pycryptodome # libsodium Ed25519 + original Keccak-256
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
- python verify_twzrd_receipt.py receipt.json
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
- python verify_twzrd_receipt.py receipt.json --self-test
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 | python verify_twzrd_receipt.py -
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",
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": {
@@ -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));