Skip to content

Commit 7ca0325

Browse files
Lawrence Nahumjgriffiths
authored andcommitted
bip85: add support for RSA derivation
1 parent e45bd84 commit 7ca0325

File tree

14 files changed

+119
-17
lines changed

14 files changed

+119
-17
lines changed

include/wally.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,12 @@ inline int bip85_get_languages(char** output) {
319319
return detail::check_ret(__FUNCTION__, ret);
320320
}
321321

322+
template <class HDKEY, class BYTES_OUT>
323+
inline int bip85_get_rsa_entropy(const HDKEY& hdkey, uint32_t key_bits, uint32_t index, BYTES_OUT& bytes_out, size_t* written) {
324+
int ret = ::bip85_get_rsa_entropy(detail::get_p(hdkey), key_bits, index, bytes_out.data(), bytes_out.size(), written);
325+
return detail::check_ret(__FUNCTION__, ret);
326+
}
327+
322328
template <class BYTES, class ADDR_FAMILY>
323329
inline int addr_segwit_from_bytes(const BYTES& bytes, const ADDR_FAMILY& addr_family, uint32_t flags, char** output) {
324330
int ret = ::wally_addr_segwit_from_bytes(bytes.data(), bytes.size(), detail::get_p(addr_family), flags, output);

include/wally_bip39.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct words;
2828
#define BIP39_WORDLIST_LEN 2048
2929

3030
/**
31-
* Get the list of default supported languages.
31+
* Get the list of default supported languages for BIP39.
3232
*
3333
* .. note:: The string returned should be freed using `wally_free_string`.
3434
*/
@@ -98,7 +98,7 @@ WALLY_CORE_API int bip39_mnemonic_from_bytes(
9898
*
9999
* :param w: Word list to use. Pass NULL to use the default English list.
100100
* :param mnemonic: Mnemonic to convert.
101-
* :param bytes_out: Where to store the resulting entropy.
101+
* :param bytes_out: Destination for the resulting entropy.
102102
* MAX_SIZED_OUTPUT(len, bytes_out, BIP39_ENTROPY_MAX_LEN)
103103
* :param written: Destination for the number of bytes written to ``bytes_out``.
104104
*/

include/wally_bip85.h

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,24 @@ extern "C" {
1010
struct ext_key;
1111

1212
/**
13-
* Get the list of default supported languages.
13+
* Get the list of default supported languages for BIP85.
1414
*
1515
* .. note:: The string returned should be freed using `wally_free_string`.
1616
*/
1717
WALLY_CORE_API int bip85_get_languages(
1818
char **output);
1919

2020
/**
21-
* Generate bip39 mnemonic entropy according to bip85.
21+
* Generate BIP39 mnemonic entropy according to BIP85.
2222
*
23-
* :param hdkey: The parent extended key to derive entropy from.
23+
* :param hdkey: The parent extended key to derive mnemonic entropy from.
2424
* :param lang: The intended language. Pass NULL to use the default English value.
2525
* :param num_words: The intended number of words. Must be 12, 18 or 24.
2626
* :param index: The index used to create the entropy. Must be less than
2727
*| `BIP32_INITIAL_HARDENED_CHILD`.
28-
* :param bytes_out: Where to store the resulting entropy.
28+
* :param bytes_out: Destination for the resulting entropy.
2929
* MAX_SIZED_OUTPUT(len, bytes_out, HMAC_SHA512_LEN)
30-
* :param written: Number of bytes in ``bytes_out`` to be used as entropy.
30+
* :param written: Destination for the number of bytes written to ``bytes_out``.
3131
*/
3232
WALLY_CORE_API int bip85_get_bip39_entropy(
3333
const struct ext_key *hdkey,
@@ -38,6 +38,30 @@ WALLY_CORE_API int bip85_get_bip39_entropy(
3838
size_t len,
3939
size_t *written);
4040

41+
/**
42+
* Generate entropy for seeding RSA key generation according to BIP85.
43+
*
44+
* :param hdkey: The parent extended key to derive RSA entropy from.
45+
* :param key_bits: The intended RSA key size in bits.
46+
* :param index: The index used to create the entropy. Must be less than
47+
*| `BIP32_INITIAL_HARDENED_CHILD`.
48+
* :param bytes_out: Destination for the resulting entropy.
49+
* MAX_SIZED_OUTPUT(len, bytes_out, HMAC_SHA512_LEN)
50+
* :param written: Destination for the number of bytes written to ``bytes_out``.
51+
*
52+
* .. note:: This function always returns HMAC_SHA512_LEN bytes on success.
53+
*
54+
* .. note:: The returned entropy must be given to BIP85-DRNG in order
55+
*| to derive the RSA key to use. It MUST NOT be used directly.
56+
*/
57+
WALLY_CORE_API int bip85_get_rsa_entropy(
58+
const struct ext_key *hdkey,
59+
uint32_t key_bits,
60+
uint32_t index,
61+
unsigned char *bytes_out,
62+
size_t len,
63+
size_t *written);
64+
4165
#ifdef __cplusplus
4266
}
4367
#endif

include/wally_core.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ WALLY_CORE_API int wally_hex_from_bytes(
212212
* Convert a hexadecimal string to bytes.
213213
*
214214
* :param hex: String to convert.
215-
* :param bytes_out: Where to store the resulting bytes.
215+
* :param bytes_out: Destination for the resulting bytes.
216216
* :param len: The length of ``bytes_out`` in bytes.
217217
* :param written: Destination for the number of bytes written to ``bytes_out``.
218218
*/

src/bip85.c

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
/* Bip85 path element values */
1010
#define BIP85_PURPOSE (BIP32_INITIAL_HARDENED_CHILD | 83696968)
1111
#define BIP85_APPLICATION_39 (BIP32_INITIAL_HARDENED_CHILD | 39)
12-
#define BIP85_ENTROPY_PATH_LEN 5
12+
#define BIP85_APPLICATION_RSA (BIP32_INITIAL_HARDENED_CHILD | 828365)
13+
#define BIP85_BIP39_ENTROPY_PATH_LEN 5
14+
#define BIP85_RSA_ENTROPY_PATH_LEN 4
1315
#define BIP85_ENTROPY_HMAC_KEY_LEN 18
1416
static const uint8_t BIP85_ENTROPY_HMAC_KEY[BIP85_ENTROPY_HMAC_KEY_LEN]
1517
= { 'b', 'i', 'p', '-', 'e', 'n', 't', 'r', 'o', 'p', 'y', '-', 'f', 'r', 'o', 'm', '-', 'k' };
@@ -46,7 +48,7 @@ int bip85_get_bip39_entropy(const struct ext_key *hdkey,
4648
size_t* written)
4749
{
4850
const size_t entropy_len = get_entropy_len(num_words);
49-
uint32_t path[BIP85_ENTROPY_PATH_LEN], lang_idx = 0; /* 0=English */
51+
uint32_t path[BIP85_BIP39_ENTROPY_PATH_LEN], lang_idx = 0; /* 0=English */
5052
struct ext_key derived;
5153
int ret;
5254

@@ -76,7 +78,7 @@ int bip85_get_bip39_entropy(const struct ext_key *hdkey,
7678
path[2] = lang_idx | BIP32_INITIAL_HARDENED_CHILD;
7779
path[3] = num_words | BIP32_INITIAL_HARDENED_CHILD;
7880
path[4] = index | BIP32_INITIAL_HARDENED_CHILD;
79-
ret = bip32_key_from_parent_path(hdkey, path, BIP85_ENTROPY_PATH_LEN,
81+
ret = bip32_key_from_parent_path(hdkey, path, BIP85_BIP39_ENTROPY_PATH_LEN,
8082
BIP32_FLAG_KEY_PRIVATE|BIP32_FLAG_SKIP_HASH,
8183
&derived);
8284

@@ -93,3 +95,39 @@ int bip85_get_bip39_entropy(const struct ext_key *hdkey,
9395
wally_clear(&derived, sizeof(derived));
9496
return ret;
9597
}
98+
99+
int bip85_get_rsa_entropy(const struct ext_key *hdkey, uint32_t key_bits, uint32_t index,
100+
unsigned char *bytes_out, size_t len, size_t *written)
101+
{
102+
uint32_t path[BIP85_RSA_ENTROPY_PATH_LEN];
103+
struct ext_key derived;
104+
int ret;
105+
106+
if (written)
107+
*written = 0;
108+
109+
if (!hdkey || key_bits & BIP32_INITIAL_HARDENED_CHILD || index & BIP32_INITIAL_HARDENED_CHILD || !bytes_out
110+
|| len != HMAC_SHA512_LEN || !written)
111+
return WALLY_EINVAL;
112+
113+
/* Derive a private key from the bip85 path for bip39 mnemonic entropy */
114+
path[0] = BIP85_PURPOSE;
115+
path[1] = BIP85_APPLICATION_RSA;
116+
path[2] = key_bits | BIP32_INITIAL_HARDENED_CHILD;
117+
path[3] = index | BIP32_INITIAL_HARDENED_CHILD;
118+
ret = bip32_key_from_parent_path(hdkey, path, BIP85_RSA_ENTROPY_PATH_LEN,
119+
BIP32_FLAG_KEY_PRIVATE | BIP32_FLAG_SKIP_HASH,
120+
&derived);
121+
122+
if (ret == WALLY_OK) {
123+
/* HMAC-SHA512 the derived private key with the fixed bip85 key
124+
* Write result directly into output buffer - 'written' indicates
125+
* how much should be used. */
126+
ret = wally_hmac_sha512(BIP85_ENTROPY_HMAC_KEY, BIP85_ENTROPY_HMAC_KEY_LEN, derived.priv_key + 1,
127+
sizeof(derived.priv_key) - 1, bytes_out, len);
128+
if (ret == WALLY_OK)
129+
*written = len;
130+
}
131+
wally_clear(&derived, sizeof(derived));
132+
return ret;
133+
}

src/mnemonic.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ char *mnemonic_from_bytes(
1818
size_t len);
1919

2020
/**
21-
* Convert a mnemonic representation into a block of bytes.
21+
* Convert a mnemonic representation into bytes.
2222
*
2323
* @w: List of words.
2424
* @mnemonic: Mnemonic sentence to store.
25-
* @bytes_out: Where to store the converted representation.
25+
* @bytes_out: Destination for the resulting bytes.
2626
* @len: The length of @bytes_out in bytes.
27-
* @written: Destination for the number of bytes written.
27+
* @written: Destination for the number of bytes written to ``bytes_out``.
2828
*/
2929
int mnemonic_to_bytes(
3030
const struct words *w,

src/swig_java/jni_extra.java_in

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@
118118
return trimBuffer(buf, len);
119119
}
120120

121+
public final static byte[] bip85_get_rsa_entropy(Object hdkey, long key_bits, long index) {
122+
final byte[] buf = new byte[HMAC_SHA512_LEN];
123+
final int len = bip85_get_rsa_entropy(hdkey, key_bits, index, buf);
124+
return checkBuffer(buf, len);
125+
}
126+
121127
public final static byte[] ecdh(byte[] jarg1, byte[] jarg2) {
122128
return ecdh(jarg1, jarg2, null);
123129
}

src/swig_java/swig.i

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) {
514514
%returns_array_(bip39_mnemonic_to_seed512, 3, 4, BIP39_SEED_LEN_512);
515515
%returns_string(bip85_get_languages);
516516
%returns_size_t(bip85_get_bip39_entropy);
517+
%returns_size_t(bip85_get_rsa_entropy);
517518
%returns_string(wally_addr_segwit_from_bytes);
518519
%returns_size_t(wally_addr_segwit_get_version);
519520
%returns_size_t(wally_addr_segwit_n_get_version);

src/swig_python/python_extra.py_in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ bip38_to_private_key = _wrap_bin(bip38_to_private_key, EC_PRIVATE_KEY_LEN)
144144
bip39_mnemonic_to_bytes = _wrap_bin(bip39_mnemonic_to_bytes, BIP39_ENTROPY_MAX_LEN, resize=True)
145145
bip39_mnemonic_to_seed512 = _wrap_bin(bip39_mnemonic_to_seed512, BIP39_SEED_LEN_512)
146146
bip85_get_bip39_entropy = _wrap_bin(bip85_get_bip39_entropy, HMAC_SHA512_LEN, resize=True)
147+
bip85_get_rsa_entropy = _wrap_bin(bip85_get_rsa_entropy, HMAC_SHA512_LEN, resize=True)
147148
descriptor_get_key_origin_fingerprint = _wrap_bin(descriptor_get_key_origin_fingerprint, BIP32_KEY_FINGERPRINT_LEN)
148149
descriptor_to_script = _wrap_bin(descriptor_to_script, descriptor_to_script_get_maximum_length, resize=True)
149150
ec_private_key_bip341_tweak = _wrap_bin(ec_private_key_bip341_tweak, EC_PRIVATE_KEY_LEN)

src/test/test_bip85.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@
2020
'divorce twin tonight reason outdoor destroy simple truth cigar social volcano')
2121
]
2222

23+
# BIP85/RSA Vectors generated using https://github.com/akarve/bipsea/blob/main/tests/test_bip85.py#L127
24+
rsa_cases = [
25+
('b3415a819ba8175a7f11b949d75133725594ee3dcf6284dbec8fe6a625d0e0df757c148e576369f9405b19aec9356a848897de64202df8da4880a5f769aac297', 1024, 0),
26+
('ca1e93031427e4f086538f89b19f5f224719332c8a7b8c87db7eb81e4be935db24dcbc71873d0607ddd3876777cd158a2f061a5a5153413307df08fe5911a857', 1024, 1),
27+
('e3ff02b1f0b934357cc0952225bb0e90081005b0cc992c5ed22f6fb8e9c628a3a0f138f9324e33ed4ba7250e43dd66d725a4e4c683dcf5a3b4015b82bcf71934', 2048, 0),
28+
('b1b4d03eb9826aeb2fabc4529dc37da5eaaa9072d3e2b7e69da79862e2b9cd8131dbb5a9001612239cd96310f6be0417bd39c39500bf8a99ba5df32571866fe6', 2048, 1),
29+
('9bd8cb61fea01892ffd981b4da7aae22f32c9641e49c48104682e249a98f7911ed55035a52e085938291d64e34537e9cc0b730f42ae9183b5ddaac33a55764ea', 3072, 0),
30+
('fc49330db1352558f615651ae8d7840b083cce5c9e731e349847569d3813a3f7f605b5d66b178bf19fdd04bd7f48d2ddb07e16793703d17ee06c86e49e19a896', 3072, 1),
31+
('12a499947a142ee3ede9c0960061383f2564b5cc569327d0dd22f7887094676f2e5d5785cd4eb683990d12209ebf6f39a5c1b5e217ea66710260e99fbe4b2be3', 4096, 0),
32+
('a6fdf91d4f4a0cadaf3d20d638744b574306725aababa0ab7136f8f8b88c5a4c5ca6104646d695cd95a72ad15e6e6912e263762eab951bfcea8e9939ed7c03f4', 4096, 1),
33+
('b3a0baa54a6fa75363e2bc0809dafd20eacea8b4d0fba9ef26f9ea9c471e135c53c1f787fd6a7a02bf736bed620d44e5b4465856fae6c2ef2d620b730098f8e9', 8192, 0),
34+
('1b5f1ae261e9e36039cd7d55d25e71934a4f0a2fdd2d93b2f73fbd272d04257d6eba8f6ff6bc1ffe1d58f68b707b794e54e983e2f573991bb776b48b8ed9a1ca', 8192, 1),
35+
]
36+
2337
# Additional test vectors
2438
extra_xpriv = 'xprv9s21ZrQH143K3pHDnUsnBxePpiB3pbhu3owZem9cVUPVQLknjYAhzDXGppVipPsLSnx8UM6cmSqh3nG6vUaPxn1EDNNqtF1eqi7XmdLt1v6'
2539
extra_cases = [
@@ -97,7 +111,7 @@ def test_invalid(self):
97111
ret, _ = bip85_get_bip39_entropy(*args)
98112
self.assertEqual(ret, WALLY_EINVAL)
99113

100-
def run_test_cases(self, master_key, cases):
114+
def run_bip39_test_cases(self, master_key, cases):
101115
ret, all_langs = bip39_get_languages()
102116
all_langs = all_langs.split()
103117

@@ -117,14 +131,22 @@ def run_test_cases(self, master_key, cases):
117131
ret = bip39_mnemonic_from_bytes(words, buf, expected_len)
118132
self.assertEqual(ret, (WALLY_OK, mnemonic))
119133

134+
def run_rsa_test_cases(self, master_key, rsa_cases):
135+
buf = create_string_buffer(HMAC_SHA512_LEN)
136+
for expected, key_bits, index in rsa_cases:
137+
ret = bip85_get_rsa_entropy(master_key, key_bits, index, buf, HMAC_SHA512_LEN)
138+
self.assertEqual(ret, (WALLY_OK, HMAC_SHA512_LEN))
139+
self.assertEqual(h(buf[:HMAC_SHA512_LEN]), utf8(expected))
140+
120141
def test_bip85_cases(self):
121-
self.run_test_cases(self.master_key, cases)
142+
self.run_bip39_test_cases(self.master_key, cases)
143+
self.run_rsa_test_cases(self.master_key, rsa_cases)
122144

123145
def test_additional_cases(self):
124146
extra_key = ext_key()
125147
ret = bip32_key_from_base58(utf8(extra_xpriv), byref(extra_key))
126148
self.assertEqual(ret, WALLY_OK)
127-
self.run_test_cases(extra_key, extra_cases)
149+
self.run_bip39_test_cases(extra_key, extra_cases)
128150

129151
if __name__ == '__main__':
130152
unittest.main()

0 commit comments

Comments
 (0)