twzrd-receipt-verifier 1.0.2 → 1.0.4

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
package/package.json CHANGED
@@ -1,14 +1,9 @@
1
1
  {
2
2
  "name": "twzrd-receipt-verifier",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/twzrd-sol/wzrd-final.git",
10
- "directory": "packages/twzrd-agent-intel/verifier"
11
- },
12
7
  "license": "MIT",
13
8
  "author": "TWZRD",
14
9
  "bin": {
@@ -69,17 +69,35 @@ function recomputeLeaf(pre) {
69
69
  }
70
70
 
71
71
  function fetchPublishedPubkey(baseUrl) {
72
- const url = baseUrl.replace(/\/+$/, '') + '/.well-known/x402';
73
- return new Promise((resolve, reject) => {
74
- https.get(url, { headers: { 'User-Agent': 'twzrd-receipt-verifier/1.0' } }, (res) => {
75
- let body = '';
76
- res.on('data', (c) => (body += c));
77
- res.on('end', () => {
78
- try { resolve(JSON.parse(body).receipt.signature.public_key); }
79
- catch (e) { reject(e); }
80
- });
81
- }).on('error', reject);
82
- });
72
+ const base = baseUrl.replace(/\/+$/, '');
73
+ const paths = [
74
+ '/.well-known/twzrd-receipt-pubkey',
75
+ '/v1/intel/pubkey',
76
+ '/.well-known/x402',
77
+ ];
78
+ const headers = { 'User-Agent': 'twzrd-receipt-verifier/1.0' };
79
+
80
+ function fetchPath(i) {
81
+ if (i >= paths.length) return Promise.reject(new Error('no pubkey endpoint responded'));
82
+ const path = paths[i];
83
+ return new Promise((resolve, reject) => {
84
+ https.get(base + path, { headers }, (res) => {
85
+ let body = '';
86
+ res.on('data', (c) => (body += c));
87
+ res.on('end', () => {
88
+ try {
89
+ const doc = JSON.parse(body);
90
+ resolve(path.endsWith('/x402')
91
+ ? doc.receipt.signature.public_key
92
+ : doc.public_key);
93
+ } catch (e) {
94
+ reject(e);
95
+ }
96
+ });
97
+ }).on('error', (err) => fetchPath(i + 1).then(resolve, reject));
98
+ });
99
+ }
100
+ return fetchPath(0);
83
101
  }
84
102
 
85
103
  function verify(receipt, trustedPubkey) {
@@ -125,12 +143,15 @@ Verifies, with NO trust in TWZRD's servers or code, that a receipt was authored
125
143
  TWZRD's published Ed25519 key and was not tampered with.
126
144
 
127
145
  usage:
128
- 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]
129
147
 
130
148
  arguments:
131
149
  <receipt.json> path to the receipt JSON, or "-" to read from stdin
132
150
  --pubkey KEY trust this base58 Ed25519 pubkey (out-of-band) instead of fetching it
133
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.
134
155
  --self-test additionally confirm a tampered copy FAILS (proves the check works)
135
156
  -h, --help show this help
136
157
 
@@ -138,7 +159,7 @@ exit code: 0 = VALID, 1 = INVALID / error
138
159
  key source: ${DEFAULT_BASE_URL}/.well-known/x402`;
139
160
  if (args.includes('-h') || args.includes('--help')) { console.log(HELP); process.exit(0); }
140
161
  if (args.length === 0) {
141
- 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]');
142
163
  console.error(' twzrd-receipt-verifier --help');
143
164
  process.exit(1);
144
165
  }
@@ -146,6 +167,8 @@ key source: ${DEFAULT_BASE_URL}/.well-known/x402`;
146
167
  const getOpt = (name) => { const i = args.indexOf(name); return i >= 0 ? args[i + 1] : undefined; };
147
168
  const selfTest = args.includes('--self-test');
148
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)
149
172
 
150
173
  // keccak self-test: refuse to run with a broken hash backend
151
174
  if (keccak256('') !== KECCAK_EMPTY) { console.error('FATAL: keccak256 backend is wrong'); process.exit(1); }
@@ -159,6 +182,25 @@ key source: ${DEFAULT_BASE_URL}/.well-known/x402`;
159
182
  console.log(`trusted pubkey: ${trusted} [source: ${src}]`);
160
183
 
161
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
+
162
204
  console.log(`leaf_valid : ${res.leaf_valid}`);
163
205
  console.log(`signature_valid : ${res.signature_valid}`);
164
206
  res.errors.forEach((e) => console.log(' - ' + e));