Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RPC-API: Implement message signing #1536

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions docs/api/wallet-rpc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does make sense, but, I find myself wondering - would it be better if we do the address -> hdpath conversion ourselves in joinmarketd? I don't think at the API client level (so, JAM), they necessarily have access to a conversion function between address and path, but we do (in wallet.py or cryptoengine.py).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just mimicked behaviour of wallet-tool.py. Should we accept both path or address here in your opinion?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the workflow probably starts with a user wanting to sign on a specific address. Of course nothing wrong with path also, though.

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:
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/jmclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand Down
21 changes: 20 additions & 1 deletion src/jmclient/wallet_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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/<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'])
def start_tumbler(self, request, walletname):
self.check_cookie(request)
Expand Down
5 changes: 3 additions & 2 deletions src/jmclient/wallet_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Loading