Skip to content

Battery Regulation Smart Contract

Visual specification of the Plutus validator that governs a manufacturer's product passport trie. Three diagram types: UTxO state transitions, the validator guard table, and lifecycle state machines.

Physical mode — the battery carries a secure element that signs its own state of health. The chain anchors the reading with a neutral timestamp.

The UTxO landscape

The validator governs one UTxO per manufacturer — the MPT root. The trie contains two leaf types: item leaves (one per battery) and reporter leaves (one per reward recipient). Reference inputs: identity trie and regulation trie.

graph LR
    subgraph Reference Inputs
        ID_UTxO["Identity UTxO<br/><b>identity trie root</b><br/><i>attested actor keys</i>"]
        REG_UTxO["Regulation UTxO<br/><b>regulation trie root</b><br/><i>manufacturer standing<br/>actor qualifications</i>"]
    end

    subgraph Spent + Produced
        TRIE_IN["Passport UTxO<br/><b>MPT root₁</b><br/><i>datum: trie state</i>"]
        TX{{"Transaction<br/><i>redeemer action</i>"}}
        TRIE_OUT["Passport UTxO'<br/><b>MPT root₂</b><br/><i>datum: trie state'</i>"]
    end

    TRIE_IN -->|spent| TX
    TX -->|produced| TRIE_OUT
    ID_UTxO -.->|reference| TX
    REG_UTxO -.->|reference| TX

    subgraph Signatures
        SIG_ITEM["Process signature<br/><i>SE050 COSE_Sign1</i>"]
        SIG_ACTOR["Actor signature<br/><i>baton holder / reporter</i>"]
    end

    SIG_ITEM -.-> TX
    SIG_ACTOR -.-> TX

The critical difference from the GDPR contract: the process signature comes from hardware — an NXP SE050 secure element inside the battery, signing via NFC. The chain verifies this signature using Plutus built-in Ed25519 or secp256k1 verifiers.

Leaf types in the trie

Two leaf types coexist under the same MPT root, distinguished by key prefix.

graph TD
    ROOT["Passport Trie Root<br/><b>MPT root hash</b>"]

    ROOT --- ITEM1["item/<br/>hash(itemPubKey₁)"]
    ROOT --- ITEM2["item/<br/>hash(itemPubKey₂)"]
    ROOT --- ITEM3["item/<br/>hash(itemPubKeyₙ)"]
    ROOT --- REP1["reporter/<br/>hash(reporterPubKey₁)"]
    ROOT --- REP2["reporter/<br/>hash(reporterPubKey₂)"]

    ITEM1 --- IL1["schemaVersion: 1<br/>metadata: 0xab... (model, chemistry)<br/>reporter: Just(reporterKey, nextReward)<br/>commitment: Just(validFrom, validUntil)"]
    ITEM2 --- IL2["schemaVersion: 1<br/>metadata: 0xcd...<br/>reporter: Just(reporterKey, nextReward)<br/>commitment: Nothing"]
    ITEM3 --- IL3["schemaVersion: 1<br/>metadata: 0xef...<br/>reporter: Nothing<br/>commitment: Nothing"]

    REP1 --- RL1["reporterKey: pubKey₁<br/>rewardsAccumulated: 30"]
    REP2 --- RL2["reporterKey: pubKey₂<br/>rewardsAccumulated: 10"]

ItemLeaf

Field Type Purpose
schemaVersion Integer Datum versioning for forward compatibility
metadata ByteString Product-specific: battery model, chemistry, manufacturing date
reporter Maybe ReporterAssignment Who is assigned to report + what reward they get
commitment Maybe Commitment Time window for the next reading (single-use)

ReporterAssignment

Field Type Invariant
reporterPubKey ByteString Key of the assigned reader (consumer, technician)
nextReward Integer Always > 0 — reward for the next valid reading

Commitment

Field Type Purpose
validFrom Integer (slot) Earliest slot the reading is accepted
validUntil Integer (slot) Latest slot — deadline for the reading

ReporterLeaf

Field Type Invariant
reporterKey ByteString Reporter's public key
rewardsAccumulated Integer Monotonically increasing — never decreases

Redeemer actions

Seven redeemer actions grouped by lifecycle phase.

graph TB
    subgraph "Registration"
        R1["RegisterItem"]
    end

    subgraph "Assignment"
        R2["AssignReporter"]
        R3["ReassignReporter"]
    end

    subgraph "Reading Protocol (2-tx)"
        R4["CreateCommitment"]
        R5["SubmitReading"]
    end

    subgraph "Ownership"
        R6["TransferOwnership"]
    end

    subgraph "End of Life"
        R7["Repurpose"]
    end

UTxO state transitions per action

RegisterItem

Manufacturer creates a new battery passport leaf in the trie. The battery is virgin — no reporter assigned, no commitment.

graph LR
    subgraph Input
        IN["Passport UTxO<br/><b>datum:</b><br/>no leaf at hash(itemPubKey)"]
    end

    subgraph Transaction
        TX{{"RegisterItem<br/><br/>redeemer:<br/>itemPubKey, metadata"}}
    end

    subgraph Output
        OUT["Passport UTxO'<br/><b>datum:</b><br/>new ItemLeaf:<br/>  schemaVersion: 1<br/>  metadata: M<br/>  reporter: Nothing<br/>  commitment: Nothing"]
    end

    IN --> TX --> OUT

    subgraph Guards
        G1["manufacturer key ∈ identity trie"]
        G2["manufacturer standing ≠ suspended<br/>(regulation trie)"]
        G3["no existing leaf at hash(itemPubKey)"]
        G4["metadata ≠ empty"]
        G5["manufacturer signature present"]
        G6["MPT insert proof valid"]
    end

    G1 -.-> TX
    G2 -.-> TX
    G3 -.-> TX
    G4 -.-> TX
    G5 -.-> TX
    G6 -.-> TX

AssignReporter

Manufacturer assigns a reporter (consumer, technician) to a battery. This determines who can trigger readings and earn rewards.

graph LR
    subgraph Input
        IN["Passport UTxO<br/><b>datum:</b><br/>ItemLeaf:<br/>  reporter: Nothing<br/>  commitment: Nothing"]
    end

    subgraph Transaction
        TX{{"AssignReporter<br/><br/>redeemer:<br/>leaf_key,<br/>reporterPubKey,<br/>nextReward"}}
    end

    subgraph Output
        OUT["Passport UTxO'<br/><b>datum:</b><br/>ItemLeaf:<br/>  reporter: Just(<br/>    reporterPubKey: K<br/>    nextReward: R)<br/>  commitment: Nothing"]
    end

    IN --> TX --> OUT

    subgraph Guards
        G1["manufacturer key ∈ identity trie"]
        G2["reporter: Nothing<br/>(not already assigned)"]
        G3["nextReward > 0"]
        G4["manufacturer signature present"]
        G5["MPT update proof valid"]
    end

    G1 -.-> TX
    G2 -.-> TX
    G3 -.-> TX
    G4 -.-> TX
    G5 -.-> TX

ReassignReporter

Replace the current reporter. The old reporter keeps their accumulated rewards.

graph LR
    subgraph Input
        IN["Passport UTxO<br/><b>datum:</b><br/>ItemLeaf:<br/>  reporter: Just(oldKey, R₁)<br/>  commitment: Nothing"]
    end

    subgraph Transaction
        TX{{"ReassignReporter<br/><br/>redeemer:<br/>leaf_key,<br/>newReporterPubKey,<br/>newReward"}}
    end

    subgraph Output
        OUT["Passport UTxO'<br/><b>datum:</b><br/>ItemLeaf:<br/>  reporter: Just(<br/>    reporterPubKey: newKey<br/>    nextReward: R₂)<br/>  commitment: Nothing"]
    end

    IN --> TX --> OUT

    subgraph Guards
        G1["manufacturer key ∈ identity trie"]
        G2["commitment: Nothing<br/>(no pending reading)"]
        G3["newReward > 0"]
        G4["manufacturer signature present"]
        G5["MPT update proof valid"]
    end

    G1 -.-> TX
    G2 -.-> TX
    G3 -.-> TX
    G4 -.-> TX
    G5 -.-> TX

CreateCommitment

The manufacturer opens a reading window on a battery. This is Tx 1 of the two-transaction protocol — the chain's slot becomes the trusted clock.

graph LR
    subgraph Input
        IN["Passport UTxO<br/><b>datum:</b><br/>ItemLeaf:<br/>  reporter: Just(K, R)<br/>  commitment: Nothing"]
    end

    subgraph Transaction
        TX{{"CreateCommitment<br/><br/>redeemer:<br/>leaf_key,<br/>validFrom, validUntil"}}
    end

    subgraph Output
        OUT["Passport UTxO'<br/><b>datum:</b><br/>ItemLeaf:<br/>  reporter: Just(K, R)<br/>  commitment: Just(<br/>    validFrom: V₁<br/>    validUntil: V₂)"]
    end

    IN --> TX --> OUT

    subgraph Guards
        G1["manufacturer key ∈ identity trie"]
        G2["reporter assigned<br/>(reporter ≠ Nothing)"]
        G3["commitment: Nothing<br/>(no pending commitment)"]
        G4["validFrom ≤ validUntil"]
        G5["validFrom ≥ current slot range lower"]
        G6["manufacturer signature present"]
        G7["MPT update proof valid"]
    end

    G1 -.-> TX
    G2 -.-> TX
    G3 -.-> TX
    G4 -.-> TX
    G5 -.-> TX
    G6 -.-> TX
    G7 -.-> TX

SubmitReading

The reporter submits a signed BMS reading from the battery's secure element. Tx 2 of the protocol — clears the commitment and credits the reward. This is the core transaction of the entire system.

graph LR
    subgraph Input
        IN["Passport UTxO<br/><b>datum:</b><br/>ItemLeaf:<br/>  reporter: Just(K, R)<br/>  commitment: Just(V₁, V₂)<br/><br/>ReporterLeaf:<br/>  rewardsAccumulated: A"]
    end

    subgraph Transaction
        TX{{"SubmitReading<br/><br/>redeemer:<br/>leaf_key,<br/>cose_sign1_envelope,<br/>reporter_key"}}
    end

    subgraph Output
        OUT["Passport UTxO'<br/><b>datum:</b><br/>ItemLeaf:<br/>  reporter: Just(K, R)<br/>  commitment: Nothing<br/><br/>ReporterLeaf:<br/>  rewardsAccumulated: A + R"]
    end

    IN --> TX --> OUT

    subgraph Guards
        G1["commitment exists<br/>(commitment ≠ Nothing)"]
        G2["COSE_Sign1 signature valid<br/>(Ed25519 or secp256k1<br/>Plutus built-in verifier)"]
        G3["payload.validFrom = commitment.validFrom<br/>payload.validUntil = commitment.validUntil"]
        G4["validFrom ≤ current_slot ≤ validUntil<br/>(freshness)"]
        G5["reporter key matches assignment"]
        G6["reporter signature present<br/>(actor authorization)"]
        G7["MPT transition proof valid<br/>(two-leaf update: item + reporter)"]
    end

    G1 -.-> TX
    G2 -.-> TX
    G3 -.-> TX
    G4 -.-> TX
    G5 -.-> TX
    G6 -.-> TX
    G7 -.-> TX

Two leaves updated atomically

SubmitReading is the only action that updates two leaves in a single transaction: the item leaf (commitment cleared) and the reporter leaf (reward credited). The MPT transition proof must cover both updates.

TransferOwnership

Battery changes hands. Both seller and buyer sign. A fresh BMS reading is attached as proof of condition at the point of sale.

graph LR
    subgraph Input
        IN["Passport UTxO<br/><b>datum:</b><br/>ItemLeaf:<br/>  reporter: Just(seller_key, R)<br/>  commitment: Nothing<br/><br/>CIP-68 User Token<br/>  owner: seller_pkh"]
    end

    subgraph Transaction
        TX{{"TransferOwnership<br/><br/>redeemer:<br/>leaf_key,<br/>new_owner_pkh,<br/>fresh_cose_reading"}}
    end

    subgraph Output
        OUT["Passport UTxO'<br/><b>datum:</b><br/>ItemLeaf:<br/>  reporter: Just(buyer_key, R)<br/>  commitment: Nothing<br/><br/>CIP-68 User Token<br/>  owner: buyer_pkh"]
    end

    IN --> TX --> OUT

    subgraph Guards
        G1["seller signature present"]
        G2["buyer signature present"]
        G3["fresh COSE_Sign1 reading valid<br/>(condition at point of sale)"]
        G4["CIP-68 token moves<br/>seller_pkh → buyer_pkh"]
        G5["commitment: Nothing<br/>(no pending reading)"]
        G6["MPT update proof valid"]
    end

    G1 -.-> TX
    G2 -.-> TX
    G3 -.-> TX
    G4 -.-> TX
    G5 -.-> TX
    G6 -.-> TX

Repurpose

End of first life. The original operator marks the leaf as handed off, and the new operator (repurposer) creates a new leaf in their own trie with a back-link.

graph LR
    subgraph "Original Operator's Trie"
        IN["Passport UTxO<br/><b>datum:</b><br/>ItemLeaf:<br/>  status: active<br/>  commitment: Nothing"]
        TX1{{"Repurpose (original)<br/><br/>redeemer:<br/>leaf_key,<br/>new_operator_id"}}
        OUT1["Passport UTxO'<br/><b>datum:</b><br/>ItemLeaf:<br/>  status: handed_off<br/>  new_operator: OP₂<br/>  handoff_slot: current_slot<br/>  <i>read-only from here</i>"]
    end

    subgraph "New Operator's Trie"
        IN2["Passport UTxO (OP₂)<br/><b>datum:</b><br/>no leaf for this item"]
        TX2{{"Repurpose (new)<br/><br/>redeemer:<br/>new_leaf_key,<br/>back_link"}}
        OUT2["Passport UTxO' (OP₂)<br/><b>datum:</b><br/>new ItemLeaf:<br/>  metadata: M'<br/>  back_link:<br/>    original_root: root₁<br/>    original_operator: OP₁<br/>    merkle_proof: π<br/>  initial_soh: fresh reading"]
    end

    IN --> TX1 --> OUT1
    IN2 --> TX2 --> OUT2
    OUT1 -.->|provenance link| OUT2

    subgraph Guards
        GR1["original operator signature"]
        GR2["new operator key ∈ identity trie"]
        GR3["new operator qualified<br/>(regulation trie)"]
        GR4["fresh BMS reading at handoff"]
        GR5["back_link merkle proof valid<br/>against original root"]
    end

    GR1 -.-> TX1
    GR2 -.-> TX2
    GR3 -.-> TX2
    GR4 -.-> TX2
    GR5 -.-> TX2

Two transactions, two operators, two tries

Repurposing is a cross-trie operation. The original operator marks their leaf as read-only. The new operator creates a new leaf in their own trie with a back-link (original root hash + Merkle proof). Full provenance is verifiable across both tries.

Validator guard table

Guard Register Assign Reassign Commit Submit Transfer Repurpose
Manufacturer key ∈ identity trie x x x x x
Standing ≠ suspended (reg trie) x
No existing leaf at key x
reporter = Nothing x
commitment = Nothing x x x x
reporter ≠ Nothing x x
commitment ≠ Nothing x
nextReward > 0 x x
validFrom ≤ validUntil x
COSE_Sign1 valid (hardware sig) x x x
Payload matches commitment x
Freshness: slot in [V₁, V₂] x
Reporter key matches assignment x
Reporter signature (actor auth) x
Seller + buyer signatures x
CIP-68 token transfer x
New operator ∈ identity trie x
New operator qualified (reg trie) x
Back-link Merkle proof valid x
Manufacturer signature x x x x x
MPT proof valid x x x x x x x

Universal guard

MPT proof validity applies to every action — the root hash must be correctly recomputed after the leaf update. For SubmitReading, the proof covers a two-leaf update (item + reporter).

Lifecycle state machines

Item leaf lifecycle

The main state machine — from manufacturing to end of life.

stateDiagram-v2
    [*] --> Virgin: RegisterItem<br/>(reporter: Nothing, commitment: Nothing)

    Virgin --> Assigned: AssignReporter<br/>(reporter: Just(K, R))

    Assigned --> Committed: CreateCommitment<br/>(commitment: Just(V₁, V₂))

    Committed --> Assigned: SubmitReading<br/>(commitment cleared,<br/>reward credited)

    Assigned --> Reassigned: ReassignReporter<br/>(new reporter key)
    Reassigned --> Committed: CreateCommitment

    Assigned --> Transferred: TransferOwnership<br/>(CIP-68 token moves,<br/>fresh reading attached)
    Transferred --> Assigned: AssignReporter<br/>(new owner's reporter)

    Assigned --> HandedOff: Repurpose<br/>(status: read-only,<br/>back-link created)
    HandedOff --> [*]: leaf frozen

    note right of Committed
        72h-scale window
        SE050 signs reading
        Single-use commitment
    end note

    note right of HandedOff
        New operator creates
        fresh leaf in their
        own trie with back-link
    end note

Commitment lifecycle (the 2-tx protocol)

The core protocol — zoomed in on the commitment field.

stateDiagram-v2
    [*] --> Empty: reporter assigned,<br/>commitment: Nothing

    Empty --> Active: CreateCommitment (Tx 1)<br/>commitment: Just(validFrom, validUntil)<br/>chain slot = trusted timestamp

    Active --> Empty: SubmitReading (Tx 2)<br/>commitment: Nothing<br/>COSE_Sign1 verified<br/>reward credited

    Active --> Expired: validUntil < current_slot<br/>(no transaction needed)
    Expired --> Empty: operator creates<br/>new commitment

    note right of Active
        The battery signs:
        { battery_id, validFrom,
          validUntil, soh_bp,
          soc_bp, cycles, volts }
        in COSE_Sign1 envelope
    end note

    note left of Expired
        Expired commitments
        are inert — SubmitReading
        will fail freshness check
    end note

Reward accumulation

stateDiagram-v2
    [*] --> NoLeaf: reporter not yet in trie

    NoLeaf --> Created: First SubmitReading<br/>insert ReporterLeaf<br/>rewardsAccumulated: R₁

    Created --> Accumulating: Subsequent SubmitReading<br/>rewardsAccumulated: A + Rₙ

    Accumulating --> Accumulating: Each reading<br/>adds nextReward<br/>(monotonically increasing)

    note right of Accumulating
        Proved in Lean:
        credit_increases
        credit_monotone
        double_credit_sum
    end note

Battery lifecycle (regulatory)

The physical lifecycle mapped to on-chain states, per (EU) 2023/1542.

stateDiagram-v2
    [*] --> Manufactured: factory produces battery<br/>(static data: chemistry,<br/>carbon footprint, recycled %)

    Manufactured --> PlacedOnMarket: RegisterItem<br/>(2027 EV, 2028 industrial)

    PlacedOnMarket --> InService: first SoH reading<br/>(AssignReporter + readings)

    InService --> InService: periodic readings<br/>(SoH, cycles, capacity fade)

    InService --> SecondLife: Repurpose<br/>(new operator, back-link)
    SecondLife --> SecondLife: readings continue<br/>(new operator's trie)

    InService --> EndOfLife: Repurpose to recycler<br/>(Art. 77(6b))
    SecondLife --> EndOfLife: Repurpose to recycler

    EndOfLife --> [*]: passport ceases to exist<br/>(leaf frozen, record preserved)

    note right of InService
        Consumer taps phone → NFC
        SE050 signs SoH reading
        Operator submits to chain
    end note

    note right of SecondLife
        Full provenance:
        new trie back-links
        to original root
    end note

The full transaction: SoH reading

End-to-end flow for the two-transaction commitment protocol, showing the hardware interaction.

sequenceDiagram
    participant MFR as Manufacturer (Operator)
    participant V as Validator
    participant IT as Identity Trie
    participant RT as Regulation Trie
    participant U as Consumer (Reporter)
    participant BMS as Battery (SE050 + Sensor)

    rect rgb(60, 60, 80)
    Note over MFR,RT: Transaction 1: CreateCommitment

    MFR->>V: Spend passport UTxO<br/>redeemer: CreateCommitment(leaf_key, V₁, V₂)
    V->>IT: Read reference: manufacturer key present?
    IT-->>V: ✓ attested
    V->>V: Check: reporter assigned on leaf
    V->>V: Check: no existing commitment
    V->>V: Check: V₁ ≤ V₂, V₁ ≥ lower slot
    V->>V: Check: manufacturer signature
    V->>V: Check: MPT update proof valid
    V-->>MFR: ✓ Produce passport UTxO'<br/>datum: commitment = Just(V₁, V₂)
    end

    Note over MFR,BMS: Off-chain: manufacturer relays<br/>commitment to consumer

    MFR->>U: Relay: commitment window (V₁, V₂)

    rect rgb(80, 60, 60)
    Note over U,BMS: NFC tap — physical world

    U->>BMS: NFC tap (phone provides power)
    BMS->>BMS: Sensor reads: SoH, SoC,<br/>cycles, voltage, temperature
    BMS->>BMS: SE050 signs COSE_Sign1:<br/>{ battery_id, V₁, V₂,<br/>  soh_bp: 8800, cycles: 342, ... }
    BMS-->>U: Signed envelope (Ed25519 or secp256k1)
    end

    U->>MFR: Submit signed envelope

    rect rgb(60, 80, 60)
    Note over MFR,RT: Transaction 2: SubmitReading

    MFR->>V: Spend passport UTxO'<br/>redeemer: SubmitReading(leaf_key, cose_envelope, reporter_key)
    V->>V: Check: commitment exists
    V->>V: Verify COSE_Sign1 signature<br/>(Plutus built-in: Ed25519 or secp256k1)
    V->>V: Check: payload.V₁ = commitment.V₁<br/>payload.V₂ = commitment.V₂
    V->>V: Check: V₁ ≤ current_slot ≤ V₂ (freshness)
    V->>V: Check: reporter key matches assignment
    V->>V: Check: reporter signature present
    V->>V: Check: MPT transition proof<br/>(two-leaf update: item + reporter)
    V-->>MFR: ✓ Produce passport UTxO''<br/>datum: commitment = Nothing<br/>reporter.rewardsAccumulated += R
    end

    Note over U: Reward credited on-chain<br/>Redeemable off-chain via<br/>reporter key signature

The hardware signing flow

How the physical measurement becomes an on-chain proof. This is the detail the GDPR contract does not have — the bridge between the physical world and the chain.

graph TD
    subgraph "Battery Module ($1.91)"
        SENSOR["Analog Sensor<br/><i>measures SoH, SoC,<br/>cycles, voltage, temp</i>"]
        I2C["I2C Bus"]
        SE["NXP SE050<br/><i>Ed25519 + secp256k1<br/>key never leaves chip</i>"]
        NFC["NXP NTAG 5 Link<br/><i>NFC + I2C master<br/>energy harvesting</i>"]
        ANT["Antenna + passives<br/><i>$0.06</i>"]
    end

    subgraph "Consumer's Phone"
        APP["Phone App<br/><i>relays commitment window<br/>receives signed envelope</i>"]
    end

    subgraph "On-chain Verification"
        BUILT_IN["Plutus Built-in<br/><i>verifyEd25519Signature or<br/>verifyEcdsaSecp256k1Signature</i>"]
    end

    SENSOR -->|analog reading| I2C
    I2C -->|digital values| SE
    SE -->|COSE_Sign1 envelope| NFC
    NFC <-->|NFC field<br/>powers the module| APP
    APP -->|HTTPS| OPERATOR["Operator API"]
    OPERATOR -->|transaction| BUILT_IN

    subgraph "What the SE050 Signs (CBOR)"
        PAYLOAD["battery_id: hash(pubKey)<br/>validFrom: slot V₁<br/>validUntil: slot V₂<br/>soh_bp: 8800 (= 88.00%)<br/>soc_bp: 6500 (= 65.00%)<br/>cycle_count: 342<br/>voltage_mv: 3720<br/>temperature_dc: 245 (= 24.5°C)<br/>schema_version: 1"]
    end

    SE -.->|signs| PAYLOAD

Integer-only encoding

All values are integers — no floating point. SoH in basis points (8800 = 88.00%), temperature in deci-Celsius (245 = 24.5°C). This enables deterministic CBOR encoding without canonicalisation, which is critical for signature verification: the byte sequence the SE050 signed must be exactly reproducible by the Plutus validator.

Plausibility checks

Beyond cryptographic verification, the validator can enforce physical plausibility. These are optional guards that catch obvious fraud without requiring domain expertise.

Check Rule Catches
SoH decreasing new_soh_bp ≤ previous_soh_bp Manufacturer inflating health
Cycles increasing new_cycles ≥ previous_cycles Resetting cycle counter
SoH range 0 ≤ soh_bp ≤ 10000 Invalid readings
Voltage range 2500 ≤ voltage_mv ≤ 4500 (Li-ion) Sensor malfunction
Temperature range -400 ≤ temp_dc ≤ 800 Sensor malfunction

These are soft guards — recorded on-chain for auditors rather than causing transaction rejection. A failing plausibility check flags the reading but does not prevent it, because the SE050 signature proves the sensor produced this value. The anomaly is real data, not fraud — the regulator investigates.

Comparison with GDPR contract

Dimension Battery Contract GDPR Contract
Mode Physical — hardware signature Process — server-side signing
Process signature source SE050 secure element (COSE_Sign1) Authenticated web session
Leaf types ItemLeaf + ReporterLeaf Consent, breach, rights, Art 30, DPIA, cert
Two-leaf atomic update Yes (item + reporter on SubmitReading) No (single leaf per action)
Reward system Yes (monotonic accumulator) No
Cross-operator handoff Repurpose (back-link to original root) Data portability (Art 20)
Plausibility checks Physics-based (SoH ≤ prev, cycles ≥ prev) Timeline-based (timeliness flag)
Timeliness recording Freshness check (reject if expired) Timeliness flag (record, don't reject)
CIP-68 token Yes (ownership transfer) No
Signature curves Ed25519, secp256k1 (hardware-constrained) Ed25519 (software, no constraint)

The battery contract is strictly more complex — it has hardware signatures, two-leaf atomic updates, reward accounting, CIP-68 token transfer, and cross-operator handoff with Merkle back-links. The GDPR contract is structurally simpler but covers more redeemer actions because GDPR has more distinct obligation types.

Sources