Browse Source

Merge JoinMarket-Org/joinmarket-clientserver#1536: RPC-API: Implement message signing

646999179b RPC-API: Implement message signing (Kristaps Kaupe)

Pull request description:

  Resolves #1533.

  My first attempt at adding new RPC-API endpoint, there could be mistakes.

  Didn't add tests in `jmclient/test/test_wallet_rpc.py` as message signing is currently supported for mainnet only, not regtest (but I tested HTTP 400 response with regtest).

Top commit has no ACKs.

Tree-SHA512: 927b1f97ef903e48f82aa3ae70c7ead05c919a28d016d1ad4fd9c7a8299f8faa7606c30ef24af0ce3cd504e8e27b37bc55d1989bf89da88572ac79af0e60ec12
master
merge-script 2 months ago
parent
commit
992d798e5c
No known key found for this signature in database
GPG Key ID: 33E472FE870C7E5D
  1. 53
      docs/api/wallet-rpc.yaml
  2. 2
      src/jmclient/__init__.py
  3. 21
      src/jmclient/wallet_rpc.py
  4. 5
      src/jmclient/wallet_utils.py

53
docs/api/wallet-rpc.yaml

@ -605,6 +605,28 @@ paths:
$ref: '#/components/responses/GetSeed-200-OK' $ref: '#/components/responses/GetSeed-200-OK'
'400': '400':
$ref: '#/components/responses/400-BadRequest' $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: components:
securitySchemes: securitySchemes:
bearerAuth: bearerAuth:
@ -1156,6 +1178,31 @@ components:
type: integer type: integer
example: 6 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. 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: ErrorMessage:
type: object type: object
properties: properties:
@ -1292,6 +1339,12 @@ components:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/YieldGenReportResponse" $ref: "#/components/schemas/YieldGenReportResponse"
SignMessage-200-OK:
description: "return signed message"
content:
application/json:
schema:
$ref: "#/components/schemas/SignMessageResponse"
202-Accepted: 202-Accepted:
description: The request has been submitted successfully for processing, but the processing has not been completed. description: The request has been submitted successfully for processing, but the processing has not been completed.
# Clientside error responses # Clientside error responses

2
src/jmclient/__init__.py

@ -60,7 +60,7 @@ from .wallet_utils import (
wallet_tool_main, wallet_generate_recover_bip39, open_wallet, wallet_tool_main, wallet_generate_recover_bip39, open_wallet,
open_test_wallet_maybe, create_wallet, get_wallet_cls, get_wallet_path, open_test_wallet_maybe, create_wallet, get_wallet_cls, get_wallet_path,
wallet_display, get_utxos_enabled_disabled, wallet_gettimelockaddress, wallet_display, get_utxos_enabled_disabled, wallet_gettimelockaddress,
wallet_change_passphrase) wallet_change_passphrase, wallet_signmessage)
from .wallet_service import WalletService from .wallet_service import WalletService
from .maker import Maker from .maker import Maker
from .yieldgenerator import YieldGenerator, YieldGeneratorBasic, ygmain, \ from .yieldgenerator import YieldGenerator, YieldGeneratorBasic, ygmain, \

21
src/jmclient/wallet_rpc.py

@ -23,7 +23,7 @@ from jmclient import Taker, jm_single, \
get_schedule, get_tumbler_parser, schedule_to_text, \ get_schedule, get_tumbler_parser, schedule_to_text, \
tumbler_filter_orders_callback, tumbler_taker_finished_update, \ tumbler_filter_orders_callback, tumbler_taker_finished_update, \
validate_address, FidelityBondMixin, BaseWallet, WalletError, \ 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 from jmbase.support import get_log, utxostr_to_utxo, JM_CORE_VERSION
jlog = get_log() jlog = get_log()
@ -1375,6 +1375,25 @@ class JMWalletDaemon(Service):
seedphrase, _ = self.services["wallet"].get_mnemonic_words() seedphrase, _ = self.services["wallet"].get_mnemonic_words()
return make_jmwalletd_response(request, seedphrase=seedphrase) return make_jmwalletd_response(request, seedphrase=seedphrase)
@app.route('/wallet/<string:walletname>/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/<string:walletname>/taker/schedule', methods=['POST']) @app.route('/wallet/<string:walletname>/taker/schedule', methods=['POST'])
def start_tumbler(self, request, walletname): def start_tumbler(self, request, walletname):
self.check_cookie(request) self.check_cookie(request)

5
src/jmclient/wallet_utils.py

@ -9,7 +9,7 @@ from optparse import OptionParser
from numbers import Integral from numbers import Integral
from collections import Counter, defaultdict from collections import Counter, defaultdict
from itertools import islice, chain 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, from jmclient import (get_network, WALLET_IMPLEMENTATIONS, Storage, podle,
jm_single, WalletError, BaseWallet, VolatileStorage, jm_single, WalletError, BaseWallet, VolatileStorage,
StoragePasswordError, is_segwit_mode, SegwitLegacyWallet, LegacyWallet, 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 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 """ Given a wallet, a BIP32 HD path (as can be output
from the display method) and a message string, returns from the display method) and a message string, returns
a base64 encoded signature along with the corresponding a base64 encoded signature along with the corresponding

Loading…
Cancel
Save