Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for Multisig Wallets #120

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3ede08f
add gnosis js examples
pavelkrolevets Sep 11, 2024
df2b860
add multisig support
pavelkrolevets Sep 12, 2024
4723ca2
update on-chain gnosis example
pavelkrolevets Sep 12, 2024
5ac76cb
add off-chain multisig gnosis example
pavelkrolevets Sep 12, 2024
896508c
add support to multisig wallets by seperating the stage of submitting…
alan-ssvlabs Sep 13, 2024
63e8b53
update reshare bulk marshaling
pavelkrolevets Sep 13, 2024
538db76
update operator handlers
alan-ssvlabs Sep 16, 2024
5312d79
update integration test
alan-ssvlabs Sep 16, 2024
9cf8c88
update Reshare and Resign hash from hash of tree root to hash of mars…
alan-ssvlabs Sep 16, 2024
5bf3667
working json bulk resign/reshare decoding
pavelkrolevets Sep 16, 2024
f06eea0
Merge remote-tracking branch 'origin/multisig' into gnosis_multisig
pavelkrolevets Sep 16, 2024
e0be332
Merge pull request #121 from ssvlabs/gnosis_multisig
alan-ssvlabs Sep 16, 2024
b73a2bf
fix bugs in hashing messages and initiating operator resign/reshare m…
alan-ssvlabs Sep 16, 2024
d5da809
fix operators marshalling
pavelkrolevets Sep 16, 2024
f83dfcf
Update test
alan-ssvlabs Sep 16, 2024
f0cb6e9
simplify code
alan-ssvlabs Sep 16, 2024
cd0759c
add error handling
alan-ssvlabs Sep 16, 2024
d80df54
revert changes on operator side to process resign/reshare message and…
alan-ssvlabs Sep 22, 2024
b64eb84
udpate initiator inputs to start resign/reshare to pass in signatures…
alan-ssvlabs Sep 22, 2024
f566ab1
update test
alan-ssvlabs Sep 22, 2024
fa7aa71
update variable naming in multisig tests
alan-ssvlabs Sep 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cli/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const (
ethKeystorePath = "ethKeystorePath"
ethKeystorePass = "ethKeystorePass"
ethEndpointURL = "ethEndpointURL"
signedReshare = "signedReshare"
alan-ssvlabs marked this conversation as resolved.
Show resolved Hide resolved
)

// WithdrawAddressFlag adds withdraw address flag to the command
Expand Down Expand Up @@ -166,6 +167,11 @@ func EthEndpointURL(c *cobra.Command) {
AddPersistentStringFlag(c, ethEndpointURL, "http://127.0.0.1:8545", "Ethereum node endpoint URL", false)
}

// KeystoreFilePath
func SignedReshareFilePath(c *cobra.Command) {
AddPersistentStringFlag(c, ethKeystorePath, "signed_reshare.json", "Path to signed reshare message json file", false)
GalRogozinski marked this conversation as resolved.
Show resolved Hide resolved
}

// AddPersistentStringFlag adds a string flag to the command
func AddPersistentStringFlag(c *cobra.Command, flag, value, description string, isRequired bool) {
req := ""
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,4 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace github.com/ssvlabs/dkg-spec v0.0.0-20240417085845-2f5e6b68f3ae => github.com/pavelkrolevets/dkg-spec v0.0.0-20240905112925-adea224edff7
replace github.com/ssvlabs/dkg-spec v0.0.0-20240417085845-2f5e6b68f3ae => github.com/pavelkrolevets/dkg-spec v0.0.0-20240911132539-d3b223c79b34
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,8 @@ github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
github.com/pavelkrolevets/dkg-spec v0.0.0-20240905112925-adea224edff7 h1:TMiQ1QTI+r0cqPs7LHQlMl5fKvTM2/5e1e8z1xnUKDU=
github.com/pavelkrolevets/dkg-spec v0.0.0-20240905112925-adea224edff7/go.mod h1:0nho9Kj4P+rrkdyj+KrlzUWGNWA50tnu17ejIHqcR74=
github.com/pavelkrolevets/dkg-spec v0.0.0-20240911132539-d3b223c79b34 h1:Dsrl64I9Mzm4nBt8/M8rlHUuYvQ5pbGqyGMufUdLBlI=
github.com/pavelkrolevets/dkg-spec v0.0.0-20240911132539-d3b223c79b34/go.mod h1:0nho9Kj4P+rrkdyj+KrlzUWGNWA50tnu17ejIHqcR74=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
Expand Down
4 changes: 2 additions & 2 deletions integration_test/init_bulk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import (
"strconv"
"testing"

"github.com/bloxapp/ssv/logging"
"github.com/ethereum/go-ethereum"
"github.com/spf13/cobra"
"github.com/ssvlabs/dkg-spec/testing/stubs"
"github.com/stretchr/testify/require"

"github.com/bloxapp/ssv/logging"
"github.com/ssvlabs/dkg-spec/testing/stubs"
cli_initiator "github.com/ssvlabs/ssv-dkg/cli/initiator"
cli_verify "github.com/ssvlabs/ssv-dkg/cli/verify"
)
Expand Down
191 changes: 191 additions & 0 deletions integration_test/multisig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package integration_test

import (
"encoding/hex"
"encoding/json"
"os"
"path/filepath"
"strconv"
"testing"

"github.com/ethereum/go-ethereum/common"
eth_crypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"

spec_crypto "github.com/ssvlabs/dkg-spec/crypto"
"github.com/ssvlabs/ssv-dkg/pkgs/wire"
)

func TestReshareBulkJSONPArsing(t *testing.T) {
reshareBytes, err := os.ReadFile(filepath.Clean("./stubs/reshare/bulk_reshare_msgs.json"))
require.NoError(t, err)
var bulkReshare wire.SignedBulkReshare
err = json.Unmarshal(reshareBytes, &bulkReshare)
require.NoError(t, err)
t.Log("Reshare unmarshal", bulkReshare)

bulkReshareMsgs, err := bulkReshare.MarshalReshareMessagesJSON()
require.NoError(t, err)
t.Log("Marshaled reshare messages", string(bulkReshareMsgs))

var finalMsg []byte
prefix := []byte("\x19Ethereum Signed Message:\n")
len := []byte(strconv.Itoa(len(bulkReshareMsgs)))
MatusKysel marked this conversation as resolved.
Show resolved Hide resolved
finalMsg = append(finalMsg, prefix...)
finalMsg = append(finalMsg, len...)
finalMsg = append(finalMsg, bulkReshareMsgs...)
var hash [32]byte
keccak256 := eth_crypto.Keccak256(finalMsg)
copy(hash[:], keccak256)
t.Log("Hash", hex.EncodeToString(hash[:]))
}

func TestResignBulkJSONPArsing(t *testing.T) {
bulkResignBytes, err := os.ReadFile(filepath.Clean("./stubs/resign/bulk_resign_msgs.json"))
require.NoError(t, err)
var signedBulkResign wire.SignedBulkResign
err = json.Unmarshal(bulkResignBytes, &signedBulkResign)
require.NoError(t, err)

bulkResignMsgs, err := signedBulkResign.MarshalResignMessagesJSON()
require.NoError(t, err)
t.Log("Marshaled reshare messages", string(bulkResignMsgs))

var finalMsg []byte
prefix := []byte("\x19Ethereum Signed Message:\n")
len := []byte(strconv.Itoa(len(bulkResignMsgs)))
finalMsg = append(finalMsg, prefix...)
finalMsg = append(finalMsg, len...)
finalMsg = append(finalMsg, bulkResignMsgs...)
var hash [32]byte
keccak256 := eth_crypto.Keccak256(finalMsg)
copy(hash[:], keccak256)
t.Log("Hash", hex.EncodeToString(hash[:]))
}

func TestVerifyMultisigSignedOnChain2of3(t *testing.T) {
t.Run("valid Gnosis 3/3 miltisig signatures", func(t *testing.T) {
gnosisAddress := common.HexToAddress("0x0205c708899bde67330456886a05Fe30De0A79b6")
ethBackend, err := ethclient.Dial("https://eth-sepolia.g.alchemy.com/v2/YyqRIEgydRXKTTT-w_0jtKSAH6sfr8qz")
MatusKysel marked this conversation as resolved.
Show resolved Hide resolved
require.NoError(t, err)

var finalMsg []byte
message := []byte("I am the owner of this Safe account")
prefix := []byte("\x19Ethereum Signed Message:\n")
len := []byte(strconv.Itoa(len(message)))

finalMsg = append(finalMsg, prefix...)
finalMsg = append(finalMsg, len...)
finalMsg = append(finalMsg, message...)
var hash [32]byte
keccak256 := eth_crypto.Keccak256(finalMsg)
copy(hash[:], keccak256)
t.Log("Hash", hex.EncodeToString(hash[:]))
// 3 sigs concatenated
encSigs, err := hex.DecodeString("e6cca66b0ce03f8049347ad9d8252f034fd538be62ddb4fc01dedccd723c7567050f8882aab359d9f5c13938ae8fa3a7109f4f5005630ef829b4683b7221377f1c6ef175759ce0e1890cdd57576e0216be371d528dfce7a27b1b843b12e49feed907d909ac1dfbd237499b8b504a8ea0ebce850987331cc56c208dc90c9c9d89601c")
require.NoError(t, err)
require.NoError(t, spec_crypto.VerifySignedMessageByOwner(ethBackend,
gnosisAddress,
hash,
encSigs))
})
}

func TestVerifyMultisigSignedOnChain(t *testing.T) {
t.Run("valid Gnosis 3/3 miltisig signatures", func(t *testing.T) {
gnosisAddress := common.HexToAddress("0x0205c708899bde67330456886a05Fe30De0A79b6")
ethBackend, err := ethclient.Dial("https://eth-sepolia.g.alchemy.com/v2/YyqRIEgydRXKTTT-w_0jtKSAH6sfr8qz")
require.NoError(t, err)

var finalMsg []byte
message := []byte("I am the owner of this Safe account")
prefix := []byte("\x19Ethereum Signed Message:\n")
len := []byte(strconv.Itoa(len(message)))

finalMsg = append(finalMsg, prefix...)
finalMsg = append(finalMsg, len...)
finalMsg = append(finalMsg, message...)
var hash [32]byte
keccak256 := eth_crypto.Keccak256(finalMsg)
copy(hash[:], keccak256)
t.Log("Hash", hex.EncodeToString(hash[:]))

require.NoError(t, err)
require.NoError(t, spec_crypto.VerifySignedMessageByOwner(ethBackend,
gnosisAddress,
hash,
nil))
})
}

func TestVerifyMultisigSignedBulkReshareOffChain(t *testing.T) {
t.Run("valid Gnosis 2/3 miltisig offchain signatures", func(t *testing.T) {
gnosisAddress := common.HexToAddress("0xC4D860871fb983d17eC665a305e98F1B3035a817")
ethBackend, err := ethclient.Dial("https://eth-sepolia.g.alchemy.com/v2/YyqRIEgydRXKTTT-w_0jtKSAH6sfr8qz")
require.NoError(t, err)

reshareBytes, err := os.ReadFile(filepath.Clean("./stubs/reshare/bulk_reshare_msgs.json"))
require.NoError(t, err)
var signedBulkReshare wire.SignedBulkReshare
err = json.Unmarshal(reshareBytes, &signedBulkReshare)
require.NoError(t, err)

bulkReshareMsgs, err := signedBulkReshare.MarshalReshareMessagesJSON()
require.NoError(t, err)

var finalMsg []byte
prefix := []byte("\x19Ethereum Signed Message:\n")
len := []byte(strconv.Itoa(len(bulkReshareMsgs)))

finalMsg = append(finalMsg, prefix...)
finalMsg = append(finalMsg, len...)
finalMsg = append(finalMsg, bulkReshareMsgs...)
var hash [32]byte
keccak256 := eth_crypto.Keccak256(finalMsg)
copy(hash[:], keccak256)
t.Log("Hash", hex.EncodeToString(hash[:]))
require.NoError(t, err)
t.Log("Signature", hex.EncodeToString(signedBulkReshare.Signature))
require.NoError(t, spec_crypto.VerifySignedMessageByOwner(ethBackend,
gnosisAddress,
hash,
signedBulkReshare.Signature))
})
}

func TestVerifyMultisigSignedBulkResignOffChain(t *testing.T) {
t.Run("valid Gnosis 2/3 miltisig offchain signatures", func(t *testing.T) {
gnosisAddress := common.HexToAddress("0xC4D860871fb983d17eC665a305e98F1B3035a817")
ethBackend, err := ethclient.Dial("https://eth-sepolia.g.alchemy.com/v2/YyqRIEgydRXKTTT-w_0jtKSAH6sfr8qz")
require.NoError(t, err)

bulkResignBytes, err := os.ReadFile(filepath.Clean("./stubs/resign/bulk_resign_msgs.json"))
require.NoError(t, err)
var signedBulkResign wire.SignedBulkResign
err = json.Unmarshal(bulkResignBytes, &signedBulkResign)
require.NoError(t, err)

bulkReshareMsgs, err := signedBulkResign.MarshalResignMessagesJSON()
require.NoError(t, err)
t.Log("Marshaled resign messages", string(bulkReshareMsgs))

var finalMsg []byte
prefix := []byte("\x19Ethereum Signed Message:\n")
len := []byte(strconv.Itoa(len(bulkReshareMsgs)))

finalMsg = append(finalMsg, prefix...)
finalMsg = append(finalMsg, len...)
finalMsg = append(finalMsg, bulkReshareMsgs...)
var hash [32]byte
keccak256 := eth_crypto.Keccak256(finalMsg)
copy(hash[:], keccak256)
t.Log("Hash", hex.EncodeToString(hash[:]))
require.NoError(t, err)
t.Log("Signature", hex.EncodeToString(signedBulkResign.Signature))
require.NoError(t, spec_crypto.VerifySignedMessageByOwner(ethBackend,
gnosisAddress,
hash,
signedBulkResign.Signature))
})
}
8 changes: 4 additions & 4 deletions integration_test/reshare_eip1271_sig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
eth_crypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ssvlabs/ssv-dkg/pkgs/initiator"
"github.com/ssvlabs/ssv-dkg/pkgs/validator"
"github.com/ssvlabs/ssv-dkg/pkgs/wire"
"github.com/stretchr/testify/require"
"go.uber.org/zap"

spec "github.com/ssvlabs/dkg-spec"
"github.com/ssvlabs/dkg-spec/eip1271"
"github.com/ssvlabs/dkg-spec/testing/stubs"
"github.com/ssvlabs/ssv-dkg/pkgs/initiator"
"github.com/ssvlabs/ssv-dkg/pkgs/validator"
"github.com/ssvlabs/ssv-dkg/pkgs/wire"
)

func TestReshareValidEOASig(t *testing.T) {
Expand Down Expand Up @@ -114,7 +114,7 @@ func TestReshareValidContractSig(t *testing.T) {
stubClient := &stubs.Client{
CallContractF: func(call ethereum.CallMsg) ([]byte, error) {
ret := make([]byte, 32) // needs to be 32 byte for packing
copy(ret[:4], eip1271.MagicValue[:])
copy(ret[:4], eip1271.MAGIC_VALUE[:])

return ret, nil
},
Expand Down
14 changes: 8 additions & 6 deletions integration_test/resign_eip1271_sig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
eth_crypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ssvlabs/ssv-dkg/pkgs/initiator"
"github.com/ssvlabs/ssv-dkg/pkgs/validator"
"github.com/ssvlabs/ssv-dkg/pkgs/wire"
"github.com/stretchr/testify/require"
"go.uber.org/zap"

spec "github.com/ssvlabs/dkg-spec"
"github.com/ssvlabs/dkg-spec/eip1271"
"github.com/ssvlabs/dkg-spec/testing/stubs"
"github.com/ssvlabs/ssv-dkg/pkgs/initiator"
"github.com/ssvlabs/ssv-dkg/pkgs/validator"
"github.com/ssvlabs/ssv-dkg/pkgs/wire"
)

func TestResignValidEOASig(t *testing.T) {
Expand Down Expand Up @@ -103,14 +103,16 @@ func TestResignInvalidEOASig(t *testing.T) {
withdraw.Bytes(),
[20]byte{},
uint64(nonce),
sk.PrivateKey,
signedProofs[0])
require.NoError(t, err)
sig, err := c.SignResign(rMsg, sk.PrivateKey)
require.NoError(t, err)
_, err = c.ResignMessageFlowHandling(
rMsg,
sig,
id,
rMsg.Operators)
require.ErrorContains(t, err, "invalid signed reshare signature") // spec
require.ErrorContains(t, err, "invalid EOA signature") // spec
})
for _, srv := range servers {
srv.HttpSrv.Close()
Expand All @@ -134,7 +136,7 @@ func TestResignValidContractSig(t *testing.T) {
stubClient := &stubs.Client{
CallContractF: func(call ethereum.CallMsg) ([]byte, error) {
ret := make([]byte, 32) // needs to be 32 byte for packing
copy(ret[:4], eip1271.MagicValue[:])
copy(ret[:4], eip1271.MAGIC_VALUE[:])

return ret, nil
},
Expand Down
Loading
Loading