diff --git a/cmd/createcluster.go b/cmd/createcluster.go index 0faa3b545..61c795901 100644 --- a/cmd/createcluster.go +++ b/cmd/createcluster.go @@ -26,6 +26,7 @@ import ( "strings" "text/template" + eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/coinbase/kryptology/pkg/signatures/bls/bls_sig" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/p2p/enode" @@ -142,7 +143,7 @@ func bindClusterFlags(flags *pflag.FlagSet, config *clusterConfig) { flags.IntVar(&config.ConfigPortStart, "config-port-start", 16000, "Starting port number used in config files. Requires --config.") } -func runCreateCluster(w io.Writer, conf clusterConfig) error { +func runCreateCluster(w io.Writer, conf clusterConfig) error { //nolint:gocognit if conf.Clean { // Remove previous directories if err := os.RemoveAll(conf.ClusterDir); err != nil { @@ -210,7 +211,11 @@ func runCreateCluster(w io.Writer, conf clusterConfig) error { } } - var depositDatas [][]byte + // TODO(xenowits): add flag to specify the number of distributed validators in a cluster + // Currently, we assume that we create a cluster of ONLY 1 Distributed Validator + var pubkeys []eth2p0.BLSPubKey + var msgSigs []eth2p0.BLSSignature + for i := 0; i < numDVs; i++ { sk := secrets[i] // Secret key for this DV pk, err := sk.GetPublicKey() @@ -244,15 +249,12 @@ func runCreateCluster(w io.Writer, conf clusterConfig) error { } sigEth2 := tblsconv.SigToETH2(sig) - bytes, err := deposit.MarshalDepositData(pubkey, withdrawalAddr, conf.Network, sigEth2) - if err != nil { - return err - } - depositDatas = append(depositDatas, bytes) + pubkeys = append(pubkeys, pubkey) + msgSigs = append(msgSigs, sigEth2) } - if err := writeDepositData(conf, depositDatas); err != nil { + if err := writeDepositData(conf, pubkeys, msgSigs, conf.WithdrawalAddr, conf.Network); err != nil { return err } @@ -344,10 +346,11 @@ func getKeys(conf clusterConfig, numDVs int) ([]*bls_sig.SecretKey, error) { } // writeDepositData writes deposit data to disk for the DVs in a cluster. -func writeDepositData(config clusterConfig, b [][]byte) error { +func writeDepositData(config clusterConfig, pubkeys []eth2p0.BLSPubKey, msgSigs []eth2p0.BLSSignature, withdrawalAddr, network string) error { depositPath := path.Join(config.ClusterDir, "deposit-data.json") - bytes, err := deposit.MarshalDepositDatas(b) + // serialize the deposit data into bytes + bytes, err := deposit.MarshalDepositData(pubkeys, msgSigs, withdrawalAddr, network) if err != nil { return err } diff --git a/eth2util/deposit/deposit.go b/eth2util/deposit/deposit.go index 054eb5b23..c8b3fa422 100644 --- a/eth2util/deposit/deposit.go +++ b/eth2util/deposit/deposit.go @@ -38,6 +38,8 @@ var ( // DOMAIN_DEPOSIT. See spec: https://benjaminion.xyz/eth2-annotated-spec/phase0/beacon-chain/#domain-types depositDomainType = eth2p0.DomainType([4]byte{0x03, 0x00, 0x00, 0x00}) + + depositCliVersion = "2.1.0" ) // getMessageRoot returns a deposit message hash root created by the parameters. @@ -60,62 +62,47 @@ func getMessageRoot(pubkey eth2p0.BLSPubKey, withdrawalAddr string) (eth2p0.Root return hashRoot, nil } -// MarshalDepositData returns a json serialized deposit data. -func MarshalDepositData(pubkey eth2p0.BLSPubKey, withdrawalAddr string, network string, msgSig eth2p0.BLSSignature) ([]byte, error) { - forkVersion := networkToForkVersion(network) - +// MarshalDepositData serializes a list of deposit data into a single file. +func MarshalDepositData(pubkeys []eth2p0.BLSPubKey, msgSigs []eth2p0.BLSSignature, withdrawalAddr, network string) ([]byte, error) { creds, err := withdrawalCredsFromAddr(withdrawalAddr) if err != nil { return nil, err } - // calculate depositMessage root - msgRoot, err := getMessageRoot(pubkey, withdrawalAddr) - if err != nil { - return nil, err - } - - // TODO(corver): Verify signature matches msgRoot. - - dd := eth2p0.DepositData{ - PublicKey: pubkey, - WithdrawalCredentials: creds[:], - Amount: validatorAmt, - Signature: msgSig, - } - dataRoot, err := dd.HashTreeRoot() - if err != nil { - return nil, errors.Wrap(err, "deposit data hash root") - } + forkVersion := networkToForkVersion(network) - bytes, err := json.MarshalIndent(&depositDataJSON{ - PubKey: fmt.Sprintf("%x", pubkey), - WithdrawalCredentials: fmt.Sprintf("%x", creds), - Amount: uint64(validatorAmt), - Signature: fmt.Sprintf("%x", msgSig), - DepositMessageRoot: fmt.Sprintf("%x", msgRoot), - DepositDataRoot: fmt.Sprintf("%x", dataRoot), - ForkVersion: fmt.Sprintf("%x", forkVersion), - NetworkName: network, - }, "", " ") - if err != nil { - return nil, errors.Wrap(err, "marshal deposit data") - } + var ddList []depositDataJSON + for i := 0; i < len(pubkeys); i++ { + // calculate depositMessage root + msgRoot, err := getMessageRoot(pubkeys[i], withdrawalAddr) + if err != nil { + return nil, err + } - return bytes, nil -} + // TODO(corver): Verify signature matches msgRoot. -// MarshalDepositDatas serializes a list of deposit data into a single file. -func MarshalDepositDatas(depositDatas [][]byte) ([]byte, error) { - var ddList []depositDataJSON - ddLen := len(depositDatas) - for i := 0; i < ddLen; i++ { - var dd depositDataJSON - err := json.Unmarshal(depositDatas[i], &dd) + dd := eth2p0.DepositData{ + PublicKey: pubkeys[i], + WithdrawalCredentials: creds[:], + Amount: validatorAmt, + Signature: msgSigs[i], + } + dataRoot, err := dd.HashTreeRoot() if err != nil { - return nil, errors.Wrap(err, "unmarshal deposit data") + return nil, errors.Wrap(err, "deposit data hash root") } - ddList = append(ddList, dd) + + ddList = append(ddList, depositDataJSON{ + PubKey: fmt.Sprintf("%x", pubkeys[i]), + WithdrawalCredentials: fmt.Sprintf("%x", creds), + Amount: uint64(validatorAmt), + Signature: fmt.Sprintf("%x", msgSigs[i]), + DepositMessageRoot: fmt.Sprintf("%x", msgRoot), + DepositDataRoot: fmt.Sprintf("%x", dataRoot), + ForkVersion: fmt.Sprintf("%x", forkVersion), + NetworkName: network, + DepositCliVersion: depositCliVersion, + }) } bytes, err := json.MarshalIndent(ddList, "", " ") @@ -212,4 +199,5 @@ type depositDataJSON struct { DepositDataRoot string `json:"deposit_data_root"` ForkVersion string `json:"fork_version"` NetworkName string `json:"network_name"` + DepositCliVersion string `json:"deposit_cli_version"` } diff --git a/eth2util/deposit/deposit_test.go b/eth2util/deposit/deposit_test.go index 3e31f3079..1d899c5be 100644 --- a/eth2util/deposit/deposit_test.go +++ b/eth2util/deposit/deposit_test.go @@ -20,6 +20,8 @@ import ( "os" "testing" + eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/coinbase/kryptology/pkg/signatures/bls/bls_sig" "github.com/stretchr/testify/require" "github.com/obolnetwork/charon/eth2util/deposit" @@ -29,38 +31,61 @@ import ( const ( // Test output file and it's input values. - testfile = "testdata/deposit_data.json" - privKey = "07e0355752a16fdc473d01676f7f82594991ea830eea928d8e2254dfd98d4beb" withdrawalAddr = "0xc0404ed740a69d11201f5ed297c5732f562c6e4e" network = "prater" ) -func TestDepositData(t *testing.T) { - // Get the private and public keys - privKeyBytes, err := hex.DecodeString(privKey) - require.NoError(t, err) - sk, err := tblsconv.SecretFromBytes(privKeyBytes) - require.NoError(t, err) - pk, err := sk.GetPublicKey() +func TestMarshalDepositData(t *testing.T) { + file := "testdata/deposit-data.json" + privKeys := []string{ + "01477d4bfbbcebe1fef8d4d6f624ecbb6e3178558bb1b0d6286c816c66842a6d", + "5b77c0f0ef7c4ddc123d55b8bd93daeefbd7116764a941c0061a496649e145b5", + "1dabcbfc9258f0f28606bf9e3b1c9f06d15a6e4eb0fbc28a43835eaaed7623fc", + "002ff4fd29d3deb6de9f5d115182a49c618c97acaa365ad66a0b240bd825c4ff", + } + + var pubkeys []eth2p0.BLSPubKey + var msgsigs []eth2p0.BLSSignature + + for i := 0; i < len(privKeys); i++ { + sk, pk := GetKeys(t, privKeys[i]) + + msgRoot, err := deposit.GetMessageSigningRoot(pk, withdrawalAddr, network) + require.NoError(t, err) + + sig, err := tbls.Sign(sk, msgRoot[:]) + require.NoError(t, err) + + sigEth2 := tblsconv.SigToETH2(sig) + + pubkeys = append(pubkeys, pk) + msgsigs = append(msgsigs, sigEth2) + } + + actual, err := deposit.MarshalDepositData(pubkeys, msgsigs, withdrawalAddr, network) require.NoError(t, err) - pubkey, err := tblsconv.KeyToETH2(pk) + + // Not using golden file since output MUST never change. + expected, err := os.ReadFile(file) require.NoError(t, err) + require.Equal(t, expected, actual) +} + +// Get the private and public keys in appropriate format for the test. +func GetKeys(t *testing.T, privKey string) (*bls_sig.SecretKey, eth2p0.BLSPubKey) { + t.Helper() - // Get deposit message signing root - msgSigningRoot, err := deposit.GetMessageSigningRoot(pubkey, withdrawalAddr, network) + privKeyBytes, err := hex.DecodeString(privKey) require.NoError(t, err) - // Sign it - s, err := tbls.Sign(sk, msgSigningRoot[:]) + sk, err := tblsconv.SecretFromBytes(privKeyBytes) require.NoError(t, err) - sigEth2 := tblsconv.SigToETH2(s) - // Check if serialized versions match. - actual, err := deposit.MarshalDepositData(pubkey, withdrawalAddr, network, sigEth2) + pk, err := sk.GetPublicKey() require.NoError(t, err) - // Not using golden file since output MUST never change. - expected, err := os.ReadFile(testfile) + pubkey, err := tblsconv.KeyToETH2(pk) require.NoError(t, err) - require.Equal(t, expected, actual) + + return sk, pubkey } diff --git a/eth2util/deposit/testdata/deposit-data.json b/eth2util/deposit/testdata/deposit-data.json new file mode 100644 index 000000000..35cdd2974 --- /dev/null +++ b/eth2util/deposit/testdata/deposit-data.json @@ -0,0 +1,46 @@ +[ + { + "pubkey": "813f5d2697f76841a752ef8c1ac11d1bb76e07003799c5745f8a569214653810def3b60920b54fb0ab3cb6deb08c3972", + "withdrawal_credentials": "010000000000000000000000c0404ed740a69d11201f5ed297c5732f562c6e4e", + "amount": 32000000000, + "signature": "b5f95360b98cb6db3c06998b43b1dce29aea4f085950018d9dbd3c9894941f4804c449c771392ed3dde1091d346d71c316273c7213237dfa6b9ea79fbcfe79b055a5880d309f5db2dcf3e9a0fee6a4c843f3adb5835958b0561996218cd63acf", + "deposit_message_root": "33109737473d6ccb5f3c4f7eccaa4a7cd3dd63de3d9878a978243053904d39bf", + "deposit_data_root": "4d97bfb4ad862d7885586186d0c8c482d1b084e3d478d9b7e55bb4729d058dfc", + "fork_version": "00001020", + "network_name": "prater", + "deposit_cli_version": "2.1.0" + }, + { + "pubkey": "940a838cd88c10daa9c26fcdf8472dfe09657c4aa4030380c6cca5ddb573a8036dfb03e61137c4baacdc9d69061f1eb2", + "withdrawal_credentials": "010000000000000000000000c0404ed740a69d11201f5ed297c5732f562c6e4e", + "amount": 32000000000, + "signature": "b8e638af308c9e1a1dfac291c7b0218dc3c1dc9baa000dbee0539ed6805cd76afd06fd50797e8192cf917d51c712c31212670e521e0cf326fadbbd8f361fa7a1e0fc80bc77279c71ddd2505835ec3da74a742a5ca19a635c6889915ef3a2dd89", + "deposit_message_root": "8cec1018fcfcb6ae295546901f1c55e2f2643834b89c9a00e4806d8c6b197169", + "deposit_data_root": "0c8b9be09c9e36bef37e9248ce262485d1ee21e005ecf3ff347a820ba0b3c69a", + "fork_version": "00001020", + "network_name": "prater", + "deposit_cli_version": "2.1.0" + }, + { + "pubkey": "80d0436ccacd2b263f5e9e7ebaa14015fe5c80d3e57dc7c37bcbda783895e3491019d3ed694ecbb49c8c80a0480c0392", + "withdrawal_credentials": "010000000000000000000000c0404ed740a69d11201f5ed297c5732f562c6e4e", + "amount": 32000000000, + "signature": "8e383948b4909a20e16c17883148adf56234c19540995830a1908720cbdf4bed309568fefbb40bec4b7f8b4b3df9e19016afb25d76899ec6e1cc7259abe5de6e7b9b04723014ebef47852925c4dd3ff343caeeeb5b4ed499c9d5be2cda1f8b84", + "deposit_message_root": "4f5f7044a71d625c59974e32e8d86aed0a2211d423675303124ce5ac70969a46", + "deposit_data_root": "892766707bb5d2bb9602d9620f942af9e0c2c915c4340ad22ef935dd999db85b", + "fork_version": "00001020", + "network_name": "prater", + "deposit_cli_version": "2.1.0" + }, + { + "pubkey": "b3d95c8790d63114ab1e813e8943f1bd59683927942df076ad724de8cc00974306c95d6614ef93505b5acd9719a6de78", + "withdrawal_credentials": "010000000000000000000000c0404ed740a69d11201f5ed297c5732f562c6e4e", + "amount": 32000000000, + "signature": "a4f5fac20a37a109200594ab9ee3bf983aab4aea6202fab637e7e79b9bbae37cef5815c746bdf01a57e659605346a9b80d4efe7e0884de144dcff28d07838a58819f03dd8fc2b48ea14e62222390fe3cb0284010f6acdcb1c4b5eb4c46a02560", + "deposit_message_root": "6b6860824055330be4cb0a378a1ffd342e4de77fef1d51621d44419c4b313ca9", + "deposit_data_root": "a7de8c4c4e26b9751afcc1bc7ecc558c3d40ee3985d62c6ba07dc08c0aa64581", + "fork_version": "00001020", + "network_name": "prater", + "deposit_cli_version": "2.1.0" + } +] \ No newline at end of file diff --git a/eth2util/deposit/testdata/deposit_data.json b/eth2util/deposit/testdata/deposit_data.json deleted file mode 100644 index 7cccaff7d..000000000 --- a/eth2util/deposit/testdata/deposit_data.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pubkey": "b6a6bb229bd3c845ae1221fe3a0b20c963ac16dd99949e0e979f01f8864af099e56dcd2e37afe21063f382624e57d183", - "withdrawal_credentials": "010000000000000000000000c0404ed740a69d11201f5ed297c5732f562c6e4e", - "amount": 32000000000, - "signature": "980a8f479f7cc3912d96b5902e956b763cc13d204c3a94ea9df15b0b6d7d470fe276af981688d94846efa4eed0d7b2be11a796d24a84b07a486d927dea4f52862fa57128ee58d3129f14f0c97da98ee7c99aff310b5df5283858361e2ac0286f", - "deposit_message_root": "4f3acee7f79a295cd13aa20e9884f1f9672ab0a1192ee08f483dd485e7c46925", - "deposit_data_root": "664b49b474a6959e992875ec8ece7bc53dbce81ac1e7bf92202a1432806c392c", - "fork_version": "00001020", - "network_name": "prater" -} \ No newline at end of file