agent-receipt-verify 0.2.3 → 0.2.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 +38 -3
- package/bin/cli.mjs +41 -24
- package/package.json +5 -3
- package/src/acceptance-binding.mjs +309 -0
- package/src/settlement-link-vectors.mjs +124 -11
- package/src/verify-delivery.mjs +507 -0
- package/src/x402-conformance-check.mjs +41 -5
- package/src/x402-discovery-census.mjs +24 -1
- package/src/x402-offer-receipt.mjs +49 -3
- package/tools/naar-kanon-vorm.mjs +172 -0
- package/tools/pack-inhoud.mjs +45 -1
- package/vectors/acceptance-binding/crossed-deliverable.json +40 -0
- package/vectors/acceptance-binding/pairs-same-deliverable.json +39 -0
- package/vectors/acceptance-binding/real-record-as-issued.json +68 -0
- package/vectors/acceptance-binding/real-record-crossed-deliverable.json +65 -0
- package/vectors/acceptance-binding/real-record-crossed-x402-keys.json +74 -0
- package/vectors/acceptance-binding/real-record-with-producer-key.json +66 -0
- package/vectors/acceptance-binding/real-record-x402-keys-match.json +76 -0
- package/vectors/settlement-link-kanon/README.md +69 -0
- package/vectors/settlement-link-kanon/find-candidates-onchain.json +43 -0
- package/vectors/settlement-link-kanon/no-candidates-not-measured.json +42 -0
- package/vectors/settlement-link-kanon/opens-live-2.json +47 -0
- package/vectors/settlement-link-kanon/opens-live-3.json +47 -0
- package/vectors/settlement-link-kanon/opens-live.json +47 -0
- package/vectors/settlement-link-kanon/two-identical-payments.json +76 -0
- package/vectors/settlement-link-kanon/wrong-nonce-live-2.json +46 -0
- package/vectors/settlement-link-kanon/wrong-nonce-live-3.json +46 -0
- package/vectors/settlement-link-kanon/wrong-nonce-live.json +46 -0
- package/vectors/settlement-link-kanon/wrong-salt-live-2.json +46 -0
- package/vectors/settlement-link-kanon/wrong-salt-live-3.json +46 -0
- package/vectors/settlement-link-kanon/wrong-salt-live.json +46 -0
|
@@ -37,24 +37,114 @@ export const VECTORMAP = path.join(WORTEL, 'vectors', 'settlement-link')
|
|
|
37
37
|
* Geeft altijd een `outcome` terug uit de drie, nooit een throw voor een gewone
|
|
38
38
|
* uitkomst: een verifieerder die bij een ongeldig bonnetje crasht, is zelf de storing.
|
|
39
39
|
*/
|
|
40
|
+
/**
|
|
41
|
+
* Matcht deze kandidaat het commitment? Ja of nee, en nooit iets daartussenin.
|
|
42
|
+
*
|
|
43
|
+
* WAAROM DIT EEN BOOLEAN IS EN GEEN WAARDE. De eerste versie van deze reparatie gaf bij
|
|
44
|
+
* een onberekenbaar veld de TEKENREEKS `0x-niet-te-berekenen` terug, en die werd daarna
|
|
45
|
+
* vergeleken met het commitment uit het bonnetje. Een foutwaarde die in dezelfde ruimte
|
|
46
|
+
* leeft als de echte waarde is geen foutwaarde maar een sleutel: een bonnetje dat
|
|
47
|
+
* precies die tekst als commitment droeg, met een kandidaat die niet te berekenen was,
|
|
48
|
+
* kwam er als GELINKT uit zonder dat er een byte cryptografie aan te pas kwam. Onze
|
|
49
|
+
* eigen keuring heeft dat gedraaid, met en zonder hoofdletters, op beide takken van
|
|
50
|
+
* beoordeelVector. Dat is precies de klasse fout die dit bestand moet uitsluiten, en hij
|
|
51
|
+
* zat er een uur in.
|
|
52
|
+
*
|
|
53
|
+
* `null` teruggeven lost het NIET op, en dat is de reden dat hier een boolean staat:
|
|
54
|
+
* `String(null)` is de tekenreeks "null", dus een bonnetje met commitment "null" zou
|
|
55
|
+
* het gat meteen heropenen. De enige vorm die dicht is, is er niets uit laten komen dat
|
|
56
|
+
* ooit vergeleken wordt. De vergelijking zelf hoort daarom HIER en nergens anders.
|
|
57
|
+
*
|
|
58
|
+
* En de oorspronkelijke reden blijft staan: een kapot veld mag de ronde niet omver
|
|
59
|
+
* trekken. Een onberekenbare kandidaat is geen treffer. Dat is hier de enige betekenis.
|
|
60
|
+
*/
|
|
61
|
+
function kandidaatMatcht(velden, commitment) {
|
|
62
|
+
if (typeof commitment !== 'string' || !commitment) return false
|
|
63
|
+
let berekend
|
|
64
|
+
try {
|
|
65
|
+
berekend = berekenCommitment(velden)
|
|
66
|
+
} catch {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
// berekenCommitment geeft altijd een 0x-sha256 terug of gooit. Deze controle staat er
|
|
70
|
+
// zodat een toekomstige wijziging daar nooit stil iets anders dan een hash kan laten
|
|
71
|
+
// doorstromen naar een gelijkheidsvergelijking.
|
|
72
|
+
if (typeof berekend !== 'string' || !/^0x[0-9a-f]{64}$/i.test(berekend)) return false
|
|
73
|
+
return berekend.toLowerCase() === commitment.toLowerCase()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Is dit een kandidaat waar uberhaupt iets mee te doen valt?
|
|
78
|
+
*
|
|
79
|
+
* Zonder deze zeef overleefde een `null` in de kandidatenlijst de filter (want
|
|
80
|
+
* `k?.authorizer` is dan gewoon undefined), en als zo'n kandidaat dan matchte, knalde
|
|
81
|
+
* `passend[0].nonce` met een TypeError. De koptekst van dit bestand belooft letterlijk
|
|
82
|
+
* dat deze loper niet gooit voor een gewone uitkomst. Gemeten in de eigen keuring.
|
|
83
|
+
*/
|
|
84
|
+
function bruikbareKandidaat(k) {
|
|
85
|
+
return Boolean(k) && typeof k === 'object' && typeof k.nonce === 'string' && typeof k.authorizer === 'string'
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Kunnen wij met ONZE eigen vaste velden uberhaupt een commitment uitrekenen?
|
|
90
|
+
*
|
|
91
|
+
* WAAROM DIT APART STAAT VAN DE KANDIDATEN. Een kapotte KANDIDAAT is geen treffer, en
|
|
92
|
+
* dat is de juiste uitkomst. Een kapot EIGEN veld (een zout of een asset die onze RPC
|
|
93
|
+
* verkeerd teruggaf) laat elke kandidaat falen, en dan rolt er een stellige "geen enkele
|
|
94
|
+
* kandidaat opende dit commitment" uit terwijl de waarheid is dat er niets te berekenen
|
|
95
|
+
* viel. De koptekst van dit bestand noemt dat zelf de ergste fout die deze loper kan
|
|
96
|
+
* maken: een geldig bonnetje weggooien bij een haperende RPC.
|
|
97
|
+
*
|
|
98
|
+
* Deze controle zat al in opentCommitment, en niet hier. Dezelfde commit repareerde de
|
|
99
|
+
* ene functie en liet de andere staan, en de eigen keuring heeft dat gemeten.
|
|
100
|
+
*/
|
|
101
|
+
function eigenVeldenMeetbaar({ method, asset, salt }) {
|
|
102
|
+
try {
|
|
103
|
+
berekenCommitment({ method, asset, authorizer: '0x' + '00'.repeat(20), identifier: '0x' + '00'.repeat(32), salt })
|
|
104
|
+
return { ok: true }
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return { ok: false, reden: 'cannot compute a commitment here: ' + String(e?.message ?? e).slice(0, 90) }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
40
110
|
export function beoordeelVector(v) {
|
|
41
111
|
if (v.requires_chain_read) return { outcome: 'SKIPPED', reden: 'requires an RPC; run the link command for this one' }
|
|
42
112
|
|
|
43
113
|
// Het geval met twee bonnetjes tegelijk: elk moet op zijn eigen kandidaat openen en
|
|
44
114
|
// op geen enkele andere.
|
|
45
115
|
if (Array.isArray(v.input?.receipts)) {
|
|
46
|
-
|
|
116
|
+
// GEEN `?? []` HIER. Dat stond er en het maakte van een ONTBREKENDE lijst een LEGE
|
|
117
|
+
// lijst, dus van NOT_MEASURED een NOT_LINKED. De koptekst van dit bestand noemt dat
|
|
118
|
+
// zelf de ergste fout die deze loper kan maken, want dan beschuldigt hij een
|
|
119
|
+
// eerlijke verkoper omdat de keten even niet te lezen was.
|
|
120
|
+
if (v.input.candidates == null) {
|
|
121
|
+
return { outcome: 'NOT_MEASURED', reden: 'no candidate set was obtained' }
|
|
122
|
+
}
|
|
123
|
+
if (!Array.isArray(v.input.candidates)) {
|
|
124
|
+
return { outcome: 'NOT_MEASURED', reden: 'the candidate set is not a list' }
|
|
125
|
+
}
|
|
126
|
+
const kandidaten = v.input.candidates
|
|
47
127
|
const mapping = []
|
|
48
128
|
for (const [i, r] of v.input.receipts.entries()) {
|
|
49
|
-
const p = r
|
|
50
|
-
|
|
51
|
-
|
|
129
|
+
const p = r?.receipt?.payload
|
|
130
|
+
if (!p) return { outcome: 'NOT_MEASURED', reden: `receipt ${i} has no payload` }
|
|
131
|
+
if (!p.authorizationCommitment) {
|
|
132
|
+
return { outcome: 'NOT_MEASURED', reden: `receipt ${i} carries no commitment` }
|
|
133
|
+
}
|
|
134
|
+
const meetbaar = eigenVeldenMeetbaar({
|
|
135
|
+
method: p.authorizationMethod ?? 'eip3009',
|
|
136
|
+
asset: r.asset,
|
|
137
|
+
salt: r.authorizationSalt,
|
|
138
|
+
})
|
|
139
|
+
if (!meetbaar.ok) return { outcome: 'NOT_MEASURED', reden: `receipt ${i}: ` + meetbaar.reden }
|
|
140
|
+
const passend = kandidaten.filter(bruikbareKandidaat).filter((k) =>
|
|
141
|
+
kandidaatMatcht({
|
|
52
142
|
method: p.authorizationMethod ?? 'eip3009',
|
|
53
143
|
asset: r.asset,
|
|
54
144
|
authorizer: k.authorizer,
|
|
55
145
|
identifier: k.nonce,
|
|
56
146
|
salt: r.authorizationSalt,
|
|
57
|
-
}
|
|
147
|
+
}, p.authorizationCommitment))
|
|
58
148
|
if (passend.length !== 1) {
|
|
59
149
|
return { outcome: 'NOT_LINKED', reden: `receipt ${i} opened on ${passend.length} candidates, expected exactly one` }
|
|
60
150
|
}
|
|
@@ -73,15 +163,25 @@ export function beoordeelVector(v) {
|
|
|
73
163
|
|
|
74
164
|
const kandidaten = v.input.candidates
|
|
75
165
|
if (kandidaten == null) return { outcome: 'NOT_MEASURED', reden: 'no candidate set was obtained' }
|
|
166
|
+
// Een lijst die geen lijst is, is ook geen meting. Zonder deze regel gooide .filter
|
|
167
|
+
// en ging de hele ronde eraan.
|
|
168
|
+
if (!Array.isArray(kandidaten)) return { outcome: 'NOT_MEASURED', reden: 'the candidate set is not a list' }
|
|
169
|
+
|
|
170
|
+
const meetbaar = eigenVeldenMeetbaar({
|
|
171
|
+
method: p.authorizationMethod ?? 'eip3009',
|
|
172
|
+
asset: v.input.asset,
|
|
173
|
+
salt: v.input.authorizationSalt,
|
|
174
|
+
})
|
|
175
|
+
if (!meetbaar.ok) return { outcome: 'NOT_MEASURED', reden: meetbaar.reden }
|
|
76
176
|
|
|
77
|
-
const passend = kandidaten.filter((k) =>
|
|
78
|
-
|
|
177
|
+
const passend = kandidaten.filter(bruikbareKandidaat).filter((k) =>
|
|
178
|
+
kandidaatMatcht({
|
|
79
179
|
method: p.authorizationMethod ?? 'eip3009',
|
|
80
180
|
asset: v.input.asset,
|
|
81
181
|
authorizer: k.authorizer,
|
|
82
182
|
identifier: k.nonce,
|
|
83
183
|
salt: v.input.authorizationSalt,
|
|
84
|
-
}
|
|
184
|
+
}, p.authorizationCommitment))
|
|
85
185
|
|
|
86
186
|
if (passend.length === 1) return { outcome: 'LINKED', identifier: passend[0].nonce }
|
|
87
187
|
if (passend.length === 0) return { outcome: 'NOT_LINKED', reden: `none of ${kandidaten.length} candidates opened the commitment` }
|
|
@@ -112,6 +212,15 @@ if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(ditBestand
|
|
|
112
212
|
console.log(` ${u.reden}`)
|
|
113
213
|
continue
|
|
114
214
|
}
|
|
215
|
+
// Een vector zonder verwachting is een kapotte vector, geen reden om de hele
|
|
216
|
+
// ronde af te breken. Zonder deze regel gooide dit een TypeError en werd geen
|
|
217
|
+
// enkele andere vector meer beoordeeld, ook de twaalf goede niet.
|
|
218
|
+
if (!v.expected || typeof v.expected.outcome !== 'string') {
|
|
219
|
+
fout++
|
|
220
|
+
console.log(` FAIL ${v.id ?? v.bestand}`)
|
|
221
|
+
console.log(' this vector carries no expected outcome, so it cannot be judged')
|
|
222
|
+
continue
|
|
223
|
+
}
|
|
115
224
|
const ok = u.outcome === v.expected.outcome
|
|
116
225
|
if (ok) goed++; else fout++
|
|
117
226
|
console.log(` ${ok ? 'pass' : 'FAIL'} ${v.id}`)
|
|
@@ -120,9 +229,13 @@ if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(ditBestand
|
|
|
120
229
|
console.log('')
|
|
121
230
|
console.log(`${goed} agree, ${fout} disagree, ${overgeslagen} skipped because they need an RPC`)
|
|
122
231
|
console.log('')
|
|
123
|
-
console.log('Every positive case here was re-derived
|
|
124
|
-
console.log('
|
|
125
|
-
console.log('
|
|
232
|
+
console.log('Every positive case here was re-derived, not copied: the commitment is recomputed')
|
|
233
|
+
console.log('from tag, method, asset, authorizer, identifier and salt, and compared against the')
|
|
234
|
+
console.log('authorizationCommitment field carried in the receipt.')
|
|
235
|
+
console.log('')
|
|
236
|
+
console.log('What this command does NOT do: it does not check the signature over that receipt.')
|
|
237
|
+
console.log('So this answers "does this commitment open on that settlement", not "is this')
|
|
238
|
+
console.log('receipt genuine". For the signature, run the conformance check on the endpoint.')
|
|
126
239
|
process.exitCode = fout === 0 ? 0 : 1
|
|
127
240
|
}
|
|
128
241
|
}
|