@open-charging-cloud/wwcp-poi 0.1.1 → 0.2.0
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/CHANGELOG.md +498 -454
- package/README.md +522 -362
- package/dist/history/commit.d.ts.map +1 -1
- package/dist/history/epoch.d.ts +2 -0
- package/dist/history/epoch.d.ts.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1404 -278
- package/dist/index.js.map +1 -1
- package/dist/keys.d.ts +2 -0
- package/dist/keys.d.ts.map +1 -1
- package/dist/proofs/stationProof.d.ts +2 -0
- package/dist/proofs/stationProof.d.ts.map +1 -1
- package/dist/proofs/trustContext.d.ts +6 -0
- package/dist/proofs/trustContext.d.ts.map +1 -1
- package/dist/verifier/chargingStation.d.ts +5 -0
- package/dist/verifier/chargingStation.d.ts.map +1 -1
- package/dist/verifier/documentKeys.d.ts +27 -0
- package/dist/verifier/documentKeys.d.ts.map +1 -0
- package/dist/verifier/historicalMeasurements.d.ts +96 -0
- package/dist/verifier/historicalMeasurements.d.ts.map +1 -0
- package/dist/verifier/measurementKeys.d.ts +48 -0
- package/dist/verifier/measurementKeys.d.ts.map +1 -0
- package/dist/verifier/observedVersions.d.ts +70 -0
- package/dist/verifier/observedVersions.d.ts.map +1 -0
- package/dist/verifier/pricePublication.d.ts +74 -0
- package/dist/verifier/pricePublication.d.ts.map +1 -0
- package/dist/verifier/sessionPrices.d.ts +75 -0
- package/dist/verifier/sessionPrices.d.ts.map +1 -0
- package/dist/verifier/trustTransitions.d.ts +67 -0
- package/dist/verifier/trustTransitions.d.ts.map +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,362 +1,522 @@
|
|
|
1
|
-
# WWCP POI for TypeScript
|
|
2
|
-
|
|
3
|
-
[](https://github.com/OpenChargingCloud/WWCP_POI.TS/actions/workflows/ci.yml)
|
|
4
|
-
[](https://github.com/OpenChargingCloud/WWCP_POI.TS/actions/workflows/nightly.yml)
|
|
5
|
-
[](https://www.npmjs.com/package/@open-charging-cloud/wwcp-poi)
|
|
6
|
-
|
|
7
|
-
Verifies what a roaming network of [WWCP/POI](https://github.com/OpenChargingCloud/WWCP_POI) publishes about its
|
|
8
|
-
charging stations and their tariffs, from the **trust anchor** of the roaming network alone: the QR code its operator
|
|
9
|
-
publishes, naming the identity keys of the operator and its quorum.
|
|
10
|
-
|
|
11
|
-
An EV driver's app selects a charging station; this library downloads the proof of the station and the price statements
|
|
12
|
-
of its EVSEs, and checks that the station, its EVSEs as they are at runtime, the charging tariffs they take and the
|
|
13
|
-
prices of the moment are the ones the roaming network operator attested and the charging station operator signed - before
|
|
14
|
-
charging begins. Months later, e.g. disputing an invoice, the evidence the app kept proves which price applied, witnessed
|
|
15
|
-
by checkpoints. The same proof gives the public keys of the energy meters, with which
|
|
16
|
-
[ChargyCore.TS](https://github.com/OpenChargingCloud/ChargyCore.TS) checks the signed meter values of the session, and the
|
|
17
|
-
transparency software and its certificate that say who checks them.
|
|
18
|
-
|
|
19
|
-
Everything is calculated as the C# implementation calculates it, byte for byte: the canonical JSON and the deterministic
|
|
20
|
-
CBOR of Styx, ETags, the effective tree with templates (section 8 of the specification), commit and checkpoint
|
|
21
|
-
identifications of every epoch, COSE keys and their thumbprints, and the signatures ES256/384/512, Ed25519, Ed448 and
|
|
22
|
-
ML-DSA-44/65/87.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## Status
|
|
26
|
-
|
|
27
|
-
**
|
|
28
|
-
[
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
[
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
checked
|
|
71
|
-
|
|
72
|
-
checked.station.
|
|
73
|
-
checked.station.
|
|
74
|
-
checked.station.
|
|
75
|
-
checked.
|
|
76
|
-
checked.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
|
201
|
-
|
|
202
|
-
|
|
|
203
|
-
|
|
|
204
|
-
|
|
|
205
|
-
|
|
|
206
|
-
|
|
|
207
|
-
|
|
|
208
|
-
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
refused as
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
import
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
- `npm run test:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
the
|
|
315
|
-
|
|
316
|
-
- `tests/
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
- `tests/
|
|
322
|
-
- `tests/
|
|
323
|
-
|
|
324
|
-
- `tests/
|
|
325
|
-
|
|
326
|
-
- `tests/
|
|
327
|
-
|
|
328
|
-
- `tests/
|
|
329
|
-
- `tests/
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
1
|
+
# WWCP POI for TypeScript
|
|
2
|
+
|
|
3
|
+
[](https://github.com/OpenChargingCloud/WWCP_POI.TS/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/OpenChargingCloud/WWCP_POI.TS/actions/workflows/nightly.yml)
|
|
5
|
+
[](https://www.npmjs.com/package/@open-charging-cloud/wwcp-poi)
|
|
6
|
+
|
|
7
|
+
Verifies what a roaming network of [WWCP/POI](https://github.com/OpenChargingCloud/WWCP_POI) publishes about its
|
|
8
|
+
charging stations and their tariffs, from the **trust anchor** of the roaming network alone: the QR code its operator
|
|
9
|
+
publishes, naming the identity keys of the operator and its quorum.
|
|
10
|
+
|
|
11
|
+
An EV driver's app selects a charging station; this library downloads the proof of the station and the price statements
|
|
12
|
+
of its EVSEs, and checks that the station, its EVSEs as they are at runtime, the charging tariffs they take and the
|
|
13
|
+
prices of the moment are the ones the roaming network operator attested and the charging station operator signed - before
|
|
14
|
+
charging begins. Months later, e.g. disputing an invoice, the evidence the app kept proves which price applied, witnessed
|
|
15
|
+
by checkpoints. The same proof gives the public keys of the energy meters, with which
|
|
16
|
+
[ChargyCore.TS](https://github.com/OpenChargingCloud/ChargyCore.TS) checks the signed meter values of the session, and the
|
|
17
|
+
transparency software and its certificate that say who checks them.
|
|
18
|
+
|
|
19
|
+
Everything is calculated as the C# implementation calculates it, byte for byte: the canonical JSON and the deterministic
|
|
20
|
+
CBOR of Styx, ETags, the effective tree with templates (section 8 of the specification), commit and checkpoint
|
|
21
|
+
identifications of every epoch, COSE keys and their thumbprints, and the signatures ES256/384/512, Ed25519, Ed448 and
|
|
22
|
+
ML-DSA-44/65/87.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Status
|
|
26
|
+
|
|
27
|
+
**0.2.0 is prepared, not yet published to npm.** It includes the shared trust and session verification APIs previously
|
|
28
|
+
supplied as 0.1.2-dev.0 development packages. See [the changelog](CHANGELOG.md#020-prepared-not-yet-published) for the
|
|
29
|
+
changes and migration requirements. The experimental profiles below remain experimental; a package release does not
|
|
30
|
+
establish production witness services or independently verified actual measurement time.
|
|
31
|
+
|
|
32
|
+
**Milestone 1: the examples of WWCP_POI, checked in TypeScript.** The examples A1 to A14 of
|
|
33
|
+
[WWCP_POI/examples](https://github.com/OpenChargingCloud/WWCP_POI/tree/master/examples) at commit `33447ac` are checked
|
|
34
|
+
from what the C# examples publish, as their `Validate()` methods check them, and the same for proofs of charging stations
|
|
35
|
+
the C# implementation makes; also from a second run of the C# examples, with new keys. See [Tests](#tests).
|
|
36
|
+
|
|
37
|
+
What a review of the C# implementation found while porting it is in
|
|
38
|
+
[docs/csharp-review.md of WWCPPOIConformanceTests](https://github.com/OpenChargingCloud/WWCPPOIConformanceTests/blob/master/docs/csharp-review.md),
|
|
39
|
+
cited here as *csharp-review N*.
|
|
40
|
+
|
|
41
|
+
Whether both implementations really calculate, take and refuse the same is the business of
|
|
42
|
+
[WWCPPOIConformanceTests](https://github.com/OpenChargingCloud/WWCPPOIConformanceTests): golden test vectors, run against
|
|
43
|
+
this library and the C# implementation alike, and where they differ, written down and pinned
|
|
44
|
+
([docs/implementation-deviations.md](https://github.com/OpenChargingCloud/WWCPPOIConformanceTests/blob/master/docs/implementation-deviations.md)).
|
|
45
|
+
This repository vendors the vectors and runs them in its own suite (`tests/conformance.test.ts`).
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
From npm, as [`@open-charging-cloud/wwcp-poi`](https://www.npmjs.com/package/@open-charging-cloud/wwcp-poi):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install @open-charging-cloud/wwcp-poi
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
ESM, Node 22.18 or newer, browsers and React Native alike: no `node:*` module, no `Buffer`, no WebCrypto. The
|
|
57
|
+
cryptography is [`@vanaheimr/cose`](https://github.com/Vanaheimr/COSE.TS) on the `@noble` family, the CBOR codec
|
|
58
|
+
[`@vanaheimr/metrological-cbor`](https://github.com/Vanaheimr/MetrologicalCBOR.TS).
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## An app: a charging station and its prices
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { POIClient } from '@open-charging-cloud/wwcp-poi';
|
|
65
|
+
|
|
66
|
+
// The text of the QR code of the roaming network, as scanned, e.g. from the website of its operator.
|
|
67
|
+
const client = new POIClient('WWCP/TA-2:B7LJ683VCBCACVCC$C6REF6B…');
|
|
68
|
+
|
|
69
|
+
// GET ~/RNs/QA/EVSEs/DE*GEF*E1*1/proof and …/priceStatements, from the first server of the trust anchor whose answer verifies.
|
|
70
|
+
const checked = await client.chargingStation({ evseId: 'DE*GEF*E1*1' });
|
|
71
|
+
|
|
72
|
+
checked.station.roamingNetwork.name; // 'Quality Assurance', as the operator signed it, not as the QR code says
|
|
73
|
+
checked.station.chargingStation; // the station as it is at runtime, with what its templates give it
|
|
74
|
+
checked.station.tariffs.get('DE*GEF*E1*1'); // the charging tariffs the EVSE takes
|
|
75
|
+
checked.station.manufacturer?.model; // its model, as its manufacturer states it: revisions, firmware, ratings
|
|
76
|
+
checked.station.energyMeters; // with their public keys, for transparency software
|
|
77
|
+
checked.station.energyMeterModels; // per meter: its manufacturer and model
|
|
78
|
+
checked.station.certificates; // of the station and its meters: each for a model, with its components
|
|
79
|
+
checked.station.transparencySoftware; // per meter: the release, its manufacturer, the certificates naming it
|
|
80
|
+
checked.prices.get('EVSE:DE*GEF*E1*1')?.statement; // the price statement valid now, signed by DE*GEF
|
|
81
|
+
checked.documents; // as they came: keep them as evidence of the session
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
What is checked (sections 8.4 to 8.6, 12.4 of the specification):
|
|
85
|
+
|
|
86
|
+
- the proof leads to a root, the `effectiveTree` ETag of a version of the roaming network;
|
|
87
|
+
- the key statement of the roaming network operator the proof gives is signed by identity keys the trust anchor pins, of
|
|
88
|
+
as many holders as its `minConsensus` requires, counted as the trust anchor names them per holder, names their holders
|
|
89
|
+
so as well, and is valid now;
|
|
90
|
+
- attestations of that root, valid now - signed then or before, until their `validUntil`, at most a day later - by
|
|
91
|
+
signing keys of the operator, valid when they attested and now, each certified by a trusted identity key of its
|
|
92
|
+
holder, come from as many holders, each counted for the holder of that identity key as the trust anchor knows it;
|
|
93
|
+
- each price statement is signed by a signing key of the charging station operator that the proof gives and that may sign
|
|
94
|
+
`priceStatements`, names only charging tariffs of its EVSE, and chains with the statements before it; it is current no
|
|
95
|
+
longer than its key.
|
|
96
|
+
|
|
97
|
+
When a roaming network moves into a new epoch and replaces the keys of its operator (example A11), its operator publishes
|
|
98
|
+
a new QR code: a key statement signed only by the new keys is refused under the old trust anchor. A verifier that follows
|
|
99
|
+
the history commit by commit follows the keys as well (`OperatorTrust.followedBy`), and finds the new trust anchor naming
|
|
100
|
+
the keys it followed.
|
|
101
|
+
|
|
102
|
+
Without the client, the same with documents of any source:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { TrustAnchor, verifyChargingStation, verifyPriceStatements } from '@open-charging-cloud/wwcp-poi';
|
|
106
|
+
|
|
107
|
+
const anchor = TrustAnchor.parse(qrCodeText);
|
|
108
|
+
const station = verifyChargingStation(anchor, proofJSON, { evseId: 'DE*GEF*E1*1' });
|
|
109
|
+
const prices = verifyPriceStatements(station, priceStatementsJSON);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Who checks the meter (example A13): the certificates the station and its energy meters name, each of a conformity
|
|
113
|
+
assessment body for a model of charging stations or of energy meters, in hardware revisions and firmware versions, with
|
|
114
|
+
its components, the transparency software and, of a station, the meters it mounts; and the transparency software each
|
|
115
|
+
meter, and a station signing measurements itself, names with its legal status, proven by the certificates naming it.
|
|
116
|
+
POI proves who states them, not that they are true. `statedBy` tells whether the body states a certificate or the
|
|
117
|
+
roaming network operator in its place, as the certificate says, and `forThisModel` whether it is for the model, the
|
|
118
|
+
hardware revision and the firmware version of what names it (csharp-review 15, 16, 17).
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
for (const { namedBy, certificate, body, statedBy, forThisModel } of station.certificates)
|
|
122
|
+
console.log(namedBy.entityId, certificate['@id'], certificate['legislation'], body['@id'], statedBy, forThisModel);
|
|
123
|
+
|
|
124
|
+
for (const { namedBy, legalStatus, release, manufacturer, certificates } of station.transparencySoftware)
|
|
125
|
+
console.log(namedBy.entityId, legalStatus, release['version'], manufacturer['craRole'], certificates.map(({ certificate }) => certificate['@id']));
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Months later, the evidence of the session (example A10): the station proof attested and the price statement valid as
|
|
129
|
+
charging began, the commit of the station proof and the statement each proven to be part of checkpoints the trusted
|
|
130
|
+
witnesses signed:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { parseEvidence, parseTrustedWitnesses, verifyEvidence } from '@open-charging-cloud/wwcp-poi';
|
|
134
|
+
|
|
135
|
+
// driver/evidence-DE_GEF_E1_1.json, as the app of example A10 keeps it
|
|
136
|
+
const evidence = verifyEvidence(parseEvidence(keptJSON), { witnesses: parseTrustedWitnesses(witnessesJSON), requiredWitnesses: 2 });
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
## The traffic report: the status of an EVSE, live
|
|
141
|
+
|
|
142
|
+
The status of an EVSE, its energy status and the price shown at this moment are the runtime status of the head of the
|
|
143
|
+
history (example A12), pushed over the WebSocket `~/RNs/{RN_Id}/history/ws` the moment they change. They are unsigned,
|
|
144
|
+
as trustworthy as the server telling them, and no evidence: a price that has to be provable is a price statement. A
|
|
145
|
+
commit pushed with them is signed like every other, and checked like every other.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { RuntimeStatus, parseFrame, parseStatusUpdate, subscribeRequest } from '@open-charging-cloud/wwcp-poi';
|
|
149
|
+
|
|
150
|
+
const status = RuntimeStatus.fromSnapshot(snapshotJSON); // the runtime status beside the head, not part of its version
|
|
151
|
+
socket.send(subscribeRequest('1')); // [2,"1","subscribe",{"topics":["statusUpdates", …, "commits"]}]
|
|
152
|
+
|
|
153
|
+
socket.onmessage = event => {
|
|
154
|
+
const frame = parseFrame(event.data); // [6, id, action, payload]: a push
|
|
155
|
+
if (frame.type === 'push' && frame.action !== 'commit')
|
|
156
|
+
status.apply(parseStatusUpdate(frame.payload)); // the latest timestamp wins: a late report is 'Outdated'
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
status.get('EVSE', 'DE*GEF*E1*1', 'status')?.value; // 'charging'
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
## A charging station operator, a witness, a server: versions and histories
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { History, TrustAnchor, verifyVersion, verifyCheckpointSignatures } from '@open-charging-cloud/wwcp-poi';
|
|
167
|
+
|
|
168
|
+
// Example A1: a version of the roaming network and its commit, from the trust anchor alone.
|
|
169
|
+
const version = verifyVersion(TrustAnchor.parse(qrCodeText), networkJSON, commitJSON);
|
|
170
|
+
version.signedBy; // [ 'AlphaOrg', 'BetaOrg', 'GammaOrg' ]
|
|
171
|
+
|
|
172
|
+
// Examples A4 to A6: join from the snapshot of a checkpoint, anchored in the trust anchor (10.5): its checkpoint signed as
|
|
173
|
+
// the roaming network operator by the holders the trust anchor requires; a snapshot of the head instead by the
|
|
174
|
+
// attestations it carries. Then take the commits since one by one, each checked as section 5.2 says: its change set
|
|
175
|
+
// signed by its owners, fitting the head, leading to the ETags of the commit. Signed commits and checkpoints are taken
|
|
176
|
+
// only with a verifier of their signatures.
|
|
177
|
+
let trust = version.trust;
|
|
178
|
+
const history = History.fromSnapshot(checkpointSnapshotJSON, {
|
|
179
|
+
trustAnchor: TrustAnchor.parse(qrCodeText),
|
|
180
|
+
verifyCommit: (commit, before, after) => {
|
|
181
|
+
trust.verifyOperatorSignatures(commit.signatures, (s, key) => commit.verifySignature(s, key), 'the commit');
|
|
182
|
+
trust = trust.followedBy(after); // the keys its operator states anew, signed by those trusted
|
|
183
|
+
},
|
|
184
|
+
verifyCheckpoint: checkpoint => verifyCheckpointSignatures(checkpoint, trust, { witnesses, requiredWitnesses: 2 }),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
for (const commit of commitsSince)
|
|
188
|
+
history.append(commit);
|
|
189
|
+
|
|
190
|
+
// Example A11: the final checkpoint closes the epoch; the next one starts from its version, its genesis the one every
|
|
191
|
+
// server makes, SHA-512 identifications and ML-DSA signatures from then on.
|
|
192
|
+
history.takeCheckpoint(finalCheckpointJSON);
|
|
193
|
+
const epoch2 = history.startNextEpoch();
|
|
194
|
+
epoch2.root.commit.id; // the published genesis of epoch 2, 128 hexadecimal digits
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
## What is ported, and what is not yet
|
|
199
|
+
|
|
200
|
+
| | |
|
|
201
|
+
|---|---|
|
|
202
|
+
| Canonical JSON, deterministic CBOR (Styx) | complete, including Styx's string escaping and decimals as `System.Decimal` holds them |
|
|
203
|
+
| Keys, thumbprints, signatures | EC2, OKP and AKP COSE keys, read in the words of WWCP_POI; all signature algorithms of section 4; the key usages `infrastructure`, `tariffs`, `priceStatements`, `models`, `software`, `certificates`, `payments`, `party` |
|
|
204
|
+
| Trust anchors | QR code (`WWCP/TA-2:` and Base45 of CBOR), URL and JSON forms, read and written; key statements followed by succession |
|
|
205
|
+
| Roaming networks | ETags `json`, `cbor`, `effectiveTree`; the roaming network operator, its key statement and certificates; the keys of every party and of the roaming network operator as WWCP_POI reads them, none twice, none both a signing and an identity key, no identity key naming usages; the identifications unique within the network (grid connection points, energy meters, parking entities, transparency software releases and certificates, energy meter templates and groups, groups of operators and providers, offers), each once, compared as the C# types compare them; hardware revisions, firmware versions and serial numbers as WWCP_POI reads them, not empty, no whitespace at either end, those of a model each once; certificates as WWCP_POI reads them, for exactly one model, with its versions and releases; the conformity assessment bodies issuing them as WWCP_POI reads them, a notified body number of four digits for one body only, the legislations they are notified for neither empty nor twice, in their ordinal order, a certificate only under one of them; the content as WWCP_POI writes it (SPECIFICATION.md 2): what it takes and writes otherwise is refused, as written again it would be another text than the one hashed (ordered lists out of their order or with untrimmed texts, lists it leaves out given empty or writes also empty left out, properties given as null, embedded objects naming a JSON-LD context, texts in languages not as Styx writes them or empty, timestamps not UTC to the millisecond, entities without their lastChange, parties without their name, identifications given as numbers, properties of keys and of EV Taler merchants and exchanges it does not read, their base URLs and currencies not as it writes them); the roaming network operator, its key statement and certificates, the tree hash algorithms and the identifications of entities in the words of WWCP_POI.; of every entity what its type in WWCP_POI3 neither writes nor reads, and values it writes otherwise (trimmed, with a scheme, in its case, coordinates as doubles), in the words of its written form since 3de830a ("the content leaves it out", "the content holds it as …"). Not yet: properties of another JSON type or missing, the formats of identifications, and the references, which WWCP_POI refuses |
|
|
206
|
+
| Effective members (9.2) | EVSEs, charging stations, charging pools, charging tariffs, energy meters. Parking spaces and energy meters of grid connection points taking templates are refused, not calculated |
|
|
207
|
+
| Proofs (8.4), attestations (8.5) | complete, with the manufacturers and models of the station and its meters, the transparency software releases and certificates of the meters; subject attestations do not count for a whole proof |
|
|
208
|
+
| Change sets (4) | `Add`, `Remove`, `UpdateProperty`, `RemoveProperty` of parties (charging station operators, e-mobility providers, grid and parking operators, charging station and energy meter manufacturers, transparency software manufacturers, conformity assessment bodies), the roaming network and its operator, of charging pools, stations, EVSEs, tariffs, templates and groups of charging station operators, energy meter templates and groups, models of manufacturers, transparency software releases and certificates, with their owners' signatures, quorum and proof of possession, and the properties a change set may change: not the version of a release, also not by removing it and adding it again; the `notifiedBodyNumber` and `notifiedFor` of a conformity assessment body by the roaming network operator only. Element changes, preconditions, offers and the entities of providers, grid and parking operators are refused, not applied. The references the C# implementation checks after every change set (a manufacturer, model, release or certificate a station, meter or certificate names) are not checked again: the `afterETags` of the change set pin the version it leads to. The identifications unique within the network are, as in every version |
|
|
209
|
+
| Histories (5, 10, 11) | linear histories from snapshots, anchored in the trust anchor as a peer of WWCP_POI starts (10.5): a snapshot of a checkpoint by the checkpoint, signed as the roaming network operator, a snapshot of the head by its attestations, none without a trust anchor unless taken unanchored explicitly; commits, checkpoints, their lines and signatures, signed ones only with a verifier; proofs of commits, archive segments; epochs: their profiles, the final checkpoint, the genesis of the next epoch, its first checkpoint. Merges are not taken yet; no peer, no server |
|
|
210
|
+
| Price statements (12) | statements, chains, checkpoint trees and proofs in every epoch |
|
|
211
|
+
| Runtime status (12.5) | status updates, frames of the WebSocket, the runtime status of a snapshot. No WebSocket client: the app brings its own |
|
|
212
|
+
| QI-Digital documents | a proof of concept of WWCP_POI, as example A14 uses it: digital calibration certificates, digital certificates of conformity of a model or of the quality system of a manufacturer, digital declarations of conformity, read in the words of WWCP_POI and checked against a roaming network (the keys of their issuers, the models, the charging stations and energy meters as they are at runtime) and against each other (a declaration and the certificates it names, a certificate and the meter it covers). Their signature input is what WWCP_POI writes again of what it read, numbers without trailing zeros. Read and checked, not signed |
|
|
213
|
+
| EV Taler | phase 1 of WWCP_POI's concept of paying for charging with GNU Taler: the merchant of a charging station operator (`evTaler`), read and refused as WWCP_POI reads it, changed by a key that may sign `payments`; payment options an open set of names, none twice whatever its case; the payment option EVTaler of a charging pool or template only with a merchant; the EV Taler tariff of an EVSE and the exchanges in its currency, from the proof of its station (`evTalerTariffOf`, `evTalerExchangesAt`, `paymentOptionName`). Nothing here takes or makes a payment |
|
|
214
|
+
|
|
215
|
+
Whatever is not ported is refused with a `POIError` saying so, but for the references named above: the library never
|
|
216
|
+
takes as checked what it can not check.
|
|
217
|
+
And whatever it calculated otherwise than the C# implementation would end in other ETags or identifications, and be
|
|
218
|
+
refused as well.
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
## Local authorization and shared trust contexts
|
|
222
|
+
|
|
223
|
+
An app that lets its users import a trust anchor before they trust its keys does not hand that anchor to a verifier: a
|
|
224
|
+
verifier given a `TrustAnchor` trusts every identity key it pins. The app evaluates a `TrustContext` instead, from the
|
|
225
|
+
identity keys it trusts locally:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import { ChargingStationProof, RoamingNetworkOperator, TrustContext, verifyChargingStation } from '@open-charging-cloud/wwcp-poi';
|
|
229
|
+
|
|
230
|
+
// The key statement and the identity keys of the operator as the proof gives them: trusted only once evaluated.
|
|
231
|
+
const proof = ChargingStationProof.parse(proofJSON);
|
|
232
|
+
const operator = RoamingNetworkOperator.parse(proof.roamingNetwork['operator'], anchor.id);
|
|
233
|
+
|
|
234
|
+
if (operator.keyStatement !== undefined) {
|
|
235
|
+
|
|
236
|
+
const result = TrustContext.evaluate(anchor, operator.keyStatement, {
|
|
237
|
+
trustedIdentityKeyIds: locallyAuthorizedKeyIds,
|
|
238
|
+
otherIdentityKeys: operator.identityKeys,
|
|
239
|
+
at: Date.now()
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Show signatures, signers, quorum and validity apart. canTrustKeys allows importing
|
|
243
|
+
// validly signed data even without a trusted quorum.
|
|
244
|
+
if (result.context !== undefined) {
|
|
245
|
+
const station = verifyChargingStation(result.context, proof, { evseId: 'DE*GEF*E1*1' });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`TrustContext.evaluate` keeps of the holders of the trust anchor the identity keys the app trusts, and checks every
|
|
252
|
+
signature of the key statement and that its key was valid when the statement was signed, that the statement is valid at
|
|
253
|
+
`at`, that it names the holders of the trusted keys as the trust anchor does, and that trusted keys of as many holders
|
|
254
|
+
as the larger of both quorums signed it. Two trusted keys of one holder still count once. A context exists only when all
|
|
255
|
+
of that holds; `TrustContext.create` evaluates the same and throws otherwise.
|
|
256
|
+
|
|
257
|
+
The context binds what is verified with it to the roaming network, its operator and the signed content of the key
|
|
258
|
+
statement, which a statement embedded without its JSON-LD context gives as well. Another key statement, even one signed
|
|
259
|
+
genuinely, needs another evaluation: a context does not follow the keys of the operator to their successors by itself.
|
|
260
|
+
Other valid signatures, or the same in another order, do not change the content; every signature given and the trusted
|
|
261
|
+
quorum are checked again.
|
|
262
|
+
|
|
263
|
+
`OperatorTrust.of` and `fromOperatorJSON`, `verifyVersion`, `verifyChargingStation`, `ChargingStationProof.verify` and
|
|
264
|
+
`verifyCheckpoint` take a context in place of a trust anchor. A context is evaluated for one time: `at` is that time or
|
|
265
|
+
left out, another time needs another context. Given a trust anchor, they verify as before, trusting every key it pins.
|
|
266
|
+
The other APIs taking a trust anchor, among them `verifyEvidence` and `POIClient`, take only one the app has authorized:
|
|
267
|
+
they are no way around its local policy.
|
|
268
|
+
|
|
269
|
+
The app keeps storing and showing its decisions, choosing keys and excluding them locally. It reads its current
|
|
270
|
+
decisions consistently and creates a context from them for every verification: a context is no authority to store, nor
|
|
271
|
+
to use again after a key was withdrawn or deleted or the policy changed, and does not see what changes after it was
|
|
272
|
+
created. The library needs no IndexedDB, localStorage, network, DOM, Node or anything of an app.
|
|
273
|
+
|
|
274
|
+
Removing a pinned key from the local roots removes the trust it started with. A key statement signed by the quorum may
|
|
275
|
+
still name it among its identity keys and so delegate to it: excluding a key locally is no global revocation or deny
|
|
276
|
+
list. Revocation and verified succession need policies of their own. Unknown imports, invalid evidence and expired data
|
|
277
|
+
never give an authorized context.
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
## Tests
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
npm run verify
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`verify` runs what CI runs: the type checks (as Node and as a browser sees `src/`), ESLint with the rules of
|
|
287
|
+
ChargyCore.TS, and `npm test`, which is
|
|
288
|
+
|
|
289
|
+
- `npm run test:node`: the tests below, in Node;
|
|
290
|
+
- `npm run build` and `npm run test:bundle`: what `dist/` holds, as an app gets it: the bundle imports only the
|
|
291
|
+
dependencies `package.json` names and neither `node:*` modules, `Buffer` nor `process`; an app imports it by the name
|
|
292
|
+
of the package in plain Node and gets its declared types with TypeScript (`nodenext` and `bundler`); `npm pack`
|
|
293
|
+
publishes nothing but `dist/` and the documents;
|
|
294
|
+
- `npm run test:browser`: a trust anchor, a commit, a station proof of ML-DSA attestations, a price and the evidence of
|
|
295
|
+
a session, checked in Chromium (Vitest browser mode with Playwright; once: `npx playwright install chromium`).
|
|
296
|
+
|
|
297
|
+
GitHub Actions run it on every push and pull request (`.github/workflows/ci.yml`): Ubuntu on Node 22.18, the minimum
|
|
298
|
+
`package.json` names, and on Node 26, Windows on Node 24. Every night (`.github/workflows/nightly.yml`) the same suite
|
|
299
|
+
runs again, and, informational, with the dependencies the lockfile does not pin, with the newest releases of
|
|
300
|
+
`@vanaheimr/cose` and `@vanaheimr/metrological-cbor`, and as `npm audit`. The C# implementation as it is today is checked
|
|
301
|
+
every night by [WWCPPOIConformanceTests](https://github.com/OpenChargingCloud/WWCPPOIConformanceTests): its nightly run
|
|
302
|
+
holds WWCP_POI master and this library to the same golden vectors, and runs the C# examples of WWCP_POI master anew,
|
|
303
|
+
checked with `examples/validate-examples.ts` of this repository, which turns red the day WWCP_POI moves on and this port
|
|
304
|
+
needs work.
|
|
305
|
+
|
|
306
|
+
The test vectors are in `tests/vectors`, vendored from
|
|
307
|
+
[WWCPPOIConformanceTests](https://github.com/OpenChargingCloud/WWCPPOIConformanceTests), where they are generated -
|
|
308
|
+
never edit them here, and its `VendoredCopiesTests` fail when these copies drift:
|
|
309
|
+
|
|
310
|
+
- `tests/vectors/poi-*.json`, the golden vectors: canonical JSON and CBOR, Base64 and Base45, the examples of section 6
|
|
311
|
+
of the specification, station proofs of its example network signed with its keys against a trust anchor, what to
|
|
312
|
+
calculate of the documents of the run below, documents to refuse, the QI-Digital documents of example A14 of that run
|
|
313
|
+
taken and refused as WWCP_POI checks them, and small histories WWCPPOIConformanceTests signs itself, replayed step by
|
|
314
|
+
step (the format: the
|
|
315
|
+
[README of the vectors](https://github.com/OpenChargingCloud/WWCPPOIConformanceTests/blob/master/test-vectors/README.md));
|
|
316
|
+
- `tests/vectors/csharp-33447ac`, what one run of the C# examples of WWCP_POI at commit `33447ac` published (public data
|
|
317
|
+
only; its output is `csharp-output.log` there), and in `proofs/`, what `proofs/MakeProofs.cs.txt` made of the same
|
|
318
|
+
history with the C# implementation: proofs of charging stations of epoch 2, the commits of epoch 2 up to the head, and
|
|
319
|
+
the ECDSA twin of a price statement.
|
|
320
|
+
|
|
321
|
+
- `tests/conformance.test.ts`: the golden vectors, as the C# implementation runs them in WWCPPOIConformanceTests.
|
|
322
|
+
- `tests/canonical.test.ts`, `tests/specification.test.ts`: the test vectors of Styx and of section 6 of the
|
|
323
|
+
specification.
|
|
324
|
+
- `tests/exampleA1.test.ts`, `tests/examples.test.ts`: the examples A1 to A9, epoch 1, the manufacturers and the models
|
|
325
|
+
they state among them.
|
|
326
|
+
- `tests/evidence.test.ts`: example A10, the evidence a driver's app keeps, and ECDSA twins of price statements.
|
|
327
|
+
- `tests/exampleA11.test.ts`: example A11, epoch 2, followed with the old trust anchor and joined with the new one.
|
|
328
|
+
- `tests/exampleA12.test.ts`: example A12, the runtime status and the commit pushed over the WebSocket.
|
|
329
|
+
- `tests/exampleA13.test.ts`: example A13, transparency software manufacturers, conformity assessment bodies, and who
|
|
330
|
+
checks the meter.
|
|
331
|
+
- `tests/qidigital.test.ts`: example A14, the QI-Digital documents of a meter as WWCP_POI signed them, and others signed
|
|
332
|
+
with the keys of section 6 of the specification, to change them at will.
|
|
333
|
+
- `tests/chargingStation.test.ts`, `tests/client.test.ts`: proofs of charging stations of epoch 2, as an app checks them.
|
|
334
|
+
- `tests/replay.test.ts`: the whole history of `QA`, both epochs, commit by commit, with every checkpoint.
|
|
335
|
+
- `tests/build/bundle.test.js`, `tests/browser/browser.test.ts`: the bundle and the browser, as above.
|
|
336
|
+
|
|
337
|
+
To check a run of your own of the C# examples, with its new keys:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
npx tsx examples/validate-examples.ts D:\Coding\OpenChargingCloud\WWCP_POI\examples\csharp
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
## Shared trust and session verification in 0.2.0
|
|
345
|
+
|
|
346
|
+
These APIs were previously supplied in the unpublished 0.1.2-dev.0 candidate. WWCP_POI3 has them as well
|
|
347
|
+
(`WWCP_POI3/Verifier`), except for the local journal of observed versions, which is the app's. The versioned historical
|
|
348
|
+
and session-price evidence, compact trust delegation and publication-receipt profiles remain experimental; they do not
|
|
349
|
+
change the strict standard Evidence schema. See [Migration](CHANGELOG.md#migration) before upgrading a consumer.
|
|
350
|
+
|
|
351
|
+
### Meter-key association
|
|
352
|
+
|
|
353
|
+
`verifyMeterKey(context, proof, { evseId, meterSerialNumber, keyFormat: 'spki-der' })` re-verifies the original proof
|
|
354
|
+
with a locally authorized `TrustContext`, selects only that EVSE's directly associated meter and requires an exact
|
|
355
|
+
serial match. Multiple keys require an explicit `keyId`; it never searches unrelated meters or tries keys until one
|
|
356
|
+
verifies. No fallback to CPO/RNO signing keys is performed.
|
|
357
|
+
|
|
358
|
+
The first pilot supports public uncompressed ECDSA P-256 measurement keys. `parseMeasurementPublicKey(value, 'spki-der'
|
|
359
|
+
| 'cose-key')` requires an explicit format: it checks SHA-256(SPKI) or the deterministic COSE key thumbprint as
|
|
360
|
+
appropriate, validates the point and produces a canonical COSE key/material ID. It retains the original ID and its
|
|
361
|
+
derivation scheme. Unsupported algorithms and ambiguous associations fail explicitly rather than becoming trusted keys.
|
|
362
|
+
|
|
363
|
+
The result carries RN/RNO, EVSE, station, meter, original/canonical key identity, commit/revision/root, KeyStatement
|
|
364
|
+
digest, evaluation time and a conservative expiry bounded by the counted attestations and their certified keys. It is a
|
|
365
|
+
current-snapshot observation, not historical key-rotation/installation evidence or a permanent authorization token.
|
|
366
|
+
Reconstruct local authorization for each operation. Future station-owned measurement signers and additional key formats
|
|
367
|
+
need explicit association rules; they are not guessed by this pilot.
|
|
368
|
+
|
|
369
|
+
The portable public test vector `tests/vectors/meter-session.json` originates from ChargyWebApp commit 97da24d
|
|
370
|
+
(`tests/fixtures/POISession`); its source manifest is `meter-session-meta.json`. It contains public demonstration keys
|
|
371
|
+
only.
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
### Historical meter evidence
|
|
375
|
+
|
|
376
|
+
`parseHistoricalMeterEvidence` and `verifyHistoricalMeterEvidence(currentContext, value, request, policy?)` keep current
|
|
377
|
+
local authorization separate from historical validity. The request supplies the actual original reading times, EVSE,
|
|
378
|
+
meter serial and explicit key format. Expired historical statements and operational certificates are checked at those
|
|
379
|
+
times; each later checkpoint uses its own supplied operator and signature times. The output is an immutable per-time
|
|
380
|
+
binding for Core, not a serialized trust token.
|
|
381
|
+
|
|
382
|
+
The versioned `historicalMeterEvidence/v1` envelope is a TS-library extension, not a change to the existing strict
|
|
383
|
+
Evidence schema. It contains `EVSEId` and `versions`, each with `chargingStationProof`, optional `commitProof`, and
|
|
384
|
+
optional `checkpoints` entries shaped as `{checkpoint, operator}`. Missing inclusion stays visible; malformed supplied
|
|
385
|
+
inclusion fails. Prices are not required just to verify a meter assignment. Only caller-authorized witnesses/revocations
|
|
386
|
+
count. `invalidFrom` compromise times apply to signature times or measurement-key use; ordinary expiry does not
|
|
387
|
+
retroactively invalidate a valid historical signature. An unauthorized current quorum still blocks all checks. Identity
|
|
388
|
+
succession not connected to today's authorized pins fails closed.
|
|
389
|
+
|
|
390
|
+
The claim is RNO attestation at the signed measurement times and optionally checkpoint inclusion. Independently
|
|
391
|
+
established actual time, sole/latest membership, complete owner-signed change replay and checkpoint continuity are not
|
|
392
|
+
inferred. A checkpoint obtained later does not prove it was public before charging. No full network archive is
|
|
393
|
+
downloaded. See the app's illustrated historical-session guide for integration.
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
`HistoricalTrustContext.create(anchor, savedReferenceStatement, {trustedIdentityKeyIds, otherIdentityKeys?, now?})`
|
|
397
|
+
authorizes historical checks from fresh caller-supplied local decisions. It verifies the saved reference at its own
|
|
398
|
+
signed time; expiry today does not require a server or a newly issued statement. Its one-minute validity bound is an
|
|
399
|
+
operation deadline, not historical document validity. Recreate it for each check and reread local authorization after
|
|
400
|
+
async work. Current `TrustContext` remains the separate path for fresh/current station checks. Unknown identity
|
|
401
|
+
succession still fails.
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
### Session-bound prices
|
|
405
|
+
|
|
406
|
+
`verifySessionPriceEvidence` verifies the attested station at the original start, CPO PriceStatements with existing
|
|
407
|
+
generic algorithms (including Ed25519 and ML-DSA), usage/validity, entity/tariff references, supplied chains and
|
|
408
|
+
explicit selection. `SessionPriceAgreement` binds the exact original start document SHA-256, meter/EVSE, time, tariff,
|
|
409
|
+
statement digest, mode and binding/profile in a CPO signature. This is a CPO assertion, not customer consent. Unknown
|
|
410
|
+
agreements never authorize themselves. The experimental `chargy-gross-rates/v1` profile fixes unconditional gross rates
|
|
411
|
+
at start. EMP contracts, variable bindings and unsupported restrictions stay unproven.
|
|
412
|
+
|
|
413
|
+
Checkpoint authority is shared with historical meter verification. Price inclusion, `after` links/continuity, selected
|
|
414
|
+
statement identity and related checkpoint forks are checked separately. A later checkpoint never proves pre-start
|
|
415
|
+
publication, independent time or sole/latest offer. Original decimals are preserved. There is no P-256-only restriction
|
|
416
|
+
on CPO price/assignment signatures. See the app's price guide.
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
### Locally observed versions
|
|
420
|
+
|
|
421
|
+
`compareObservedVersions` is a pure, shared policy for monotonic current selection and persistent authenticated
|
|
422
|
+
contradictions. Metadata projection helpers for station proofs, checkpoint positions/root revisions and PriceStatements
|
|
423
|
+
do not authenticate anything: call the usual role, quorum, subject and validity verifiers before recording them. The
|
|
424
|
+
caller owns durable storage and must compare and replace the state atomically. The app uses a separate IndexedDB journal
|
|
425
|
+
scoped to the exact local anchor policy and authenticated RN/RNO identity.
|
|
426
|
+
|
|
427
|
+
Current selection rejects known rollbacks, unresolved forks, missing checkpoint, price or live predecessors, and
|
|
428
|
+
unverified epoch changes. Historical inspection allows older coordinates and never advances current heads. Authenticated
|
|
429
|
+
gaps and rejected conflicting originals remain negative knowledge. A rejected gap cannot advance its head by
|
|
430
|
+
resubmitting the same tip. Station snapshots permit revision jumps without claiming commit ancestry. Limits are 4096
|
|
431
|
+
points and 128 conflicts; storage must fail closed rather than silently evict history. These local rules establish
|
|
432
|
+
neither global freshness nor independently authorized witnesses.
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
### CPO envelope authority and compact trust succession
|
|
436
|
+
|
|
437
|
+
`verifyChargingDocumentKeys` re-verifies the original station/EVSE proof, exact CPO, document-time validity and the
|
|
438
|
+
explicit `chargeTransparencyRecords` usage (or an unrestricted party key). It returns normalized COSE material for the
|
|
439
|
+
shared Core envelope verifier; a price-only key does not authorize document signing. This is independent of the
|
|
440
|
+
P-256/OCMF meter pilot.
|
|
441
|
+
|
|
442
|
+
A transition names the policy it continues by `trustPolicyId(anchor)`: the SHA-256 of the canonical JSON of `{network,
|
|
443
|
+
holders, minConsensus, urls}`, the URLs as a WHATWG URL serializes them without trailing slashes, in their ordinal order,
|
|
444
|
+
as WWCP_POI3 calculates it. `TrustTransition` and `VerifiedTrustTransition` implement an experimental compact delegation
|
|
445
|
+
certificate, signed by both old authorized and target identity holder quorums, binding exact policies, statements,
|
|
446
|
+
sources and predecessor. It is not History owner/change-set replay and does not misuse `followedBy`.
|
|
447
|
+
`applyVerifiedTrustTransition` preserves negative journal knowledge while adopting explicit policy delegation and exact
|
|
448
|
+
final-checkpoint/genesis/first-checkpoint epoch evidence. Unverified epoch jumps and quorum reductions fail closed.
|
|
449
|
+
`TrustContext.withEpoch` carries verified profiles into subsequent proof checks; historical contexts can carry a
|
|
450
|
+
reverified authority timeline. These objects are recreated from local decisions and original evidence, never serialized
|
|
451
|
+
green flags. See the app's illustrated trust-succession guide and genuine public fixtures.
|
|
452
|
+
|
|
453
|
+
### Independent price publication receipts
|
|
454
|
+
|
|
455
|
+
`parseWitnessPublicationPolicy` parses separately authorized organization, network, key and clock decisions. Parsing a
|
|
456
|
+
recommendation grants no trust. Pass the accepted policy as `publicationPolicy` to `verifySessionPriceEvidence`; its
|
|
457
|
+
optional `publicationReceipts` and `witnessCandidates` do not authorize themselves. `PricePublicationReceipt` signs a
|
|
458
|
+
versioned public-retrieval assertion for the exact selected price/EVSE/network/checkpoint and public source. The
|
|
459
|
+
experimental key usage is `witnessPublications`; existing usage restrictions still apply.
|
|
460
|
+
|
|
461
|
+
The result `publicationBeforeStart: 'witnessed-before-signed-start'` requires the separate distinct-organization quorum,
|
|
462
|
+
valid original signatures and inclusion, and every counted latest signing bound (including locally accepted clock error)
|
|
463
|
+
strictly before the original signed start. Several keys/aliases of one approved organization count once; reused key
|
|
464
|
+
material cannot create another organization. Known RNO/CPO key material is excluded from independent witnessing. Actual
|
|
465
|
+
clock accuracy and organizational independence remain explicit caller trust assumptions. The physical charging start,
|
|
466
|
+
global latest/sole offer, customer consent and final billing are not independently proven. This is not an RFC 3161
|
|
467
|
+
implementation.
|
|
468
|
+
|
|
469
|
+
`tests/vectors/price-publication.json` contains public Ed25519/ML-DSA-65 vectors and negative timing, scope, signature
|
|
470
|
+
and duplicate-organization cases. The caller must reread current policy across asynchronous work and persist original
|
|
471
|
+
receipts, never serialized green verdicts. No production witness service is implied.
|
|
472
|
+
|
|
473
|
+
Publication receipts additionally bind `priceStatementSHA256` (the complete original CPO-signed object including its
|
|
474
|
+
signature value) and `tariffSHA256` (the exact selected tariff including its currency). The ordinary statement digest
|
|
475
|
+
excludes its signature value and cannot alone prove that a signed offer, rather than its draft, was retrieved.
|
|
476
|
+
`pricePublicationOfferHashes` computes these bindings consistently for generators and callers. Neither alternate
|
|
477
|
+
signature encodings nor changed tariff definitions reuse another original's receipt.
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
## Publishing
|
|
481
|
+
|
|
482
|
+
Version 0.2.0 is prepared locally. Preparation does not publish to npm, create a release tag or change a consumer's
|
|
483
|
+
pinned development package. Validate the source and inspect the package before approving a release:
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
npm ci
|
|
487
|
+
npx playwright install chromium
|
|
488
|
+
npm run verify
|
|
489
|
+
npm run examples
|
|
490
|
+
npm pack --dry-run
|
|
491
|
+
npm pack
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
After release approval, replace the prepared-release markers in this README and the changelog with the actual release
|
|
495
|
+
status and date, rebuild and check the final archive. Commit the reviewed release files, then tag and push that exact
|
|
496
|
+
commit to each configured remote:
|
|
497
|
+
|
|
498
|
+
```bash
|
|
499
|
+
git tag -s v0.2.0 -m "v0.2.0"
|
|
500
|
+
git push origin master v0.2.0
|
|
501
|
+
git push git1 master v0.2.0
|
|
502
|
+
git push git2 master v0.2.0
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Publish the inspected archive only after that separate approval:
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
npm login
|
|
509
|
+
npm whoami
|
|
510
|
+
npm publish ./open-charging-cloud-wwcp-poi-0.2.0.tgz --access public
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
After publication, consumers can replace their local POI development archive with
|
|
514
|
+
`@open-charging-cloud/wwcp-poi@^0.2.0`, refresh their lockfile and verify a clean install. Their separate ChargyCore
|
|
515
|
+
candidate still needs its own release; publishing POI alone does not replace it.
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
## License
|
|
519
|
+
|
|
520
|
+
[GNU Affero General Public License v3.0](LICENSE), as WWCP_POI.
|
|
521
|
+
|
|
522
|
+
Copyright 2014-2026 GraphDefined GmbH
|