diff --git a/internal/rpchelp/helpdescs_en_US.go b/internal/rpchelp/helpdescs_en_US.go index ec32bcb2c..14195a460 100644 --- a/internal/rpchelp/helpdescs_en_US.go +++ b/internal/rpchelp/helpdescs_en_US.go @@ -594,6 +594,10 @@ var helpDescsEnUS = map[string]string{ "redeemmultisigouts-toaddress": "Address to look for (if not internal addresses).", "redeemmultisigouts-fromscraddress": "Input script hash address.", + // RemoveImported help. + "removeimported--synopsis": "Removes the provided imported script or private key from the wallet.", + "removeimported-data": "The imported script or private key to remove.", + // RescanWallet help. "rescanwallet--synopsis": "Rescan the block chain for wallet data, blocking until the rescan completes or exits with an error", "rescanwallet-beginheight": "The height of the first block to begin the rescan from", diff --git a/internal/rpchelp/methods.go b/internal/rpchelp/methods.go index 17bfd7fe4..ae89575cf 100644 --- a/internal/rpchelp/methods.go +++ b/internal/rpchelp/methods.go @@ -77,6 +77,7 @@ var Methods = []struct { {"redeemmultisigout", []interface{}{(*types.RedeemMultiSigOutResult)(nil)}}, {"redeemmultisigouts", []interface{}{(*types.RedeemMultiSigOutResult)(nil)}}, {"renameaccount", nil}, + {"removeimported", nil}, {"rescanwallet", nil}, {"revoketickets", nil}, {"sendfrom", returnsString}, diff --git a/rpc/jsonrpc/methods.go b/rpc/jsonrpc/methods.go index 35aaee8f7..21a3f0072 100644 --- a/rpc/jsonrpc/methods.go +++ b/rpc/jsonrpc/methods.go @@ -41,9 +41,9 @@ import ( // API version constants const ( - jsonrpcSemverString = "6.2.0" + jsonrpcSemverString = "6.3.0" jsonrpcSemverMajor = 6 - jsonrpcSemverMinor = 2 + jsonrpcSemverMinor = 3 jsonrpcSemverPatch = 0 ) @@ -119,6 +119,7 @@ var handlers = map[string]handler{ "sweepaccount": {fn: (*Server).sweepAccount}, "redeemmultisigout": {fn: (*Server).redeemMultiSigOut}, "redeemmultisigouts": {fn: (*Server).redeemMultiSigOuts}, + "removeimported": {fn: (*Server).removeImported}, "stakepooluserinfo": {fn: (*Server).stakePoolUserInfo}, "ticketsforaddress": {fn: (*Server).ticketsForAddress}, "validateaddress": {fn: (*Server).validateAddress}, @@ -2426,6 +2427,42 @@ func (s *Server) redeemMultiSigOuts(ctx context.Context, icmd interface{}) (inte return types.RedeemMultiSigOutsResult{Results: rmsoResults}, nil } +// removeImported purges the provided imported script or private key from the wallet. +func (s *Server) removeImported(ctx context.Context, icmd interface{}) (interface{}, error) { + cmd := icmd.(*types.RemoveImportedCmd) + w, ok := s.walletLoader.LoadedWallet() + if !ok { + return nil, errUnloadedWallet + } + + // Parse the imported entity as a private key and remove the associated address. + wif, wErr := dcrutil.DecodeWIF(cmd.Data, w.ChainParams().PrivateKeyID) + if wErr == nil { + serializedPubKey := wif.SerializePubKey() + pubKeyHash := dcrutil.Hash160(serializedPubKey) + addr, err := dcrutil.NewAddressPubKeyHash(pubKeyHash, w.ChainParams(), + dcrec.STEcdsaSecp256k1) + if err != nil { + return nil, + rpcErrorf(dcrjson.ErrRPCInvalidAddressOrKey, + "unable to generate address from pubkey hash: %v", err) + } + + err = w.RemoveAddress(addr) + return nil, err + } + + // Parse the imported entity as a script and remove the associated address. + addr, err := dcrutil.NewAddressScriptHash([]byte(cmd.Data), w.ChainParams()) + if err != nil { + return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, + "invalid private key or script provided") + } + + err = w.RemoveAddress(addr) + return nil, err +} + // rescanWallet initiates a rescan of the block chain for wallet data, blocking // until the rescan completes or exits with an error. func (s *Server) rescanWallet(ctx context.Context, icmd interface{}) (interface{}, error) { diff --git a/rpc/jsonrpc/rpcserverhelp.go b/rpc/jsonrpc/rpcserverhelp.go index 050555e22..93969a437 100644 --- a/rpc/jsonrpc/rpcserverhelp.go +++ b/rpc/jsonrpc/rpcserverhelp.go @@ -54,6 +54,7 @@ func helpDescsEnUS() map[string]string { "redeemmultisigout": "redeemmultisigout \"hash\" index tree (\"address\")\n\nTakes the input and constructs a P2PKH paying to the specified address.\n\nArguments:\n1. hash (string, required) Hash of the input transaction\n2. index (numeric, required) Idx of the input transaction\n3. tree (numeric, required) Tree the transaction is on.\n4. address (string, optional) Address to pay to.\n\nResult:\n{\n \"hex\": \"value\", (string) Resulting hash.\n \"complete\": true|false, (boolean) Shows if opperation was completed.\n \"errors\": [{ (array of object) Any errors generated.\n \"txid\": \"value\", (string) The transaction hash of the referenced previous output\n \"vout\": n, (numeric) The output index of the referenced previous output\n \"scriptSig\": \"value\", (string) The hex-encoded signature script\n \"sequence\": n, (numeric) Script sequence number\n \"error\": \"value\", (string) Verification or signing error related to the input\n },...], \n} \n", "redeemmultisigouts": "redeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\n\nTakes a hash, looks up all unspent outpoints and generates list artially signed transactions spending to either an address specified or internal addresses\n\nArguments:\n1. fromscraddress (string, required) Input script hash address.\n2. toaddress (string, optional) Address to look for (if not internal addresses).\n3. number (numeric, optional) Number of outpoints found.\n\nResult:\n{\n \"hex\": \"value\", (string) Resulting hash.\n \"complete\": true|false, (boolean) Shows if opperation was completed.\n \"errors\": [{ (array of object) Any errors generated.\n \"txid\": \"value\", (string) The transaction hash of the referenced previous output\n \"vout\": n, (numeric) The output index of the referenced previous output\n \"scriptSig\": \"value\", (string) The hex-encoded signature script\n \"sequence\": n, (numeric) Script sequence number\n \"error\": \"value\", (string) Verification or signing error related to the input\n },...], \n} \n", "renameaccount": "renameaccount \"oldaccount\" \"newaccount\"\n\nRenames an account.\n\nArguments:\n1. oldaccount (string, required) The old account name to rename\n2. newaccount (string, required) The new name for the account\n\nResult:\nNothing\n", + "removeimported": "removeimported \"data\"\n\nRemoves the provided imported script or private key from the wallet.\n\nArguments:\n1. data (string, required) The imported script or private key to remove.\n\nResult:\nNothing\n", "rescanwallet": "rescanwallet (beginheight=0)\n\nRescan the block chain for wallet data, blocking until the rescan completes or exits with an error\n\nArguments:\n1. beginheight (numeric, optional, default=0) The height of the first block to begin the rescan from\n\nResult:\nNothing\n", "revoketickets": "revoketickets\n\nRequests the wallet create revovactions for any previously missed tickets. Wallet must be unlocked.\n\nArguments:\nNone\n\nResult:\nNothing\n", "sendfrom": "sendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\n\nDEPRECATED -- Authors, signs, and sends a transaction that outputs some amount to a payment address.\nA change output is automatically included to send extra output value back to the original account.\n\nArguments:\n1. fromaccount (string, required) Account to pick unspent outputs from\n2. toaddress (string, required) Address to pay\n3. amount (numeric, required) Amount to send to the payment address valued in decred\n4. minconf (numeric, optional, default=1) Minimum number of block confirmations required before a transaction output is eligible to be spent\n5. comment (string, optional) Unused\n6. commentto (string, optional) Unused\n\nResult:\n\"value\" (string) The transaction hash of the sent transaction\n", @@ -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\"\nremoveimported \"data\"\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" diff --git a/rpc/jsonrpc/types/methods.go b/rpc/jsonrpc/types/methods.go index 9912d4039..7353646ba 100644 --- a/rpc/jsonrpc/types/methods.go +++ b/rpc/jsonrpc/types/methods.go @@ -899,6 +899,11 @@ func NewRenameAccountCmd(oldAccount, newAccount string) *RenameAccountCmd { } } +// RemoveImportedCmd defines the removeimported JSON-RPC command. +type RemoveImportedCmd struct { + Data string +} + // RescanWalletCmd describes the rescanwallet JSON-RPC request and parameters. type RescanWalletCmd struct { BeginHeight *int `jsonrpcdefault:"0"` @@ -1326,6 +1331,7 @@ func init() { {"redeemmultisigout", (*RedeemMultiSigOutCmd)(nil)}, {"redeemmultisigouts", (*RedeemMultiSigOutsCmd)(nil)}, {"renameaccount", (*RenameAccountCmd)(nil)}, + {"removeimported", (*RemoveImportedCmd)(nil)}, {"rescanwallet", (*RescanWalletCmd)(nil)}, {"revoketickets", (*RevokeTicketsCmd)(nil)}, {"sendfrom", (*SendFromCmd)(nil)}, diff --git a/wallet/udb/addressdb.go b/wallet/udb/addressdb.go index da323b4b1..c1ac090bb 100644 --- a/wallet/udb/addressdb.go +++ b/wallet/udb/addressdb.go @@ -8,6 +8,7 @@ package udb import ( "crypto/sha256" "encoding/binary" + "fmt" "time" "github.com/decred/dcrwallet/errors" @@ -108,8 +109,9 @@ type dbImportedAddressRow struct { // address in the database. type dbScriptAddressRow struct { dbAddressRow - encryptedHash []byte - encryptedScript []byte + hash []byte + script []byte + encrypted bool } // Key names for various database fields. @@ -795,6 +797,34 @@ func putAddrAccountIndex(ns walletdb.ReadWriteBucket, account uint32, addrHash [ return nil } +// removeAddrAccountIndex removes the provided key to the address account index of the database. +func removeAddrAccountIndex(ns walletdb.ReadWriteBucket, addrHash []byte) error { + bucket := ns.NestedReadWriteBucket(addrAcctIdxBucketName) + accountB := bucket.Get(addrHash) + + if accountB != nil { + msg := fmt.Sprintf("no account value found for address hash %x", addrHash) + return errors.E(errors.IO, msg) + } + + err := bucket.Delete(addrHash) + if err != nil { + return errors.E(errors.IO, err) + } + + abkt := bucket.NestedReadWriteBucket(accountB) + if err != nil { + return errors.E(errors.IO, err) + } + + err = abkt.Delete(addrHash) + if err != nil { + return errors.E(errors.IO, err) + } + + return nil +} + // putAccountRow stores the provided account information to the database. This // is used a common base for storing the various account types. func putAccountRow(ns walletdb.ReadWriteBucket, account uint32, row *dbAccountRow) error { @@ -968,15 +998,16 @@ func serializeImportedAddress(encryptedPubKey, encryptedPrivKey []byte) []byte { // row as a script address. func deserializeScriptAddress(row *dbAddressRow) (*dbScriptAddressRow, error) { // The serialized script address raw data format is: - // + //