BEDROCK-SERVICE-DECLARATION-PROTOCOL
| Field | Value |
|---|---|
| Name | Bedrock Service Declaration Protocol |
| Slug | 87 |
| Status | raw |
| Category | Standards Track |
| Editor | Marcin Pawlowski [email protected] |
| Contributors | Mehmet Gonen [email protected], Daniel Sanchez Quiros [email protected], Álvaro Castro-Castilla [email protected], Thomas Lavaur [email protected], Gusto Bacvinka [email protected], David Rusu [email protected], Filip Dimitrijevic [email protected] |
Timeline
- 2026-01-19 —
f24e567— Chore/updates mdbook (#262) - 2026-01-16 —
89f2ea8— Chore/mdbook updates (#258)
Abstract
This specification defines the Service Declaration Protocol (SDP), a mechanism enabling validators to declare their participation in specific protocols that require a known and agreed-upon list of participants. Examples include Data Availability (DA) and the Blend Network. SDP creates a single repository of identifiers used to establish secure communication between validators and provide services. Before being admitted to the repository, a validator proves that it has locked at least a minimum stake.
Keywords: service declaration, validator, stake, declaration, withdrawal, session, minimum stake, provider, locator, Blend Network, Data Availability
Semantics
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Definitions
| Terminology | Description |
|---|---|
| SDP | Service Declaration Protocol for node participation in Nomos Services. |
| Declaration | A message confirming a validator's willingness to provide a specific service. |
| Service Type | The type of service being declared (e.g., BN for Blend Network, DA for Data Availability). |
| Minimum Stake | The minimum amount of stake a node MUST lock to declare for a service. |
| Session | A fixed-length window defined per service via session_length. |
| Lock Period | The minimum time during which a declaration cannot be withdrawn. |
| Inactivity Period | The maximum time during which an activation message MUST be sent. |
| Retention Period | The time after which a declaration can be safely deleted. |
| Provider ID | An Ed25519 public key used to sign SDP messages and establish secure links. |
| ZK ID | A public key used for zero-knowledge operations including rewarding. |
| Locator | The network address of a validator following the multiaddr scheme. |
| Declaration ID | A unique identifier for a declaration, computed as a hash. |
Background
In many protocols, a known and agreed-upon list of participants is required. Examples include Data Availability and the Blend Network. SDP enables nodes to declare their eligibility to serve a specific service and withdraw their declarations.
Requirements
The protocol requirements are:
- A declaration MUST be backed by confirmation that the sender owns a certain value of stake.
- A declaration is valid until it is withdrawn or is not used for a service-specific amount of time.
Actions Overview
The protocol defines the following actions:
- Declare: A node sends a declaration confirming its willingness to provide a specific service, backed by locking a threshold of stake.
- Active: A node marks that its participation in the protocol is active according to the service-specific activity logic. This action enables the protocol to monitor the node's activity. It is crucial to exclude inactive nodes from the set of active nodes, as it enhances the stability of services.
- Withdraw: A node withdraws its declaration and stops providing a service.
Protocol Flow
-
A node sends a declaration message for a specific service and proves it has a minimum stake.
-
The declaration is registered on the blockchain ledger, and the node can commence its service according to the service-specific logic.
-
After a service-specific service-providing time, the node confirms its activity.
-
The node MUST confirm its activity with a service-specific minimum frequency; otherwise, its declaration is inactive.
-
After the service-specific locking period, the node can send a withdrawal message, and its declaration is removed from the blockchain ledger (after the necessary retention period), meaning the node will no longer provide the service.
Note: Protocol messages are subject to finality, meaning messages become part of the immutable ledger after a delay. The delay is defined by the consensus.
Protocol Specification
Service Types
The following services are defined for service declaration:
BN: Blend Network service.DA: Data Availability service.
class ServiceType(Enum):
BN = "BN" # Blend Network
DA = "DA" # Data Availability
A declaration can be generated for any of the services above. Any declaration that is not one of the above MUST be rejected. The number of services MAY grow in the future.
Minimum Stake
The minimum stake is a global value defining the minimum stake a node MUST have to perform any service.
The MinStake structure holds the value of the stake stake_threshold
and the block number at which it was set (timestamp).
class MinStake:
stake_threshold: StakeThreshold
timestamp: BlockNumber
The stake_thresholds structure aggregates all defined MinStake values:
stake_thresholds: list[MinStake]
Service Parameters
The service parameters structure defines the parameters necessary for handling interaction between the protocol and services. Each service type MUST be mapped to the following parameters:
session_length: The session length expressed as the number of blocks. Sessions are counted from blocktimestamp.lock_period: The minimum time (as a number of sessions) during which the declaration cannot be withdrawn. This time MUST include the period necessary for finalizing the declaration and provision of a service for at least a single session. It can be expressed as blocks by multiplying bysession_length.inactivity_period: The maximum time (as a number of sessions) during which an activation message MUST be sent; otherwise, the declaration is considered inactive. It can be expressed as blocks by multiplying bysession_length.retention_period: The time (as a number of sessions) after which the declaration can be safely deleted by the Garbage Collection mechanism. It can be expressed as blocks by multiplying bysession_length.timestamp: The block number at which the parameter was set.
class ServiceParameters:
session_length: NumberOfBlocks
lock_period: NumberOfSessions
inactivity_period: NumberOfSessions
retention_period: NumberOfSessions
timestamp: BlockNumber
The parameters structure aggregates all defined ServiceParameters values:
parameters: list[ServiceParameters]
Session Tracking
A session is a fixed-length window defined per service
via ServiceParameters.session_length.
The session length MUST be at least k, the consensus finality parameter.
Session numbers start at 0 and are computed as follows:
def get_session_number(current_block_number, service_parameters):
return current_block_number // service_parameters.session_length
At the start of session n,
each node takes a snapshot (get_snapshot_at_block) of the SDP registry
at a specified block height from the finalized part of the chain:
def get_session_snapshot(session_number, service_parameters):
if session_number < 2:
# Take the genesis block for the first two sessions
return get_snapshot_at_block(0)
# Take the last block of the previous session for the rest
return get_snapshot_at_block(
(session_number - 1) * service_parameters.session_length - 1
)
The function get_snapshot_at_block(block_number) returns the state
of the SDP registry at block_number,
including state changes made by that block.
This snapshot defines the declaration state for the session—
each snapshot updates the common view of the registry.
Changes to the declaration registry take effect with a one-session delay:
messages sent during session n are included
in the next snapshot (for session n+1).
Sessions 0 and 1 read the snapshot at block 0, because the chain has not yet progressed far enough to provide a later finalized block.
Identifiers
The following identifiers are used for service-specific cryptographic operations:
provider_id: Used to sign SDP messages and establish secure links between validators. It is anEd25519PublicKey.zk_id: Used for zero-knowledge operations by the validator, including rewarding (Zero Knowledge Signature Scheme).
Locators
A Locator is the address of a validator
used to establish secure communication between validators.
It follows the multiaddr addressing scheme from libp2p,
but it MUST contain only the location part
and MUST NOT contain the node identity (peer_id).
The provider_id MUST be used as the node identity.
Therefore, the Locator MUST be completed
by adding the provider_id at the end of it,
making the Locator usable in the context of libp2p.
The length of the Locator is restricted to 329 characters.
The syntax of every Locator entry MUST be validated.
Common formatting of every Locator MUST be applied
to maintain its unambiguity and make deterministic ID generation work consistently.
The Locator MUST at least contain only lowercase letters
and every part of the address MUST be explicit (no implicit defaults).
Declaration Message
The construction of the declaration message is as follows:
class DeclarationMessage:
service_type: ServiceType
locators: list[Locator]
provider_id: Ed25519PublicKey
locked_note_id: NoteId
zk_id: ZkPublicKey
The locators list length MUST be limited to reduce the potential for abuse.
The length of the list MUST NOT be longer than 8.
The message MUST be signed by the provider_id key
to prove ownership of the key used for network-level authentication.
The locked_note_id points to a locked note
used for minimum stake threshold verification purposes.
The message MUST also be signed by the zk_id key.
Declaration Storage
Only valid declaration messages can be stored on the blockchain ledger.
The DeclarationInfo structure is defined as follows:
class DeclarationInfo:
service: ServiceType
provider_id: Ed25519PublicKey
locked_note_id: NoteId
zk_id: ZkPublicKey
locators: list[Locator]
created: BlockNumber
active: BlockNumber
withdrawn: BlockNumber
nonce: Nonce
Where:
service: The service type of the declaration.provider_id: AnEd25519PublicKeyused to sign the message by the validator.locked_note_id: ANoteIdused for minimum stake threshold verification.zk_id: Used for zero-knowledge operations including rewarding.locators: A copy of the locators from theDeclarationMessage.created: The block number of the block that contained the declaration.active: The latest block number for which the active message was sent (set tocreatedby default).withdrawn: The block number for which the service declaration was withdrawn (set to 0 by default).nonce: MUST be set to 0 for the declaration message and MUST increase monotonically by every message sent for thedeclaration_id.
The declaration_id (of type DeclarationId)
is the unique identifier of DeclarationInfo,
calculated as a hash of the concatenation of
service, provider_id, zk_id, and locators.
The hash function implementation is blake2b using 256 bits of output:
declaration_id = Hash(service || provider_id || zk_id || locators)
The declaration_id is not stored as part of DeclarationInfo
but is used to index it.
All DeclarationInfo references are stored in declarations
and are indexed by declaration_id:
declarations: list[declaration_id]
Active Message
The construction of the active message is as follows:
class ActiveMessage:
declaration_id: DeclarationId
nonce: Nonce
metadata: Metadata
Where metadata is service-specific node activeness metadata.
The message MUST be signed by the zk_id key
associated with the declaration_id.
The nonce MUST increase monotonically
by every message sent for the declaration_id.
Withdraw Message
The construction of the withdraw message is as follows:
class WithdrawMessage:
declaration_id: DeclarationId
locked_note_id: NoteId
nonce: Nonce
The message MUST be signed by the zk_id key from the declaration_id.
The locked_note_id is a NoteId
that was used for minimum stake threshold verification purposes
and will be unlocked after withdrawal.
The nonce MUST increase monotonically
by every message sent for the declaration_id.
Indexing
Every event MUST be correctly indexed
to enable lighter synchronization of the changes.
Events are indexed by EventType, ServiceType, and Timestamp,
where EventType = { "created", "active", "withdrawn" }
corresponds to the type of message:
events = {
event_type: {
service_type: {
timestamp: {
declarations: list[declaration_id]
}
}
}
}
Protocol Actions
Declare
The Declare action associates a validator with a service it wants to provide.
It requires sending a valid DeclarationMessage,
which is then processed and stored.
The declaration message is considered valid when all of the following are met:
- The sender meets the stake requirements and its
locked_note_idis valid. - The
declaration_idis unique. - The sender knows the secret behind the
provider_ididentifier. - The length of the
locatorslist MUST NOT be longer than 8. - The
nonceincreases monotonically.
If all conditions are fulfilled, the message is stored on the blockchain ledger; otherwise, the message is discarded.
Active
The Active action enables marking the provider as actively providing a service.
It requires sending a valid ActiveMessage,
which is relayed to the service-specific node activity logic.
The Active action updates the active value of the DeclarationInfo,
which also activates inactive (but not expired) providers.
The SDP active action logic is:
-
A node sends an
ActiveMessagetransaction. -
The
ActiveMessageis verified by the SDP logic:- The
declaration_idreturns an existingDeclarationInfo. - The transaction containing
ActiveMessageis signed by thezk_id. - The
withdrawnfrom theDeclarationInfois set to zero. - The
nonceincreases monotonically.
- The
-
If any of these conditions fail, discard the message and stop processing.
-
The message is processed by the service-specific activity logic alongside the
activevalue indicating the period since the last active message was sent. Theactivevalue comes from theDeclarationInfo. -
If the service-specific activity logic approves the node active message, then the
activefield of theDeclarationInfois set to the current block height.
Withdraw
The Withdraw action enables withdrawal of a service declaration.
It requires sending a valid WithdrawMessage.
The withdrawal cannot happen before the end of the locking period,
defined as the number of blocks counted since created.
This lock period is stored as lock_period in the Service Parameters.
The logic of the withdraw action is:
-
A node sends a
WithdrawMessagetransaction. -
The
WithdrawMessageis verified by the SDP logic:- The
declaration_idreturns an existingDeclarationInfo. - The transaction containing
WithdrawMessageis signed by thezk_id. - The
withdrawnfromDeclarationInfois set to zero. - The
nonceincreases monotonically.
- The
-
If any of the above is not correct, discard the message and stop.
-
Set the
withdrawnfrom theDeclarationInfoto the current block height. -
Unlock the stake (release the
locked_note_id).
Garbage Collection
The protocol requires a garbage collection mechanism
that periodically removes unused DeclarationInfo entries.
The logic of garbage collection is:
For every DeclarationInfo in the declarations set,
remove the entry if either:
-
The entry is past the retention period:
withdrawn + (retention_period * session_length) < current_block_height. -
The entry is inactive beyond the inactivity and retention periods:
active + (inactivity_period + retention_period) * session_length < current_block_height.
Query Interface
The protocol MUST enable querying the blockchain ledger with at least the following queries:
GetAllProviderId(timestamp): Returns allprovider_ids associated with the timestamp.GetAllProviderIdSince(timestamp): Returns allprovider_ids since the timestamp.GetAllDeclarationInfo(timestamp): Returns allDeclarationInfoentries associated with the timestamp.GetAllDeclarationInfoSince(timestamp): Returns allDeclarationInfoentries since the timestamp.GetDeclarationInfo(provider_id): Returns theDeclarationInfoentry identified by theprovider_id.GetDeclarationInfo(declaration_id): Returns theDeclarationInfoentry identified by thedeclaration_id.GetAllServiceParameters(timestamp): Returns all entries of theServiceParametersstore for the timestamp.GetAllServiceParametersSince(timestamp): Returns all entries of theServiceParametersstore since the timestamp.GetServiceParameters(service_type, timestamp): Returns the service parameter entry for aservice_typeat a timestamp.GetMinStake(timestamp): Returns theMinStakestructure at the requested timestamp.GetMinStakeSince(timestamp): Returns a set ofMinStakestructures since the requested timestamp.
The query MUST return an error if the retention period for the declaration has passed and the requested information is not available.
The list of queries MAY be extended.
Every query MUST return information for a finalized state only.
Security Considerations
Stake Requirements
Validators MUST lock a minimum stake before declaring for a service. This prevents Sybil attacks by ensuring economic commitment to the network.
Message Authentication
All SDP messages MUST be cryptographically signed:
DeclarationMessageMUST be signed by bothprovider_idandzk_id.ActiveMessageMUST be signed byzk_id.WithdrawMessageMUST be signed byzk_id.
Nonce Monotonicity
The nonce MUST increase monotonically for each declaration_id
to prevent replay attacks.
Locator Validation
The syntax of every Locator entry MUST be validated
to prevent malformed addresses from being registered.
The length restriction of 329 characters
and the limit of 8 locators per declaration
prevent resource exhaustion attacks.
References
Normative
- BEDROCK-MANTLE-SPECIFICATION - Mantle Transaction and Operation specification
Informative
- Service Declaration Protocol - Original specification document
- libp2p multiaddr - Multiaddr addressing scheme
Copyright
Copyright and related rights waived via CC0.