EIP.tools

EIP.tools

⚠️ DraftStandards Track: ERC

ERC-8260: Dive Log

An interface specification for storing, retrieving, and verifying dive log data.

Authors
Created2026-05-09
Discussion Linkhttps://ethereum-magicians.org/t/erc-8260-dive-logbook/28433
Requires

Markdown

https://raw.githubusercontent.com/ethereum/ERCs/re...
Pull Request#1735PR open

EIP-GPT summary

Contents
AbstractMotivationSpecificationEnumsData StructuresDiveDataEnvironmentDecompressionGasDataDiveLogDiveInputVoidInfoAttestationInterface DefinitionEIP-712 Typed DataDomainAttestation TypeFull Dive Data Types (Off-Chain)Behavior RequirementsDive LoggingCorrective Ledger (Void/Supersede)Cryptographic AttestationERC-165 ComplianceEventsRationaleWallet-as-IdentitySovereign Interface (No Registry)Corrective Ledger (Void/Supersede)EIP-712 AttestationsAttestation NoncesStructs Instead of Individual FieldsDual Unit SystemOn-Chain Storage vs. EventsAppend-Only with Void OverlaySeparated Void and Attestation QueriesBackwards CompatibilityReference ImplementationSecurity ConsiderationsCopyright

Abstract

This proposal defines a standard interface (IDiveLog) for storing, retrieving, and cryptographically verifying dive log data on EVM-compatible blockchains. A single contract implementing this interface serves as a diver's sovereign logbook — no registry or factory contract is required. The standard specifies an append-only data schema derived from U.S. military diving log forms (DD Form 2544, ENG Form 4615), a corrective ledger mechanism (void/supersede) that preserves immutable integrity while allowing error correction, and EIP-712 typed data structures for cryptographic attestation by buddies, instructors, or dive supervisors.

Motivation

Dive logs are safety-critical records that track decompression history, demonstrate qualifications, and provide evidence of experience. Current storage methods are fragile: paper logbooks are lost or destroyed, centralized applications shut down when companies fail, and institutional databases disappear when organizations restructure. No interoperability exists between logging systems — each application defines its own schema, creating proprietary silos.

Additionally, no cryptographic proof mechanism exists for buddy sign-offs. This enables "pencil-whipping" (faking log entries), undermining the integrity of commercial and scientific diving operations where verified experience is a safety requirement.

A blockchain-based standard addresses these problems:

  • Permanence: Data survives any single point of failure.
  • Ownership: Divers control their own records via wallet keys.
  • Interoperability: A single schema that any application can read and write.
  • Portability: No vendor lock-in. Any compliant tool can render a diver's career from a contract address and chain ID.
  • Cryptographic trust: EIP-712 signed attestations provide verifiable proof of dive verification.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Every ERC-8260 compliant contract MUST implement the IDiveLog and IERC165 interfaces:

pragma solidity ^0.8.20;

interface IERC165 {
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

Enums

enum UnitSystem { Imperial, Metric }
enum DiveMode { SSA, SCUBA }
enum BreathingGas { Air, Nitrox, Heliox, Trimix, Oxygen, Mixed }
enum DivePurpose {
    Training, Inspection, Repair, Search, Salvage,
    Recovery, Construction, Research, EOD, Security,
    Photographic, Recreational, Other
}
enum SuitType { Wet, Dry, HotWater, Swim }
enum DecompressionType {
    NoneDecomp, Standard, SurfaceDecompO2, SurfaceDecompAir,
    Saturation, Repetitive, ExceptionalExposure
}
enum BottomType { Sand, Coral, Rock, Wreck, Silt, Other }

struct Coordinates {
    int32 latitude;
    int32 longitude;
}

Data Structures

Every dive record MUST declare a unit system. Consumers MUST interpret numeric fields according to the declared unit system:

FieldImperialMetric
TemperatureDegrees Fahrenheit (°F)Degrees Celsius (°C)
DepthFeet of Seawater (FSW)Meters of Seawater (MSW)
PressurePSIBar
CurrentKnotsKnots

Each dive record carries its own UnitSystem field, allowing dives to be logged in either unit system regardless of the diver's preferred convention. Multiple dives MAY share the same diveDate; getDivesByDate returns all matching dive IDs.

DiveData

Core dive parameters:

struct DiveData {
    uint64 leaveSurfaceTime;     // Unix timestamp — diver descends from surface
    uint64 leaveBottomTime;      // Unix timestamp — diver leaves bottom
    uint64 reachSurfaceTime;     // Unix timestamp — diver reaches surface
    uint32 bottomTimeMinutes;    // Total bottom time in minutes
    uint32 maxDepth;              // Maximum depth attained (positive = below surface)
    uint32 averageDepth;          // Average depth (OPTIONAL, 0 = not recorded)
    DiveMode mode;               // Dive mode
    DivePurpose purpose;         // Purpose of the dive
    SuitType suit;               // Exposure suit type
}

Constraints:

  • maxDepth MUST be greater than 0 (the uint32 type enforces non-negative at the ABI level).
  • bottomTimeMinutes MUST be greater than 0.
  • leaveSurfaceTime, leaveBottomTime, reachSurfaceTime SHOULD be monotonically increasing.

Environment

struct Environment {
    int32 airTemp;               // Air temperature (unit per dive's UnitSystem)
    int32 waterTemp;             // Water temperature (unit per dive's UnitSystem)
    int16 currentKnots;          // Current speed in knots
    BottomType bottomType;       // Bottom composition
    Coordinates coords;          // Location coordinates
    string location;             // Human-readable dive location
    string weatherConditions;    // Weather description
}

All environment fields are OPTIONAL. Empty strings or zero values indicate not recorded.

Decompression

struct Decompression {
    DecompressionType decompType;
    uint32 totalDecompTimeMinutes;
    int32 maxDepthAttained;
    bytes32 tableSchedule;       // Table/schedule identifier (e.g., "USN 9-7")
    bytes1 repetitiveGroup;      // Repetitive group designator (ASCII letter)
    uint32 surfaceIntervalMinutes;
    bytes1 newRepetitiveGroup;
}

For no-decompression dives, decompType SHOULD be set to NoneDecomp and time fields to 0.

GasData

struct GasData {
    BreathingGas gasType;
    uint16 o2Percent;
    uint16 hePercent;
    uint16 n2Percent;
    uint32 cylinderPressureIn;
    uint32 cylinderPressureOut;
    uint32 gasConsumed;
    uint32 bailoutPressure;
}

Pressure fields store values in PSI when UnitSystem is Imperial, and in Bar when UnitSystem is Metric. Consumers MUST interpret pressure values according to the dive's declared UnitSystem.

DiveLog

struct DiveLog {
    uint256 id;
    uint64 diveDate;
    UnitSystem units;
    DiveData data;
    Environment env;
    Decompression decomp;
    GasData gas;
    string remarks;
}

DiveInput

Input struct for logDive and batchLogDives. Contains all fields needed to record a dive, excluding the assigned id.

struct DiveInput {
    uint64 diveDate;
    UnitSystem units;
    DiveData data;
    Environment env;
    Decompression decomp;
    GasData gas;
    string remarks;
}

VoidInfo

The corrective ledger structure. Used to void or supersede a dive record without deletion.

struct VoidInfo {
    uint256 supersededById;     // 0 = voided without replacement
    bool isVoided;
    address voidedBy;
    uint64 voidedAt;
    string reason;
}

Attestation

Cryptographic buddy/instructor sign-off record.

struct Attestation {
    address attester;
    uint64 attestedAt;
    uint256 nonce;
}

Interface Definition

/// @title ERC-8260 Dive Logbook Interface
///  Note: the ERC-165 identifier for this interface is 0x<to be calculated>.
interface IDiveLog is IERC165 {

    /// @dev Emitted when a new dive is logged.
    ///  The diveId is the sequential identifier assigned to the dive.
    ///  The diveDate is the Unix timestamp of the dive date.
    event DiveLogged(uint256 indexed diveId, uint64 indexed diveDate);

    /// @dev Emitted when a dive is voided or superseded.
    ///  supersededById is 0 if the dive is voided without replacement,
    ///  or the diveId of the correcting dive.
    event DiveVoided(
        uint256 indexed diveId,
        uint256 indexed supersededById,
        address indexed voidedBy,
        string reason
    );

    /// @dev Emitted when a third party cryptographically attests to a dive.
    ///  The attester is recovered from the EIP-712 signature, not msg.sender.
    event DiveAttested(uint256 indexed diveId, address indexed attester);

    /// @dev Thrown when a non-owner attempts a restricted operation.
    error NotOwner();

    /// @dev Thrown when maxDepth is zero.
    error InvalidDepth();

    /// @dev Thrown when bottomTimeMinutes is zero.
    error InvalidTimes();

    /// @dev Thrown when a dive lookup fails for the given diveId.
    error DiveNotFound(uint256 diveId);

    /// @dev Thrown when attempting to void an already-voided dive.
    error DiveAlreadyVoided(uint256 diveId);

    /// @dev Thrown when the supersede target is invalid.
    error InvalidSupersede(uint256 voidedId, uint256 supersededId);

    /// @dev Thrown when the same attester attempts to attest the same dive twice.
    error AlreadyAttested(uint256 diveId, address attester);

    /// @dev Thrown when an attestation signature cannot be recovered.
    error InvalidSignature();

    /// @dev Thrown when the attestation nonce does not match the expected value.
    error NonceMismatch(uint256 expected, uint256 provided);

    /// @notice Log a single dive record.
    /// @dev Throws unless msg.sender is the owner. Throws if maxDepth == 0
    ///  or bottomTimeMinutes == 0. Assigns a sequential diveId starting at 1.
    /// @param input The dive data to log
    /// @return diveId The sequential identifier assigned to this dive
    function logDive(DiveInput calldata input) external returns (uint256 diveId);

    /// @notice Log multiple dive records in a single transaction.
    /// @dev Throws unless msg.sender is the owner. Validates each dive
    ///  identically to logDive. Reverts the entire batch if any single dive
    ///  is invalid. Emits one DiveLogged per dive.
    function batchLogDives(DiveInput[] calldata inputs) external returns (uint256[] memory diveIds);

    /// @notice Void or supersede a dive record.
    /// @dev Throws unless msg.sender is the owner. The original dive data
    ///  remains readable via getDive even after voiding. A dive can only be
    ///  voided once. If supersededById is 0, the dive is voided without
    ///  replacement. If supersededById > 0, it MUST reference an existing
    ///  dive that is not the dive being voided.
    /// @param diveId The dive to void
    /// @param supersededById The dive that replaces this one, or 0 for void-only
    /// @param reason Human-readable explanation for the void
    function voidDive(
        uint256 diveId,
        uint256 supersededById,
        string calldata reason
    ) external;

    /// @notice Submit a cryptographic attestation for a dive.
    /// @dev The attester is recovered from the signature, not msg.sender.
    ///  Any address MAY submit the transaction. The signature MUST be a valid
    ///  EIP-712 signature over the Attestation type hash (see EIP-712 Typed
    ///  Data section). The nonce MUST match the attester's current on-chain
    ///  nonce (queriable via attesterNonce). After a successful attestation,
    ///  the attester's nonce is incremented. Throws if the dive does not exist,
    ///  has been voided, the nonce is incorrect, the signature is invalid,
    ///  or the attester has already attested.
    /// @param diveId The dive being attested
    /// @param nonce The attester's current nonce
    /// @param signature The EIP-712 signature from the attester (65 bytes)
    function attestDive(
        uint256 diveId,
        uint256 nonce,
        bytes calldata signature
    ) external;

    /// @notice Retrieve a single dive record by its identifier.
    /// @dev Throws if diveId is zero or exceeds the total dive count.
    ///  Returns the dive data even if the dive has been voided.
    function getDive(uint256 diveId) external view returns (DiveLog memory);

    /// @notice Get all dive identifiers logged on a specific date.
    /// @param date Unix timestamp of the dive date
    /// @return An array of diveId values
    function getDivesByDate(uint64 date) external view returns (uint256[] memory);

    /// @notice Retrieve multiple dive records by their identifiers.
    /// @dev Throws if any requested diveId is invalid.
    function getMultipleDives(uint256[] calldata diveIds) external view returns (DiveLog[] memory);

    /// @notice Get all dive identifiers in this logbook.
    /// @return An array of all sequential diveId values (1 through diveCount)
    function getAllDiveIds() external view returns (uint256[] memory);

    /// @notice Get the total number of dives logged.
    function getDiveCount() external view returns (uint256);

    /// @notice Check whether a dive has been voided.
    /// @dev Throws if diveId is invalid.
    function isDiveVoided(uint256 diveId) external view returns (bool);

    /// @notice Get the void/supersede information for a dive.
    /// @dev Throws if diveId is invalid. Returns a VoidInfo with
    ///  isVoided == false if the dive has not been voided.
    function getVoidInfo(uint256 diveId) external view returns (VoidInfo memory);

    /// @notice Get all attestations for a dive.
    /// @dev Throws if diveId is invalid. Returns an empty array if
    ///  no attestations exist.
    function getAttestations(uint256 diveId) external view returns (Attestation[] memory);

    /// @notice Get the current attestation nonce for an address.
    /// @param attester The address to query
    /// @return The current nonce value
    function attesterNonce(address attester) external view returns (uint256);

}

EIP-712 Typed Data

This standard defines EIP-712 typed data structures for attestation signature generation and verification. This ensures that a signature created by one application is valid when submitted via any other compliant application.

Domain

keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
// name: "DiveLog"
// version: "1"

The verifyingContract is the dive log contract address. The chainId is the chain ID where the contract is deployed. This prevents cross-contract and cross-chain replay.

Attestation Type

keccak256("Attestation(uint256 diveId,address verifyingContract,uint256 nonce)")

An attester signs the EIP-712 typed hash of this structure. The verifyingContract binds the attestation to a specific dive log contract. The nonce binds the attestation to a specific signing order, allowing attesters to invalidate pending signatures by advancing their on-chain nonce.

Full Dive Data Types (Off-Chain)

For hardware interoperability (e.g., dive computers signing complete dive profiles via Bluetooth/app gateways), the following EIP-712 types are defined:

keccak256("Coordinates(int32 latitude,int32 longitude)")

keccak256("DiveData(uint64 leaveSurfaceTime,uint64 leaveBottomTime,uint64 reachSurfaceTime,uint32 bottomTimeMinutes,uint32 maxDepth,uint32 averageDepth,uint8 mode,uint8 purpose,uint8 suit)")

keccak256("Environment(int32 airTemp,int32 waterTemp,int16 currentKnots,uint8 bottomType,Coordinates coords,string location,string weatherConditions)Coordinates(int32 latitude,int32 longitude)")

keccak256("Decompression(uint8 decompType,uint32 totalDecompTimeMinutes,int32 maxDepthAttained,bytes32 tableSchedule,bytes1 repetitiveGroup,uint32 surfaceIntervalMinutes,bytes1 newRepetitiveGroup)")

keccak256("GasData(uint8 gasType,uint16 o2Percent,uint16 hePercent,uint16 n2Percent,uint32 cylinderPressureIn,uint32 cylinderPressureOut,uint32 gasConsumed,uint32 bailoutPressure)")

keccak256("DiveLog(uint256 id,uint64 diveDate,uint8 units,DiveData data,Environment env,Decompression decomp,GasData gas,string remarks)Coordinates(int32 latitude,int32 longitude)Decompression(uint8 decompType,uint32 totalDecompTimeMinutes,int32 maxDepthAttained,bytes32 tableSchedule,bytes1 repetitiveGroup,uint32 surfaceIntervalMinutes,bytes1 newRepetitiveGroup)DiveData(uint64 leaveSurfaceTime,uint64 leaveBottomTime,uint64 reachSurfaceTime,uint32 bottomTimeMinutes,uint32 maxDepth,uint32 averageDepth,uint8 mode,uint8 purpose,uint8 suit)Environment(int32 airTemp,int32 waterTemp,int16 currentKnots,uint8 bottomType,Coordinates coords,string location,string weatherConditions)GasData(uint8 gasType,uint16 o2Percent,uint16 hePercent,uint16 n2Percent,uint32 cylinderPressureIn,uint32 cylinderPressureOut,uint32 gasConsumed,uint32 bailoutPressure)")

Dynamic types (string, bytes) within structs MUST be hashed as keccak256(abi.encode(...)) for the enclosing struct hash, using keccak256(bytes(value)) for string fields, as specified by EIP-712.

Behavior Requirements

Dive Logging

  • Dive identifiers MUST be sequential, starting at 1.
  • Dive records MUST be append-only. No deletion or modification of existing records is permitted.
  • logDive MUST revert with InvalidDepth if maxDepth == 0.
  • logDive MUST revert with InvalidTimes if bottomTimeMinutes == 0.
  • logDive MUST revert if diveDate is zero (the reference implementation reverts with InvalidDate).
  • logDive MUST emit DiveLogged with the assigned diveId and diveDate.
  • batchLogDives MUST validate each dive identically to logDive.
  • batchLogDives MUST revert the entire batch if any single dive is invalid. No partial writes.
  • batchLogDives MUST reject empty batches and MAY enforce a maximum batch size to bound transaction gas (the reference implementation reverts with InvalidBatchSize for empty batches or batches exceeding 100 dives).
  • batchLogDives MUST emit one DiveLogged event per successfully logged dive.

Corrective Ledger (Void/Supersede)

The corrective ledger does NOT allow edit or delete operations. Instead, it provides a "Void/Supersede" mechanism that preserves the complete audit trail.

  • voidDive MUST revert unless msg.sender is the owner.
  • voidDive MUST revert with DiveNotFound if diveId is invalid (zero or exceeds dive count).
  • voidDive MUST revert with DiveAlreadyVoided if the dive has been previously voided.
  • voidDive MUST revert with InvalidSupersede if supersededById is non-zero and references the same dive as diveId or references a nonexistent dive.
  • voidDive with supersededById == 0 marks the dive as voided without replacement.
  • voidDive with supersededById > 0 marks the dive as superseded by the specified dive.
  • voidDive MUST emit DiveVoided with all indexed fields.
  • The original dive data MUST remain readable via getDive even after voiding.
  • isDiveVoided MUST return true for voided dives.
  • getVoidInfo MUST return the full VoidInfo struct including the reason and superseding dive ID.

Cryptographic Attestation

  • attestDive MAY be callable by any address (the attester is recovered from the signature, not msg.sender).
  • attestDive MUST revert with DiveNotFound if the dive does not exist.
  • attestDive MUST revert with DiveAlreadyVoided if the dive has been voided.
  • attestDive MUST revert with NonceMismatch if the provided nonce does not match the attester's current on-chain nonce.
  • attestDive MUST revert with InvalidSignature if the signature cannot be recovered to a valid (non-zero) address.
  • attestDive MUST revert with AlreadyAttested if the recovered attester has previously attested this dive.
  • attestDive MUST increment the attester's on-chain nonce after a successful attestation.
  • attestDive MUST emit DiveAttested with the recovered attester address.
  • getAttestations MUST return all attestations for a dive in the order they were recorded.
  • attesterNonce MUST return the current nonce for the given address (starting at 0).

ERC-165 Compliance

  • Implementations MUST implement ERC-165 interface detection.
  • supportsInterface MUST return true when queried with the IDiveLog interface ID.
  • supportsInterface MUST return true when queried with the IERC165 interface ID (0x01ffc9a7).
  • supportsInterface MUST return false when queried with 0xffffffff.

Events

Events are designed for efficient off-chain indexing without a central registry:

EventIndexed FieldsUse Case
DiveLoggeddiveId, diveDateFilter dives by date across contracts
DiveVoideddiveId, supersededById, voidedByTrack void/supersede chains, filter by voiding authority
DiveAttesteddiveId, attesterFind all dives attested by a specific address

The emitting contract address is implicitly available in every event log, enabling per-diver filtering without an additional indexed field.

Rationale

Wallet-as-Identity

The contract uses the owner's wallet address as the sole on-chain identity. Personal information (name, age, biometrics) is deliberately excluded from on-chain storage. Linking a wallet address to a real-world identity is a KYC/process concern that belongs at the application layer — not in the contract. Front-ends and training agencies manage profiles through their own identity verification workflows, using wallet signatures to prove ownership. This keeps the contract privacy-preserving by design while remaining compatible with any identity system a consumer chooses to use.

Sovereign Interface (No Registry)

A central registry creates a single point of failure and a gatekeeper for the ecosystem. The sovereign model allows any entity — training agencies (PADI, NAUI), military units, commercial diving operators, or individual developers — to deploy compliant contracts without permission from a registry operator. ERC-165 interface detection allows any tool to verify compliance at the contract level. A diver presents their contract address and chain ID; any compliant application queries the IDiveLog interface and renders the full career.

Corrective Ledger (Void/Supersede)

Dive logs are safety records. Allowing modification or deletion undermines their evidentiary value. The void/supersede mechanism preserves the complete audit trail: the original dive remains readable, its void status is queryable, and the superseding dive is linked. This mirrors professional accounting practices where corrections are made via journal entries, not by erasing the ledger.

EIP-712 Attestations

EIP-712 provides a standardized, human-readable signing format. This ensures that a signature generated by one application (e.g., a dive instructor's mobile app) is verifiable by any other application (e.g., a dive boat's verification system). Without EIP-712, each application would define its own hashing scheme, breaking cross-application portability.

Attestation Nonces

Including a nonce in the attestation signature gives attesters a cancellation mechanism. Without it, a signed but unsubmitted attestation remains valid indefinitely — the attester has no way to revoke it. With nonces, an attester can advance their on-chain nonce (by submitting any attestation, including a self-attestation on a dummy dive or having the contract owner create a cancellation mechanism), invalidating all pending signatures tied to the old nonce. This follows the same pattern used by ERC-2612 permit nonces.

Structs Instead of Individual Fields

Grouping related data into structs (DiveInput, DiveData, Environment, Decompression, GasData) reduces the number of function parameters, improves readability, and maps cleanly to the military source forms (DD Form 2544, ENG Form 4615) which group fields into sections. Using a DiveInput struct for logDive and batchLogDives eliminates the risk of parameter misalignment that arises with parallel arrays or long parameter lists.

Dual Unit System

The U.S. military diving community operates in Imperial units (FSW, PSI, °F). International and scientific diving communities use Metric (MSW, bar, °C). Supporting both maximizes adoption. Per-dive unit declarations allow a single diver to mix conventions across dives without ambiguity.

On-Chain Storage vs. Events

Events are not reliably accessible from smart contracts and may be pruned by nodes. Struct storage in contract state ensures data is always retrievable by any on-chain or off-chain consumer.

Append-Only with Void Overlay

The core dive data is append-only (immutable). The void/supersede mechanism is an overlay that marks a dive as voided without modifying the underlying data. This separation keeps the core storage simple while allowing for error correction.

Separated Void and Attestation Queries

Void info and attestations are queried via dedicated functions (getVoidInfo, getAttestations) rather than embedded in the DiveLog struct. This keeps the core data structure compact and allows implementations to optimize storage independently.

Backwards Compatibility

This proposal does not conflict with any existing ERC standards. It does not modify or extend any existing token or interface standard.

Contracts implementing this standard MUST also implement ERC-165 interface detection.

Reference Implementation

A reference implementation is provided in the assets directory: SovereignDiveLog.sol. It implements the complete IDiveLog interface with:

  • Sequential dive ID assignment
  • Append-only storage with date indexing
  • Void/supersede corrective ledger
  • EIP-712 signature verification for attestations
  • Owner-restricted write operations

Security Considerations

Access control: Write operations (logDive, voidDive) are restricted to the contract owner. The owner is set at construction and SHOULD be immutable. Implementations deployed with upgradeable proxy patterns MUST preserve the invariant that only the designated owner can write.

No personal data on-chain: This standard deliberately stores no personally identifying information on-chain. No names, ages, biometrics, or other PII are part of the contract interface. The wallet address serves as the sole on-chain identity. Implementors SHOULD NEVER store PII on-chain. Consumers that require personal information (e.g., for regulatory compliance) MUST manage it off-chain through their own identity verification processes. Note: Free-text fields (location, remarks, bottomType, weatherConditions, reason in VoidInfo) are stored permanently on-chain. Implementors SHOULD warn divers that any personal information entered in these fields becomes irretrievably public.

Corrective ledger integrity: Void/supersede preserves immutable integrity. A voided dive cannot be un-voided. The original data is never modified or deleted, maintaining a complete audit trail.

Attestation security: EIP-712 signatures bind the attestation to a specific (diveId, verifyingContract, chainId, nonce) tuple, preventing replay attacks across contracts, chains, and signing orders. The nonce mechanism allows attesters to invalidate pending signatures by advancing their on-chain nonce without submitting the original attestation. The attester is recovered from the signature, not from msg.sender, so front-running an attestation transaction has no impact on the recorded attester identity.

Signature malleability: For any valid ECDSA signature (v, r, s), the tuple with s replaced by secp256k1n - s is also valid and recovers the same address. Implementations SHOULD reject non-canonical signatures where s exceeds half the secp256k1 group order, as required for canonical transactions by EIP-2. The reference implementation enforces this check in _recoverSigner; combined with the per-attester nonce and per-dive deduplication, malleated signatures cannot be replayed.

Gas costs: Storing a full DiveLog struct requires significant gas (approximately 350,000 gas per dive). Consumers SHOULD use batchLogDives for gas efficiency when logging multiple dives. Implementations on Layer 2 chains or chains with lower gas costs are RECOMMENDED for cost-sensitive use cases.

Immutable contracts: Implementations SHOULD be deployed without proxy upgradeability to guarantee the append-only property and the immutability of void records. If proxies are used, the implementation MUST NOT expose functions that modify or delete existing dive records.

Attestation on voided dives: The specification requires that attestDive reverts for voided dives. This prevents attestations on records that the diver has explicitly marked as incorrect.

Copyright and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth