Timekeeper Attestations
How timekeepers attest to lease state
Lease acceptance and settlement require timekeeper attestations — signed timestamps from trusted nodes that prove when an event occurred. This prevents providers from manipulating start or settle times to shorten lease durations or claim early rewards.
#Overview
Timekeepers are trusted nodes that can each independently provide a signed timestamp. Any node in the network can serve as a timekeeper — the trust is established by listing its public key in the sys.timekeepers state chain entry.
Both lease_accept and lease_settle blocks carry an array of attestations. The ledger validates that a threshold of distinct trusted timekeepers have attested a timestamp within an acceptable skew window, then uses the median timestamp as the canonical time.
#Why require multiple attestations?
A single trusted timekeeper is sufficient to prove when an event occurred. The threshold exists as a defense against a compromised timekeeper.
Timestamps directly control XE emission: the attested start time on lease_accept and the attested end time on lease_settle determine how long a lease ran, which determines how much XE is minted. A compromised timekeeper could sign a far-future settle timestamp, making a lease appear to have run longer than it did and minting more XE than earned.
The MaxAttestationSkew (10 minutes) limits how far any single attestation can deviate from wall clock time, but within that window there is still room for manipulation — especially on short leases. Requiring a majority (currently 2-of-3) and taking the median ensures that a single compromised timekeeper cannot bias the canonical timestamp at all. To manipulate the median, an attacker would need to compromise a majority of timekeepers simultaneously.
This is a conservative design choice. The nodes are operator-controlled trusted infrastructure, so compromise of a single node is already a serious incident. The threshold adds defense-in-depth, not trustlessness.
Provider Node Timekeepers │ │ │ │ │ attest_timestamp request │ │ │ │──────────────────────────►│ │ │ │──────────────────────────────►│ │ │─────────────────────────────────►│ │ │ │ │ │ signed attestation │ │ │ │◄──────────────────────────│ │ │ │◄────────────────────────────│ │ │◄───────────────────────────────│ │ │ │ │ │ attach to block │ │ │ │ (excluded from hash) │ │ │#TimekeeperAttestation struct
type TimekeeperAttestation struct { PublicKey string // hex-encoded ed25519 public key of the timekeeper Timestamp int64 // unix nanoseconds attested Signature string // hex-encoded ed25519 signature over the attestation payload}#TimekeeperConfig struct
type TimekeeperConfig struct { Keys []string // hex-encoded ed25519 public keys of trusted timekeepers Threshold int // number of valid attestations required (quorum)}The timekeeper configuration is stored in the state chain under the sys.timekeepers key. It must be present in the genesis block for any network that supports compute leases. The current testnet uses the 3 bootstrap nodes as timekeepers with a threshold of 2.
#Attestation payload
The signed payload is a SHA-256 hash of the lease hash concatenated with the timestamp:
payload = sha256(lease_hash_bytes || timestamp_big_endian_8_bytes)Where:
lease_hash_bytesis the 32-byte decoded hex of the original lease block's hash.timestamp_big_endian_8_bytesis the 8-byte big-endian encoding of the unix nanosecond timestamp.
func AttestationPayload(leaseHash string, timestamp int64) ([]byte, error) { hashBytes, _ := hex.DecodeString(leaseHash) // 32 bytes var ts [8]byte binary.BigEndian.PutUint64(ts[:], uint64(timestamp)) h := sha256.New() h.Write(hashBytes) h.Write(ts[:]) return h.Sum(nil), nil}#Core functions
| Function | Description |
|---|---|
AttestationPayload(leaseHash, timestamp) | Computes the SHA-256 payload to be signed |
SignAttestation(leaseHash, timestamp, keyPair) | Creates a signed attestation using an ed25519 key pair |
VerifyAttestation(attestation, leaseHash) | Verifies a single attestation's signature |
ValidateAttestations(attestations, leaseHash, config) | Validates quorum and returns the median timestamp |
#Validation rules
ValidateAttestations enforces the following:
| Rule | Detail |
|---|---|
| Quorum | At least config.Threshold valid attestations from distinct trusted keys |
| Trusted keys only | Attestation's PublicKey must be in config.Keys |
| No duplicates | Each trusted key counted at most once |
| Signature valid | ed25519.Verify must pass for the attestation payload |
| Skew limit | abs(attestation.Timestamp - now) must be <= MaxAttestationSkew (10 minutes) |
| Array cap | At most MaxAttestationsPerBlock (20) attestations per block |
#Median timestamp
After filtering to valid attestations, the median is used as the canonical timestamp rather than the mean or any single value:
sort.Slice(validTimestamps, func(i, j int) bool { return validTimestamps[i] < validTimestamps[j]})median := validTimestamps[(len(validTimestamps)-1)/2]For even counts, the lower-middle value is used. This is deliberately conservative — it underestimates time rather than overestimates. Since timestamps control XE emission (later settle time = more XE minted), underestimating is the safe direction. A single compromised timekeeper contributing a high timestamp cannot shift the lower-middle median upward.
#Attestation gathering
The node gathers attestations by sending attest_timestamp direct messages to all connected peers in parallel:
- Self-sign -- if the node itself is a trusted timekeeper, it signs locally first.
- Fan-out -- sends
AttestationRequest{LeaseHash}to all connected peers concurrently. - Collect -- waits up to 15 seconds (
AttestationGatherMax) for responses. - Check threshold -- returns an error if fewer than
config.Thresholdattestations were gathered.
#Rate limiting
Timekeeper nodes rate-limit attestation requests: one attestation per peer per lease per 30 seconds (AttestationRateLimit). This prevents flooding.
#Timekeeper endpoint
Any node can act as a timekeeper. When a node receives an attest_timestamp message, it:
- Validates the identifier is a 64-character hex string (lease hash or provider address).
- Checks the rate limit for the requesting peer.
- Signs an attestation with the current time and the node's key pair.
- Returns the signed attestation.
#Constants
const ( MaxAttestationSkew = 10 * time.Minute // max allowed timestamp drift MaxAttestationsPerBlock = 20 // hard cap per block AttestationTimeout = 10 * time.Second // per-peer request timeout AttestationGatherMax = 15 * time.Second // total gather deadline AttestationRateLimit = 30 * time.Second // per-peer-per-lease cooldown)