Skip to content

Commit 1f109de

Browse files
committed
keystore: move keystore_secp256k1_pubkey_hash160 to Rust
1 parent 8d4be74 commit 1f109de

File tree

13 files changed

+127
-92
lines changed

13 files changed

+127
-92
lines changed

src/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,6 @@ add_custom_target(rust-bindgen
319319
--allowlist-function keystore_bip39_mnemonic_to_seed
320320
--allowlist-function keystore_get_root_fingerprint
321321
--allowlist-function keystore_mock_unlocked
322-
--allowlist-function keystore_secp256k1_pubkey_hash160
323322
--allowlist-var EC_PUBLIC_KEY_UNCOMPRESSED_LEN
324323
--allowlist-var EC_PUBLIC_KEY_LEN
325324
--allowlist-function keystore_encode_xpub_at_keypath

src/keystore.c

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -569,19 +569,6 @@ static bool _compressed_to_uncompressed(const uint8_t* pubkey_bytes, uint8_t* un
569569
return true;
570570
}
571571

572-
bool keystore_secp256k1_pubkey_hash160(
573-
const uint32_t* keypath,
574-
size_t keypath_len,
575-
uint8_t* hash160_out)
576-
{
577-
struct ext_key xpub __attribute__((__cleanup__(keystore_zero_xkey))) = {0};
578-
if (!keystore_get_xpub(keypath, keypath_len, &xpub)) {
579-
return false;
580-
}
581-
memcpy(hash160_out, xpub.hash160, sizeof(xpub.hash160));
582-
return true;
583-
}
584-
585572
bool keystore_secp256k1_pubkey_uncompressed(
586573
const uint32_t* keypath,
587574
size_t keypath_len,

src/keystore.h

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -175,18 +175,6 @@ void keystore_zero_xkey(struct ext_key* xkey);
175175
*/
176176
USE_RESULT bool keystore_get_bip39_word(uint16_t idx, char** word_out);
177177

178-
/**
179-
* Return the hash160 of the secp256k1 public key at the keypath.
180-
* @param[in] keypath derivation keypath
181-
* @param[in] keypath_len size of keypath buffer
182-
* @param[out] hash160_out serialized output. Must be HASH160_LEN bytes.
183-
* @return true on success, false if the keystore is locked or the input is invalid.
184-
*/
185-
USE_RESULT bool keystore_secp256k1_pubkey_hash160(
186-
const uint32_t* keypath,
187-
size_t keypath_len,
188-
uint8_t* hash160_out);
189-
190178
/**
191179
* Return the serialized secp256k1 public key at the keypath, in uncompressed format.
192180
* @param[in] keypath derivation keypath

src/rust/bitbox02-rust/Cargo.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ zeroize = "1.5.5"
3838
num-bigint = { version = "0.4.3", default-features = false, optional = true }
3939
num-traits = { version = "0.2", default-features = false, optional = true }
4040
bip32-ed25519 = { git = "https://github.com/digitalbitbox/rust-bip32-ed25519", tag = "v0.1.0", optional = true }
41-
bs58 = { version = "0.4.0", default-features = false, features = ["alloc", "check"], optional = true }
41+
bs58 = { version = "0.4.0", default-features = false, features = ["alloc", "check"] }
4242
bech32 = { version = "0.8.1", default-features = false, optional = true }
4343
blake2 = { version = "0.10.5", default-features = false, optional = true }
4444
minicbor = { version = "0.18.0", default-features = false, features = ["alloc"], optional = true }
@@ -78,14 +78,12 @@ app-ethereum = [
7878

7979
app-bitcoin = [
8080
# enable these dependencies
81-
"bs58",
8281
"bech32",
8382
# enable this feature in the deps
8483
"bitbox02/app-bitcoin",
8584
]
8685
app-litecoin = [
8786
# enable these dependencies
88-
"bs58",
8987
"bech32",
9088
# enable this feature in the deps
9189
"bitbox02/app-litecoin",
@@ -100,7 +98,6 @@ app-cardano = [
10098
# enable these deps
10199
"bech32",
102100
"blake2",
103-
"bs58",
104101
"minicbor",
105102
"crc",
106103

src/rust/bitbox02-rust/src/bip32.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,12 @@ pub fn serialize_xpub_str(xpub: &pb::XPub, xpub_type: XPubType) -> Result<String
6161
.into_string())
6262
}
6363

64-
#[cfg(test)]
64+
/// Parses a Base58Check-encoded xpub string. The 4 version bytes are not checked and discarded.
6565
pub fn parse_xpub(xpub: &str) -> Result<pb::XPub, ()> {
66-
let decoded = bs58::decode(xpub).into_vec().or(Err(()))?;
66+
let decoded = bs58::decode(xpub).with_check(None).into_vec().or(Err(()))?;
67+
if decoded.len() != 78 {
68+
return Err(());
69+
}
6770
Ok(pb::XPub {
6871
depth: decoded[4..5].to_vec(),
6972
parent_fingerprint: decoded[5..9].to_vec(),

src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,14 @@ impl Payload {
7777
) -> Result<Self, Error> {
7878
match simple_type {
7979
SimpleType::P2wpkh => Ok(Payload {
80-
data: keystore::secp256k1_pubkey_hash160(keypath)?.to_vec(),
80+
data: crate::keystore::secp256k1_pubkey_hash160(keypath)?,
8181
output_type: BtcOutputType::P2wpkh,
8282
}),
8383
SimpleType::P2wpkhP2sh => {
8484
let payload_p2wpkh = Payload::from_simple(params, SimpleType::P2wpkh, keypath)?;
8585
let pkscript_p2wpkh = payload_p2wpkh.pk_script(params)?;
8686
Ok(Payload {
87-
data: bitbox02::app_btc::hash160(&pkscript_p2wpkh).to_vec(),
87+
data: bitbox02::hash160(&pkscript_p2wpkh).to_vec(),
8888
output_type: BtcOutputType::P2sh,
8989
})
9090
}
@@ -134,7 +134,7 @@ impl Payload {
134134
pb::btc_script_config::multisig::ScriptType::P2wshP2sh => {
135135
let pkscript_p2wsh = payload_p2wsh.pk_script(params)?;
136136
Ok(Payload {
137-
data: bitbox02::app_btc::hash160(&pkscript_p2wsh).to_vec(),
137+
data: bitbox02::hash160(&pkscript_p2wsh).to_vec(),
138138
output_type: BtcOutputType::P2sh,
139139
})
140140
}

src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ fn sighash_script(
275275
| pb::btc_script_config::SimpleType::P2wpkh => {
276276
// See https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#specification, item 5:
277277
// > For P2WPKH witness program, the scriptCode is 0x1976a914{20-byte-pubkey-hash}88ac.
278-
let pubkey_hash160 = bitbox02::keystore::secp256k1_pubkey_hash160(keypath)?;
278+
let pubkey_hash160 = crate::keystore::secp256k1_pubkey_hash160(keypath)?;
279279
let mut result = Vec::<u8>::new();
280280
result.extend_from_slice(b"\x76\xa9\x14");
281281
result.extend_from_slice(&pubkey_hash160);

src/rust/bitbox02-rust/src/keystore.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,105 @@
1414

1515
#[cfg(feature = "ed25519")]
1616
pub mod ed25519;
17+
18+
use super::pb;
19+
20+
use alloc::vec::Vec;
21+
22+
use crate::bip32;
23+
use bitbox02::keystore;
24+
25+
/// Derives an xpub from the keystore seed at the given keypath.
26+
pub fn get_xpub(keypath: &[u32]) -> Result<pb::XPub, ()> {
27+
// Convert from C keystore to Rust by encoding the xpub in C and decoding it in Rust. Would be a
28+
// bit better to encode/decoding using the raw 78 bytes, not the base58Check encoding.
29+
bip32::parse_xpub(&keystore::encode_xpub_at_keypath(
30+
keypath,
31+
keystore::xpub_type_t::XPUB,
32+
)?)
33+
}
34+
35+
/// Return the hash160 of the secp256k1 public key at the keypath.
36+
pub fn secp256k1_pubkey_hash160(keypath: &[u32]) -> Result<Vec<u8>, ()> {
37+
let xpub = get_xpub(keypath)?;
38+
Ok(bitbox02::hash160(&xpub.public_key).to_vec())
39+
}
40+
41+
#[cfg(test)]
42+
mod tests {
43+
use super::*;
44+
45+
use bitbox02::testing::{mock_unlocked, mock_unlocked_using_mnemonic};
46+
use util::bip32::HARDENED;
47+
48+
#[test]
49+
fn test_get_xpub() {
50+
let keypath = &[44 + HARDENED, 0 + HARDENED, 0 + HARDENED];
51+
52+
keystore::lock();
53+
assert!(get_xpub(keypath).is_err());
54+
55+
// 24 words
56+
mock_unlocked_using_mnemonic("sleep own lobster state clean thrive tail exist cactus bitter pass soccer clinic riot dream turkey before sport action praise tunnel hood donate man");
57+
assert_eq!(
58+
bip32::serialize_xpub_str(
59+
&get_xpub(&[]).unwrap(),
60+
bip32::XPubType::Xpub
61+
)
62+
.unwrap(),
63+
"xpub661MyMwAqRbcEhX8d9WJh78SZrxusAzWFoykz4n5CF75uYRzixw5FZPUSoWyhaaJ1bpiPFdzdHSQqJN38PcTkyrLmxT4J2JDYfoGJQ4ioE2",
64+
);
65+
assert_eq!(
66+
bip32::serialize_xpub_str(
67+
&get_xpub(keypath).unwrap(),
68+
bip32::XPubType::Xpub
69+
)
70+
.unwrap(),
71+
"xpub6Cj6NNCGj2CRPHvkuEG1rbW3nrNCAnLjaoTg1P67FCGoahSsbg9WQ7YaMEEP83QDxt2kZ3hTPAPpGdyEZcfAC1C75HfR66UbjpAb39f4PnG",
72+
);
73+
74+
// 18 words
75+
mock_unlocked_using_mnemonic("sleep own lobster state clean thrive tail exist cactus bitter pass soccer clinic riot dream turkey before subject");
76+
assert_eq!(
77+
bip32::serialize_xpub_str(
78+
&get_xpub(keypath).unwrap(),
79+
bip32::XPubType::Xpub
80+
)
81+
.unwrap(),
82+
"xpub6C7fKxGtTzEVxCC22U2VHx4GpaVy77DzU6KdZ1CLuHgoUGviBMWDc62uoQVxqcRa5RQbMPnffjpwxve18BG81VJhJDXnSpRe5NGKwVpXiAb",
83+
);
84+
85+
// 12 words
86+
mock_unlocked_using_mnemonic(
87+
"sleep own lobster state clean thrive tail exist cactus bitter pass sniff",
88+
);
89+
assert_eq!(
90+
bip32::serialize_xpub_str(
91+
&get_xpub(keypath).unwrap(),
92+
bip32::XPubType::Xpub
93+
)
94+
.unwrap(),
95+
"xpub6DLvpzjKpJ8k4xYrWYPmZQkUe9dkG1eRig2v6Jz4iYgo8hcpHWx87gGoCGDaB2cHFZ3ExUfe1jDiMu7Ch6gA4ULCBhvwZj29mHCPYSux3YV",
96+
)
97+
}
98+
99+
#[test]
100+
fn test_secp256k1_pubkey_hash160() {
101+
let keypath = &[84 + HARDENED, HARDENED, HARDENED, 0, 0];
102+
103+
keystore::lock();
104+
assert!(secp256k1_pubkey_hash160(keypath).is_err());
105+
106+
mock_unlocked();
107+
assert_eq!(
108+
secp256k1_pubkey_hash160(keypath).unwrap(),
109+
*b"\xb5\x12\x5c\xec\xa0\xc1\xc8\x90\xda\x07\x9a\x12\x88\xdc\xf7\x7a\xa6\xac\xc4\x99"
110+
);
111+
112+
mock_unlocked_using_mnemonic("sleep own lobster state clean thrive tail exist cactus bitter pass soccer clinic riot dream turkey before sport action praise tunnel hood donate man");
113+
assert_eq!(
114+
secp256k1_pubkey_hash160(&[44 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 2]).unwrap(),
115+
*b"\xe5\xf8\x9a\xb6\x54\x37\x44\xf7\x8f\x15\x86\x7c\x43\x06\xee\x86\x6b\xb1\x1d\xf9"
116+
);
117+
}
118+
}

src/rust/bitbox02-rust/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ pub mod async_usb;
3030
pub mod attestation;
3131
pub mod backup;
3232
pub mod bb02_async;
33-
#[cfg(any(feature = "app-bitcoin", feature = "app-litecoin"))]
3433
mod bip32;
3534
pub mod hww;
3635
pub mod keystore;

src/rust/bitbox02/src/app_btc.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,3 @@ pub fn pkscript_from_multisig(
3737
false => Err(()),
3838
}
3939
}
40-
41-
pub fn hash160(data: &[u8]) -> [u8; 20] {
42-
let mut out = [0u8; 20];
43-
unsafe {
44-
bitbox02_sys::wally_hash160(
45-
data.as_ptr(),
46-
data.len() as _,
47-
out.as_mut_ptr(),
48-
out.len() as _,
49-
);
50-
}
51-
out
52-
}

0 commit comments

Comments
 (0)