Skip to content

Commit

Permalink
multi: changes for adding verifySeed jsonrpc call.
Browse files Browse the repository at this point in the history
A function deriveCoinTypeKey is created within methods.go that is used in another a new method of the jsonrpc serve VerifySeed.  All necessary changes to autogenerate the help for this rpc call are included as well.
  • Loading branch information
githubsands committed Oct 11, 2019
1 parent fe6e4c8 commit ea63432
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 1 deletion.
86 changes: 86 additions & 0 deletions internal/rpc/jsonrpc/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/decred/dcrwallet/wallet/v3"
"github.com/decred/dcrwallet/wallet/v3/txrules"
"github.com/decred/dcrwallet/wallet/v3/udb"
"github.com/decred/dcrwallet/walletseed"
"golang.org/x/sync/errgroup"
)

Expand Down Expand Up @@ -123,6 +124,7 @@ var handlers = map[string]handler{
"ticketsforaddress": {fn: (*Server).ticketsForAddress},
"validateaddress": {fn: (*Server).validateAddress},
"verifymessage": {fn: (*Server).verifyMessage},
"verifyseed": {fn: (*Server).verifySeed},
"version": {fn: (*Server).version},
"walletinfo": {fn: (*Server).walletInfo},
"walletlock": {fn: (*Server).walletLock},
Expand Down Expand Up @@ -3399,6 +3401,90 @@ WrongAddrKind:
return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "address must be secp256k1 P2PK or P2PKH")
}

func deriveCoinTypeKey(seed []byte, coinType uint32, params *chaincfg.Params) (*hdkeychain.ExtendedKey, error) {
// Create new root from the inputted seed and the current net
root, err := hdkeychain.NewMaster(seed[:], params)
if err != nil {
return nil, err
}

// BIP0032 hierarchy: m/<purpose>'/
// Where purpose = 44 and the ' indicates hardening with the HardenedKeyStart 0x80000000
purpose, err := root.Child(44 + hdkeychain.HardenedKeyStart)
if err != nil {
return nil, err
}
defer purpose.Zero()

// BIP0044 hierarchy: m/<purpose>'/<coin type>'
// Where coin type is either the legacy coin type, 20, or the coin type described in SLIP0044, 44. Note these parameters
// are only appropraite for main net.
coinTypePrivKey, err := purpose.Child(coinType + hdkeychain.HardenedKeyStart)
if err != nil {
return nil, err
}

return coinTypePrivKey, nil
}

// verifySeed checks if a user inputted seed is equivalent to the running wallets.
func (s *Server) verifySeed(ctx context.Context, icmd interface{}) (interface{}, error) {
cmd := icmd.(*types.VerifySeedCmd)
w, ok := s.walletLoader.LoadedWallet()
if !ok {
return nil, errUnloadedWallet
}

// obtain the wallet public key to check against the wallet derived seed
account := 0
if cmd.Account != nil {
account = int(*cmd.Account)
}

// snag the coin type from the running wallet
coinType, err := w.CoinType(ctx)
if err != nil {
return nil, err
}

decodedSeed, err := walletseed.DecodeUserInput(cmd.Seed)
if err != nil {
return nil, err
}

// derive the coin type using the user inputted seed, the wallets coin type, and the current chains parameters.
coinTypePrivKey, err := deriveCoinTypeKey(decodedSeed, coinType, w.ChainParams())
if err != nil {
return nil, err
}
defer coinTypePrivKey.Zero()

// both derivedAccountKey and walletDerivedAccountKey use the BIP044 hierachy: m/44'/<coin type>'/<account>'
accountKey, err := coinTypePrivKey.Child(uint32(account) + hdkeychain.HardenedKeyStart)
if err != nil {
return nil, err
}
defer accountKey.Zero()

// to be matched with walletxPubKey
seedPubKey, err := accountKey.Neuter()
if err != nil {
return nil, err
}

// get the master public key from the wallet and take the account # if non 0 to get the wallet's public key
walletPubKey, err := w.MasterPubKey(ctx, uint32(account))
if err != nil {
return nil, err
}

// compare the wallets public key to the inputted seeds public key, and return the results
return &types.VerifySeedResult{
Result: walletPubKey.String() == seedPubKey.String(),
CoinType: coinType,
}, nil
}

// version handles the version command by returning the RPC API versions of the
// wallet and, optionally, the consensus RPC server as well if it is associated
// with the server. The chainClient is optional, and this is simply a helper
Expand Down
3 changes: 2 additions & 1 deletion internal/rpc/jsonrpc/rpcserverhelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func helpDescsEnUS() map[string]string {
"ticketsforaddress": "ticketsforaddress \"address\"\n\nRequest all the tickets for an address.\n\nArguments:\n1. address (string, required) Address to look for.\n\nResult:\ntrue|false (boolean) Tickets owned by the specified address.\n",
"validateaddress": "validateaddress \"address\"\n\nVerify that an address is valid.\nExtra details are returned if the address is controlled by this wallet.\nThe following fields are valid only when the address is controlled by this wallet (ismine=true): isscript, pubkey, iscompressed, account, addresses, hex, script, and sigsrequired.\nThe following fields are only valid when address has an associated public key: pubkey, iscompressed.\nThe following fields are only valid when address is a pay-to-script-hash address: addresses, hex, and script.\nIf the address is a multisig address controlled by this wallet, the multisig fields will be left unset if the wallet is locked since the redeem script cannot be decrypted.\n\nArguments:\n1. address (string, required) Address to validate\n\nResult:\n{\n \"isvalid\": true|false, (boolean) Whether or not the address is valid\n \"address\": \"value\", (string) The payment address (only when isvalid is true)\n \"ismine\": true|false, (boolean) Whether this address is controlled by the wallet (only when isvalid is true)\n \"iswatchonly\": true|false, (boolean) Unset\n \"isscript\": true|false, (boolean) Whether the payment address is a pay-to-script-hash address (only when isvalid is true)\n \"pubkeyaddr\": \"value\", (string) The pubkey for this payment address (only when isvalid is true)\n \"pubkey\": \"value\", (string) The associated public key of the payment address, if any (only when isvalid is true)\n \"iscompressed\": true|false, (boolean) Whether the address was created by hashing a compressed public key, if any (only when isvalid is true)\n \"account\": \"value\", (string) The account this payment address belongs to (only when isvalid is true)\n \"addresses\": [\"value\",...], (array of string) All associated payment addresses of the script if address is a multisig address (only when isvalid is true)\n \"hex\": \"value\", (string) The redeem script \n \"script\": \"value\", (string) The class of redeem script for a multisig address\n \"sigsrequired\": n, (numeric) The number of required signatures to redeem outputs to the multisig address\n} \n",
"verifymessage": "verifymessage \"address\" \"signature\" \"message\"\n\nVerify a message was signed with the associated private key of some address.\n\nArguments:\n1. address (string, required) Address used to sign message\n2. signature (string, required) The signature to verify\n3. message (string, required) The message to verify\n\nResult:\ntrue|false (boolean) Whether the message was signed with the private key of 'address'\n",
"verifyseed": "verifyseed \"seed\" (account)\n\nVerifies if the wallets seed matches the inputted seed\n\nArguments:\n1. seed (string, required) The seed to check against the running wallet\n2. account (numeric, optional) The account to check the wallet account derived seed with\nthe accounts master public key. See BIP32: hierarchical deterministic wallets for more info.\n\nResult:\n{\n \"keyresult\": true|false, (boolean) The result of whether or not if the inputted seed matches the wallet\n \"cointype\": n, (numeric) The coin type to be used in the hierarchical wallet derivation process.\nSee BIP32: hierarchical deterministic wallets for more info\n} \n",
"version": "version\n\nReturns application and API versions (semver) keyed by their names\n\nArguments:\nNone\n\nResult:\n{\n \"Program or API name\": Object containing the semantic version, (object) Version objects keyed by the program or API name\n ...\n}\n",
"walletinfo": "walletinfo\n\nReturns global information about the wallet\n\nArguments:\nNone\n\nResult:\n{\n \"daemonconnected\": true|false, (boolean) Whether or not the wallet is currently connected to the daemon RPC\n \"unlocked\": true|false, (boolean) Whether or not the wallet is unlocked\n \"cointype\": n, (numeric) Active coin type. Not available for watching-only wallets.\n \"txfee\": n.nnn, (numeric) Transaction fee per kB of the serialized tx size in coins\n \"ticketfee\": n.nnn, (numeric) Ticket fee per kB of the serialized tx size in coins\n \"ticketpurchasing\": true|false, (boolean) Whether or not the wallet is currently purchasing tickets\n \"votebits\": n, (numeric) Vote bits setting\n \"votebitsextended\": \"value\", (string) Extended vote bits setting\n \"voteversion\": n, (numeric) Version of votes that will be generated\n \"voting\": true|false, (boolean) Whether or not the wallet is currently voting tickets\n} \n",
"walletislocked": "walletislocked\n\nReturns whether or not the wallet is locked.\n\nArguments:\nNone\n\nResult:\ntrue|false (boolean) Whether the wallet is locked\n",
Expand All @@ -84,4 +85,4 @@ var localeHelpDescs = map[string]func() map[string]string{
"en_US": helpDescsEnUS,
}

var requestUsages = "abandontransaction \"hash\"\naccountaddressindex \"account\" branch\naccountsyncaddressindex \"account\" branch index\naddmultisigaddress nrequired [\"key\",...] (\"account\")\naddticket \"tickethex\"\nconsolidate inputs (\"account\" \"address\")\ncreatemultisig nrequired [\"key\",...]\ncreatenewaccount \"account\"\ndumpprivkey \"address\"\ngeneratevote \"blockhash\" height \"tickethash\" votebits \"votebitsext\"\ngetaccountaddress \"account\"\ngetaccount \"address\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1)\ngetbestblockhash\ngetbestblock\ngetblockcount\ngetblockhash index\ngetinfo\ngetmasterpubkey (\"account\")\ngetmultisigoutinfo \"hash\" index\ngetnewaddress (\"account\" \"gappolicy\")\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngetstakeinfo\ngetticketfee\ngettickets includeimmature\ngettransaction \"txid\" (includewatchonly=false)\ngetunconfirmedbalance (\"account\")\ngetvotechoices\ngetwalletfee\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true scanfrom)\nimportscript \"hex\" (rescan=true scanfrom)\nlistaccounts (minconf=1)\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistscripts\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...]\npurchaseticket \"fromaccount\" spendlimit (minconf=1 \"ticketaddress\" numtickets \"pooladdress\" poolfees expiry \"comment\" ticketfee)\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nrenameaccount \"oldaccount\" \"newaccount\"\nrescanwallet (beginheight=0)\nrevoketickets\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsetticketfee fee\nsettxfee amount\nsetvotechoice \"agendaid\" \"choiceid\"\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\nstakepooluserinfo \"user\"\nsweepaccount \"sourceaccount\" \"destinationaddress\" (requiredconfirmations feeperkb)\nticketsforaddress \"address\"\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nversion\nwalletinfo\nwalletislocked\nwalletlock\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\nwalletpassphrase \"passphrase\" timeout"
var requestUsages = "abandontransaction \"hash\"\naccountaddressindex \"account\" branch\naccountsyncaddressindex \"account\" branch index\naddmultisigaddress nrequired [\"key\",...] (\"account\")\naddticket \"tickethex\"\nconsolidate inputs (\"account\" \"address\")\ncreatemultisig nrequired [\"key\",...]\ncreatenewaccount \"account\"\ndumpprivkey \"address\"\ngeneratevote \"blockhash\" height \"tickethash\" votebits \"votebitsext\"\ngetaccountaddress \"account\"\ngetaccount \"address\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1)\ngetbestblockhash\ngetbestblock\ngetblockcount\ngetblockhash index\ngetinfo\ngetmasterpubkey (\"account\")\ngetmultisigoutinfo \"hash\" index\ngetnewaddress (\"account\" \"gappolicy\")\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngetstakeinfo\ngetticketfee\ngettickets includeimmature\ngettransaction \"txid\" (includewatchonly=false)\ngetunconfirmedbalance (\"account\")\ngetvotechoices\ngetwalletfee\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true scanfrom)\nimportscript \"hex\" (rescan=true scanfrom)\nlistaccounts (minconf=1)\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistscripts\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...]\npurchaseticket \"fromaccount\" spendlimit (minconf=1 \"ticketaddress\" numtickets \"pooladdress\" poolfees expiry \"comment\" ticketfee)\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nrenameaccount \"oldaccount\" \"newaccount\"\nrescanwallet (beginheight=0)\nrevoketickets\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsetticketfee fee\nsettxfee amount\nsetvotechoice \"agendaid\" \"choiceid\"\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\nstakepooluserinfo \"user\"\nsweepaccount \"sourceaccount\" \"destinationaddress\" (requiredconfirmations feeperkb)\nticketsforaddress \"address\"\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nverifyseed \"seed\" (account)\nversion\nwalletinfo\nwalletislocked\nwalletlock\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\nwalletpassphrase \"passphrase\" timeout"
11 changes: 11 additions & 0 deletions internal/rpchelp/helpdescs_en_US.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,17 @@ var helpDescsEnUS = map[string]string{
"verifymessage-message": "The message to verify",
"verifymessage--result0": "Whether the message was signed with the private key of 'address'",

// VerifySeedCmd help.
"verifyseed--synopsis": "Verifies if the wallets seed matches the inputted seed",
"verifyseed-seed": "The seed to check against the running wallet",
"verifyseed-account": "The account to check the wallet account derived seed with\n" +
"the accounts master public key. See BIP32: hierarchical deterministic wallets for more info.",

// VerifySeedResult help.
"verifyseedresult-cointype": "The coin type to be used in the hierarchical wallet derivation process.\n" +
"See BIP32: hierarchical deterministic wallets for more info",
"verifyseedresult-keyresult": "The result of whether or not if the inputted seed matches the wallet",

// Version help
"version--synopsis": "Returns application and API versions (semver) keyed by their names",
"version--result0--desc": "Version objects keyed by the program or API name",
Expand Down
1 change: 1 addition & 0 deletions internal/rpchelp/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ var Methods = []struct {
{"ticketsforaddress", returnsBool},
{"validateaddress", []interface{}{(*types.ValidateAddressWalletResult)(nil)}},
{"verifymessage", returnsBool},
{"verifyseed", []interface{}{(*types.VerifySeedResult)(nil)}},
{"version", []interface{}{(*map[string]dcrdtypes.VersionResult)(nil)}},
{"walletinfo", []interface{}{(*types.WalletInfoResult)(nil)}},
{"walletislocked", returnsBool},
Expand Down

0 comments on commit ea63432

Please sign in to comment.