diff --git a/docs/api/wallet-rpc.yaml b/docs/api/wallet-rpc.yaml index 5bc34bfc0..5d8f878aa 100644 --- a/docs/api/wallet-rpc.yaml +++ b/docs/api/wallet-rpc.yaml @@ -584,6 +584,28 @@ paths: $ref: '#/components/responses/GetSeed-200-OK' '400': $ref: '#/components/responses/400-BadRequest' + /wallet/{walletname}/signmessage: + get: + security: + - bearerAuth: [] + summary: Sign a message with the private key from an address in the wallet. + parameters: + - name: walletname + in: path + description: name of the wallet including .jmdat + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SignMessageRequest' + responses: + '200': + $ref: '#/components/responses/SignMessage-200-OK' + '400': + $ref: '#/components/responses/400-BadRequest' components: securitySchemes: bearerAuth: @@ -1120,6 +1142,31 @@ components: type: integer example: 6 description: Bitcoin miner fee to use for transaction. A number higher than 1000 is used as satoshi per kvB tx fee. The number lower than that uses the dynamic fee estimation of blockchain provider as confirmation target. + SignMessageRequest: + type: object + required: + - hd_path + - message + properties: + hd_path: + type: string + example: m/84'/0'/0'/0/0 + message: + type: string + SignMessageResponse: + type: object + required: + - signature + - message + - address + properties: + signature: + type: string + message: + type: string + address: + type: string + example: bcrt1qu7k4dppungsqp95nwc7ansqs9m0z95h72j9mze ErrorMessage: type: object properties: @@ -1250,6 +1297,12 @@ components: application/json: schema: $ref: "#/components/schemas/YieldGenReportResponse" + SignMessage-200-OK: + description: "return signed message" + content: + application/json: + schema: + $ref: "#/components/schemas/SignMessageResponse" 202-Accepted: description: The request has been submitted successfully for processing, but the processing has not been completed. # Clientside error responses diff --git a/src/jmclient/__init__.py b/src/jmclient/__init__.py index 567be2205..ab4c3ec09 100644 --- a/src/jmclient/__init__.py +++ b/src/jmclient/__init__.py @@ -60,7 +60,7 @@ wallet_tool_main, wallet_generate_recover_bip39, open_wallet, open_test_wallet_maybe, create_wallet, get_wallet_cls, get_wallet_path, wallet_display, get_utxos_enabled_disabled, wallet_gettimelockaddress, - wallet_change_passphrase) + wallet_change_passphrase, wallet_signmessage) from .wallet_service import WalletService from .maker import Maker from .yieldgenerator import YieldGenerator, YieldGeneratorBasic, ygmain, \ diff --git a/src/jmclient/wallet_rpc.py b/src/jmclient/wallet_rpc.py index a2fdc7d6b..5d33b372e 100644 --- a/src/jmclient/wallet_rpc.py +++ b/src/jmclient/wallet_rpc.py @@ -23,7 +23,7 @@ get_schedule, get_tumbler_parser, schedule_to_text, \ tumbler_filter_orders_callback, tumbler_taker_finished_update, \ validate_address, FidelityBondMixin, BaseWallet, WalletError, \ - ScheduleGenerationErrorNoFunds, BIP39WalletMixin, auth + ScheduleGenerationErrorNoFunds, BIP39WalletMixin, auth, wallet_signmessage from jmbase.support import get_log, utxostr_to_utxo, JM_CORE_VERSION jlog = get_log() @@ -1352,6 +1352,25 @@ def getseed(self, request, walletname): seedphrase, _ = self.services["wallet"].get_mnemonic_words() return make_jmwalletd_response(request, seedphrase=seedphrase) + @app.route('/wallet//signmessage', methods=['POST']) + def signmessage(self, request, walletname): + self.check_cookie(request) + if not self.services["wallet"]: + raise NoWalletFound() + if not self.wallet_name == walletname: + raise InvalidRequestFormat() + + request_data = self.get_POST_body(request, ["hd_path", "message"]) + result = wallet_signmessage(self.services["wallet"], + request_data["hd_path"], request_data["message"], + out_str=False) + if type(result) == str: + return make_jmwalletd_response(request, status=400, + message=result) + else: + return make_jmwalletd_response(request, + signature=result[0], message=result[1], address=result[2]) + @app.route('/wallet//taker/schedule', methods=['POST']) def start_tumbler(self, request, walletname): self.check_cookie(request) diff --git a/src/jmclient/wallet_utils.py b/src/jmclient/wallet_utils.py index 0fe397fde..67ec02ce8 100644 --- a/src/jmclient/wallet_utils.py +++ b/src/jmclient/wallet_utils.py @@ -9,7 +9,7 @@ from numbers import Integral from collections import Counter, defaultdict from itertools import islice, chain -from typing import Callable, Optional, Tuple +from typing import Callable, Optional, Tuple, Union from jmclient import (get_network, WALLET_IMPLEMENTATIONS, Storage, podle, jm_single, WalletError, BaseWallet, VolatileStorage, StoragePasswordError, is_segwit_mode, SegwitLegacyWallet, LegacyWallet, @@ -1176,7 +1176,8 @@ def wallet_dumpprivkey(wallet, hdpath): return wallet.get_wif_path(path) # will raise exception on invalid path -def wallet_signmessage(wallet, hdpath, message, out_str=True): +def wallet_signmessage(wallet, hdpath: str, message: str, + out_str: bool = True) -> Union[Tuple[str, str, str], str]: """ Given a wallet, a BIP32 HD path (as can be output from the display method) and a message string, returns a base64 encoded signature along with the corresponding