wallet: make --wif optional in import-multisig command

We have a full list of public keys in the import-multisig command, so if
 we have a key in the wallet that corresponds to one of these keys
 (simple sig), just reuse it for the account automatically

Closes #3266

Signed-off-by: Ekaterina Pavlova <ekt@morphbits.io>
This commit is contained in:
Ekaterina Pavlova 2024-01-18 23:47:40 +03:00
parent 36b89215ab
commit 57aa05add3
3 changed files with 109 additions and 4 deletions

View file

@ -236,8 +236,15 @@ func NewCommands() []cli.Command {
{ {
Name: "import-multisig", Name: "import-multisig",
Usage: "import multisig contract", Usage: "import multisig contract",
UsageText: "import-multisig -w wallet [--wallet-config path] --wif <wif> [--name <account_name>] --min <n>" + UsageText: "import-multisig -w wallet [--wallet-config path] [--wif <wif>] [--name <account_name>] --min <m>" +
" [<pubkey1> [<pubkey2> [...]]]", " [<pubkey1> [<pubkey2> [...]]]",
Description: `Imports a standard multisignature contract with "m out of n" signatures required where "m" is
specified by --min flag and "n" is the length of provided set of public keys. If
--wif flag is provided, it's used to create an account with the given name (or
without a name if --name flag is not provided). Otherwise, the command tries to
find an account with one of the given public keys and convert it to multisig. If
no suitable account is found and no --wif flag is specified, an error is returned.
`,
Action: importMultisig, Action: importMultisig,
Flags: []cli.Flag{ Flags: []cli.Flag{
walletPathFlag, walletPathFlag,
@ -521,6 +528,12 @@ loop:
} }
func importMultisig(ctx *cli.Context) error { func importMultisig(ctx *cli.Context) error {
var (
label *string
acc *wallet.Account
accPub *keys.PublicKey
)
wall, pass, err := openWallet(ctx, true) wall, pass, err := openWallet(ctx, true)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
@ -542,12 +555,45 @@ func importMultisig(ctx *cli.Context) error {
} }
} }
var label *string
if ctx.IsSet("name") { if ctx.IsSet("name") {
l := ctx.String("name") l := ctx.String("name")
label = &l label = &l
} }
acc, err := newAccountFromWIF(ctx.App.Writer, ctx.String("wif"), wall.Scrypt, label, pass)
loop:
for _, pub := range pubs {
for _, wallAcc := range wall.Accounts {
if wallAcc.ScriptHash().Equals(pub.GetScriptHash()) {
if acc != nil {
// Multiple matching accounts found, fallback to WIF-based conversion.
acc = nil
break loop
}
acc = new(wallet.Account)
*acc = *wallAcc
accPub = pub
}
}
}
if acc != nil {
err = acc.ConvertMultisigEncrypted(accPub, m, pubs)
if err != nil {
return cli.NewExitError(err, 1)
}
if label != nil {
acc.Label = *label
}
if err := addAccountAndSave(wall, acc); err != nil {
return cli.NewExitError(err, 1)
}
return nil
}
if !ctx.IsSet("wif") {
return cli.NewExitError(errors.New("none of the provided public keys correspond to an existing key in the wallet or multiple matching accounts found in the wallet, and no WIF is provided"), 1)
}
acc, err = newAccountFromWIF(ctx.App.Writer, ctx.String("wif"), wall.Scrypt, label, pass)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }

View file

@ -487,6 +487,58 @@ func TestWalletInit(t *testing.T) {
hex.EncodeToString(pubs[2].Bytes()), hex.EncodeToString(pubs[2].Bytes()),
hex.EncodeToString(pubs[3].Bytes()))...) hex.EncodeToString(pubs[3].Bytes()))...)
}) })
privs, pubs = testcli.GenerateKeys(t, 3)
script, err = smartcontract.CreateMultiSigRedeemScript(2, pubs)
require.NoError(t, err)
// Create a wallet and import a standard account
e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath)
e.In.WriteString("standardacc\rstdpass\rstdpass\r")
e.Run(t, "neo-go", "wallet", "import",
"--wallet", walletPath,
"--wif", privs[0].WIF())
w, err = wallet.NewWalletFromFile(walletPath)
require.NoError(t, err)
actual = w.GetAccount(privs[0].GetScriptHash())
require.NotNil(t, actual)
require.NotEqual(t, actual.Contract.Script, script)
// Test when a public key of an already imported account is present
t.Run("existing account public key, no WIF", func(t *testing.T) {
e.Run(t, "neo-go", "wallet", "import-multisig",
"--wallet", walletPath,
"--min", "2",
hex.EncodeToString(pubs[0].Bytes()), // Public key of the already imported account
hex.EncodeToString(pubs[1].Bytes()),
hex.EncodeToString(pubs[2].Bytes()))
w, err := wallet.NewWalletFromFile(walletPath)
require.NoError(t, err)
actual := w.GetAccount(hash.Hash160(script))
require.NotNil(t, actual)
require.Equal(t, actual.Contract.Script, script)
require.NoError(t, actual.Decrypt("stdpass", w.Scrypt))
require.NotEqual(t, actual.Address, w.GetAccount(privs[0].GetScriptHash()).Address)
})
// Test when no public key of an already imported account is present, and no WIF is provided
t.Run("no existing account public key, no WIF", func(t *testing.T) {
_, pubsNew := testcli.GenerateKeys(t, 3)
scriptNew, err := smartcontract.CreateMultiSigRedeemScript(2, pubsNew)
require.NoError(t, err)
e.RunWithError(t, "neo-go", "wallet", "import-multisig",
"--wallet", walletPath,
"--min", "2",
hex.EncodeToString(pubsNew[0].Bytes()),
hex.EncodeToString(pubsNew[1].Bytes()),
hex.EncodeToString(pubsNew[2].Bytes()))
w, err := wallet.NewWalletFromFile(walletPath)
require.NoError(t, err)
actual := w.GetAccount(hash.Hash160(scriptNew))
require.Nil(t, actual)
})
}) })
}) })
} }

View file

@ -283,8 +283,15 @@ func (a *Account) ConvertMultisig(m int, pubs []*keys.PublicKey) error {
if a.privateKey == nil { if a.privateKey == nil {
return errors.New("account key is not available (need to decrypt?)") return errors.New("account key is not available (need to decrypt?)")
} }
var found bool
accKey := a.privateKey.PublicKey() accKey := a.privateKey.PublicKey()
return a.ConvertMultisigEncrypted(accKey, m, pubs)
}
// ConvertMultisigEncrypted sets a's contract to an encrypted multisig contract
// with m sufficient signatures. The encrypted private key is not modified and
// remains the same.
func (a *Account) ConvertMultisigEncrypted(accKey *keys.PublicKey, m int, pubs []*keys.PublicKey) error {
var found bool
for i := range pubs { for i := range pubs {
if accKey.Equal(pubs[i]) { if accKey.Equal(pubs[i]) {
found = true found = true