Open Charging Technology · Whitepapers

A signed metrological record, end to end

A worked example of a charging transaction carried as CBOR: two meter readings expressed as metrological values, signed by the meter, bundled by the charging station, and endorsed by the operator before the customer receives them.

Everything below is real output. The 713 bytes of Section 6 are pinned by SignedMetrologyExampleTests, which derives every other listing in this document from them and verifies all three signatures against the published public keys. The document is generated from that constant, never retyped.

Every signature here is deterministic (RFC 6979): the nonce is derived from the private key and the message rather than drawn at random. So this document is not merely verifiable, it is recomputable — sign the same readings with the same keys and the same 713 bytes come out, on any implementation. The test does exactly that before it checks anything else.

1. The three layers

Layer Who Mechanism Says
1 the meter COSE_Sign1 per reading "I measured this"
2 the charging station COSE_Sign1 over a payload of its own "these readings belong to this transaction at this point"
3 the operator countersignature (RFC 9338) "I saw the station's signature"

Layers 1 and 2 are nested: the station has something of its own to say, so it signs a new payload that contains the signed readings. Layer 3 is a countersignature: the operator asserts nothing new about the data, so it endorses the station's signature instead of wrapping it. That distinction matters for the customer: the station's signature stays verifiable on its own, and every reading keeps the meter's signature no matter how many parties handled it afterwards.

2. The meter reading

The payload, in CBOR diagnostic notation:

{"time": 0("2026-08-15T08:14:00Z"), "meter": "1ISA0000000042",
 "energy": 44252([4([-3, 1234567]), 2, 3,
                  {1: 4([-1, 123]), 2: 2, 3: 4([-2, 95]), 4: 1}]),
 "context": "Transaction.Begin", "transaction": "a4f1c9e2"}

The members are in the order a deterministic encoder puts them — sorted by their encoded key, shortest first — rather than in the order a person would write them down. That is not decoration. A record like this one is received, parsed and forwarded, and whoever forwards it re-encodes it; an encoder following RFC 8949 Section 4.2.1 produces exactly these bytes. Had the meter written the members in reading order instead, the forwarded record would carry a signature over a spelling nobody downstream reproduces — and that failure is indistinguishable from tampering.

134 bytes, of which the reading itself is 31:

A56474696D65C074323032362D30382D31355430383A31343A30305A656D657465726E31
4953413030303030303030343266656E65726779D9ACDC84C482221A0012D6870203A401
C48220187B020203C48221185F040167636F6E74657874715472616E73616374696F6E2E
426567696E6B7472616E73616374696F6E686134663163396532

The energy member is the point of this document. Read from the inside out:

Element Meaning
4([-3, 1234567]) 1234.567 — a decimal fraction, not a binary float. The instrument showed three decimal places and the wire says so; 1234.567 and 1234.5670 stay distinguishable.
2 the unit: watt hour
3 the SI prefix: kilo. It scales the quantity, it is not folded into the value — 1234.567 kWh never silently becomes 1234567 Wh.
{1: …, 2: 2, 3: …, 4: 1} the measurement uncertainty per GUM: magnitude 12.3, coverage factor k = 2, coverage probability 0.95, distribution normal. The standard uncertainty follows as u = U/k = 6.15 kWh.

So the whole statement — value, scale, unit, prefix and a complete GUM uncertainty — is 31 bytes, and a generic CBOR decoder that has never heard of tag 44252 still sees a well-formed tagged array of integers and standard decimal fractions.

Signed by the meter with ESB256 (ECDSA on brainpoolP256r1 with SHA-256), the reading becomes 221 bytes:

D28445A101390108A10448C6738177A6E6D04B5886A56474696D65C074323032362D3038
2D31355430383A31343A30305A656D657465726E31495341303030303030303034326665
6E65726779D9ACDC84C482221A0012D6870203A401C48220187B020203C48221185F0401
67636F6E74657874715472616E73616374696F6E2E426567696E6B7472616E7361637469
6F6E6861346631633965325840A8C6B9738D3A312248D78467C688147EA583170D25E8F2
D14475BA2404C8DE62369749AE5425975F50886C3C7C957A154DA788EF46C45276B1BEC4
FCE2A00FA5

The second reading, Transaction.End at 1259.869 kWh, is 219 bytes of the same shape. The billed quantity is the difference of two independently signed readings: 25.302 kWh.

3. The charging station's bundle

The station has its own statement to make — which readings belong to which transaction at which point — so it signs a payload of its own:

{"readings": <2 signed readings>, "transaction": "a4f1c9e2",
 "chargingStation": "DE*GEF*E12345678*1"}

The readings go in as byte strings, complete with the meter's signatures. That payload is 511 bytes; signed with ES256 on P-256 the message is 713.

4. The operator's countersignature

The operator adds nothing to the data. It vouches for the station's signature, and does so with a countersignature in the unprotected bucket — which is why the station's message keeps its bytes and stays verifiable without knowing that anyone endorsed it:

A2                        the unprotected bucket, two parameters
  04 48 4F4E…3440         the station's key identifier
  0B                      11: countersignature (RFC 9338)
    83                      a COSE_Countersignature
      44 A1013822           protected: {1: -35}, ES384
      A1 04 48 6B1F…88BB    the operator's key identifier
      5860 …                the signature, 48 bytes of r and s each

The version 2 structure of RFC 9338 is what is used here, and the difference is not cosmetic. Its predecessor covered the payload but not the signature it was countersigning, so it never actually attested to having seen it. Version 2 appends that signature to the signed structure — replacing the station's signature, even with another valid signature over the same payload, therefore invalidates the endorsement.

5. Verifying it

var record   = COSESign1.Parse(bytes);

// The operator vouched for the station's signature...
record.VerifyCountersignature(record.Countersignatures[0], operatorKey, out var e1);

// ...the station signed the bundle...
record.Verify(stationKey, out var e2);

// ...and the meter signed every reading within it.
foreach (var reading in CBORValue.Parse(record.Payload!)["readings"].AsArray())
    COSESign1.Parse(reading.AsBytes()).Verify(meterKey, out var e3);

Each layer answers a different question, and a failure at one does not invalidate the answers of the others. A single altered digit in a reading is caught by the meter's signature even if the station's and the operator's signatures were recomputed around it.

Recomputing it

COSESign1.Sign(payload, meterKey, COSEAlgorithm.ESB256, meterKeyId, Deterministic: true);

Deterministic derives the ECDSA nonce from the private key and the message as RFC 6979 defines, so the signature stops being a random value that merely verifies and becomes a function of what it signs.

The payload is written in the deterministic encoding of RFC 8949 Section 4.2.1 before it is signed, which COSESign1.Sign does by default. That is the other half of the same idea: the nonce is a function of the message, and the message is a function of the data. Rebuilding this record from the keys of Section 7 therefore reproduces these exact 713 bytes — and so does any other implementation given the same readings, which is what makes this a worked example rather than an illustration.

That is worth having beyond documentation. A meter or a smart card has no dependable source of randomness, and an ECDSA nonce that repeats hands over the private key — a determinism that removes the requirement removes the failure mode with it.

6. The complete record

713 bytes:

D28443A10126A204484F4E4267CBA434400B8344A1013822A104486B1F337BA0EC88BB58
6061C12A64FC1DB9E8943FCB43F8D9786D2FF7F8FF4EB6BD11AA175068F6DCA81EDC7EF9
38E169461927DF33CC63E2DD90A9247CB85B5D5D95FAC1B24C5E775482E817D331E84878
416C8A43F7C7486692A3CA5F6D8FF8A182D6008BC72B092C595901FFA36872656164696E
67738258DDD28445A101390108A10448C6738177A6E6D04B5886A56474696D65C0743230
32362D30382D31355430383A31343A30305A656D657465726E3149534130303030303030
30343266656E65726779D9ACDC84C482221A0012D6870203A401C48220187B020203C482
21185F040167636F6E74657874715472616E73616374696F6E2E426567696E6B7472616E
73616374696F6E6861346631633965325840A8C6B9738D3A312248D78467C688147EA583
170D25E8F2D14475BA2404C8DE62369749AE5425975F50886C3C7C957A154DA788EF46C4
5276B1BEC4FCE2A00FA558DBD28445A101390108A10448C6738177A6E6D04B5884A56474
696D65C074323032362D30382D31355430393A30323A30305A656D657465726E31495341
3030303030303030343266656E65726779D9ACDC84C482221A0013395D0203A401C48220
187E020203C48221185F040167636F6E746578746F5472616E73616374696F6E2E456E64
6B7472616E73616374696F6E6861346631633965325840008A537E8890CEF6D909BC8324
94718173315CC01E48FD779D6897FCC081E83270FCBE16A5E6939D5F8B1D5B80C1C4EC56
9335D5B175B3B49EB0DEFD994C0A6C6B7472616E73616374696F6E686134663163396532
6F6368617267696E6753746174696F6E7244452A4745462A4531323334353637382A3158
40EE16FB2B5B12407D00DFDC582601AE543AFE062D797CE222A1411A00C92EEEB6D68E3E
B9F259C02531AB438D6CC65BC7CC888C4DC5DE27DE106AF82AD13E89A7
bytes
one meter reading, unsigned 134
one meter reading, signed 221
both readings plus the station's metadata 511
the station's signed bundle 713
the operator's countersignature, within those 713 96 of signature

Four signatures by three signers — the meter signs each reading — two complete metrological statements with their uncertainties, and the identities of all three signers, in 713 bytes.

What the same record costs after the quantum transition

The elliptic curve signatures above are 64, 64, 64 and 96 bytes: 288 of those 713 bytes are signature. ML-DSA (FIPS 204, registered for COSE by RFC 9964) replaces each of them with 2420, 3309 or 4627 bytes depending on the parameter set — so the same four signatures at ML-DSA-87 are 18 508 bytes, and the record grows by more than twenty-five times what it carries.

A single reading shows the same thing without the nesting. The 31-byte metrological value of Section 2, signed on its own, is a 118-byte COSE_Sign1 with ESB256 and a 4675-byte one with ML-DSA-87.

None of this changes the tag, and the specification takes no position on which algorithm anyone should use. It does sharpen the point of Section 6 of the tag specification: a signature is a byte string, and a byte string costs its bytes here. The same 4627-byte signature spelled out in base64 within a textual format is 6172 characters — 1545 bytes of pure overhead on the largest field in the message, and that penalty grows with exactly the field that post-quantum cryptography makes grow.

7. The keys

Example keys. They were generated for this document, they secure nothing, and they must never appear anywhere else.

curve algorithm key identifier
meter brainpoolP256r1 ESB256 (−265) C6738177A6E6D04B
charging station P-256 ES256 (−7) 4F4E4267CBA43440
operator P-384 ES384 (−35) 6B1F337BA0EC88BB
meter    x  A734FB1962C381113C746BDDBCBC774801E3B73FA7F73479615D290E91E48889
         y  8A188C8261A560197B37C73044E3009BA1DAED226C324A35FEE76AA144740678
         d  08F001BB03BEF4FBD1C59F10B50555CD37D2B53421331DBFA98815A581326FB3

station  x  7951E32509303CD4DB14127765B3FC9F32F62AC5C0F12350BD3ED7C746C72FE9
         y  A35716031E2C44A942D886626C5D4C41E0FF62E44FED7EDA3ACC1408D90720DC
         d  875E51ECF18073E8B970E6DCC5A115433456E13DF966034A5A782945D2B684D3

operator x  5DEF24F33251A911F43205134D568C1FB3547E2BD0B602D4B18A5FA476FF1FB8
            E6D321CC4ED1DCF754A81159C63389D2
         y  D8298F873104BC9AE145888BB7DC574AB26501E1E78DC4613CCB4B4C1B842720
            724671655551F9E2918C8943EAE8C2FA
         d  6952487A0A16EACE6E9A69EFD062D7671D68D23FF68722326348827C3A94E2A1
            743A1DF8901B948412CCA26CA4372CED

Every key identifier above is the leading 8 bytes of the RFC 9679 thumbprint of its own key, so a verifier can check that the key it was handed is the key the record names. Because the thumbprint covers the curve, a signer who changes algorithm necessarily gets a different identifier — an algorithm downgrade under an unchanged identity is not expressible.

8. What this is and is not

This is a proposal for how a digital, signed SI quantity can look on the wire, and a demonstration that the pieces fit: the metrological content of tag 44252 — value, scale, unit, prefix, GUM uncertainty — carried through three independent signature layers without losing a decimal place or a coverage factor.

It is not a conformity statement. In the regulated part of the charging infrastructure that follows from the type approval of the measuring instrument and from the data being checkable with the verification software the approval covers. What this format offers is the part underneath: a representation whose encoding is a pure function of the measured quantity, so that the same reading always produces the same bytes and therefore the same signature.

References