This document describes the implementation of the Firmware-Update Authorization Gate feature for Utility-Drip-Contracts. This feature enables secure, time-limited firmware updates on IoT devices while protecting against billing manipulation during the update window.
IoT devices require periodic firmware updates for security and functionality improvements. However, firmware updates create a unique billing challenge:
- Devices cannot accurately report usage during updates
- Providers should prevent billing during update windows to avoid inaccurate charges
- Without controls, devices could remain in "updating" state indefinitely to avoid billing
The firmware update authorization gate implements three key protections:
- Billing Pause During Update: When a provider initiates a firmware update, the meter's billing is automatically suspended using the
is_updatingflag - Time Limit Enforcement: Updates are limited to a maximum 2-hour window to prevent perpetual suspension
- Cryptographic Proof of Completion: Only devices with a valid Ed25519 signature matching the device's public key can resume billing
pub struct Meter {
// ... existing fields ...
// Issue #178: Firmware Update Authorization Gate Fields
pub is_updating: bool, // Flag indicating device is under firmware update
pub update_start_timestamp: u64, // Timestamp when update was initiated (seconds)
}pub struct FirmwareUpdateStartedEvent {
pub meter_id: u64, // Meter identifier
pub update_start_timestamp: u64, // Timestamp when update began
pub provider: Address, // Provider who initiated update
pub max_update_window_secs: u64, // Maximum allowed update duration (7200 = 2 hours)
}
pub struct FirmwareUpdateFinishedEvent {
pub meter_id: u64, // Meter identifier
pub update_start_timestamp: u64, // Original start timestamp
pub update_completed_timestamp: u64,// When update completed
pub update_duration_secs: u64, // Actual duration of update
pub device_signature_valid: bool, // Whether signature verification succeeded
}pub struct UpdateCompleteData {
pub meter_id: u64, // Meter being updated
pub update_start_timestamp: u64, // Must match meter's registered start time
pub completion_timestamp: u64, // When device completed update
}
pub struct SignedUpdateComplete {
pub meter_id: u64,
pub update_start_timestamp: u64,
pub completion_timestamp: u64,
pub signature: BytesN<64>, // Ed25519 signature (64 bytes)
pub device_public_key: BytesN<32>, // Device's public key (32 bytes)
}const FIRMWARE_UPDATE_WINDOW_SECS: u64 = 2 * HOUR_IN_SECONDS; // 7200 seconds (2 hours)
const HOUR_IN_SECONDS: u64 = 60 * 60; // 3600 secondspub enum ContractError {
// ... existing errors ...
FirmwareUpdateInProgress = 27, // Meter is currently updating, billing paused
FirmwareUpdateWindowExpired = 28, // Update window exceeded (> 2 hours)
InvalidFirmwareUpdateSignature = 29,// Device signature verification failed
}Authorization: Provider-only (requires provider authentication)
Purpose: Initiates a firmware update for a meter and suspends billing
Parameters:
meter_id: The meter to update
Behavior:
- Authenticates caller as the meter's provider
- Checks if meter is already updating (error:
FirmwareUpdateInProgress) - Sets
is_updating = true - Sets
update_start_timestamp = current_time - Stores updated meter state
- Emits
FirmwareUpdateStartedEvent
Returns: None
Error Conditions:
ContractError::FirmwareUpdateInProgress: Meter already under updateContractError::Unauthorized: Caller is not the provider
Authorization: Device holder (via cryptographic proof)
Purpose: Completes firmware update and resumes billing with cryptographic proof
Parameters:
signed_update:SignedUpdateCompletestruct containing:- meter_id
- update_start_timestamp
- completion_timestamp
- signature (Ed25519, 64 bytes)
- device_public_key (32 bytes)
Behavior:
- Retrieves meter; checks if currently updating
- Verifies update window hasn't expired:
- Current time - update_start_timestamp ≤ 7200 seconds (2 hours)
- Verifies update_start_timestamp matches meter's timestamp
- Verifies device_public_key matches meter's registered device_public_key
- Verifies Ed25519 signature of UpdateCompleteData
- Sets
is_updating = false - Clears
update_start_timestamp = 0 - Updates
last_update = current_time - Stores updated meter state
- Emits
FirmwareUpdateFinishedEvent
Returns: None
Error Conditions:
ContractError::MeterNotFound: Meter not updating or doesn't existContractError::FirmwareUpdateWindowExpired: Update duration > 2 hoursContractError::PublicKeyMismatch: Device public key doesn't matchContractError::InvalidFirmwareUpdateSignature: Timestamp mismatch or invalid signature
New Gate: Billing pause check added
Lines added (after existing checks):
// Issue #178: Check if meter is under firmware update
// Billing is paused during authorized update window
if meter.is_updating {
panic_with_error!(&env, ContractError::FirmwareUpdateInProgress);
}Behavior Change:
deduct_units()now rejects withFirmwareUpdateInProgressif meter is updating- This ensures no usage charges accrue during the update window
- Implementation:
is_updatingflag added to Meter structinitiate_firmware_update()sets flag when calleddeduct_units()checks flag and rejects if truecomplete_firmware_update()clears flag to resume
- Verification: Test
test_firmware_update_acceptance_1_billing_pauses_during_window
- Implementation:
complete_firmware_update()enforces 2-hour maximum window- Window calculated:
now - update_start_timestamp > FIRMWARE_UPDATE_WINDOW_SECS - Returns
FirmwareUpdateWindowExpirederror if exceeded
- Verification: Test
test_firmware_update_acceptance_2_time_limits_prevent_perpetual_suspension
- Implementation:
complete_firmware_update()requires valid Ed25519 signature- Signature verified against
device_public_keyregistered with meter - Uses Soroban's
env.crypto().ed25519_verify() - Signature must be exactly 64 bytes
- Public key must be exactly 32 bytes
- Verification: Test
test_firmware_update_acceptance_3_hardware_signatures_required
- Uses Ed25519 algorithm (industry standard for IoT)
- Signature is 64 bytes, public key is 32 bytes
- Message contains: meter_id, update_start_timestamp, completion_timestamp
- Prevents unauthorized devices from resuming billing
- Each UpdateComplete must include the exact
update_start_timestamp - Prevents old signatures from being reused
- Timestamp mismatch results in
InvalidFirmwareUpdateSignature
- 2-hour maximum prevents indefinite billing suspension
- Provider cannot extend window; only device can resume
- Expired windows prevent completion with any signature
- Only provider can initiate updates (requires auth)
- Only device with matching public key can complete updates
- No administrative override for expired windows
Emitted when: initiate_firmware_update() succeeds
Properties:
- meter_id: u64
- update_start_timestamp: u64
- provider: Address
- max_update_window_secs: u64
Emitted when: complete_firmware_update() succeeds
Properties:
- meter_id: u64
- update_start_timestamp: u64
- update_completed_timestamp: u64
- update_duration_secs: u64
- device_signature_valid: bool
-
Acceptance Criteria Tests
test_firmware_update_acceptance_1_billing_pauses_during_windowtest_firmware_update_acceptance_2_time_limits_prevent_perpetual_suspensiontest_firmware_update_acceptance_3_hardware_signatures_required
-
Integration Tests
test_firmware_update_integration_workflow- Full workflow from start to completion
-
Edge Case Tests
- Multiple consecutive update attempts
- Window expiration at boundary (7200 seconds)
- Signature with wrong timestamp
- Public key mismatch
-
Authorization Tests
- Provider-only authorization for
initiate_firmware_update() - Device signature requirement for
complete_firmware_update()
- Provider-only authorization for
-
Event Emission Tests
- Proper event emission with correct fields
- Event symbol verification
# Run unit tests
cargo test -p utility_contracts --lib firmware_update
# Run integration tests
cargo test --test firmware_update_tests
# Run all tests with output
cargo test -p utility_contracts -- --nocaptureProvider calls: initiate_firmware_update(meter_id=123)
Result:
- is_updating = true
- update_start_timestamp = current_time
- Billing paused, deduct_units() will reject
- FirmwareUpdateStartedEvent emitted
Device calls: complete_firmware_update({
meter_id: 123,
update_start_timestamp: 1000,
completion_timestamp: 1600,
signature: [ed25519_signature_64_bytes],
device_public_key: [public_key_32_bytes]
})
Steps:
1. Verifies device_public_key matches meter's registered key
2. Checks 1600 - 1000 = 600 seconds (within 7200 limit) ✓
3. Verifies Ed25519 signature
4. Sets is_updating = false
5. Emits FirmwareUpdateFinishedEvent with 600 second duration
Now deduct_units() succeeds because:
- is_updating = false
- Update billing continues normally
✓ Meter struct extended with firmware update fields ✓ Event structures defined (FirmwareUpdateStartedEvent, FirmwareUpdateFinishedEvent) ✓ Error codes added (FirmwareUpdateInProgress, FirmwareUpdateWindowExpired, InvalidFirmwareUpdateSignature) ✓ initiate_firmware_update() function implemented ✓ complete_firmware_update() function with signature verification ✓ deduct_units() modified to enforce update pause ✓ Constants defined (FIRMWARE_UPDATE_WINDOW_SECS = 7200) ✓ Comprehensive test suite created ✓ Documentation completed
contracts/utility_contracts/src/lib.rs- Added event structures
- Added error codes
- Extended Meter struct
- Updated register_meter_with_mode()
- Added initiate_firmware_update()
- Added complete_firmware_update()
- Modified deduct_units()
- Added constant FIRMWARE_UPDATE_WINDOW_SECS
contracts/utility_contracts/tests/firmware_update_tests.rs- Comprehensive test suite with acceptance criteria tests
- Issue: #178 - Firmware-Update Authorization Gate
- Labels: iot, maintenance, state-machine
- Soroban Crypto: https://github.com/stellar/rs-soroban-sdk
- Ed25519 Signatures: https://en.wikipedia.org/wiki/EdDSA
- Add optional firmware version tracking
- Support multiple concurrent updates (per component)
- Automatic rollback on extended offline state
- Update progress reporting (percentage complete)
- Multiple device support per meter