Overview

Finality providers are responsible for voting at a finality round on top of the consumer system. A finality provider can receive voting power delegations from BTC stakers, and can earn commission from the staking rewards denominated in the consumer’s native token.

A finality provider connects to a Extractable One-Time Signature (EOTS) manager, a daemon responsible for securely maintaining the finality provider’s private key and producing EOTS signatures from it. BTC staking litepaper contains more details about EOTS.

EOTS key manager daemon

EOTS key manager runs as a stand-alone daemon. It generates and manages the finality providers' BTC Schnorr secret keys.

Interfaces

The EOTS manager needs to expose the following interfaces that can be called by a finality provider:

type EOTSManager interface {
	// CreateKey generates a key pair at the given name and persists it in storage.
	// The key pair is formatted by BIP-340 (Schnorr Signatures)
	// It fails if there is an existing key Info with the same name or public key.
	CreateKey(name, passphrase, hdPath string) ([]byte, error)

	// CreateRandomnessPairList generates a list of Schnorr randomness pairs from
	// startHeight to startHeight+(num-1) where num means the number of public randomness
	// It fails if the finality provider does not exist or a randomness pair has been created before
	// or passPhrase is incorrect
	// NOTE: the randomness is deterministically generated based on the EOTS key, chainID and
	// block height
	CreateRandomnessPairList(uid []byte, chainID []byte, startHeight uint64, num uint32, passphrase string) ([]*btcec.FieldVal, error)

	// KeyRecord returns the finality provider record
	// It fails if the finality provider does not exist or passPhrase is incorrect
	KeyRecord(uid []byte, passphrase string) (*types.KeyRecord, error)

	// SignEOTS signs an EOTS using the private key of the finality provider and the corresponding
	// secret randomness of the give chain at the given height
	// It fails if the finality provider does not exist or there's no randomness committed to the given height
	// or passPhrase is incorrect
	SignEOTS(uid []byte, chainID []byte, msg []byte, height uint64, passphrase string) (*btcec.ModNScalar, error)

	// SignSchnorrSig signs a Schnorr signature using the private key of the finality provider
	// It fails if the finality provider does not exist or the message size is not 32 bytes
	// or passPhrase is incorrect
	SignSchnorrSig(uid []byte, msg []byte, passphrase string) (*schnorr.Signature, error)

	Close() error
}

The implementations of the above interfaces depend on the actual consumer. For example, if the consumer is a Cosmos SDK based blockchain, then we need specific packages to provide the above RPC endpoints.

Reference implementations

Babylon team provides an EOTS manager implementation at https://github.com/babylonchain/finality-provider/tree/dev/eotsmanager. It is fully compliant to the interfaces defined above.

Finality provider daemon

The finality provider daemon is responsible for monitoring for new blocks in the consumer system, committing public randomness for the blocks, and submitting finality signatures (i.e., EOTS signatures). In particular, a finality provider daemon has the following functionalities:

  1. Creation and registration: Creates and registers finality providers to the consumer system.
  2. Public randomness commitment: The daemon monitors the consumer system and commits EOTS public randomness for every block it intends to vote for. The commit intervals can be a parameter in the configuration. The EOTS public randomness is retrieved through the finality provider daemon's connection with the EOTS daemon.
  3. Finality signature Submission: The daemon monitors the consumer system and produces finality signatures for each block it has committed public randomness to. The finality signature is retrieved through the finality provider daemon's connection with the EOTS key manager daemon.

Suggested KV store format

The finality provider daemon needs to maintain a KV store of its managed finality providers. The key is the finality provider’s Bitcoin public key, and the value is the finality provider object defined as follows:


message StoreFinalityProvider {
    // consumer_pk is the consumer PK of this finality provider in consumer
    bytes consumer_pk = 1;
    // btc_pk is the BTC secp256k1 PK of the finality provider encoded in BIP-340 spec
    bytes btc_pk = 2;
    // description defines the description terms for the finality provider
    bytes description = 3;
    // commission defines the commission rate for the finality provider
    string commission = 4 [
        (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
        (gogoproto.nullable)   = false
    ];
    // pop is the proof of possession of consumer_pk and btc_pk
    ProofOfPossession pop = 5;
    // key_name is the identifier of the keyring
    string key_name = 6;
    // chain_id is the identifier of the consumer chain that the finality provider connected to
    string chain_id = 7;
    // last_voted_height defines the height of the last voted consumer block
    uint64 last_voted_height = 8;
    // status defines the current finality provider status
    FinalityProviderStatus status = 9;
}

// Description defines description fields for a finality provider
message Description {
    string moniker = 1;
    string identity = 2;
    string website = 3;
    string security_contact = 4;
    string details = 5;
}

// ProofOfPossession is the proof of possession that a consumer
// secret key and a Bitcoin secp256k1 secret key are held by the same
// person
message ProofOfPossession {
    // consumer_sig is the signature generated via sign(sk_consumer, pk_btc)
    bytes consumer_sig = 1;
    // btc_sig is the signature generated via sign(sk_btc, consumer_sig)
    // the signature follows encoding in BIP-340 spec
    bytes btc_sig = 2;
}

// FinalityProviderStatus is the status of a finality provider
// a FinalityProvider object has 4 states:
//  - Created - created and managed by finality provider client, not registered to
//  consumer yet
//  - Registered - created and registered to consumer, but not voting yet (No
//  delegated stake)
//  - Active - created and registered to consumer with stake to vote
//  - Inactive - created and registered to consumer with no stake to vote.
//  Finality Provider was already active.
// Valid State Transactions:
//  - Created   -> Registered
//  - Registered -> Active
//  - Active    -> Inactive
//  - Inactive  -> Active
enum FinalityProviderStatus {
    option (gogoproto.goproto_enum_prefix) = false;

    // CREATED defines a finality provider that is awaiting registration
    CREATED = 0 [(gogoproto.enumvalue_customname) = "CREATED"];
    // REGISTERED defines a finality provider that has been registered
    // to Babylon but has no delegated stake
    REGISTERED = 1 [(gogoproto.enumvalue_customname) = "REGISTERED"];
    // ACTIVE defines a finality provider that is delegated to vote
    ACTIVE = 2 [(gogoproto.enumvalue_customname) = "ACTIVE"];
    // INACTIVE defines a finality provider whose delegations are reduced to zero but not slashed
    INACTIVE = 3 [(gogoproto.enumvalue_customname) = "INACTIVE"];
    // SLASHED defines a finality provider that has been slashed
    SLASHED = 4 [(gogoproto.enumvalue_customname) = "SLASHED"];
}