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 +3 -0
- package/package.json +1 -6
- package/verify_twzrd_receipt.js +55 -13
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.
|
|
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": {
|
package/verify_twzrd_receipt.js
CHANGED
|
@@ -69,17 +69,35 @@ function recomputeLeaf(pre) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
function fetchPublishedPubkey(baseUrl) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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));
|