diff --git a/pkg/wallet/account.go b/pkg/wallet/account.go index a503d4906..90635e5c6 100644 --- a/pkg/wallet/account.go +++ b/pkg/wallet/account.go @@ -1,6 +1,7 @@ package wallet import ( + "bytes" "encoding/hex" "encoding/json" "errors" @@ -8,6 +9,7 @@ import ( "github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/keys" + "github.com/CityOfZion/neo-go/pkg/encoding/address" "github.com/CityOfZion/neo-go/pkg/smartcontract" "github.com/CityOfZion/neo-go/pkg/util" ) @@ -177,6 +179,34 @@ func NewAccountFromEncryptedWIF(wif string, pass string) (*Account, error) { return a, nil } +// ConvertMultisig sets a's contract to multisig contract with m sufficient signatures. +func (a *Account) ConvertMultisig(m int, pubs []*keys.PublicKey) error { + var found bool + for i := range pubs { + if bytes.Equal(a.publicKey, pubs[i].Bytes()) { + found = true + break + } + } + + if !found { + return errors.New("own public key was not found among multisig keys") + } + + script, err := smartcontract.CreateMultiSigRedeemScript(m, pubs) + if err != nil { + return err + } + + a.Address = address.Uint160ToString(hash.Hash160(script)) + a.Contract = &Contract{ + Script: script, + Parameters: getContractParams(m), + } + + return nil +} + // newAccountFromPrivateKey creates a wallet from the given PrivateKey. func newAccountFromPrivateKey(p *keys.PrivateKey) *Account { pubKey := p.PublicKey() diff --git a/pkg/wallet/account_test.go b/pkg/wallet/account_test.go index f5e7db583..654566c1e 100644 --- a/pkg/wallet/account_test.go +++ b/pkg/wallet/account_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/CityOfZion/neo-go/pkg/crypto/hash" + "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/internal/keytestcases" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -87,6 +88,51 @@ func TestContract_ScriptHash(t *testing.T) { require.Equal(t, hash.Hash160(script), c.ScriptHash()) } +func TestAccount_ConvertMultisig(t *testing.T) { + // test is based on a wallet1_solo.json accounts from neo-local + a, err := NewAccountFromEncryptedWIF("6PYLmjBYJ4wQTCEfqvnznGJwZeW9pfUcV5m5oreHxqryUgqKpTRAFt9L8Y", "one") + require.NoError(t, err) + + hexs := []string{ + "02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2", // <- this is our key + "02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e", + "02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62", + "03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699", + } + + t.Run("invalid number of signatures", func(t *testing.T) { + pubs := convertPubs(t, hexs) + require.Error(t, a.ConvertMultisig(0, pubs)) + }) + + t.Run("account key is missing from multisig", func(t *testing.T) { + pubs := convertPubs(t, hexs[1:]) + require.Error(t, a.ConvertMultisig(1, pubs)) + }) + + t.Run("1/1 multisig", func(t *testing.T) { + pubs := convertPubs(t, hexs[:1]) + require.NoError(t, a.ConvertMultisig(1, pubs)) + require.Equal(t, "AbU69m8WUZJSWanfr1Cy66cpEcsmMcX7BR", a.Address) + }) + + t.Run("3/4 multisig", func(t *testing.T) { + pubs := convertPubs(t, hexs) + require.NoError(t, a.ConvertMultisig(3, pubs)) + require.Equal(t, "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", a.Address) + }) +} + +func convertPubs(t *testing.T, hexKeys []string) []*keys.PublicKey { + pubs := make([]*keys.PublicKey, len(hexKeys)) + for i := range pubs { + var err error + pubs[i], err = keys.NewPublicKeyFromString(hexKeys[i]) + require.NoError(t, err) + } + return pubs +} + func compareFields(t *testing.T, tk keytestcases.Ktype, acc *Account) { if want, have := tk.Address, acc.Address; want != have { t.Fatalf("expected %s got %s", want, have)