twzrd-receipt-verifier 1.0.6 → 1.0.7

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
@@ -1,7 +1,7 @@
1
1
  # TWZRD Receipt Verifier (standalone)
2
2
 
3
- Verify a **signed TWZRD trust receipt** offline, trusting **nothing from TWZRD's
4
- servers or codebase** - only the receipt, TWZRD's published public key, and two
3
+ Verify a TWZRD **AO-Receipt V5** offline, trusting **nothing from TWZRD's servers
4
+ or codebase** - only the receipt, TWZRD's published public key, and two
5
5
  widely-audited crypto libraries.
6
6
 
7
7
  A TWZRD trust receipt has two layers:
@@ -32,16 +32,6 @@ npx twzrd-receipt-verifier receipt.json --pubkey 9V6Pn19kiUA5Rn6JpQfNduanvGt2aXG
32
32
  npx twzrd-receipt-verifier receipt.json --pubkey 9V6Pn19kiUA5Rn6JpQfNduanvGt2aXGwsarosNfa2Ldf --max-age 60
33
33
  ```
34
34
 
35
- ## Ingest the corpus
36
-
37
- The receipts this tool verifies are issued from the only live cross-facilitator x402
38
- payer corpus on Solana. Pull the scored signal population as a feed (no auth, paginated):
39
-
40
- curl -s 'https://intel.twzrd.xyz/v1/intel/corpus_feed?limit=5' | jq .
41
-
42
- Browser demo: https://twzrd.xyz/demo/ingest/ · schema: https://intel.twzrd.xyz/docs ·
43
- ingestion pilots: https://twzrd.xyz/grants (email privacy@twzrd.xyz).
44
-
45
35
  ## The published signing key
46
36
 
47
37
  | field | value |
@@ -60,8 +50,8 @@ Also published, machine-readable, at:
60
50
 
61
51
  ## Get a receipt to verify
62
52
 
63
- Any signed TWZRD trust receipt works. To mint a fresh one, pay the trust endpoint
64
- (x402, 0.05 USDC on Solana mainnet) - e.g. via AgentCash:
53
+ Any TWZRD V5 receipt works. To mint a fresh one, pay the trust endpoint (x402,
54
+ 0.05 USDC on Solana mainnet) - e.g. via AgentCash:
65
55
 
66
56
  ```bash
67
57
  npx agentcash@latest fetch https://intel.twzrd.xyz/v1/intel/trust/<PUBKEY> > resp.json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twzrd-receipt-verifier",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
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": [
6
6
  "twzrd",
@@ -33,8 +33,7 @@
33
33
  "LICENSE"
34
34
  ],
35
35
  "scripts": {
36
- "verify": "node verify_twzrd_receipt.js",
37
- "test": "node --test test/*.test.js"
36
+ "verify": "node verify_twzrd_receipt.js"
38
37
  },
39
38
  "engines": {
40
39
  "node": ">=16"
@@ -37,6 +37,29 @@ function b58decode(s) { return Buffer.from(bs58.decode(s)); }
37
37
 
38
38
  function u16le(n) { const b = Buffer.alloc(2); b.writeUInt16LE(n & 0xffff, 0); return b; }
39
39
  function u64le(n) { const b = Buffer.alloc(8); b.writeBigUInt64LE(BigInt(n), 0); return b; }
40
+ function i64le(n) { const b = Buffer.alloc(8); b.writeBigInt64LE(BigInt(n), 0); return b; }
41
+
42
+ // V6 reputation block: 1-byte presence flag (0x00 null / 0x01 present) + fixed-width
43
+ // value when present. reputation_score is i64 LE (null-vs-0 safe); version/quality
44
+ // are u16-len-prefixed UTF-8; feature_window is u64 LE. "" is present (distinct from
45
+ // null). Mirrors the issuer's RECEIPT_V6_LEAF_SPEC.md byte layout exactly.
46
+ function encodeReputationBlockV6(pre) {
47
+ const optInt = (v, enc) => (v === null || v === undefined)
48
+ ? Buffer.from([0x00])
49
+ : Buffer.concat([Buffer.from([0x01]), enc(v)]);
50
+ const optStr = (v) => {
51
+ if (v === null || v === undefined) return Buffer.from([0x00]);
52
+ const raw = Buffer.from(String(v), 'utf8');
53
+ return Buffer.concat([Buffer.from([0x01]), u16le(raw.length), raw]);
54
+ };
55
+ return Buffer.concat([
56
+ optInt(pre.reputation_score, i64le),
57
+ optInt(pre.reputation_confidence_bps, u16le),
58
+ optStr(pre.reputation_score_version),
59
+ optInt(pre.reputation_feature_window_start_unix, u64le),
60
+ optStr(pre.reputation_data_quality),
61
+ ]);
62
+ }
40
63
 
41
64
  function payer32(payer) {
42
65
  try { const raw = b58decode(payer); if (raw.length === 32) return raw; } catch (_) {}
@@ -51,12 +74,19 @@ function anchor32(tx) {
51
74
  }
52
75
 
53
76
  function recomputeLeaf(pre) {
77
+ // Use the exact domain the receipt carries. V6 binds reputation_* into the leaf
78
+ // (V5 left them unsigned/forgeable); a V6 receipt verified with V5 rules would
79
+ // fail on a legitimate receipt, so the block is appended whenever domain is _V6.
54
80
  const dom = String(pre.domain || '').toUpperCase();
81
+ const isV6 = dom.includes('_V6');
55
82
  const isAttention = dom.includes('ATTENTION');
56
- const domain = Buffer.from(isAttention ? 'TWZRD:AO_ATTENTION_RECEIPT_V5' : 'TWZRD:AO_REPUTATION_RECEIPT_V5', 'ascii');
83
+ const domainStr = isAttention
84
+ ? (isV6 ? 'TWZRD:AO_ATTENTION_RECEIPT_V6' : 'TWZRD:AO_ATTENTION_RECEIPT_V5')
85
+ : (isV6 ? 'TWZRD:AO_REPUTATION_RECEIPT_V6' : 'TWZRD:AO_REPUTATION_RECEIPT_V5');
86
+ const domain = Buffer.from(domainStr, 'ascii');
57
87
  const score = isAttention ? (pre.attention_score || 0) : (pre.score || 0);
58
88
  const agent = Buffer.from(pre.agent_id, 'utf8');
59
- const msg = Buffer.concat([
89
+ const parts = [
60
90
  domain,
61
91
  u16le(agent.length), agent,
62
92
  u16le(score),
@@ -64,8 +94,9 @@ function recomputeLeaf(pre) {
64
94
  u64le(pre.timestamp_unix),
65
95
  payer32(pre.payer),
66
96
  anchor32(pre.settlement_tx || pre.settlement_anchor),
67
- ]);
68
- return Buffer.from(keccak256.arrayBuffer(msg));
97
+ ];
98
+ if (isV6) parts.push(encodeReputationBlockV6(pre));
99
+ return Buffer.from(keccak256.arrayBuffer(Buffer.concat(parts)));
69
100
  }
70
101
 
71
102
  function fetchPublishedPubkey(baseUrl) {
@@ -220,9 +251,4 @@ key source: ${DEFAULT_BASE_URL}/.well-known/x402`;
220
251
  process.exit(ok ? 0 : 1);
221
252
  }
222
253
 
223
- // Run the CLI only when invoked directly; stay importable for tests.
224
- if (require.main === module) {
225
- main().catch((e) => { console.error('error:', e.message); process.exit(1); });
226
- }
227
-
228
- module.exports = { verify, recomputeLeaf, b58decode, payer32, anchor32 };
254
+ main().catch((e) => { console.error('error:', e.message); process.exit(1); });