Skip to content

Commit 6ab12bb

Browse files
author
Jamie C. Driver
committed
bip85: sign digests with bip85 rsa key
1 parent 397b948 commit 6ab12bb

File tree

8 files changed

+709
-2
lines changed

8 files changed

+709
-2
lines changed

docs/index.rst

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,66 @@ get_bip85_pubkey reply
11171117
* 'result' is a public key pem string
11181118

11191119

1120+
.. _sign_bip85_digests_request:
1121+
1122+
sign_bip85_digests request
1123+
--------------------------
1124+
1125+
Request to sign sha256 digests with a bip85-based deterministic key for a given type, key size and index.
1126+
1127+
NOTE: currently this call only supports 'RSA', with sizes 1024, 2048, 3072 and 4096.
1128+
1129+
NOTE: Uses current wallet seed and bip85 specification to produce deterministic entropy to initialise shake256 PRNG, used to create a deterministic key.
1130+
However, since there is no well-specified deterministic recipe for creating keys, this implementation output may differ from other deterministic key-generation algorithms.
1131+
This key is then used to sign the digests passed.
1132+
1133+
.. code-block:: cbor
1134+
1135+
{
1136+
"id": "16",
1137+
"method": "get_bip85_pubkey",
1138+
"params": {
1139+
"key_type": "RSA",
1140+
"key_bits": 2048,
1141+
"index": 1,
1142+
"digests": [
1143+
<32-bytes>,
1144+
<32-bytes>,
1145+
...
1146+
]
1147+
}
1148+
}
1149+
1150+
* 'key_type' must be 'RSA'
1151+
* 'key_bits' must be one of 1024, 2048, 3072 or 4096.
1152+
* 'digests' must be an array of 32-byte digests
1153+
* The maximum number of digests that can be signed in a single call depends upon the key (and hence signature) size.
1154+
key_bits max digests
1155+
1024 8
1156+
2048 8
1157+
3072 6
1158+
4096 4
1159+
1160+
1161+
.. _sign_bip85_digests_reply:
1162+
1163+
sign_bip85_digests reply
1164+
------------------------
1165+
1166+
.. code-block:: cbor
1167+
1168+
{
1169+
"id": "16",
1170+
"result": [
1171+
<bytes>,
1172+
<bytes>,
1173+
...
1174+
]
1175+
}
1176+
1177+
* 'result' is a public key pem string
1178+
1179+
11201180
.. _get_identity_pubkey_request:
11211181

11221182
get_identity_pubkey request

jade_bip85_rsa_sign.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#!/usr/bin/env python
2+
3+
import os
4+
import sys
5+
import argparse
6+
import hashlib
7+
import logging
8+
from jadepy import JadeAPI
9+
10+
TEST_MNEMONIC = 'fish inner face ginger orchard permit useful method fence \
11+
kidney chuckle party favorite sunset draw limb science crane oval letter \
12+
slot invite sadness banana'
13+
14+
# Enable jade logging
15+
jadehandler = logging.StreamHandler()
16+
logger = logging.getLogger('jadepy.jade')
17+
logger.setLevel(logging.DEBUG)
18+
logger.addHandler(jadehandler)
19+
device_logger = logging.getLogger('jadepy.jade-device')
20+
device_logger.setLevel(logging.DEBUG)
21+
device_logger.addHandler(jadehandler)
22+
23+
24+
def get_digest_args(inputs):
25+
digests = []
26+
for hexdata in inputs:
27+
try:
28+
# Expect the arguments to be digest hex
29+
digest = bytes.fromhex(hexdata)
30+
31+
if digest and len(digest) == 32:
32+
digests.append(digest)
33+
else:
34+
print('Invalid hex digest:', hexdata)
35+
except ValueError:
36+
pass
37+
38+
return digests
39+
40+
41+
def get_digest_files(inputs):
42+
digests = []
43+
for filename in inputs:
44+
try:
45+
# Expect file contents to be the digest bytes
46+
with open(filename, 'rb') as f:
47+
digest = f.read()
48+
49+
if digest and len(digest) == 32:
50+
digests.append(digest)
51+
else:
52+
print('Invalid digest:', d)
53+
except FileNotFoundError as e:
54+
print(e)
55+
56+
return digests
57+
58+
59+
def get_digest_from_data(inputs):
60+
digests = []
61+
for filename in inputs:
62+
try:
63+
# Hash file contents to create digest
64+
with open(filename, 'rb') as f:
65+
data = f.read()
66+
67+
digest = hashlib.sha256(data).digest()
68+
assert digest and len(digest) == 32
69+
digests.append(digest)
70+
71+
except FileNotFoundError as e:
72+
print(e)
73+
74+
return digests
75+
76+
77+
def get_jade_rsa_data(args, digests):
78+
# Connect to Jade over serial
79+
with JadeAPI.create_serial(device=args.serialport) as jade:
80+
# Get the version info and push entropy
81+
verinfo = jade.get_version_info()
82+
assert jade.add_entropy(os.urandom(64))
83+
84+
# Maybe push mnemonic (dev only)
85+
if args.pushmnemonic:
86+
jade.set_mnemonic(TEST_MNEMONIC, temporary_wallet=True)
87+
else:
88+
# The network to use is deduced from the version-info
89+
network = 'testnet' if verinfo.get('JADE_NETWORKS') == 'TEST' else 'mainnet'
90+
jade.auth_user(network)
91+
92+
# Get pubkey if requested
93+
get_pubkey = args.printpubkey or args.pubkeyfile
94+
pubkey_pem = jade.get_bip85_pubkey('RSA', args.keylen, args.index) if get_pubkey else None
95+
96+
# Sign digests
97+
sigs = jade.sign_bip85_digests('RSA', args.keylen, args.index, digests) if digests else None
98+
99+
return pubkey_pem, sigs
100+
101+
102+
if __name__ == '__main__':
103+
parser = argparse.ArgumentParser()
104+
105+
parser.add_argument('--serialport',
106+
action='store',
107+
dest='serialport',
108+
help='Serial port or device',
109+
default=None)
110+
111+
parser.add_argument('--keylen',
112+
action='store',
113+
dest='keylen',
114+
type=int,
115+
choices=[1024, 2048, 3072, 4096],
116+
help='Key length, in bits',
117+
default=3072)
118+
119+
parser.add_argument('--index',
120+
action='store',
121+
dest='index',
122+
type=int,
123+
help='BIP85 key index',
124+
default=1784767589)
125+
126+
pubkeygrp = parser.add_mutually_exclusive_group()
127+
pubkeygrp.add_argument('--printpubkey',
128+
action='store_true',
129+
dest='printpubkey',
130+
help='Download and print BIP85 pubkey pem',
131+
default=False)
132+
133+
pubkeygrp.add_argument('--savepubkey',
134+
action='store',
135+
dest='pubkeyfile',
136+
help='Download and save BIP85 pubkey pem',
137+
default=False)
138+
139+
ingrp = parser.add_mutually_exclusive_group()
140+
ingrp.add_argument('--digest-files',
141+
action='store_true',
142+
dest='digestfiles',
143+
help='Expect sha256 hash digests in the given input files',
144+
default=False)
145+
146+
ingrp.add_argument('--digest-args',
147+
action='store_true',
148+
dest='digestargs',
149+
help='Expect sha256 hash digests on the command line',
150+
default=False)
151+
152+
parser.add_argument('--push-mnemonic',
153+
action='store_true',
154+
dest='pushmnemonic',
155+
help='Sets a test mnemonic - only works with debug build of Jade',
156+
default=False)
157+
158+
parser.add_argument('--log',
159+
action='store',
160+
dest='loglevel',
161+
help='Jade logging level',
162+
choices=['DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL'],
163+
default='ERROR')
164+
165+
parser.add_argument('inputs',
166+
action='store',
167+
nargs='*',
168+
help='Digest hex or filenames (digests or undigested data)',
169+
default=None)
170+
171+
args = parser.parse_args()
172+
jadehandler.setLevel(getattr(logging, args.loglevel))
173+
logger.debug(f'args: {args}')
174+
175+
if args.digestargs:
176+
digests = get_digest_args(args.inputs)
177+
elif args.digestfiles:
178+
digests = get_digest_files(args.inputs)
179+
else:
180+
digests = get_digest_from_data(args.inputs)
181+
182+
if len(digests) != len(args.inputs):
183+
sys.exit(1)
184+
185+
pubkey_pem, sigs = get_jade_rsa_data(args, digests)
186+
187+
if pubkey_pem:
188+
if args.pubkeyfile:
189+
with open(args.pubkeyfile, 'w') as f:
190+
f.write(pubkey_pem)
191+
else:
192+
assert args.printpubkey
193+
print(pubkey_pem)
194+
195+
# Files in, files out
196+
# Command-line hex in, just print sig hex out
197+
if sigs:
198+
assert len(sigs) == len(args.inputs)
199+
for inputdata, sig in zip(args.inputs, sigs):
200+
assert len(sig) == args.keylen / 8
201+
if args.digestargs:
202+
print(sig.hex())
203+
else:
204+
filename = inputdata + '.sig'
205+
with open(filename, 'wb') as f:
206+
f.write(sig)

jadepy/jade.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,43 @@ def get_bip85_pubkey(self, key_type, key_bits, index):
12761276
'index': index}
12771277
return self._jadeRpc('get_bip85_pubkey', params)
12781278

1279+
def sign_bip85_digests(self, key_type, key_bits, index, digests):
1280+
"""
1281+
RPC call to sign digests with a bip85-derived key.
1282+
1283+
Parameters
1284+
----------
1285+
key_type : string
1286+
The type of key to be derived.
1287+
At this time only 'RSA' is supported.
1288+
1289+
key_bits : int
1290+
The number of bits in the desired key. Must be valid for the key type.
1291+
At this time must be 1024, 2048, 3096 or 4092
1292+
1293+
index : int
1294+
The index to use in the bip32 path to calculate the entropy to generate the key.
1295+
1296+
digests : [bytes]
1297+
An array of digests to sign. The maximum number of digests that can be signed in a
1298+
single call depends upon the key (and hence signature) size.
1299+
key_bits max digests
1300+
1024 8
1301+
2048 8
1302+
3072 6
1303+
4096 4
1304+
1305+
Returns
1306+
-------
1307+
[bytes]
1308+
Array of signatures, same size as input digests array
1309+
"""
1310+
params = {'key_type': key_type,
1311+
'key_bits': key_bits,
1312+
'index': index,
1313+
'digests': digests}
1314+
return self._jadeRpc('sign_bip85_digests', params)
1315+
12791316
def get_identity_pubkey(self, identity, curve, key_type, index=0):
12801317
"""
12811318
RPC call to fetch a pubkey for the given identity (slip13/slip17).

main/process/dashboard.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ void get_commitments_process(void* process_ptr);
149149
void get_blinding_factor_process(void* process_ptr);
150150
void sign_liquid_tx_process(void* process_ptr);
151151
void get_bip85_pubkey_process(void* process_ptr);
152+
void sign_bip85_digests_process(void* process_ptr);
152153
#ifdef CONFIG_DEBUG_MODE
153154
void get_bip85_bip39_entropy_process(void* process_ptr);
154155
void get_bip85_rsa_entropy_process(void* process_ptr);
@@ -598,6 +599,8 @@ static void dispatch_message(jade_process_t* process)
598599
task_function = get_shared_nonce_process;
599600
} else if (IS_METHOD("get_bip85_pubkey")) {
600601
task_function = get_bip85_pubkey_process;
602+
} else if (IS_METHOD("sign_bip85_digests")) {
603+
task_function = sign_bip85_digests_process;
601604
} else if (IS_METHOD("ota_data") || IS_METHOD("ota_complete") || IS_METHOD("tx_input")
602605
|| IS_METHOD("get_extended_data") || IS_METHOD("get_signature") || IS_METHOD("pin")) {
603606
// Method we only expect as part of a multi-message protocol

0 commit comments

Comments
 (0)