diff --git a/.gitignore b/.gitignore index b955fe58d6..4dc53ab9e2 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ cmake_install.cmake install_manifest.txt compile_commands.json CTestTestfile.cmake +.cache/ .vs .vscode/* @@ -58,47 +59,36 @@ release/ cmake-build-*/ docs -wallet/unittests/wallet_test beam/beam -beam/unittests/node_test bin/address_test bin/asyncevent_test bin/reactor_test bin/tcpserver_test bin/timer_test -chain/unittests/chain_test -miner/unittests/equihash_test -miner/unittests/miner_test -pool/unittests/pool_test -utility/unittest/serialize_test -utility/unittest/serialization_adapters_test bin/channel_test -utility/unittest/timer_test -keychain/unittests/keychain_test -p2p/unittest/connectivity_stub_test -utility/unittest/address_test -utility/unittest/asyncevent_test -utility/unittest/channel_test -utility/unittest/logger_test -utility/unittest/reactor_test -utility/unittest/shared_data_test -utility/unittest/tcpclient_test -utility/unittest/tcpserver_test -wallet/unittests/keychain_test -wallet/unittests/wallet.dat -wallet/unittests/wallet_network_test -wallet/unittests/private_key_test -core/unittest/storage_test -core/unittest/ecc_test -.vs/ CMakeSettings.json -/utility/unittest/config_test -/p2p/unittest/dialog_test -/p2p/unittest/msg_serializer_test -/p2p/unittest/twopeers_test -/utility/unittest/bridge_test -/utility/unittest/config_test beam/wallet.dat + +# In-tree CMake executables (Unix/macOS; out-of-source builds use ignored build/) +beam/beam-node +bvm/ethash_service/ethash-service +bvm/sid_generator/generate-sid +explorer/explorer-node +node/functionaltests/*_test +node/utils/laser_beam_demo +node/utils/node_net_sim +node/utils/pipe_link +pow/miner_client +wallet/api/wallet-api +wallet/broadcaster/broadcaster +wallet/cli/beam-wallet +wasmclient/wasm-client +3rdparty/libbitcoin/examples/atomic_swap +3rdparty/libbitcoin/examples/electrum_get_balance +3rdparty/libbitcoin/examples/ethereum_example +3rdparty/libbitcoin/examples/get_info +3rdparty/libbitcoin/examples/sign_tx + 3rdparty/opencl-miner/equihash_150_5.dat ui/beam.rc ui/beam.png @@ -110,6 +100,8 @@ ui/*.qm ui/translations.qrc beam_version.gen keykeeper/wasm-key-keeper.* +*/unittest/* +*/unittests/* -out/ -wmake.sh +out/ +wmake.sh diff --git a/bvm/Shaders/Explorer/Parser.cpp b/bvm/Shaders/Explorer/Parser.cpp index 69b0bb34b7..b633a87154 100644 --- a/bvm/Shaders/Explorer/Parser.cpp +++ b/bvm/Shaders/Explorer/Parser.cpp @@ -3118,7 +3118,7 @@ void ParserContext::OnState_DEX(uint32_t /* iVer */) DocAddTableHeader("Amount2"); DocAddTableHeader("Amount-LP-Token"); DocAddTableHeader("Rate 1:2"); - DocAddTableHeader("Rate 2:2"); + DocAddTableHeader("Rate 2:1"); } Env::Key_T k0, k1; diff --git a/bvm/Shaders/Explorer/Parser.wasm b/bvm/Shaders/Explorer/Parser.wasm old mode 100644 new mode 100755 index 4cfd23076c..63a66fccf5 Binary files a/bvm/Shaders/Explorer/Parser.wasm and b/bvm/Shaders/Explorer/Parser.wasm differ diff --git a/utility/cli/options.cpp b/utility/cli/options.cpp index 1ec70ae28d..f92fee7939 100644 --- a/utility/cli/options.cpp +++ b/utility/cli/options.cpp @@ -159,6 +159,10 @@ namespace beam const char* PAYMENT_PROOF_EXPORT = "payment_proof_export"; const char* PAYMENT_PROOF_VERIFY = "payment_proof_verify"; const char* PAYMENT_PROOF_DATA = "payment_proof"; + const char* SIGN_MESSAGE = "sign_message"; + const char* VERIFY_MESSAGE = "verify_message"; + const char* MSG_TO_SIGN = "message"; + const char* SIGNATURE = "signature"; const char* TX_ID = "tx_id"; const char* SEED_PHRASE = "seed_phrase"; const char* IGNORE_DICTIONARY = "ignore_dictionary"; @@ -475,6 +479,8 @@ namespace beam (cli::KEY_SUBKEY, po::value>(), "miner key index (use with export_miner_key)") (cli::WALLET_ADDR, po::value()->default_value("*"), "wallet address") (cli::PAYMENT_PROOF_DATA, po::value(), "payment proof data to verify") + (cli::MSG_TO_SIGN, po::value()->default_value(""), "message to sign or verify") + (cli::SIGNATURE, po::value(), "hex-encoded signature (for verify_message)") (cli::HID_INSTALL_FILE, po::value(), "App image file to install on HID device. If not specified - integrated image will be used") (cli::UTXO, po::value>()->multitoken(), "set IDs of specific UTXO to send") (cli::IMPORT_EXPORT_PATH, po::value()->default_value("export.dat"), "path to import or export wallet data (should be used with import_data|export_data)") diff --git a/utility/cli/options.h b/utility/cli/options.h index 963e393d0d..2ca3f49c59 100644 --- a/utility/cli/options.h +++ b/utility/cli/options.h @@ -144,6 +144,10 @@ namespace beam extern const char* VERSION_FULL; extern const char* GIT_COMMIT_HASH; extern const char* WALLET_ADDR; + extern const char* SIGN_MESSAGE; + extern const char* VERIFY_MESSAGE; + extern const char* MSG_TO_SIGN; + extern const char* SIGNATURE; extern const char* CHANGE_ADDRESS_EXPIRATION; extern const char* WALLET_ADDRESS_LIST; extern const char* WALLET_ADDRESS_VERIFY; diff --git a/wallet/api/v7_0/v7_0_api_defs.h b/wallet/api/v7_0/v7_0_api_defs.h index eaf5ec5af2..cb1c7d551b 100644 --- a/wallet/api/v7_0/v7_0_api_defs.h +++ b/wallet/api/v7_0/v7_0_api_defs.h @@ -16,7 +16,9 @@ #include #include #include +#include #include "core/ecc_native.h" +#include "wallet/core/common.h" namespace beam::wallet { @@ -28,7 +30,8 @@ namespace beam::wallet macro(IPFSUnpin, "ipfs_unpin", API_WRITE_ACCESS, API_ASYNC, APPS_ALLOWED) \ macro(IPFSGc, "ipfs_gc", API_WRITE_ACCESS, API_ASYNC, APPS_ALLOWED) \ macro(SignMessage, "sign_message", API_READ_ACCESS, API_SYNC, APPS_ALLOWED) \ - macro(VerifySignature, "verify_signature", API_READ_ACCESS, API_SYNC, APPS_ALLOWED) + macro(VerifySignature, "verify_signature", API_READ_ACCESS, API_SYNC, APPS_ALLOWED) \ + macro(VerifyMessage, "verify_message", API_READ_ACCESS, API_SYNC, APPS_ALLOWED) // TODO:IPFS add ipfs_caps/ev_ipfs_state methods that returns all available capabilities and ipfs state struct IPFSAdd @@ -96,7 +99,8 @@ namespace beam::wallet struct SignMessage { - std::vector keyMaterial; + boost::optional> keyMaterial; // deprecated, kept for backward compat + boost::optional address; // preferred: own wallet address std::string message; struct Response { @@ -114,4 +118,15 @@ namespace beam::wallet bool result; }; }; + + struct VerifyMessage + { + WalletID address = Zero; + std::string message; + std::vector signature; + struct Response + { + bool isValid; + }; + }; } diff --git a/wallet/api/v7_0/v7_0_api_handle.cpp b/wallet/api/v7_0/v7_0_api_handle.cpp index 3d2597219e..4c73e7723e 100644 --- a/wallet/api/v7_0/v7_0_api_handle.cpp +++ b/wallet/api/v7_0/v7_0_api_handle.cpp @@ -224,19 +224,53 @@ namespace beam::wallet << ss.str() >> hv; } + + void GetMessageHash(ECC::Hash::Value& hv, const PeerID& pk, const std::string& message) + { + ECC::Hash::Processor() + << "beam.signed.message" + << pk + << (uint64_t) message.size() + << Blob(message.data(), (uint32_t) message.size()) + >> hv; + } } void V70Api::onHandleSignMessage(const JsonRpcId& id, SignMessage&& req) { SignMessage::Response resp; ECC::Hash::Value hv; - MyProcessor::DeriveKeyPreimage(hv, Blob(req.keyMaterial)); - auto db = getWalletDB(); - auto pKdf = db->get_MasterKdf(); ECC::Scalar::Native sk; - pKdf->DeriveKey(sk, hv); - GetMessageHash(hv, req.message); + auto db = getWalletDB(); + + if (req.keyMaterial) + { + // Legacy path: derive key from raw key material + MyProcessor::DeriveKeyPreimage(hv, Blob(*req.keyMaterial)); + auto pKdf = db->get_MasterKdf(); + pKdf->DeriveKey(sk, hv); + GetMessageHash(hv, req.message); + } + else + { + // Address-based path + WalletAddress addr; + if (req.address) + { + auto found = db->getAddressByToken(*req.address); + if (!found || !found->isOwn()) + throw jsonrpc_exception(ApiError::InvalidParamsJsonRpc, "Address not found or not owned by this wallet"); + addr = *found; + } + else + { + db->getDefaultAddressAlways(addr); + } + PeerID pid; + db->get_SbbsPeerID(sk, pid, addr.m_OwnID); + GetMessageHash(hv, pid, req.message); + } ECC::Signature sig; sig.Sign(hv, sk); @@ -261,4 +295,17 @@ namespace beam::wallet resp.result = sig.IsValid(hv, req.publicKey); doResponse(id, resp); } + + void V70Api::onHandleVerifyMessage(const JsonRpcId& id, VerifyMessage&& req) + { + ECC::Hash::Value hv; + GetMessageHash(hv, req.address.m_Pk, req.message); + + Deserializer d; + ECC::Signature sig; + d.reset(req.signature); + d & sig; + + doResponse(id, VerifyMessage::Response{req.address.m_Pk.CheckSignature(hv, sig)}); + } } diff --git a/wallet/api/v7_0/v7_0_api_parse.cpp b/wallet/api/v7_0/v7_0_api_parse.cpp index 3390c46a8f..3699882022 100644 --- a/wallet/api/v7_0/v7_0_api_parse.cpp +++ b/wallet/api/v7_0/v7_0_api_parse.cpp @@ -207,9 +207,20 @@ namespace beam::wallet std::pair V70Api::onParseSignMessage(const JsonRpcId& id, const nlohmann::json& params) { SignMessage message; - message.message = getMandatoryParam(params, "message"); - auto km = getMandatoryParam(params, "key_material"); - message.keyMaterial = from_hex(km); + message.message = getOptionalParam(params, "message").get_value_or(""); + + auto km = getOptionalParam(params, "key_material"); + auto addr = getOptionalParam(params, "address"); + + if (km && addr) + throw jsonrpc_exception(ApiError::InvalidParamsJsonRpc, "Provide either 'address' or 'key_material', not both"); + + if (km) + message.keyMaterial = from_hex(*km); + else if (addr) + message.address = *addr; + // else: neither provided – default address will be used in the handler + return std::make_pair(message, MethodInfo()); } @@ -233,7 +244,7 @@ namespace beam::wallet message.message = getMandatoryParam(params, "message"); message.publicKey = getMandatoryParam(params, "public_key"); message.signature = getMandatoryParam(params, "signature"); - + return std::make_pair(message, MethodInfo()); } @@ -246,4 +257,31 @@ namespace beam::wallet {"result", res.result } }; } + + std::pair V70Api::onParseVerifyMessage(const JsonRpcId& id, const nlohmann::json& params) + { + VerifyMessage message; + auto addrStr = getMandatoryParam(params, "address"); + if (!message.address.FromHex(addrStr)) + throw jsonrpc_exception(ApiError::InvalidParamsJsonRpc, "Invalid 'address'"); + + message.message = getOptionalParam(params, "message").get_value_or(""); + message.signature = getMandatoryParam(params, "signature"); + + return std::make_pair(message, MethodInfo()); + } + + void V70Api::getResponse(const JsonRpcId& id, const VerifyMessage::Response& res, json& msg) + { + msg = json + { + {JsonRpcHeader, JsonRpcVersion}, + {"id", id}, + {"result", + { + {"is_valid", res.isValid} + } + } + }; + } } diff --git a/wallet/cli/cli.cpp b/wallet/cli/cli.cpp index ecc2d5a538..b1401ce680 100644 --- a/wallet/cli/cli.cpp +++ b/wallet/cli/cli.cpp @@ -3326,6 +3326,111 @@ namespace return 1; } + void GetSignMessageHash(ECC::Hash::Value& hv, const PeerID& pk, const std::string& message) + { + ECC::Hash::Processor() + << "beam.signed.message" + << pk + << (uint64_t) message.size() + << Blob(message.data(), (uint32_t) message.size()) + >> hv; + } + + int SignMessage(const po::variables_map& vm) + { + auto walletDB = OpenDataBase(vm); + + // Resolve address + WalletAddress addr; + std::string addressToken = vm[cli::WALLET_ADDR].as(); + if (addressToken == "*") + { + walletDB->getDefaultAddressAlways(addr); + } + else + { + auto found = walletDB->getAddressByToken(addressToken); + if (!found) + { + BEAM_LOG_ERROR() << "Address not found: " << addressToken; + return -1; + } + addr = *found; + } + + if (!addr.isOwn()) + { + BEAM_LOG_ERROR() << "Address is not owned by this wallet"; + return -1; + } + + // Derive the signing key + ECC::Scalar::Native sk; + PeerID pid; + walletDB->get_SbbsPeerID(sk, pid, addr.m_OwnID); + + // Hash the message + std::string message = vm[cli::MSG_TO_SIGN].as(); + ECC::Hash::Value hv; + GetSignMessageHash(hv, pid, message); + + // Sign + ECC::Signature sig; + sig.Sign(hv, sk); + + Serializer s; + s & sig; + auto sigHex = to_hex(s.buffer().first, s.buffer().second); + + cout << "Signature: " << sigHex << std::endl; + cout << "Address: " << std::to_string(addr.m_BbsAddr) << std::endl; + return 0; + } + + int VerifyMessage(const po::variables_map& vm) + { + std::string addressStr = vm[cli::WALLET_ADDR].as(); + if (addressStr == "*") + { + BEAM_LOG_ERROR() << "Please specify --address for verify_message"; + return -1; + } + + WalletID wid; + if (!wid.FromHex(addressStr)) + { + BEAM_LOG_ERROR() << "Invalid address: " << addressStr; + return -1; + } + + if (vm.count(cli::SIGNATURE) == 0) + { + BEAM_LOG_ERROR() << "Please specify --signature"; + return -1; + } + + std::string message = vm[cli::MSG_TO_SIGN].as(); + ECC::Hash::Value hv; + GetSignMessageHash(hv, wid.m_Pk, message); + + ByteBuffer sigBuf = from_hex(vm[cli::SIGNATURE].as()); + Deserializer d; + ECC::Signature sig; + d.reset(sigBuf); + d & sig; + + if (wid.m_Pk.CheckSignature(hv, sig)) + { + cout << "Good signature from " << addressStr << std::endl; + return 0; + } + else + { + cout << "Invalid signature from " << addressStr << std::endl; + return -1; + } + } + } // namespace io::Reactor::Ptr reactor; @@ -3363,6 +3468,8 @@ int main(int argc, char* argv[]) {cli::TX_DETAILS, TxDetails, "print details of the transaction with given ID"}, {cli::PAYMENT_PROOF_EXPORT, ExportPaymentProof, "export payment proof by transaction ID"}, {cli::PAYMENT_PROOF_VERIFY, VerifyPaymentProof, "verify payment proof"}, + {cli::SIGN_MESSAGE, SignMessage, "sign a message with a wallet address"}, + {cli::VERIFY_MESSAGE, VerifyMessage, "verify a message signature for a wallet address"}, {cli::GENERATE_PHRASE, GeneratePhrase, "generate new seed phrase"}, {cli::WALLET_ADDRESS_LIST, ShowAddressList, "print addresses"}, {cli::WALLET_ADDRESS_VERIFY, VerifyAddress, "verify your Endpoint on the attached HW wallet"},