Skip to content

Commit

Permalink
feat(finality): merkle-tree-based public randomness commitment (#354)
Browse files Browse the repository at this point in the history
Closes #350 

This PR implements Merkle tree-based public randomness commitment. This
includes

- reverting the BIP-32 thing that is vulnerable
- introducing storage of Merkle proofs 
- revising semantics of submitting public randomness and finality
signatures

Future works that will be done in subsequent PRs:

- **DB schema:** instead of storing all proofs (which is O(n log n)), we
need to find an efficient way to store Merkle tree and keep it in memory
when it's used.
- **Optimisation of proof generation/veirfictaion:** we are considering
to use pollarding of Merkle tree, i.e., babylon side remembers the top X
levels in the Merkle tree and the Merkle proof will become hashses in
the levels lower than X. see
https://github.com/wealdtech/go-merkletree/blob/master/pollard.go
- **Better vector commitment schemes:** Merkle tree has other variants
that have better performance in scenarios with millions of leaves.
Ethereum community has stuff like Verkle tree and SSZ
  • Loading branch information
SebastianElvis authored and maurolacy committed Jul 5, 2024
1 parent ab41ff7 commit 60cec01
Show file tree
Hide file tree
Showing 46 changed files with 1,796 additions and 1,203 deletions.
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ RUN go mod download
COPY ./ /go/src/github.com/babylonchain/finality-provider/

# Cosmwasm - Download correct libwasmvm version
RUN WASMVM_VERSION=$(go list -m github.com/CosmWasm/wasmvm | cut -d ' ' -f 2) && \
RUN WASMVM_VERSION=$(grep github.com/CosmWasm/wasmvm go.mod | cut -d' ' -f2) && \
wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/libwasmvm_muslc.$(uname -m).a \
-O /lib/libwasmvm_muslc.a && \
-O /lib/libwasmvm_muslc.$(uname -m).a && \
# verify checksum
wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/checksums.txt -O /tmp/checksums.txt && \
sha256sum /lib/libwasmvm_muslc.a | grep $(cat /tmp/checksums.txt | grep libwasmvm_muslc.$(uname -m) | cut -d ' ' -f 1)
sha256sum /lib/libwasmvm_muslc.$(uname -m).a | grep $(cat /tmp/checksums.txt | grep libwasmvm_muslc.$(uname -m) | cut -d ' ' -f 1)

RUN CGO_LDFLAGS="$CGO_LDFLAGS -lstdc++ -lm -lsodium" \
CGO_ENABLED=1 \
Expand Down
149 changes: 90 additions & 59 deletions clientcontroller/babylon.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,18 @@ import (
"time"

sdkErr "cosmossdk.io/errors"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"

"cosmossdk.io/math"
bbnclient "github.com/babylonchain/babylon/client/client"
bbntypes "github.com/babylonchain/babylon/types"
btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types"
btclctypes "github.com/babylonchain/babylon/x/btclightclient/types"
btcstakingtypes "github.com/babylonchain/babylon/x/btcstaking/types"
ckpttypes "github.com/babylonchain/babylon/x/checkpointing/types"
finalitytypes "github.com/babylonchain/babylon/x/finality/types"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
cmtcrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkquery "github.com/cosmos/cosmos-sdk/types/query"
Expand Down Expand Up @@ -110,55 +109,94 @@ func (bc *BabylonController) reliablySendMsgs(msgs []sdk.Msg, expectedErrs []*sd
}

// RegisterFinalityProvider registers a finality provider via a MsgCreateFinalityProvider to Babylon
// it returns tx hash, registered epoch, and error
// it returns tx hash and error
func (bc *BabylonController) RegisterFinalityProvider(
chainPk []byte,
fpPk *btcec.PublicKey,
pop []byte,
commission *math.LegacyDec,
description []byte,
masterPubRand string,
) (*types.TxResponse, uint64, error) {
) (*types.TxResponse, error) {
var bbnPop btcstakingtypes.ProofOfPossession
if err := bbnPop.Unmarshal(pop); err != nil {
return nil, 0, fmt.Errorf("invalid proof-of-possession: %w", err)
return nil, fmt.Errorf("invalid proof-of-possession: %w", err)
}

var sdkDescription sttypes.Description
if err := sdkDescription.Unmarshal(description); err != nil {
return nil, 0, fmt.Errorf("invalid description: %w", err)
return nil, fmt.Errorf("invalid description: %w", err)
}

msg := &btcstakingtypes.MsgCreateFinalityProvider{
Signer: bc.mustGetTxSigner(),
BabylonPk: &secp256k1.PubKey{Key: chainPk},
BtcPk: bbntypes.NewBIP340PubKeyFromBTCPK(fpPk),
Pop: &bbnPop,
Commission: commission,
Description: &sdkDescription,
MasterPubRand: masterPubRand,
Signer: bc.mustGetTxSigner(),
BabylonPk: &secp256k1.PubKey{Key: chainPk},
BtcPk: bbntypes.NewBIP340PubKeyFromBTCPK(fpPk),
Pop: &bbnPop,
Commission: commission,
Description: &sdkDescription,
}

res, err := bc.reliablySendMsg(msg, emptyErrs, emptyErrs)
if err != nil {
return nil, 0, err
return nil, err
}

return &types.TxResponse{TxHash: res.TxHash, Events: res.Events}, nil
}

// CommitPubRandList commits a list of Schnorr public randomness via a MsgCommitPubRand to Babylon
// it returns tx hash and error
func (bc *BabylonController) CommitPubRandList(
fpPk *btcec.PublicKey,
startHeight uint64,
numPubRand uint64,
commitment []byte,
sig *schnorr.Signature,
) (*types.TxResponse, error) {
msg := &finalitytypes.MsgCommitPubRandList{
Signer: bc.mustGetTxSigner(),
FpBtcPk: bbntypes.NewBIP340PubKeyFromBTCPK(fpPk),
StartHeight: startHeight,
NumPubRand: numPubRand,
Commitment: commitment,
Sig: bbntypes.NewBIP340SignatureFromBTCSig(sig),
}

registeredEpoch, err := bc.QueryFinalityProviderRegisteredEpoch(fpPk)
unrecoverableErrs := []*sdkErr.Error{
finalitytypes.ErrInvalidPubRand,
finalitytypes.ErrTooFewPubRand,
finalitytypes.ErrNoPubRandYet,
btcstakingtypes.ErrFpNotFound,
}

res, err := bc.reliablySendMsg(msg, emptyErrs, unrecoverableErrs)
if err != nil {
return nil, 0, err
return nil, err
}

return &types.TxResponse{TxHash: res.TxHash, Events: res.Events}, registeredEpoch, nil
return &types.TxResponse{TxHash: res.TxHash, Events: res.Events}, nil
}

// SubmitFinalitySig submits the finality signature via a MsgAddVote to Babylon
func (bc *BabylonController) SubmitFinalitySig(fpPk *btcec.PublicKey, blockHeight uint64, blockHash []byte, sig *btcec.ModNScalar) (*types.TxResponse, error) {
func (bc *BabylonController) SubmitFinalitySig(
fpPk *btcec.PublicKey,
block *types.BlockInfo,
pubRand *btcec.FieldVal,
proof []byte, // TODO: have a type for proof
sig *btcec.ModNScalar,
) (*types.TxResponse, error) {
cmtProof := cmtcrypto.Proof{}
if err := cmtProof.Unmarshal(proof); err != nil {
return nil, err
}

msg := &finalitytypes.MsgAddFinalitySig{
Signer: bc.mustGetTxSigner(),
FpBtcPk: bbntypes.NewBIP340PubKeyFromBTCPK(fpPk),
BlockHeight: blockHeight,
BlockAppHash: blockHash,
BlockHeight: block.Height,
PubRand: bbntypes.NewSchnorrPubRandFromFieldVal(pubRand),
Proof: &cmtProof,
BlockAppHash: block.Hash,
FinalitySig: bbntypes.NewSchnorrEOTSSigFromModNScalar(sig),
}

Expand All @@ -177,17 +215,30 @@ func (bc *BabylonController) SubmitFinalitySig(fpPk *btcec.PublicKey, blockHeigh
}

// SubmitBatchFinalitySigs submits a batch of finality signatures to Babylon
func (bc *BabylonController) SubmitBatchFinalitySigs(fpPk *btcec.PublicKey, blocks []*types.BlockInfo, sigs []*btcec.ModNScalar) (*types.TxResponse, error) {
func (bc *BabylonController) SubmitBatchFinalitySigs(
fpPk *btcec.PublicKey,
blocks []*types.BlockInfo,
pubRandList []*btcec.FieldVal,
proofList [][]byte,
sigs []*btcec.ModNScalar,
) (*types.TxResponse, error) {
if len(blocks) != len(sigs) {
return nil, fmt.Errorf("the number of blocks %v should match the number of finality signatures %v", len(blocks), len(sigs))
}

msgs := make([]sdk.Msg, 0, len(blocks))
for i, b := range blocks {
cmtProof := cmtcrypto.Proof{}
if err := cmtProof.Unmarshal(proofList[i]); err != nil {
return nil, err
}

msg := &finalitytypes.MsgAddFinalitySig{
Signer: bc.mustGetTxSigner(),
FpBtcPk: bbntypes.NewBIP340PubKeyFromBTCPK(fpPk),
BlockHeight: b.Height,
PubRand: bbntypes.NewSchnorrPubRandFromFieldVal(pubRandList[i]),
Proof: &cmtProof,
BlockAppHash: b.Hash,
FinalitySig: bbntypes.NewSchnorrEOTSSigFromModNScalar(sigs[i]),
}
Expand Down Expand Up @@ -227,34 +278,32 @@ func (bc *BabylonController) QueryFinalityProviderVotingPower(fpPk *btcec.Public
blockHeight,
)
if err != nil {
return 0, fmt.Errorf("failed to query the finality provider's voting power at height %d: %w", blockHeight, err)
return 0, fmt.Errorf("failed to query BTC delegations: %w", err)
}

return res.VotingPower, nil
}

// QueryFinalityProviderRegisteredEpoch queries the registered epoch of the finality provider
func (bc *BabylonController) QueryFinalityProviderRegisteredEpoch(fpPk *btcec.PublicKey) (uint64, error) {
res, err := bc.bbnClient.QueryClient.FinalityProvider(
bbntypes.NewBIP340PubKeyFromBTCPK(fpPk).MarshalHex(),
)
if err != nil {
return 0, fmt.Errorf("failed to query finality provider registered epoch: %w", err)
}

return res.FinalityProvider.RegisteredEpoch, nil
}

func (bc *BabylonController) QueryLatestFinalizedBlocks(count uint64) ([]*types.BlockInfo, error) {
return bc.queryLatestBlocks(nil, count, finalitytypes.QueriedBlockStatus_FINALIZED, true)
}

func (bc *BabylonController) QueryLastFinalizedEpoch() (uint64, error) {
resp, err := bc.bbnClient.LatestEpochFromStatus(ckpttypes.Finalized)
// QueryLastCommittedPublicRand returns the last public randomness commitments
func (bc *BabylonController) QueryLastCommittedPublicRand(fpPk *btcec.PublicKey, count uint64) (map[uint64]*finalitytypes.PubRandCommitResponse, error) {
fpBtcPk := bbntypes.NewBIP340PubKeyFromBTCPK(fpPk)

pagination := &sdkquery.PageRequest{
// NOTE: the count is limited by pagination queries
Limit: count,
Reverse: true,
}

res, err := bc.bbnClient.QueryClient.ListPubRandCommit(fpBtcPk.MarshalHex(), pagination)
if err != nil {
return 0, err
return nil, fmt.Errorf("failed to query committed public randomness: %w", err)
}
return resp.RawCheckpoint.EpochNum, nil

return res.PubRandCommitMap, nil
}

func (bc *BabylonController) QueryBlocks(startHeight, endHeight, limit uint64) ([]*types.BlockInfo, error) {
Expand Down Expand Up @@ -359,10 +408,6 @@ func (bc *BabylonController) Close() error {
Implementations for e2e tests only
*/

func (bc *BabylonController) GetBBNClient() *bbnclient.Client {
return bc.bbnClient
}

func (bc *BabylonController) CreateBTCDelegation(
delBabylonPk *secp256k1.PubKey,
delBtcPk *bbntypes.BIP340PubKey,
Expand Down Expand Up @@ -550,17 +595,3 @@ func (bc *BabylonController) SubmitCovenantSigs(

return &types.TxResponse{TxHash: res.TxHash, Events: res.Events}, nil
}

func (bc *BabylonController) InsertSpvProofs(submitter string, proofs []*btcctypes.BTCSpvProof) (*provider.RelayerTxResponse, error) {
msg := &btcctypes.MsgInsertBTCSpvProof{
Submitter: submitter,
Proofs: proofs,
}

res, err := bc.reliablySendMsg(msg, emptyErrs, emptyErrs)
if err != nil {
return nil, err
}

return res, nil
}
19 changes: 12 additions & 7 deletions clientcontroller/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (

"cosmossdk.io/math"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg"
"go.uber.org/zap"

finalitytypes "github.com/babylonchain/babylon/x/finality/types"
fpcfg "github.com/babylonchain/finality-provider/finality-provider/config"
"github.com/babylonchain/finality-provider/types"
)
Expand All @@ -26,14 +28,17 @@ type ClientController interface {
pop []byte,
commission *math.LegacyDec,
description []byte,
masterPubRand string,
) (*types.TxResponse, uint64, error)
) (*types.TxResponse, error)

// CommitPubRandList commits a list of EOTS public randomness the consumer chain
// it returns tx hash and error
CommitPubRandList(fpPk *btcec.PublicKey, startHeight uint64, numPubRand uint64, commitment []byte, sig *schnorr.Signature) (*types.TxResponse, error)

// SubmitFinalitySig submits the finality signature to the consumer chain
SubmitFinalitySig(fpPk *btcec.PublicKey, blockHeight uint64, blockHash []byte, sig *btcec.ModNScalar) (*types.TxResponse, error)
SubmitFinalitySig(fpPk *btcec.PublicKey, block *types.BlockInfo, pubRand *btcec.FieldVal, proof []byte, sig *btcec.ModNScalar) (*types.TxResponse, error)

// SubmitBatchFinalitySigs submits a batch of finality signatures to the consumer chain
SubmitBatchFinalitySigs(fpPk *btcec.PublicKey, blocks []*types.BlockInfo, sigs []*btcec.ModNScalar) (*types.TxResponse, error)
SubmitBatchFinalitySigs(fpPk *btcec.PublicKey, blocks []*types.BlockInfo, pubRandList []*btcec.FieldVal, proofList [][]byte, sigs []*btcec.ModNScalar) (*types.TxResponse, error)

// Note: the following queries are only for PoC

Expand All @@ -46,6 +51,9 @@ type ClientController interface {
// QueryLatestFinalizedBlocks returns the latest finalized blocks
QueryLatestFinalizedBlocks(count uint64) ([]*types.BlockInfo, error)

// QueryLastCommittedPublicRand returns the last committed public randomness
QueryLastCommittedPublicRand(fpPk *btcec.PublicKey, count uint64) (map[uint64]*finalitytypes.PubRandCommitResponse, error)

// QueryBlock queries the block at the given height
QueryBlock(height uint64) (*types.BlockInfo, error)

Expand All @@ -59,9 +67,6 @@ type ClientController interface {
// error will be returned if the consumer chain has not been activated
QueryActivatedHeight() (uint64, error)

// QueryLastFinalizedEpoch returns the last finalised epoch of Babylon
QueryLastFinalizedEpoch() (uint64, error)

Close() error
}

Expand Down
25 changes: 17 additions & 8 deletions eotsmanager/client/rpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,27 @@ func (c *EOTSManagerGRpcClient) CreateKey(name, passphrase, hdPath string) ([]by
return res.Pk, nil
}

func (c *EOTSManagerGRpcClient) CreateMasterRandPair(uid, chainID []byte, passphrase string) (string, error) {
req := &proto.CreateMasterRandPairRequest{
Uid: uid,
ChainId: chainID,
Passphrase: passphrase,
func (c *EOTSManagerGRpcClient) CreateRandomnessPairList(uid, chainID []byte, startHeight uint64, num uint32, passphrase string) ([]*btcec.FieldVal, error) {
req := &proto.CreateRandomnessPairListRequest{
Uid: uid,
ChainId: chainID,
StartHeight: startHeight,
Num: num,
Passphrase: passphrase,
}
res, err := c.client.CreateMasterRandPair(context.Background(), req)
res, err := c.client.CreateRandomnessPairList(context.Background(), req)
if err != nil {
return "", err
return nil, err
}

pubRandFieldValList := make([]*btcec.FieldVal, 0, len(res.PubRandList))
for _, r := range res.PubRandList {
var fieldVal btcec.FieldVal
fieldVal.SetByteSlice(r)
pubRandFieldValList = append(pubRandFieldValList, &fieldVal)
}

return res.MasterPubRand, nil
return pubRandFieldValList, nil
}

func (c *EOTSManagerGRpcClient) KeyRecord(uid []byte, passphrase string) (*types.KeyRecord, error) {
Expand Down
11 changes: 7 additions & 4 deletions eotsmanager/eotsmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ type EOTSManager interface {
// It fails if there is an existing key Info with the same name or public key.
CreateKey(name, passphrase, hdPath string) ([]byte, error)

// CreateMasterRandPair generates a pair of master secret/public randomness
// It fails if the finality provider does not exist or passPhrase is incorrect
// NOTE: the master randomness pair is deterministically generated based on the EOTS key and chainID
CreateMasterRandPair(uid []byte, chainID []byte, passphrase string) (string, 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
Expand Down
Loading

0 comments on commit 60cec01

Please sign in to comment.