diff options
-rw-r--r-- | .github/FUNDING.yml | 2 | ||||
-rw-r--r-- | README.md | 41 | ||||
m--------- | external/supercop | 0 | ||||
-rw-r--r-- | src/cryptonote_config.h | 1 | ||||
-rw-r--r-- | src/device/device_ledger.cpp | 12 | ||||
-rw-r--r-- | src/device/device_ledger.hpp | 4 | ||||
-rw-r--r-- | src/rpc/daemon_handler.cpp | 6 | ||||
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 34 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 4 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 117 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 6 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 24 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_commands_defs.h | 10 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_error_codes.h | 1 | ||||
-rwxr-xr-x | tests/functional_tests/sign_message.py | 17 | ||||
-rw-r--r-- | tests/fuzz/signature.cpp | 4 | ||||
-rw-r--r-- | utils/python-rpc/framework/wallet.py | 3 |
17 files changed, 211 insertions, 75 deletions
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 41a0036cf..42a92ac31 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -custom: https://web.getmonero.org/get-started/contributing/ +custom: https://www.getmonero.org/get-started/contributing/ @@ -73,11 +73,11 @@ Monero is a private, secure, untraceable, decentralised digital currency. You ar **Privacy:** Monero uses a cryptographically sound system to allow you to send and receive funds without your transactions being easily revealed on the blockchain (the ledger of transactions that everyone has). This ensures that your purchases, receipts, and all transfers remain absolutely private by default. -**Security:** Using the power of a distributed peer-to-peer consensus network, every transaction on the network is cryptographically secured. Individual wallets have a 25 word mnemonic seed that is only displayed once, and can be written down to backup the wallet. Wallet files are encrypted with a passphrase to ensure they are useless if stolen. +**Security:** Using the power of a distributed peer-to-peer consensus network, every transaction on the network is cryptographically secured. Individual wallets have a 25-word mnemonic seed that is only displayed once and can be written down to backup the wallet. Wallet files are encrypted with a passphrase to ensure they are useless if stolen. -**Untraceability:** By taking advantage of ring signatures, a special property of a certain type of cryptography, Monero is able to ensure that transactions are not only untraceable, but have an optional measure of ambiguity that ensures that transactions cannot easily be tied back to an individual user or computer. +**Untraceability:** By taking advantage of ring signatures, a special property of a certain type of cryptography, Monero is able to ensure that transactions are not only untraceable but have an optional measure of ambiguity that ensures that transactions cannot easily be tied back to an individual user or computer. -**Decentralization:** The utility of monero depends on its decentralised peer-to-peer consensus network - anyone should be able to run the monero software, validate the integrity of the blockchain, and participate in all aspects of the monero network using consumer-grade commodity hardware. Decentralization of the monero network is maintained by software development that minimizes the costs of running the monero software and inhibits the proliferation of specialized, non-commodity hardware. +**Decentralization:** The utility of Monero depends on its decentralised peer-to-peer consensus network - anyone should be able to run the monero software, validate the integrity of the blockchain, and participate in all aspects of the monero network using consumer-grade commodity hardware. Decentralization of the monero network is maintained by software development that minimizes the costs of running the monero software and inhibits the proliferation of specialized, non-commodity hardware. ## About this project @@ -85,11 +85,11 @@ This is the core implementation of Monero. It is open source and completely free As with many development projects, the repository on Github is considered to be the "staging" area for the latest changes. Before changes are merged into that branch on the main repository, they are tested by individual developers in their own branches, submitted as a pull request, and then subsequently tested by contributors who focus on testing and code reviews. That having been said, the repository should be carefully considered before using it in a production environment, unless there is a patch in the repository for a particular show-stopping issue you are experiencing. It is generally a better idea to use a tagged release for stability. -**Anyone is welcome to contribute to Monero's codebase!** If you have a fix or code change, feel free to submit it as a pull request directly to the "master" branch. In cases where the change is relatively small or does not affect other parts of the codebase it may be merged in immediately by any one of the collaborators. On the other hand, if the change is particularly large or complex, it is expected that it will be discussed at length either well in advance of the pull request being submitted, or even directly on the pull request. +**Anyone is welcome to contribute to Monero's codebase!** If you have a fix or code change, feel free to submit it as a pull request directly to the "master" branch. In cases where the change is relatively small or does not affect other parts of the codebase, it may be merged in immediately by any one of the collaborators. On the other hand, if the change is particularly large or complex, it is expected that it will be discussed at length either well in advance of the pull request being submitted, or even directly on the pull request. ## Supporting the project -Monero is a 100% community-sponsored endeavor. If you want to join our efforts, the easiest thing you can do is support the project financially. Both Monero and Bitcoin donations can be made to **donate.getmonero.org** if using a client that supports the [OpenAlias](https://openalias.org) standard. Alternatively you can send XMR to the Monero donation address via the `donate` command (type `help` in the command-line wallet for details). +Monero is a 100% community-sponsored endeavor. If you want to join our efforts, the easiest thing you can do is support the project financially. Both Monero and Bitcoin donations can be made to **donate.getmonero.org** if using a client that supports the [OpenAlias](https://openalias.org) standard. Alternatively, you can send XMR to the Monero donation address via the `donate` command (type `help` in the command-line wallet for details). The Monero donation address is: `888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H` (viewkey: `f359631075708155cc3d92a32b75a7d02a5dcf27756707b47a2b31b21c389501`) @@ -150,7 +150,7 @@ Approximately three months prior to a scheduled software upgrade, a branch from The following table summarizes the tools and libraries required to build. A few of the libraries are also included in this repository (marked as -"Vendored"). By default, the build uses the library installed on the system, +"Vendored"). By default, the build uses the library installed on the system and ignores the vendored sources. However, if no library is found installed on the system, then the vendored source will be built and used. The vendored sources are also used for statically-linked builds because distribution @@ -288,7 +288,7 @@ Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch ( * If using an external hard disk without an external power supply, ensure it gets enough power to avoid hardware issues when syncing, by adding the line "max_usb_current=1" to /boot/config.txt -* Clone monero and checkout the most recent release version: +* Clone Monero and checkout the most recent release version: ```bash git clone https://github.com/monero-project/monero.git @@ -314,7 +314,7 @@ Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch ( #### *Note for Raspbian Jessie users:* -If you are using the older Raspbian Jessie image, compiling Monero is a bit more complicated. The version of Boost available in the Debian Jessie repositories is too old to use with Monero, and thus you must compile a newer version yourself. The following explains the extra steps, and has been tested on a Raspberry Pi 2 with a clean install of minimal Raspbian Jessie. +If you are using the older Raspbian Jessie image, compiling Monero is a bit more complicated. The version of Boost available in the Debian Jessie repositories is too old to use with Monero, and thus you must compile a newer version yourself. The following explains the extra steps and has been tested on a Raspberry Pi 2 with a clean install of minimal Raspbian Jessie. * As before, `apt-get update && apt-get upgrade` to install all of the latest software, and increase the system swap size @@ -326,7 +326,7 @@ If you are using the older Raspbian Jessie image, compiling Monero is a bit more ``` -* Then, install the dependencies for Monero except `libunwind` and `libboost-all-dev` +* Then, install the dependencies for Monero except for `libunwind` and `libboost-all-dev` * Install the latest version of boost (this may first require invoking `apt-get remove --purge libboost*-dev` to remove a previous version if you're not using a clean install): @@ -347,7 +347,7 @@ If you are using the older Raspbian Jessie image, compiling Monero is a bit more * Wait ~4 hours -* From here, follow the [general Raspberry Pi instructions](#on-the-raspberry-pi) from the "Clone monero and checkout most recent release version" step. +* From here, follow the [general Raspberry Pi instructions](#on-the-raspberry-pi) from the "Clone Monero and checkout most recent release version" step. #### On Windows: @@ -411,7 +411,7 @@ application. ``` * If you would like a specific [version/tag](https://github.com/monero-project/monero/tags), do a git checkout for that version. eg. 'v0.16.0.0'. If you don't care about the version and just want binaries from master, skip this step: - + ```bash git checkout v0.16.0.0 ``` @@ -447,7 +447,7 @@ application. ### On FreeBSD: The project can be built from scratch by following instructions for Linux above(but use `gmake` instead of `make`). -If you are running monero in a jail, you need to add `sysvsem="new"` to your jail configuration, otherwise lmdb will throw the error message: `Failed to open lmdb environment: Function not implemented`. +If you are running Monero in a jail, you need to add `sysvsem="new"` to your jail configuration, otherwise lmdb will throw the error message: `Failed to open lmdb environment: Function not implemented`. Monero is also available as a port or package as 'monero-cli`. @@ -460,7 +460,7 @@ Running the test suite also requires `py-requests` package. Build monero: `env DEVELOPER_LOCAL_TOOLS=1 BOOST_ROOT=/usr/local gmake release-static` -Note: you may encounter the following error, when compiling the latest version of monero as a normal user: +Note: you may encounter the following error when compiling the latest version of Monero as a normal user: ``` LLVM ERROR: out of memory @@ -563,7 +563,6 @@ Packages are available for ``` More info and versions in the [Debian package tracker](https://tracker.debian.org/pkg/monero). - * Arch Linux (via Community packages): [`monero`](https://www.archlinux.org/packages/community/x86_64/monero/) @@ -599,13 +598,13 @@ More info and versions in the [Debian package tracker](https://tracker.debian.or ```bash # Build using all available cores docker build -t monero . - + # or build using a specific number of cores (reduce RAM requirement) docker build --build-arg NPROC=1 -t monero . - + # either run in foreground docker run -it -v /monero/chain:/root/.bitmonero -v /monero/wallet:/wallet -p 18080:18080 monero - + # or in background docker run -it -d -v /monero/chain:/root/.bitmonero -v /monero/wallet:/wallet -p 18080:18080 monero ``` @@ -618,7 +617,7 @@ Packaging for your favorite distribution would be a welcome contribution! ## Running monerod The build places the binary in `bin/` sub-directory within the build directory -from which cmake was invoked (repository root by default). To run in +from which cmake was invoked (repository root by default). To run in the foreground: ```bash @@ -629,7 +628,7 @@ To list all available options, run `./bin/monerod --help`. Options can be specified either on the command line or in a configuration file passed by the `--config-file` argument. To specify an option in the configuration file, add a line with the syntax `argumentname=value`, where `argumentname` is the name -of the argument without the leading dashes, for example `log-level=1`. +of the argument without the leading dashes, for example, `log-level=1`. To run in background: @@ -654,7 +653,7 @@ See [README.i18n.md](README.i18n.md). ## Using Tor > There is a new, still experimental, [integration with Tor](ANONYMITY_NETWORKS.md). The -> feature allows connecting over IPv4 and Tor simulatenously - IPv4 is used for +> feature allows connecting over IPv4 and Tor simultaneously - IPv4 is used for > relaying blocks and relaying transactions received by peers whereas Tor is > used solely for relaying transactions received over local RPC. This provides > privacy and better protection against surrounding node (sybil) attacks. @@ -753,7 +752,7 @@ Print the stack trace with `bt` coredumpctl -1 gdb ``` -#### To run monero within gdb: +#### To run Monero within gdb: Type `gdb /path/to/monerod` diff --git a/external/supercop b/external/supercop -Subproject 7d8b6878260061da56ade6d23dc833288659d0a +Subproject 633500ad8c8759995049ccd022107d1fa8a1bbc diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index f4709dc01..f50ab6a40 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -230,6 +230,7 @@ namespace config const unsigned char HASH_KEY_CLSAG_ROUND[] = "CLSAG_round"; const unsigned char HASH_KEY_CLSAG_AGG_0[] = "CLSAG_agg_0"; const unsigned char HASH_KEY_CLSAG_AGG_1[] = "CLSAG_agg_1"; + const char HASH_KEY_MESSAGE_SIGNING[] = "MoneroMessageSignature"; namespace testnet { diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 4e89f835d..3e0afeb65 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -259,7 +259,7 @@ namespace hw { static int device_id = 0; - #define PROTOCOL_VERSION 3 + #define PROTOCOL_VERSION 4 #define INS_NONE 0x00 #define INS_RESET 0x02 @@ -2126,7 +2126,7 @@ namespace hw { AUTO_LOCK_CMD(); #ifdef DEBUG_HWDEVICE const rct::key p_x = hw::ledger::decrypt(p); - const rct::key z_x = hw::ledger::decrypt(z); + const rct::key z_x = z; rct::key I_x; rct::key D_x; const rct::key H_x = H; @@ -2146,7 +2146,8 @@ namespace hw { //p this->send_secret(p.bytes, offset); //z - this->send_secret(z.bytes, offset); + memmove(this->buffer_send+offset, z.bytes, 32); + offset += 32; //H memmove(this->buffer_send+offset, H.bytes, 32); offset += 32; @@ -2225,7 +2226,7 @@ namespace hw { const rct::key c_x = c; const rct::key a_x = hw::ledger::decrypt(a); const rct::key p_x = hw::ledger::decrypt(p); - const rct::key z_x = hw::ledger::decrypt(z); + const rct::key z_x = z; const rct::key mu_P_x = mu_P; const rct::key mu_C_x = mu_C; rct::key s_x; @@ -2249,7 +2250,8 @@ namespace hw { //p this->send_secret(p.bytes, offset); //z - this->send_secret(z.bytes, offset); + memmove(this->buffer_send+offset, z.bytes, 32); + offset += 32; //mu_P memmove(this->buffer_send+offset, mu_P.bytes, 32); offset += 32; diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index d3ec08288..5cb834e02 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -44,8 +44,8 @@ namespace hw { /* Minimal supported version */ #define MINIMAL_APP_VERSION_MAJOR 1 - #define MINIMAL_APP_VERSION_MINOR 3 - #define MINIMAL_APP_VERSION_MICRO 1 + #define MINIMAL_APP_VERSION_MINOR 6 + #define MINIMAL_APP_VERSION_MICRO 0 #define VERSION(M,m,u) ((M)<<16|(m)<<8|(u)) #define VERSION_MAJOR(v) (((v)>>16)&0xFF) diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index 0a26a4d5d..248c54afb 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -183,7 +183,11 @@ namespace rpc { bwt.transactions.emplace_back(); bwt.transactions.back().pruned = req.prune; - if (!parse_and_validate_tx_from_blob(blob.second, bwt.transactions.back())) + + const bool parsed = req.prune ? + parse_and_validate_tx_base_from_blob(blob.second, bwt.transactions.back()) : + parse_and_validate_tx_from_blob(blob.second, bwt.transactions.back()); + if (!parsed) { res.blocks.clear(); res.output_indices.clear(); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 02540a844..f37d77933 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -223,7 +223,7 @@ namespace const char* USAGE_GET_TX_NOTE("get_tx_note <txid>"); const char* USAGE_GET_DESCRIPTION("get_description"); const char* USAGE_SET_DESCRIPTION("set_description [free text note]"); - const char* USAGE_SIGN("sign [<account_index>,<address_index>] <filename>"); + const char* USAGE_SIGN("sign [<account_index>,<address_index>] [--spend|--view] <filename>"); const char* USAGE_VERIFY("verify <filename> <address> <signature>"); const char* USAGE_EXPORT_KEY_IMAGES("export_key_images [all] <filename>"); const char* USAGE_IMPORT_KEY_IMAGES("import_key_images <filename>"); @@ -9877,7 +9877,7 @@ bool simple_wallet::sign(const std::vector<std::string> &args) fail_msg_writer() << tr("command not supported by HW wallet"); return true; } - if (args.size() != 1 && args.size() != 2) + if (args.size() != 1 && args.size() != 2 && args.size() != 3) { PRINT_USAGE(USAGE_SIGN); return true; @@ -9893,17 +9893,29 @@ bool simple_wallet::sign(const std::vector<std::string> &args) return true; } + tools::wallet2::message_signature_type_t message_signature_type = tools::wallet2::sign_with_spend_key; subaddress_index index{0, 0}; - if (args.size() == 2) + for (unsigned int idx = 0; idx + 1 < args.size(); ++idx) { unsigned int a, b; - if (sscanf(args[0].c_str(), "%u,%u", &a, &b) != 2) + if (sscanf(args[idx].c_str(), "%u,%u", &a, &b) == 2) { - fail_msg_writer() << tr("Invalid subaddress index format"); + index.major = a; + index.minor = b; + } + else if (args[idx] == "--spend") + { + message_signature_type = tools::wallet2::sign_with_spend_key; + } + else if (args[idx] == "--view") + { + message_signature_type = tools::wallet2::sign_with_view_key; + } + else + { + fail_msg_writer() << tr("Invalid subaddress index format, and not a signature type: ") << args[idx]; return true; } - index.major = a; - index.minor = b; } const std::string &filename = args.back(); @@ -9917,7 +9929,7 @@ bool simple_wallet::sign(const std::vector<std::string> &args) SCOPED_WALLET_UNLOCK(); - std::string signature = m_wallet->sign(data, index); + std::string signature = m_wallet->sign(data, message_signature_type, index); success_msg_writer() << signature; return true; } @@ -9948,14 +9960,14 @@ bool simple_wallet::verify(const std::vector<std::string> &args) return true; } - r = m_wallet->verify(data, info.address, signature); - if (!r) + tools::wallet2::message_signature_result_t result = m_wallet->verify(data, info.address, signature); + if (!result.valid) { fail_msg_writer() << tr("Bad signature from ") << address_string; } else { - success_msg_writer() << tr("Good signature from ") << address_string; + success_msg_writer() << tr("Good signature from ") << address_string << (result.old ? " (using old signature algorithm)" : "") << " with " << (result.type == tools::wallet2::sign_with_spend_key ? "spend key" : result.type == tools::wallet2::sign_with_view_key ? "view key" : "unknown key combination (suspicious)"); } return true; } diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index e00f9d2e9..152a7dcd7 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1998,7 +1998,7 @@ bool WalletImpl::checkReserveProof(const std::string &address, const std::string std::string WalletImpl::signMessage(const std::string &message) { - return m_wallet->sign(message); + return m_wallet->sign(message, tools::wallet2::sign_with_spend_key); } bool WalletImpl::verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const @@ -2008,7 +2008,7 @@ bool WalletImpl::verifySignedMessage(const std::string &message, const std::stri if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address)) return false; - return m_wallet->verify(message, info.address, signature); + return m_wallet->verify(message, info.address, signature).valid; } std::string WalletImpl::signMultisigParticipant(const std::string &message) const diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 31aaaddea..918b3fd41 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -12207,51 +12207,130 @@ void wallet2::set_account_tag_description(const std::string& tag, const std::str m_account_tags.first[tag] = description; } -std::string wallet2::sign(const std::string &data, cryptonote::subaddress_index index) const -{ +// Set up an address signature message hash +// Hash data: domain separator, spend public key, view public key, mode identifier, payload data +static crypto::hash get_message_hash(const std::string &data, const crypto::public_key &spend_key, const crypto::public_key &view_key, const uint8_t mode) +{ + KECCAK_CTX ctx; + keccak_init(&ctx); + keccak_update(&ctx, (const uint8_t*)config::HASH_KEY_MESSAGE_SIGNING, sizeof(config::HASH_KEY_MESSAGE_SIGNING)); // includes NUL + keccak_update(&ctx, (const uint8_t*)&spend_key, sizeof(crypto::public_key)); + keccak_update(&ctx, (const uint8_t*)&view_key, sizeof(crypto::public_key)); + keccak_update(&ctx, (const uint8_t*)&mode, sizeof(uint8_t)); + char len_buf[(sizeof(size_t) * 8 + 6) / 7]; + char *ptr = len_buf; + tools::write_varint(ptr, data.size()); + CHECK_AND_ASSERT_THROW_MES(ptr > len_buf && ptr <= len_buf + sizeof(len_buf), "Length overflow"); + keccak_update(&ctx, (const uint8_t*)len_buf, ptr - len_buf); + keccak_update(&ctx, (const uint8_t*)data.data(), data.size()); crypto::hash hash; - crypto::cn_fast_hash(data.data(), data.size(), hash); + keccak_finish(&ctx, (uint8_t*)&hash); + return hash; +} + +// Sign a message with a private key from either the base address or a subaddress +// The signature is also bound to both keys and the signature mode (spend, view) to prevent unintended reuse +std::string wallet2::sign(const std::string &data, message_signature_type_t signature_type, cryptonote::subaddress_index index) const +{ const cryptonote::account_keys &keys = m_account.get_keys(); crypto::signature signature; - crypto::secret_key skey; + crypto::secret_key skey, m; + crypto::secret_key skey_spend, skey_view; crypto::public_key pkey; + crypto::public_key pkey_spend, pkey_view; // to include both in hash + crypto::hash hash; + uint8_t mode; + + // Use the base address if (index.is_zero()) { - skey = keys.m_spend_secret_key; - pkey = keys.m_account_address.m_spend_public_key; + switch (signature_type) + { + case sign_with_spend_key: + skey = keys.m_spend_secret_key; + pkey = keys.m_account_address.m_spend_public_key; + mode = 0; + break; + case sign_with_view_key: + skey = keys.m_view_secret_key; + pkey = keys.m_account_address.m_view_public_key; + mode = 1; + break; + default: CHECK_AND_ASSERT_THROW_MES(false, "Invalid signature type requested"); + } + hash = get_message_hash(data,keys.m_account_address.m_spend_public_key,keys.m_account_address.m_view_public_key,mode); } + // Use a subaddress else { - skey = keys.m_spend_secret_key; - crypto::secret_key m = m_account.get_device().get_subaddress_secret_key(keys.m_view_secret_key, index); - sc_add((unsigned char*)&skey, (unsigned char*)&m, (unsigned char*)&skey); + skey_spend = keys.m_spend_secret_key; + m = m_account.get_device().get_subaddress_secret_key(keys.m_view_secret_key, index); + sc_add((unsigned char*)&skey_spend, (unsigned char*)&m, (unsigned char*)&skey_spend); + secret_key_to_public_key(skey_spend,pkey_spend); + sc_mul((unsigned char*)&skey_view, (unsigned char*)&keys.m_view_secret_key, (unsigned char*)&skey_spend); + secret_key_to_public_key(skey_view,pkey_view); + switch (signature_type) + { + case sign_with_spend_key: + skey = skey_spend; + pkey = pkey_spend; + mode = 0; + break; + case sign_with_view_key: + skey = skey_view; + pkey = pkey_view; + mode = 1; + break; + default: CHECK_AND_ASSERT_THROW_MES(false, "Invalid signature type requested"); + } secret_key_to_public_key(skey, pkey); + hash = get_message_hash(data,pkey_spend,pkey_view,mode); } crypto::generate_signature(hash, pkey, skey, signature); - return std::string("SigV1") + tools::base58::encode(std::string((const char *)&signature, sizeof(signature))); + return std::string("SigV2") + tools::base58::encode(std::string((const char *)&signature, sizeof(signature))); } -bool wallet2::verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const +tools::wallet2::message_signature_result_t wallet2::verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const { - const size_t header_len = strlen("SigV1"); - if (signature.size() < header_len || signature.substr(0, header_len) != "SigV1") { + static const size_t v1_header_len = strlen("SigV1"); + static const size_t v2_header_len = strlen("SigV2"); + const bool v1 = signature.size() >= v1_header_len && signature.substr(0, v1_header_len) == "SigV1"; + const bool v2 = signature.size() >= v2_header_len && signature.substr(0, v2_header_len) == "SigV2"; + if (!v1 && !v2) + { LOG_PRINT_L0("Signature header check error"); - return false; + return {}; } crypto::hash hash; - crypto::cn_fast_hash(data.data(), data.size(), hash); + if (v1) + { + crypto::cn_fast_hash(data.data(), data.size(), hash); + } std::string decoded; - if (!tools::base58::decode(signature.substr(header_len), decoded)) { + if (!tools::base58::decode(signature.substr(v1 ? v1_header_len : v2_header_len), decoded)) { LOG_PRINT_L0("Signature decoding error"); - return false; + return {}; } crypto::signature s; if (sizeof(s) != decoded.size()) { LOG_PRINT_L0("Signature decoding error"); - return false; + return {}; } memcpy(&s, decoded.data(), sizeof(s)); - return crypto::check_signature(hash, address.m_spend_public_key, s); + + // Test each mode and return which mode, if either, succeeded + if (v2) + hash = get_message_hash(data,address.m_spend_public_key,address.m_view_public_key,(uint8_t) 0); + if (crypto::check_signature(hash, address.m_spend_public_key, s)) + return {true, v1 ? 1u : 2u, !v2, sign_with_spend_key }; + + if (v2) + hash = get_message_hash(data,address.m_spend_public_key,address.m_view_public_key,(uint8_t) 1); + if (crypto::check_signature(hash, address.m_view_public_key, s)) + return {true, v1 ? 1u : 2u, !v2, sign_with_view_key }; + + // Both modes failed + return {}; } std::string wallet2::sign_multisig_participant(const std::string& data) const diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 117e4e2f2..62ed111f1 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1341,8 +1341,10 @@ private: */ void set_account_tag_description(const std::string& tag, const std::string& description); - std::string sign(const std::string &data, cryptonote::subaddress_index index = {0, 0}) const; - bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; + enum message_signature_type_t { sign_with_spend_key, sign_with_view_key }; + std::string sign(const std::string &data, message_signature_type_t signature_type, cryptonote::subaddress_index index = {0, 0}) const; + struct message_signature_result_t { bool valid; unsigned version; bool old; message_signature_type_t type; }; + message_signature_result_t verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; /*! * \brief sign_multisig_participant signs given message with the multisig public signer key diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 0ed749cb7..03db8b70f 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2007,7 +2007,18 @@ namespace tools return false; } - res.signature = m_wallet->sign(req.data, {req.account_index, req.address_index}); + tools::wallet2::message_signature_type_t signature_type = tools::wallet2::sign_with_spend_key; + if (req.signature_type == "spend" || req.signature_type == "") + signature_type = tools::wallet2::sign_with_spend_key; + else if (req.signature_type == "view") + signature_type = tools::wallet2::sign_with_view_key; + else + { + er.code = WALLET_RPC_ERROR_CODE_INVALID_SIGNATURE_TYPE; + er.message = "Invalid signature type requested"; + return false; + } + res.signature = m_wallet->sign(req.data, signature_type, {req.account_index, req.address_index}); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -2042,7 +2053,16 @@ namespace tools return false; } - res.good = m_wallet->verify(req.data, info.address, req.signature); + const auto result = m_wallet->verify(req.data, info.address, req.signature); + res.good = result.valid; + res.version = result.version; + res.old = result.old; + switch (result.type) + { + case tools::wallet2::sign_with_spend_key: res.signature_type = "spend"; break; + case tools::wallet2::sign_with_view_key: res.signature_type = "view"; break; + default: res.signature_type = "invalid"; break; + } return true; } //------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 5b2536bf1..81f83fb18 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 19 +#define WALLET_RPC_VERSION_MINOR 20 #define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools @@ -1618,11 +1618,13 @@ namespace wallet_rpc std::string data; uint32_t account_index; uint32_t address_index; + std::string signature_type; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(data) KV_SERIALIZE_OPT(account_index, 0u) KV_SERIALIZE_OPT(address_index, 0u) + KV_SERIALIZE(signature_type) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1657,9 +1659,15 @@ namespace wallet_rpc struct response_t { bool good; + unsigned version; + bool old; + std::string signature_type; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(good); + KV_SERIALIZE(version); + KV_SERIALIZE(old); + KV_SERIALIZE(signature_type); END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index 9b455af6a..f7c5bb0e1 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -76,3 +76,4 @@ #define WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC -43 #define WALLET_RPC_ERROR_CODE_INVALID_LOG_LEVEL -44 #define WALLET_RPC_ERROR_CODE_ATTRIBUTE_NOT_FOUND -45 +#define WALLET_RPC_ERROR_CODE_INVALID_SIGNATURE_TYPE -47 diff --git a/tests/functional_tests/sign_message.py b/tests/functional_tests/sign_message.py index 7ce2f2c18..dbb7cfd6d 100755 --- a/tests/functional_tests/sign_message.py +++ b/tests/functional_tests/sign_message.py @@ -43,8 +43,10 @@ from framework.wallet import Wallet class MessageSigningTest(): def run_test(self): self.create() - self.check_signing(False) - self.check_signing(True) + self.check_signing(False, False) + self.check_signing(False, True) + self.check_signing(True, False) + self.check_signing(True, True) def create(self): print('Creating wallets') @@ -66,8 +68,8 @@ class MessageSigningTest(): assert res.address == self.address[i] assert res.seed == seeds[i] - def check_signing(self, subaddress): - print('Signing/verifing messages with ' + ('subaddress' if subaddress else 'standard address')) + def check_signing(self, subaddress, spend_key): + print('Signing/verifing messages with ' + ('subaddress' if subaddress else 'standard address') + ' ' + ('spend key' if spend_key else 'view key')) messages = ['foo', ''] if subaddress: address = [] @@ -84,17 +86,22 @@ class MessageSigningTest(): account_index = 0 address_index = 0 for message in messages: - res = self.wallet[0].sign(message, account_index = account_index, address_index = address_index) + res = self.wallet[0].sign(message, account_index = account_index, address_index = address_index, signature_type = 'spend' if spend_key else 'view') signature = res.signature for i in range(2): res = self.wallet[i].verify(message, address[0], signature) assert res.good + assert not res.old + assert res.version == 2 + assert res.signature_type == 'spend' if spend_key else 'view' res = self.wallet[i].verify('different', address[0], signature) assert not res.good res = self.wallet[i].verify(message, address[1], signature) assert not res.good res = self.wallet[i].verify(message, address[0], signature + 'x') assert not res.good + res = self.wallet[i].verify(message, address[0], signature.replace('SigV2','SigV1')) + assert not res.good if __name__ == '__main__': MessageSigningTest().run_test() diff --git a/tests/fuzz/signature.cpp b/tests/fuzz/signature.cpp index 2a3e65c25..c587ff6cd 100644 --- a/tests/fuzz/signature.cpp +++ b/tests/fuzz/signature.cpp @@ -59,6 +59,6 @@ BEGIN_INIT_SIMPLE_FUZZER() END_INIT_SIMPLE_FUZZER() BEGIN_SIMPLE_FUZZER() - bool valid = wallet->verify("test", address, std::string((const char*)buf, len)); - std::cout << "Signature " << (valid ? "valid" : "invalid") << std::endl; + tools::wallet2::message_signature_result_t result = wallet->verify("test", address, s); + std::cout << "Signature " << (result.valid ? "valid" : "invalid") << std::endl; END_SIMPLE_FUZZER() diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index ac9ba2d3a..d97c24143 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -706,13 +706,14 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(check_reserve_proof) - def sign(self, data, account_index = 0, address_index = 0): + def sign(self, data, account_index = 0, address_index = 0, signature_type = ""): sign = { 'method': 'sign', 'params' : { 'data': data, 'account_index': account_index, 'address_index': address_index, + 'signature_type': signature_type, }, 'jsonrpc': '2.0', 'id': '0' |