From 186a4f96b1a71a9a366b8a8ce11a2b7edcb59335 Mon Sep 17 00:00:00 2001 From: Jonathan Chappelow Date: Fri, 3 Dec 2021 14:40:18 -0600 Subject: [PATCH] legacyrpc: add address type arg to getNewAddress and getRawChangeAddress This handles the AddressType argument in the getNewAddress RPC handler. It recognizes "legacy", "p2sh-segwit", and "bech32" to match Bitcoin Core's RPC options. These correspond to bip44, bip49 "plus", and bip84. These are the waddrmgr.DefaultKeyScopes. The validateaddress RPC is already able to recognize these addresses, and it may be used to verify the addresses returned by getnewaddress. This adds btcjson.ErrRPCWalletInvalidAddressType with a code (-5) and message ("unknown address type") to match Bitcoin Core's v22. --- go.mod | 2 +- go.sum | 3 ++- internal/rpchelp/helpdescs_en_US.go | 14 +++++++------ rpc/legacyrpc/errors.go | 5 +++++ rpc/legacyrpc/methods.go | 32 +++++++++++++++++++++++++---- rpc/legacyrpc/rpcserverhelp.go | 6 +++--- 6 files changed, 47 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index b3d296a858..81e2502905 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/btcsuite/btcwallet require ( - github.com/btcsuite/btcd v0.22.0-beta.0.20220316175102-8d5c75c28923 + github.com/btcsuite/btcd v0.23.1-0.20220606230932-8fc2d707f65b github.com/btcsuite/btcd/btcec/v2 v2.1.3 github.com/btcsuite/btcd/btcutil v1.1.1 github.com/btcsuite/btcd/btcutil/psbt v1.1.4 diff --git a/go.sum b/go.sum index 9810b3e530..4088d41440 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,9 @@ github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13P github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.22.0-beta.0.20220204213055-eaf0459ff879/go.mod h1:osu7EoKiL36UThEgzYPqdRaxeo0NU8VoXqgcnwpey0g= github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08= -github.com/btcsuite/btcd v0.22.0-beta.0.20220316175102-8d5c75c28923 h1:6H47xWODLXYDuzHapvx4dauPqFjegX4+rHgUkFQPvfw= github.com/btcsuite/btcd v0.22.0-beta.0.20220316175102-8d5c75c28923/go.mod h1:taIcYprAW2g6Z9S0gGUxyR+zDwimyDMK5ePOX+iJ2ds= +github.com/btcsuite/btcd v0.23.1-0.20220606230932-8fc2d707f65b h1:jP744fAW2Wp+5PdhDwApSKtl9ylJ8zuePhIH7s7aqnE= +github.com/btcsuite/btcd v0.23.1-0.20220606230932-8fc2d707f65b/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE= diff --git a/internal/rpchelp/helpdescs_en_US.go b/internal/rpchelp/helpdescs_en_US.go index 1af738ac57..0eab581651 100644 --- a/internal/rpchelp/helpdescs_en_US.go +++ b/internal/rpchelp/helpdescs_en_US.go @@ -84,14 +84,16 @@ var helpDescsEnUS = map[string]string{ "infowalletresult-keypoololdest": "Unset", // GetNewAddressCmd help. - "getnewaddress--synopsis": "Generates and returns a new payment address.", - "getnewaddress-account": "DEPRECATED -- Account name the new address will belong to (default=\"default\")", - "getnewaddress--result0": "The payment address", + "getnewaddress--synopsis": "Generates and returns a new payment address.", + "getnewaddress-account": "DEPRECATED -- Account name the new address will belong to (default=\"default\")", + "getnewaddress-addresstype": "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\".(default=\"legacy\")", + "getnewaddress--result0": "The payment address", // GetRawChangeAddressCmd help. - "getrawchangeaddress--synopsis": "Generates and returns a new internal payment address for use as a change address in raw transactions.", - "getrawchangeaddress-account": "Account name the new internal address will belong to (default=\"default\")", - "getrawchangeaddress--result0": "The internal payment address", + "getrawchangeaddress--synopsis": "Generates and returns a new internal payment address for use as a change address in raw transactions.", + "getrawchangeaddress-account": "Account name the new internal address will belong to (default=\"default\")", + "getrawchangeaddress-addresstype": "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\".(default=\"legacy\")", + "getrawchangeaddress--result0": "The internal payment address", // GetReceivedByAccountCmd help. "getreceivedbyaccount--synopsis": "DEPRECATED -- Returns the total amount received by addresses of some account, including spent outputs.", diff --git a/rpc/legacyrpc/errors.go b/rpc/legacyrpc/errors.go index 911bd7fc68..b3136f30cb 100644 --- a/rpc/legacyrpc/errors.go +++ b/rpc/legacyrpc/errors.go @@ -52,6 +52,11 @@ var ( Message: "address not found in wallet", } + ErrAddressTypeUnknown = btcjson.RPCError{ + Code: btcjson.ErrRPCWalletInvalidAddressType, + Message: "unknown address type", + } + ErrAccountNameNotFound = btcjson.RPCError{ Code: btcjson.ErrRPCWalletInvalidAccountName, Message: "account name not found", diff --git a/rpc/legacyrpc/methods.go b/rpc/legacyrpc/methods.go index 5aac1e7b1f..9467f8c941 100644 --- a/rpc/legacyrpc/methods.go +++ b/rpc/legacyrpc/methods.go @@ -685,11 +685,23 @@ func getNewAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error) { if cmd.Account != nil { acctName = *cmd.Account } - account, err := w.AccountNumber(waddrmgr.KeyScopeBIP0044, acctName) + keyScope := waddrmgr.KeyScopeBIP0044 + if cmd.AddressType != nil { + switch *cmd.AddressType { + case "p2sh-segwit": + keyScope = waddrmgr.KeyScopeBIP0049Plus + case "bech32": + keyScope = waddrmgr.KeyScopeBIP0084 + case "legacy": // default if unset + default: + return nil, &ErrAddressTypeUnknown + } + } + account, err := w.AccountNumber(keyScope, acctName) if err != nil { return nil, err } - addr, err := w.NewAddress(account, waddrmgr.KeyScopeBIP0044) + addr, err := w.NewAddress(account, keyScope) if err != nil { return nil, err } @@ -710,11 +722,23 @@ func getRawChangeAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error if cmd.Account != nil { acctName = *cmd.Account } - account, err := w.AccountNumber(waddrmgr.KeyScopeBIP0044, acctName) + keyScope := waddrmgr.KeyScopeBIP0044 + if cmd.AddressType != nil { + switch *cmd.AddressType { + case "p2sh-segwit": + keyScope = waddrmgr.KeyScopeBIP0049Plus + case "bech32": + keyScope = waddrmgr.KeyScopeBIP0084 + case "legacy": // default if unset + default: + return nil, &ErrAddressTypeUnknown + } + } + account, err := w.AccountNumber(keyScope, acctName) if err != nil { return nil, err } - addr, err := w.NewChangeAddress(account, waddrmgr.KeyScopeBIP0044) + addr, err := w.NewChangeAddress(account, keyScope) if err != nil { return nil, err } diff --git a/rpc/legacyrpc/rpcserverhelp.go b/rpc/legacyrpc/rpcserverhelp.go index 9894c90f90..37847aaeb7 100644 --- a/rpc/legacyrpc/rpcserverhelp.go +++ b/rpc/legacyrpc/rpcserverhelp.go @@ -14,8 +14,8 @@ func helpDescsEnUS() map[string]string { "getbestblockhash": "getbestblockhash\n\nReturns the hash of the newest block in the best chain that wallet has finished syncing with.\n\nArguments:\nNone\n\nResult:\n\"value\" (string) The hash of the most recent synced-to block\n", "getblockcount": "getblockcount\n\nReturns the blockchain height of the newest block in the best chain that wallet has finished syncing with.\n\nArguments:\nNone\n\nResult:\nn.nnn (numeric) The blockchain height of the most recent synced-to block\n", "getinfo": "getinfo\n\nReturns a JSON object containing various state info.\n\nArguments:\nNone\n\nResult:\n{\n \"version\": n, (numeric) The version of the server\n \"protocolversion\": n, (numeric) The latest supported protocol version\n \"walletversion\": n, (numeric) The version of the address manager database\n \"balance\": n.nnn, (numeric) The balance of all accounts calculated with one block confirmation\n \"blocks\": n, (numeric) The number of blocks processed\n \"timeoffset\": n, (numeric) The time offset\n \"connections\": n, (numeric) The number of connected peers\n \"proxy\": \"value\", (string) The proxy used by the server\n \"difficulty\": n.nnn, (numeric) The current target difficulty\n \"testnet\": true|false, (boolean) Whether or not server is using testnet\n \"keypoololdest\": n, (numeric) Unset\n \"keypoolsize\": n, (numeric) Unset\n \"unlocked_until\": n, (numeric) Unset\n \"paytxfee\": n.nnn, (numeric) The increment used each time more fee is required for an authored transaction\n \"relayfee\": n.nnn, (numeric) The minimum relay fee for non-free transactions in BTC/KB\n \"errors\": \"value\", (string) Any current errors\n} \n", - "getnewaddress": "getnewaddress (\"account\")\n\nGenerates and returns a new payment address.\n\nArguments:\n1. account (string, optional) DEPRECATED -- Account name the new address will belong to (default=\"default\")\n\nResult:\n\"value\" (string) The payment address\n", - "getrawchangeaddress": "getrawchangeaddress (\"account\")\n\nGenerates and returns a new internal payment address for use as a change address in raw transactions.\n\nArguments:\n1. account (string, optional) Account name the new internal address will belong to (default=\"default\")\n\nResult:\n\"value\" (string) The internal payment address\n", + "getnewaddress": "getnewaddress (\"account\" \"addresstype\")\n\nGenerates and returns a new payment address.\n\nArguments:\n1. account (string, optional) DEPRECATED -- Account name the new address will belong to (default=\"default\")\n2. addresstype (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\".(default=\"legacy\")\n\nResult:\n\"value\" (string) The payment address\n", + "getrawchangeaddress": "getrawchangeaddress (\"account\" \"addresstype\")\n\nGenerates and returns a new internal payment address for use as a change address in raw transactions.\n\nArguments:\n1. account (string, optional) Account name the new internal address will belong to (default=\"default\")\n2. addresstype (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\".(default=\"legacy\")\n\nResult:\n\"value\" (string) The internal payment address\n", "getreceivedbyaccount": "getreceivedbyaccount \"account\" (minconf=1)\n\nDEPRECATED -- Returns the total amount received by addresses of some account, including spent outputs.\n\nArguments:\n1. account (string, required) Account name to query total received amount for\n2. minconf (numeric, optional, default=1) Minimum number of block confirmations required before an output's value is included in the total\n\nResult:\nn.nnn (numeric) The total received amount valued in bitcoin\n", "getreceivedbyaddress": "getreceivedbyaddress \"address\" (minconf=1)\n\nReturns the total amount received by a single address, including spent outputs.\n\nArguments:\n1. address (string, required) Payment address which received outputs to include in total\n2. minconf (numeric, optional, default=1) Minimum number of block confirmations required before an output's value is included in the total\n\nResult:\nn.nnn (numeric) The total received amount valued in bitcoin\n", "gettransaction": "gettransaction \"txid\" (includewatchonly=false)\n\nReturns a JSON object with details regarding a transaction relevant to this wallet.\n\nArguments:\n1. txid (string, required) Hash of the transaction to query\n2. includewatchonly (boolean, optional, default=false) Also consider transactions involving watched addresses\n\nResult:\n{\n \"amount\": n.nnn, (numeric) The total amount this transaction credits to the wallet, valued in bitcoin\n \"fee\": n.nnn, (numeric) The total input value minus the total output value, or 0 if 'txid' is not a sent transaction\n \"confirmations\": n, (numeric) The number of block confirmations of the transaction\n \"blockhash\": \"value\", (string) The hash of the block this transaction is mined in, or the empty string if unmined\n \"blockindex\": n, (numeric) Unset\n \"blocktime\": n, (numeric) The Unix time of the block header this transaction is mined in, or 0 if unmined\n \"txid\": \"value\", (string) The transaction hash\n \"walletconflicts\": [\"value\",...], (array of string) Unset\n \"time\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"timereceived\": n, (numeric) The earliest Unix time this transaction was known to exist\n \"details\": [{ (array of object) Additional details for each recorded wallet credit and debit\n \"account\": \"value\", (string) DEPRECATED -- Unset\n \"address\": \"value\", (string) The address an output was paid to, or the empty string if the output is nonstandard or this detail is regarding a transaction input\n \"amount\": n.nnn, (numeric) The amount of a received output\n \"category\": \"value\", (string) The kind of detail: \"send\" for sent transactions, \"immature\" for immature coinbase outputs, \"generate\" for mature coinbase outputs, or \"recv\" for all other received outputs\n \"involveswatchonly\": true|false, (boolean) Unset\n \"fee\": n.nnn, (numeric) The included fee for a sent transaction\n \"vout\": n, (numeric) The transaction output index\n },...], \n \"hex\": \"value\", (string) The transaction encoded as a hexadecimal string\n} \n", @@ -56,4 +56,4 @@ var localeHelpDescs = map[string]func() map[string]string{ "en_US": helpDescsEnUS, } -var requestUsages = "addmultisigaddress nrequired [\"key\",...] (\"account\")\ncreatemultisig nrequired [\"key\",...]\ndumpprivkey \"address\"\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1)\ngetbestblockhash\ngetblockcount\ngetinfo\ngetnewaddress (\"account\")\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngettransaction \"txid\" (includewatchonly=false)\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true)\nkeypoolrefill (newsize=100)\nlistaccounts (minconf=1)\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"txid\":\"value\",\"vout\":n},...]\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsettxfee amount\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\ncreatenewaccount \"account\"\nexportwatchingwallet (\"account\" download=false)\ngetbestblock\ngetunconfirmedbalance (\"account\")\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nrenameaccount \"oldaccount\" \"newaccount\"\nwalletislocked" +var requestUsages = "addmultisigaddress nrequired [\"key\",...] (\"account\")\ncreatemultisig nrequired [\"key\",...]\ndumpprivkey \"address\"\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1)\ngetbestblockhash\ngetblockcount\ngetinfo\ngetnewaddress (\"account\" \"addresstype\")\ngetrawchangeaddress (\"account\" \"addresstype\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngettransaction \"txid\" (includewatchonly=false)\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true)\nkeypoolrefill (newsize=100)\nlistaccounts (minconf=1)\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"txid\":\"value\",\"vout\":n},...]\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsettxfee amount\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\ncreatenewaccount \"account\"\nexportwatchingwallet (\"account\" download=false)\ngetbestblock\ngetunconfirmedbalance (\"account\")\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nrenameaccount \"oldaccount\" \"newaccount\"\nwalletislocked"