diff --git a/integration_test/integration_test.go b/integration_test/integration_test.go index f884b22a..d5e5c199 100644 --- a/integration_test/integration_test.go +++ b/integration_test/integration_test.go @@ -17,12 +17,10 @@ import ( "github.com/bloxapp/ssv/logging" "github.com/bloxapp/ssv/utils/rsaencryption" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" eth_crypto "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "github.com/bloxapp/ssv-dkg/pkgs/crypto" - ourcrypto "github.com/bloxapp/ssv-dkg/pkgs/crypto" "github.com/bloxapp/ssv-dkg/pkgs/initiator" "github.com/bloxapp/ssv-dkg/pkgs/utils" "github.com/bloxapp/ssv-dkg/pkgs/utils/test_utils" @@ -80,7 +78,8 @@ func TestHappyFlows(t *testing.T) { require.NoError(t, err) err = testSharesData(ops, 4, []*rsa.PrivateKey{srv1.PrivKey, srv2.PrivKey, srv3.PrivKey, srv4.PrivKey}, sharesDataSigned, pubkeyraw, owner, 0) require.NoError(t, err) - testDepositData(t, depositData, withdraw.Bytes(), owner, 0) + err = initiator.VerifyDepositData(depositData, withdraw.Bytes(), owner, 0) + require.NoError(t, err) }) t.Run("test 7 operators happy flow", func(t *testing.T) { id := crypto.NewID() @@ -92,7 +91,8 @@ func TestHappyFlows(t *testing.T) { require.NoError(t, err) err = testSharesData(ops, 7, []*rsa.PrivateKey{srv1.PrivKey, srv2.PrivKey, srv3.PrivKey, srv4.PrivKey, srv5.PrivKey, srv6.PrivKey, srv7.PrivKey}, sharesDataSigned, pubkeyraw, owner, 0) require.NoError(t, err) - testDepositData(t, depositData, withdraw.Bytes(), owner, 0) + err = initiator.VerifyDepositData(depositData, withdraw.Bytes(), owner, 0) + require.NoError(t, err) }) t.Run("test 10 operators happy flow", func(t *testing.T) { id := crypto.NewID() @@ -104,7 +104,8 @@ func TestHappyFlows(t *testing.T) { require.NoError(t, err) err = testSharesData(ops, 10, []*rsa.PrivateKey{srv1.PrivKey, srv2.PrivKey, srv3.PrivKey, srv4.PrivKey, srv5.PrivKey, srv6.PrivKey, srv7.PrivKey, srv8.PrivKey, srv9.PrivKey, srv10.PrivKey}, sharesDataSigned, pubkeyraw, owner, 0) require.NoError(t, err) - testDepositData(t, depositData, withdraw.Bytes(), owner, 0) + err = initiator.VerifyDepositData(depositData, withdraw.Bytes(), owner, 0) + require.NoError(t, err) }) t.Run("test 13 operators happy flow", func(t *testing.T) { id := crypto.NewID() @@ -116,7 +117,8 @@ func TestHappyFlows(t *testing.T) { require.NoError(t, err) err = testSharesData(ops, 13, []*rsa.PrivateKey{srv1.PrivKey, srv2.PrivKey, srv3.PrivKey, srv4.PrivKey, srv5.PrivKey, srv6.PrivKey, srv7.PrivKey, srv8.PrivKey, srv9.PrivKey, srv10.PrivKey, srv11.PrivKey, srv12.PrivKey, srv13.PrivKey}, sharesDataSigned, pubkeyraw, owner, 0) require.NoError(t, err) - testDepositData(t, depositData, withdraw.Bytes(), owner, 0) + err = initiator.VerifyDepositData(depositData, withdraw.Bytes(), owner, 0) + require.NoError(t, err) }) t.Run("test 13 operators - random operators order", func(t *testing.T) { id := crypto.NewID() @@ -128,7 +130,8 @@ func TestHappyFlows(t *testing.T) { require.NoError(t, err) err = testSharesData(ops, 13, []*rsa.PrivateKey{srv1.PrivKey, srv2.PrivKey, srv3.PrivKey, srv4.PrivKey, srv5.PrivKey, srv6.PrivKey, srv7.PrivKey, srv8.PrivKey, srv9.PrivKey, srv10.PrivKey, srv11.PrivKey, srv12.PrivKey, srv13.PrivKey}, sharesDataSigned, pubkeyraw, owner, 0) require.NoError(t, err) - testDepositData(t, depositData, withdraw.Bytes(), owner, 0) + err = initiator.VerifyDepositData(depositData, withdraw.Bytes(), owner, 0) + require.NoError(t, err) }) srv1.HttpSrv.Close() srv2.HttpSrv.Close() @@ -328,7 +331,8 @@ func TestUnhappyFlows(t *testing.T) { require.NoError(t, err) err = testSharesData(ops, 4, []*rsa.PrivateKey{srv1.PrivKey, srv2.PrivKey, srv3.PrivKey, srv4.PrivKey}, sharesDataSigned, pubkeyraw, owner, 0) require.NoError(t, err) - testDepositData(t, depositData, withdraw.Bytes(), owner, 0) + err = initiator.VerifyDepositData(depositData, withdraw.Bytes(), owner, 0) + require.NoError(t, err) t.Run("test wrong operators shares order at SSV payload", func(t *testing.T) { withdraw := newEthAddress(t) owner := newEthAddress(t) @@ -341,8 +345,8 @@ func TestUnhappyFlows(t *testing.T) { require.NoError(t, err) signatureOffset := phase0.SignatureLength pubKeysOffset := phase0.PublicKeyLength*13 + signatureOffset - _ = splitBytes(sharesDataSigned[signatureOffset:pubKeysOffset], phase0.PublicKeyLength) - encryptedKeys := splitBytes(sharesDataSigned[pubKeysOffset:], len(sharesDataSigned[pubKeysOffset:])/13) + _ = utils.SplitBytes(sharesDataSigned[signatureOffset:pubKeysOffset], phase0.PublicKeyLength) + encryptedKeys := utils.SplitBytes(sharesDataSigned[pubKeysOffset:], len(sharesDataSigned[pubKeysOffset:])/13) wrongOrderSharesData := make([]byte, 0) wrongOrderSharesData = append(wrongOrderSharesData, sharesDataSigned[:pubKeysOffset]...) for i := len(encryptedKeys) - 1; i >= 0; i-- { @@ -428,7 +432,8 @@ func TestReshareHappyFlow(t *testing.T) { require.NoError(t, err) err = testSharesData(ops, 4, []*rsa.PrivateKey{srv1.PrivKey, srv2.PrivKey, srv3.PrivKey, srv4.PrivKey}, sharesDataSigned, pubkeyraw, owner, 0) require.NoError(t, err) - testDepositData(t, depositData, withdraw.Bytes(), owner, 0) + err = initiator.VerifyDepositData(depositData, withdraw.Bytes(), owner, 0) + require.NoError(t, err) newIds := []uint64{5, 6, 7, 8, 9} newId := crypto.NewID() ks, err = i.StartReshare(newId, id, ids, newIds, owner, 0) @@ -457,7 +462,8 @@ func TestReshareHappyFlow(t *testing.T) { require.NoError(t, err) err = testSharesData(ops, 4, []*rsa.PrivateKey{srv1.PrivKey, srv2.PrivKey, srv3.PrivKey, srv4.PrivKey}, sharesDataSigned, pubkeyraw, owner, 0) require.NoError(t, err) - testDepositData(t, depositData, withdraw.Bytes(), owner, 0) + err = initiator.VerifyDepositData(depositData, withdraw.Bytes(), owner, 0) + require.NoError(t, err) newIds := []uint64{1, 2, 7, 8, 9} newId := crypto.NewID() ks, err = i.StartReshare(newId, id, ids, newIds, owner, 0) @@ -486,7 +492,8 @@ func TestReshareHappyFlow(t *testing.T) { require.NoError(t, err) err = testSharesData(ops, 5, []*rsa.PrivateKey{srv1.PrivKey, srv2.PrivKey, srv3.PrivKey, srv4.PrivKey, srv5.PrivKey}, sharesDataSigned, pubkeyraw, owner, 0) require.NoError(t, err) - testDepositData(t, depositData, withdraw.Bytes(), owner, 0) + err = initiator.VerifyDepositData(depositData, withdraw.Bytes(), owner, 0) + require.NoError(t, err) newIds := []uint64{1, 2, 3, 4, 5, 8, 9} newId := crypto.NewID() ks, err = i.StartReshare(newId, id, ids, newIds, owner, 0) @@ -525,17 +532,17 @@ func testSharesData(ops map[uint64]initiator.Operator, operatorCount int, keys [ } signature := sharesData[:signatureOffset] msg := []byte("Hello") - err := ourcrypto.VerifyOwnerNoceSignature(signature, owner, validatorPublicKey, nonce) + err := crypto.VerifyOwnerNoceSignature(signature, owner, validatorPublicKey, nonce) if err != nil { return err } - _ = splitBytes(sharesData[signatureOffset:pubKeysOffset], phase0.PublicKeyLength) - encryptedKeys := splitBytes(sharesData[pubKeysOffset:], len(sharesData[pubKeysOffset:])/operatorCount) + _ = utils.SplitBytes(sharesData[signatureOffset:pubKeysOffset], phase0.PublicKeyLength) + encryptedKeys := utils.SplitBytes(sharesData[pubKeysOffset:], len(sharesData[pubKeysOffset:])/operatorCount) sigs2 := make(map[uint64][]byte) opsIDs := make([]uint64, 0) for i, enck := range encryptedKeys { var priv *rsa.PrivateKey - if contains(keys, i) { + if utils.Contains(keys, i) { priv = keys[i] } else { continue @@ -632,53 +639,3 @@ func newEthAddress(t *testing.T) common.Address { address := eth_crypto.PubkeyToAddress(*publicKeyECDSA) return address } - -func splitBytes(buf []byte, lim int) [][]byte { - var chunk []byte - chunks := make([][]byte, 0, len(buf)/lim+1) - for len(buf) >= lim { - chunk, buf = buf[:lim], buf[lim:] - chunks = append(chunks, chunk) - } - if len(buf) > 0 { - chunks = append(chunks, buf[:]) - } - return chunks -} - -func testDepositData(t *testing.T, depsitDataJson *initiator.DepositDataJson, withdrawCred []byte, owner common.Address, nonce uint16) { - require.True(t, bytes.Equal(ourcrypto.ETH1WithdrawalCredentialsHash(withdrawCred), hexutil.MustDecode("0x"+depsitDataJson.WithdrawalCredentials))) - masterSig := &bls.Sign{} - require.NoError(t, masterSig.DeserializeHexStr(depsitDataJson.Signature)) - valdatorPubKey := &bls.PublicKey{} - require.NoError(t, valdatorPubKey.DeserializeHexStr(depsitDataJson.PubKey)) - - // Check root - var fork [4]byte - copy(fork[:], hexutil.MustDecode("0x"+depsitDataJson.ForkVersion)) - depositDataRoot, err := ourcrypto.DepositDataRoot(withdrawCred, valdatorPubKey, utils.GetNetworkByFork(fork), initiator.MaxEffectiveBalanceInGwei) - require.NoError(t, err) - res := masterSig.VerifyByte(valdatorPubKey, depositDataRoot[:]) - require.True(t, res) - depositData, _, err := ourcrypto.DepositData(masterSig.Serialize(), withdrawCred, valdatorPubKey.Serialize(), utils.GetNetworkByFork(fork), initiator.MaxEffectiveBalanceInGwei) - require.NoError(t, err) - res, err = ourcrypto.VerifyDepositData(depositData, utils.GetNetworkByFork(fork)) - require.NoError(t, err) - require.True(t, res) - depositMsg := &phase0.DepositMessage{ - WithdrawalCredentials: depositData.WithdrawalCredentials, - Amount: initiator.MaxEffectiveBalanceInGwei, - } - copy(depositMsg.PublicKey[:], depositData.PublicKey[:]) - depositMsgRoot, _ := depositMsg.HashTreeRoot() - require.True(t, bytes.Equal(depositMsgRoot[:], hexutil.MustDecode("0x"+depsitDataJson.DepositMessageRoot))) -} - -func contains(s []*rsa.PrivateKey, i int) bool { - for k := range s { - if k == i { - return true - } - } - return false -} diff --git a/pkgs/crypto/crypto.go b/pkgs/crypto/crypto.go index 6cc9def3..382efa38 100644 --- a/pkgs/crypto/crypto.go +++ b/pkgs/crypto/crypto.go @@ -20,7 +20,7 @@ import ( e2m_core "github.com/bloxapp/eth2-key-manager/core" e2m_deposit "github.com/bloxapp/eth2-key-manager/eth1_deposit" "github.com/drand/kyber/share" - "github.com/drand/kyber/share/dkg" + drand_dkg "github.com/drand/kyber/share/dkg" "github.com/ethereum/go-ethereum/common" eth_crypto "github.com/ethereum/go-ethereum/crypto" "github.com/google/uuid" @@ -32,6 +32,8 @@ import ( ) const ( + // b64 encrypted key length is 256 + EncryptedKeyLength = 256 // BLSWithdrawalPrefixByte is the BLS withdrawal prefix BLSWithdrawalPrefixByte = byte(0) ETH1WithdrawalPrefixByte = byte(1) @@ -78,7 +80,7 @@ func VerifyRSA(pk *rsa.PublicKey, msg, signature []byte) error { } // ResultToShareSecretKey converts a private share at kyber DKG result to github.com/herumi/bls-eth-go-binary/bls private key -func ResultToShareSecretKey(result *dkg.Result) (*bls.SecretKey, error) { +func ResultToShareSecretKey(result *drand_dkg.Result) (*bls.SecretKey, error) { share := result.Key.PriShare() bytsSk, err := share.V.MarshalBinary() if err != nil { @@ -105,7 +107,7 @@ func KyberShareToBLSKey(share *share.PriShare) (*bls.SecretKey, error) { } // ResultsToValidatorPK converts a public polynomial at kyber DKG result to github.com/herumi/bls-eth-go-binary/bls public key -func ResultToValidatorPK(result *dkg.Result, suite dkg.Suite) (*bls.PublicKey, error) { +func ResultToValidatorPK(result *drand_dkg.Result, suite drand_dkg.Suite) (*bls.PublicKey, error) { exp := share.NewPubPoly(suite, suite.Point().Base(), result.Key.Commitments()) bytsPK, err := exp.Commit().MarshalBinary() if err != nil { @@ -222,6 +224,7 @@ func ConvertPemToPrivateKey(skPem string) (*rsa.PrivateKey, error) { return parsePrivateKey(b) } +// parsePrivateKey parses an encoded x509 RSA private key func parsePrivateKey(derBytes []byte) (*rsa.PrivateKey, error) { parsedSk, err := x509.ParsePKCS1PrivateKey(derBytes) if err != nil { @@ -230,6 +233,7 @@ func parsePrivateKey(derBytes []byte) (*rsa.PrivateKey, error) { return parsedSk, nil } +// RecoverValidatorPublicKey recovers a BLS master public key (validator pub key) from provided partial pub keys func RecoverValidatorPublicKey(sharePks map[uint64]*bls.PublicKey) (*bls.PublicKey, error) { validatorRecoveredPK := bls.PublicKey{} idVec := make([]bls.ID, 0) @@ -247,6 +251,8 @@ func RecoverValidatorPublicKey(sharePks map[uint64]*bls.PublicKey) (*bls.PublicK } return &validatorRecoveredPK, nil } + +// RecoverMasterSig recovers a BLS master signature from T-threshold partial signatures func RecoverMasterSig(sigDepositShares map[uint64]*bls.Sign) (*bls.Sign, error) { reconstructedDepositMasterSig := bls.Sign{} idVec := make([]bls.ID, 0) @@ -265,6 +271,7 @@ func RecoverMasterSig(sigDepositShares map[uint64]*bls.Sign) (*bls.Sign, error) return &reconstructedDepositMasterSig, nil } +// DepositData crates and signs a ETH2 deposit message func DepositData(masterSig, withdrawalPubKey, publicKey []byte, network e2m_core.Network, amount phase0.Gwei) (*phase0.DepositData, [32]byte, error) { if !e2m_deposit.IsSupportedDepositNetwork(network) { return nil, [32]byte{}, fmt.Errorf("network %s is not supported", network) @@ -330,6 +337,7 @@ func ETH1WithdrawalCredentialsHash(withdrawalAddr []byte) []byte { return withdrawalCredentials } +// DepositDataRoot computes a deposit root used for ETH2 deposit message func DepositDataRoot(withdrawalPubKey []byte, publicKey *bls.PublicKey, network e2m_core.Network, amount phase0.Gwei) ([]byte, error) { if !e2m_deposit.IsSupportedDepositNetwork(network) { return nil, fmt.Errorf("network %s is not supported", network) @@ -366,6 +374,7 @@ func DepositDataRoot(withdrawalPubKey []byte, publicKey *bls.PublicKey, network return root[:], nil } +// VerifyDepositData reconstructs and checks BLS signatures for ETH2 deposit message func VerifyDepositData(depositData *phase0.DepositData, network e2m_core.Network) (bool, error) { depositMessage := &phase0.DepositMessage{ WithdrawalCredentials: depositData.WithdrawalCredentials, @@ -410,6 +419,7 @@ func VerifyDepositData(depositData *phase0.DepositData, network e2m_core.Network return sig.Verify(signingRoot[:], pubkey), nil } +// SignDepositData creates a BLS signature for ETH2 deposit message func SignDepositData(validationKey *bls.SecretKey, withdrawalPubKey []byte, validatorPublicKey *bls.PublicKey, network e2m_core.Network, amount phase0.Gwei) (*bls.Sign, []byte, error) { if !e2m_deposit.IsSupportedDepositNetwork(network) { return nil, nil, errors.Errorf("Network %s is not supported", network) @@ -451,6 +461,7 @@ func SignDepositData(validationKey *bls.SecretKey, withdrawalPubKey []byte, vali return sig, root[:], nil } +// VerifyPartialSigs verifies provided partial BLS signatures func VerifyPartialSigs(sigShares map[uint64]*bls.Sign, sharePks map[uint64]*bls.PublicKey, data []byte) error { res := make(map[uint64]bool) for index, pub := range sharePks { @@ -473,6 +484,7 @@ func VerifyPartialSigs(sigShares map[uint64]*bls.Sign, sharePks map[uint64]*bls. return nil } +// EncryptedPrivateKey reads an encoded RSA priv key from path encrypted with password func EncryptedPrivateKey(path, pass string) (*rsa.PrivateKey, error) { data, err := os.ReadFile(path) if err != nil { @@ -487,6 +499,7 @@ func EncryptedPrivateKey(path, pass string) (*rsa.PrivateKey, error) { return privateKey, nil } +// PrivateKey reads an encoded RSA priv key from path func PrivateKey(path string) (*rsa.PrivateKey, error) { data, err := os.ReadFile(path) if err != nil { @@ -516,6 +529,7 @@ func PrivateKey(path string) (*rsa.PrivateKey, error) { return parsedSk, nil } +// NewID generates a random ID from 2 random concat UUIDs func NewID() [24]byte { var id [24]byte b := uuid.New() @@ -525,6 +539,7 @@ func NewID() [24]byte { return id } +// GenerateSecurePassword randomly generates a password consisting of digits + english letters func GenerateSecurePassword() (string, error) { const alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" var pass []rune @@ -566,6 +581,7 @@ func ReconstructSignatures(signatures map[uint64][]byte) (*bls.Sign, error) { return &reconstructedSig, err } +// VerifyReconstructedSignature checks a reconstructed msg master signature against validator public key func VerifyReconstructedSignature(sig *bls.Sign, validatorPubKey []byte, msg []byte) error { pk := &bls.PublicKey{} if err := pk.Deserialize(validatorPubKey); err != nil { @@ -577,16 +593,3 @@ func VerifyReconstructedSignature(sig *bls.Sign, validatorPubKey []byte, msg []b } return nil } - -func SplitBytes(buf []byte, lim int) [][]byte { - var chunk []byte - chunks := make([][]byte, 0, len(buf)/lim+1) - for len(buf) >= lim { - chunk, buf = buf[:lim], buf[lim:] - chunks = append(chunks, chunk) - } - if len(buf) > 0 { - chunks = append(chunks, buf[:]) - } - return chunks -} diff --git a/pkgs/dkg/drand.go b/pkgs/dkg/drand.go index 80531b40..7374b398 100644 --- a/pkgs/dkg/drand.go +++ b/pkgs/dkg/drand.go @@ -7,7 +7,6 @@ import ( "fmt" "github.com/attestantio/go-eth2-client/spec/phase0" - eth2_key_manager_core "github.com/bloxapp/eth2-key-manager/core" ssvspec_types "github.com/bloxapp/ssv-spec/types" "github.com/bloxapp/ssv/storage/kv" "github.com/drand/kyber" @@ -32,15 +31,8 @@ var StoreShare bool const ( // MaxEffectiveBalanceInGwei is the max effective balance MaxEffectiveBalanceInGwei phase0.Gwei = 32000000000 - // BLSWithdrawalPrefixByte is the BLS withdrawal prefix - BLSWithdrawalPrefixByte = byte(0) ) -// IsSupportedDepositNetwork returns true if the given network is supported -var IsSupportedDepositNetwork = func(network eth2_key_manager_core.Network) bool { - return network == eth2_key_manager_core.PraterNetwork || network == eth2_key_manager_core.MainNetwork || network == eth2_key_manager_core.HoleskyNetwork -} - // Operator structure contains information about external operator participating in the DKG ceremony type Operator struct { IP string diff --git a/pkgs/dkg/drand_test.go b/pkgs/dkg/drand_test.go index cc447f71..3a7ab7be 100644 --- a/pkgs/dkg/drand_test.go +++ b/pkgs/dkg/drand_test.go @@ -53,9 +53,9 @@ func (tv *testVerify) Verify(id uint64, msg, sig []byte) error { } type testState struct { - T *testing.T - ops map[uint64]*LocalOwner - tv *testVerify + T *testing.T + ops map[uint64]*LocalOwner + tv *testVerify } func (ts *testState) Broadcast(id uint64, data []byte) error { @@ -199,7 +199,7 @@ func TestDKG(t *testing.T) { ts.ops[op.ID] = op } opsarr := make([]*wire2.Operator, 0, len(ts.ops)) - for id, _ := range ts.ops { + for id := range ts.ops { pktobytes, err := crypto.EncodePublicKey(ts.tv.ops[id]) require.NoError(t, err) opsarr = append(opsarr, &wire2.Operator{ diff --git a/pkgs/initiator/initiator.go b/pkgs/initiator/initiator.go index 166753c3..80a41293 100644 --- a/pkgs/initiator/initiator.go +++ b/pkgs/initiator/initiator.go @@ -18,7 +18,9 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" eth2_key_manager_core "github.com/bloxapp/eth2-key-manager/core" ssvspec_types "github.com/bloxapp/ssv-spec/types" + "github.com/bloxapp/ssv/utils/rsaencryption" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" eth_crypto "github.com/ethereum/go-ethereum/crypto" "github.com/herumi/bls-eth-go-binary/bls" "github.com/imroc/req/v3" @@ -31,19 +33,6 @@ import ( "github.com/bloxapp/ssv-dkg/pkgs/wire" ) -// b64 encrypted key length is 256 -const encryptedKeyLength = 256 - -const ( - // MaxEffectiveBalanceInGwei is the max effective balance - MaxEffectiveBalanceInGwei phase0.Gwei = 32000000000 -) - -// IsSupportedDepositNetwork returns true if the given network is supported -var IsSupportedDepositNetwork = func(network eth2_key_manager_core.Network) bool { - return network == eth2_key_manager_core.PyrmontNetwork || network == eth2_key_manager_core.PraterNetwork || network == eth2_key_manager_core.MainNetwork -} - // Operator structure represents operators info which is public type Operator struct { Addr string // ip:port @@ -148,7 +137,7 @@ func GeneratePayload(result []dkg.Result, sigOwnerNonce []byte) (*KeyShares, err operatorCount := len(result) signatureOffset := phase0.SignatureLength pubKeysOffset := phase0.PublicKeyLength*operatorCount + signatureOffset - sharesExpectedLength := encryptedKeyLength*operatorCount + pubKeysOffset + sharesExpectedLength := crypto.EncryptedKeyLength*operatorCount + pubKeysOffset if sharesExpectedLength != len(sharesDataSigned) { return nil, fmt.Errorf("malformed ssv share data") @@ -424,7 +413,7 @@ func (c *Initiator) messageFlowHandlingReshare(reshare *wire.Reshare, newID [24] // reconstructAndVerifyDepositData verifies incoming from operators DKG result data and creates a resulting DepositDataJson structure to store as JSON file func (c *Initiator) reconstructAndVerifyDepositData(withdrawCredentials []byte, validatorPubKey *bls.PublicKey, network eth2_key_manager_core.Network, sigDepositShares map[uint64]*bls.Sign, sharePks map[uint64]*bls.PublicKey) (*DepositDataJson, error) { - shareRoot, err := crypto.DepositDataRoot(withdrawCredentials, validatorPubKey, network, MaxEffectiveBalanceInGwei) + shareRoot, err := crypto.DepositDataRoot(withdrawCredentials, validatorPubKey, network, dkg.MaxEffectiveBalanceInGwei) if err != nil { return nil, err } @@ -453,7 +442,7 @@ func (c *Initiator) reconstructAndVerifyDepositData(withdrawCredentials []byte, return nil, fmt.Errorf("deposit root signature recovered from shares is invalid") } - depositData, root, err := crypto.DepositData(reconstructedDepositMasterSig.Serialize(), withdrawCredentials, validatorPubKey.Serialize(), network, MaxEffectiveBalanceInGwei) + depositData, root, err := crypto.DepositData(reconstructedDepositMasterSig.Serialize(), withdrawCredentials, validatorPubKey.Serialize(), network, dkg.MaxEffectiveBalanceInGwei) if err != nil { return nil, err } @@ -467,7 +456,7 @@ func (c *Initiator) reconstructAndVerifyDepositData(withdrawCredentials []byte, } depositMsg := &phase0.DepositMessage{ WithdrawalCredentials: depositData.WithdrawalCredentials, - Amount: MaxEffectiveBalanceInGwei, + Amount: dkg.MaxEffectiveBalanceInGwei, } copy(depositMsg.PublicKey[:], depositData.PublicKey[:]) depositMsgRoot, _ := depositMsg.HashTreeRoot() @@ -478,14 +467,14 @@ func (c *Initiator) reconstructAndVerifyDepositData(withdrawCredentials []byte, if !bytes.Equal(depositData.WithdrawalCredentials, crypto.ETH1WithdrawalCredentialsHash(withdrawCredentials)) { return nil, fmt.Errorf("deposit data is invalid. Wrong withdrawal address %x", depositData.WithdrawalCredentials) } - if !(MaxEffectiveBalanceInGwei == depositData.Amount) { + if !(dkg.MaxEffectiveBalanceInGwei == depositData.Amount) { return nil, fmt.Errorf("deposit data is invalid. Wrong amount %d", depositData.Amount) } forkbytes := network.GenesisForkVersion() depositDataJson := &DepositDataJson{ PubKey: hex.EncodeToString(validatorPubKey.Serialize()), WithdrawalCredentials: hex.EncodeToString(depositData.WithdrawalCredentials), - Amount: MaxEffectiveBalanceInGwei, + Amount: dkg.MaxEffectiveBalanceInGwei, Signature: hex.EncodeToString(reconstructedDepositMasterSig.Serialize()), DepositMessageRoot: hex.EncodeToString(depositMsgRoot[:]), DepositDataRoot: hex.EncodeToString(root[:]), @@ -676,20 +665,6 @@ func (c *Initiator) CreateVerifyFunc(ops []*wire.Operator) (func(id uint64, msg }, nil } -// getNetworkByFork finds a network name by fork id bytes -func getNetworkByFork(fork [4]byte) eth2_key_manager_core.Network { - switch fork { - case [4]byte{0x00, 0x00, 0x20, 0x09}: - return eth2_key_manager_core.PyrmontNetwork - case [4]byte{0x00, 0x00, 0x10, 0x20}: - return eth2_key_manager_core.PraterNetwork - case [4]byte{0, 0, 0, 0}: - return eth2_key_manager_core.MainNetwork - default: - return eth2_key_manager_core.MainNetwork - } -} - // ProcessDKGResultResponse deserializes incoming DKG result messages from operators func (c *Initiator) ProcessDKGResultResponse(responseResult [][]byte, id [24]byte) ([]dkg.Result, *bls.PublicKey, map[ssvspec_types.OperatorID]*bls.PublicKey, map[ssvspec_types.OperatorID]*bls.Sign, map[ssvspec_types.OperatorID]*bls.Sign, error) { dkgResults := make([]dkg.Result, 0) @@ -937,3 +912,103 @@ func LoadOperatorsJson(operatorsMetaData []byte) (Operators, error) { } return opmap, nil } + +func VerifyDepositData(depsitDataJson *DepositDataJson, withdrawCred []byte, owner common.Address, nonce uint16) error { + if !bytes.Equal(crypto.ETH1WithdrawalCredentialsHash(withdrawCred), hexutil.MustDecode("0x"+depsitDataJson.WithdrawalCredentials)) { + return fmt.Errorf("wrong WithdrawalCredentials at result") + } + masterSig := &bls.Sign{} + if err := masterSig.DeserializeHexStr(depsitDataJson.Signature); err != nil { + return err + } + valdatorPubKey := &bls.PublicKey{} + if err := valdatorPubKey.DeserializeHexStr(depsitDataJson.PubKey); err != nil { + return err + } + // Check root + var fork [4]byte + copy(fork[:], hexutil.MustDecode("0x"+depsitDataJson.ForkVersion)) + depositDataRoot, err := crypto.DepositDataRoot(withdrawCred, valdatorPubKey, utils.GetNetworkByFork(fork), dkg.MaxEffectiveBalanceInGwei) + if err != nil { + return err + } + res := masterSig.VerifyByte(valdatorPubKey, depositDataRoot[:]) + if !res { + return fmt.Errorf("wrong master sig at result") + } + depositData, _, err := crypto.DepositData(masterSig.Serialize(), withdrawCred, valdatorPubKey.Serialize(), utils.GetNetworkByFork(fork), dkg.MaxEffectiveBalanceInGwei) + if err != nil { + return err + } + res, err = crypto.VerifyDepositData(depositData, utils.GetNetworkByFork(fork)) + if err != nil { + return err + } + if !res { + return fmt.Errorf("wrong deposit data") + } + depositMsg := &phase0.DepositMessage{ + WithdrawalCredentials: depositData.WithdrawalCredentials, + Amount: dkg.MaxEffectiveBalanceInGwei, + } + copy(depositMsg.PublicKey[:], depositData.PublicKey[:]) + depositMsgRoot, _ := depositMsg.HashTreeRoot() + if !bytes.Equal(depositMsgRoot[:], hexutil.MustDecode("0x"+depsitDataJson.DepositMessageRoot)) { + return fmt.Errorf("wrong DepositMessageRoot at result") + } + return nil +} + +func VerifySharesData(ops map[uint64]Operator, keys []*rsa.PrivateKey, ks *KeyShares, owner common.Address, nonce uint16) error { + sharesData, err := hex.DecodeString(ks.Payload.SharesData[2:]) + if err != nil { + return err + } + validatorPublicKey, err := hex.DecodeString(ks.Payload.PublicKey[2:]) + if err != nil { + return err + } + operatorCount := len(keys) + signatureOffset := phase0.SignatureLength + pubKeysOffset := phase0.PublicKeyLength*operatorCount + signatureOffset + sharesExpectedLength := crypto.EncryptedKeyLength*operatorCount + pubKeysOffset + if len(sharesData) != sharesExpectedLength { + return fmt.Errorf("wrong sharesData length") + } + signature := sharesData[:signatureOffset] + msg := []byte("Hello") + if err := crypto.VerifyOwnerNoceSignature(signature, owner, validatorPublicKey, nonce); err != nil { + return err + } + _ = utils.SplitBytes(sharesData[signatureOffset:pubKeysOffset], phase0.PublicKeyLength) + encryptedKeys := utils.SplitBytes(sharesData[pubKeysOffset:], len(sharesData[pubKeysOffset:])/operatorCount) + sigs2 := make(map[uint64][]byte) + for i, enck := range encryptedKeys { + priv := keys[i] + share, err := rsaencryption.DecodeKey(priv, enck) + if err != nil { + return err + } + secret := &bls.SecretKey{} + if err := secret.SetHexString(string(share)); err != nil { + return err + } + // Find operator ID by PubKey + var operatorID uint64 + for id, op := range ops { + if bytes.Equal(priv.PublicKey.N.Bytes(), op.PubKey.N.Bytes()) { + operatorID = id + } + } + sig := secret.SignByte(msg) + sigs2[operatorID] = sig.Serialize() + } + recon, err := crypto.ReconstructSignatures(sigs2) + if err != nil { + return err + } + if err := crypto.VerifyReconstructedSignature(recon, validatorPublicKey, msg); err != nil { + return err + } + return nil +} diff --git a/pkgs/initiator/initiator_test.go b/pkgs/initiator/initiator_test.go index 50cdf722..44f81583 100644 --- a/pkgs/initiator/initiator_test.go +++ b/pkgs/initiator/initiator_test.go @@ -1,24 +1,17 @@ package initiator import ( - "bytes" "crypto/rsa" - "encoding/hex" "math/big" "testing" - "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/bloxapp/ssv/logging" "github.com/bloxapp/ssv/utils/rsaencryption" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/herumi/bls-eth-go-binary/bls" "github.com/stretchr/testify/require" "go.uber.org/zap" "github.com/bloxapp/ssv-dkg/pkgs/crypto" - ourcrypto "github.com/bloxapp/ssv-dkg/pkgs/crypto" - "github.com/bloxapp/ssv-dkg/pkgs/utils" "github.com/bloxapp/ssv-dkg/pkgs/utils/test_utils" ) @@ -72,8 +65,10 @@ func TestStartDKG(t *testing.T) { id := crypto.NewID() depositData, keyshares, err := initiator.StartDKG(id, withdraw.Bytes(), []uint64{1, 2, 3, 4}, "mainnet", owner, 0) require.NoError(t, err) - VerifySharesData(t, ops, []*rsa.PrivateKey{srv1.PrivKey, srv2.PrivKey, srv3.PrivKey, srv4.PrivKey}, keyshares, owner, 0) - VerifyDepositData(t, depositData, withdraw.Bytes(), owner, 0) + err = VerifySharesData(ops, []*rsa.PrivateKey{srv1.PrivKey, srv2.PrivKey, srv3.PrivKey, srv4.PrivKey}, keyshares, owner, 0) + require.NoError(t, err) + err = VerifyDepositData(depositData, withdraw.Bytes(), owner, 0) + require.NoError(t, err) }) t.Run("test wrong amount of opeators < 4", func(t *testing.T) { initiator := New(priv, ops, logger) @@ -100,72 +95,6 @@ func TestStartDKG(t *testing.T) { srv4.HttpSrv.Close() } -func VerifyDepositData(t *testing.T, depsitDataJson *DepositDataJson, withdrawCred []byte, owner common.Address, nonce uint16) { - require.True(t, bytes.Equal(ourcrypto.ETH1WithdrawalCredentialsHash(withdrawCred), hexutil.MustDecode("0x"+depsitDataJson.WithdrawalCredentials))) - masterSig := &bls.Sign{} - require.NoError(t, masterSig.DeserializeHexStr(depsitDataJson.Signature)) - valdatorPubKey := &bls.PublicKey{} - require.NoError(t, valdatorPubKey.DeserializeHexStr(depsitDataJson.PubKey)) - - // Check root - var fork [4]byte - copy(fork[:], hexutil.MustDecode("0x"+depsitDataJson.ForkVersion)) - depositDataRoot, err := ourcrypto.DepositDataRoot(withdrawCred, valdatorPubKey, utils.GetNetworkByFork(fork), MaxEffectiveBalanceInGwei) - require.NoError(t, err) - res := masterSig.VerifyByte(valdatorPubKey, depositDataRoot[:]) - require.True(t, res) - depositData, _, err := ourcrypto.DepositData(masterSig.Serialize(), withdrawCred, valdatorPubKey.Serialize(), utils.GetNetworkByFork(fork), MaxEffectiveBalanceInGwei) - require.NoError(t, err) - res, err = ourcrypto.VerifyDepositData(depositData, utils.GetNetworkByFork(fork)) - require.NoError(t, err) - require.True(t, res) - depositMsg := &phase0.DepositMessage{ - WithdrawalCredentials: depositData.WithdrawalCredentials, - Amount: MaxEffectiveBalanceInGwei, - } - copy(depositMsg.PublicKey[:], depositData.PublicKey[:]) - depositMsgRoot, _ := depositMsg.HashTreeRoot() - require.True(t, bytes.Equal(depositMsgRoot[:], hexutil.MustDecode("0x"+depsitDataJson.DepositMessageRoot))) -} - -func VerifySharesData(t *testing.T, ops map[uint64]Operator, keys []*rsa.PrivateKey, ks *KeyShares, owner common.Address, nonce uint16) { - sharesData, err := hex.DecodeString(ks.Payload.SharesData[2:]) - require.NoError(t, err) - validatorPublicKey, err := hex.DecodeString(ks.Payload.PublicKey[2:]) - require.NoError(t, err) - - operatorCount := len(keys) - signatureOffset := phase0.SignatureLength - pubKeysOffset := phase0.PublicKeyLength*operatorCount + signatureOffset - sharesExpectedLength := encryptedKeyLength*operatorCount + pubKeysOffset - require.Len(t, sharesData, sharesExpectedLength) - signature := sharesData[:signatureOffset] - msg := []byte("Hello") - require.NoError(t, ourcrypto.VerifyOwnerNoceSignature(signature, owner, validatorPublicKey, nonce)) - _ = utils.SplitBytes(sharesData[signatureOffset:pubKeysOffset], phase0.PublicKeyLength) - encryptedKeys := utils.SplitBytes(sharesData[pubKeysOffset:], len(sharesData[pubKeysOffset:])/operatorCount) - sigs2 := make(map[uint64][]byte) - for i, enck := range encryptedKeys { - priv := keys[i] - share, err := rsaencryption.DecodeKey(priv, enck) - require.NoError(t, err) - secret := &bls.SecretKey{} - require.NoError(t, secret.SetHexString(string(share))) - // Find operator ID by PubKey - var operatorID uint64 - for id, op := range ops { - if bytes.Equal(priv.PublicKey.N.Bytes(), op.PubKey.N.Bytes()) { - operatorID = id - } - } - sig := secret.SignByte(msg) - sigs2[operatorID] = sig.Serialize() - } - recon, err := crypto.ReconstructSignatures(sigs2) - require.NoError(t, err) - require.NoError(t, crypto.VerifyReconstructedSignature(recon, validatorPublicKey, msg)) -} - func TestLoadOperators(t *testing.T) { t.Run("test load happy flow", func(t *testing.T) { ops, err := LoadOperatorsJson(jsonStr)