Skip to content

Commit 896c5f5

Browse files
authored
Merge pull request #1926 from mintlayer/feature/account-extended-pk
Add a wallet command to get the account extended public key
2 parents e2beb19 + 3b78955 commit 896c5f5

File tree

19 files changed

+229
-40
lines changed

19 files changed

+229
-40
lines changed

crypto/src/key/extended.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ use crate::key::secp256k1::extended_keys::{
2525
use crate::key::{PrivateKey, PublicKey};
2626
use randomness::{make_true_rng, CryptoRng, Rng};
2727

28+
use super::hdkd::chain_code::ChainCode;
29+
2830
#[derive(Debug, PartialEq, Eq, Clone, Decode, Encode)]
2931
pub enum ExtendedKeyKind {
3032
#[codec(index = 0)]
@@ -133,6 +135,15 @@ impl ExtendedPublicKey {
133135
ExtendedPublicKeyHolder::Secp256k1Schnorr(k) => k.into_public_key().into(),
134136
}
135137
}
138+
139+
pub fn into_public_key_and_chain_code(self) -> (PublicKey, ChainCode) {
140+
match self.pub_key {
141+
ExtendedPublicKeyHolder::Secp256k1Schnorr(k) => {
142+
let chain_code = k.chain_code();
143+
(k.into_public_key().into(), chain_code)
144+
}
145+
}
146+
}
136147
}
137148

138149
impl Derivable for ExtendedPrivateKey {

crypto/src/key/secp256k1/extended_keys.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ impl Secp256k1ExtendedPublicKey {
191191
self.public_key
192192
}
193193

194+
pub fn chain_code(&self) -> ChainCode {
195+
self.chain_code
196+
}
197+
194198
pub fn from_private_key(private_key: &Secp256k1ExtendedPrivateKey) -> Self {
195199
let secp: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
196200
Secp256k1ExtendedPublicKey {

test/functional/test_framework/wallet_cli_controller.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ async def new_public_key(self, address: Optional[str] = None) -> bytes:
262262
async def reveal_public_key_as_address(self, address: str) -> str:
263263
return await self._write_command(f"address-reveal-public-key-as-address {address}\n")
264264

265+
async def account_extended_public_key(self) -> str:
266+
return await self._write_command(f"account-extended-public-key-as-hex\n")
267+
265268
async def new_address(self) -> str:
266269
return await self._write_command(f"address-new\n")
267270

test/functional/wallet_get_address_usage.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ async def async_test(self):
8181
best_block_height = await wallet.get_best_block_height()
8282
assert_equal(best_block_height, '0')
8383

84+
result = await wallet.account_extended_public_key()
85+
assert_in("The account extended public key is: 00028b42cd4776376c82791b494155151f56c2d7b471e0c7a526a7ce60dd872e38676b22c5123ba10adeaf4bfcbb45d1a02d828f25bf8646957a98d06287c4e2b850", result)
86+
8487
# new address
8588
for _ in range(4):
8689
pub_key_bytes = await wallet.new_public_key()

wallet/src/account/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use common::size_estimation::{
3333
tx_size_with_num_inputs_and_outputs, DestinationInfoProvider,
3434
};
3535
use common::Uint256;
36+
use crypto::key::extended::ExtendedPublicKey;
3637
use crypto::key::hdkd::child_number::ChildNumber;
3738
use mempool::FeeRate;
3839
use serialization::hex_encoded::HexEncoded;
@@ -1696,6 +1697,10 @@ impl<K: AccountKeyChains> Account<K> {
16961697
.ok_or(WalletError::AddressNotFound)
16971698
}
16981699

1700+
pub fn get_extended_public_key(&self) -> &ExtendedPublicKey {
1701+
self.account_info.account_key()
1702+
}
1703+
16991704
pub fn get_all_issued_addresses(
17001705
&self,
17011706
key_purpose: KeyPurpose,

wallet/src/wallet/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ use common::primitives::id::{hash_encoded, WithId};
5959
use common::primitives::{Amount, BlockHeight, Id, H256};
6060
use common::size_estimation::SizeEstimationError;
6161
use consensus::PoSGenerateBlockInputData;
62+
use crypto::key::extended::ExtendedPublicKey;
6263
use crypto::key::hdkd::child_number::ChildNumber;
6364
use crypto::key::hdkd::derivable::Derivable;
6465
use crypto::key::hdkd::u31::U31;
@@ -1441,6 +1442,13 @@ where
14411442
}
14421443
}
14431444

1445+
pub fn account_extended_public_key(
1446+
&self,
1447+
account_index: U31,
1448+
) -> WalletResult<&ExtendedPublicKey> {
1449+
Ok(self.get_account(account_index)?.get_extended_public_key())
1450+
}
1451+
14441452
pub fn get_transaction_list(
14451453
&self,
14461454
account_index: U31,

wallet/src/wallet/tests.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,21 @@ fn wallet_seed_phrase_check_address() {
678678
.wallet()
679679
.unwrap();
680680

681+
let (public_key, chain_code) = wallet
682+
.account_extended_public_key(DEFAULT_ACCOUNT_INDEX)
683+
.unwrap()
684+
.clone()
685+
.into_public_key_and_chain_code();
686+
// m/44'/19788'/0' for MNEMONIC
687+
let expected_pk = "029103888be8638b733d54eba6c5a96ae12583881dfab4b9585366548b54e3f8fd";
688+
assert_eq!(
689+
expected_pk,
690+
public_key.hex_encode().strip_prefix("00").unwrap()
691+
);
692+
// m/44'/19788'/0' chain code for MNEMONIC
693+
let expected_chain_code = "0b71f99e82c97a4c8f75d8d215e7260bcf9e964d437ec252af26877adf7e8683";
694+
assert_eq!(expected_chain_code, chain_code.hex_encode());
695+
681696
let address = wallet.get_new_address(DEFAULT_ACCOUNT_INDEX).unwrap();
682697
let pk = wallet.find_public_key(DEFAULT_ACCOUNT_INDEX, address.1.into_object()).unwrap();
683698

wallet/wallet-cli-commands/src/command_handler/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,25 @@ where
462462
))
463463
}
464464

465+
ColdWalletCommand::AccountExtendedPublicKey => {
466+
let (wallet, selected_account) = wallet_and_selected_acc(&mut self.wallet).await?;
467+
let key = wallet.get_account_extended_public_key(selected_account).await?;
468+
let hex_public_key = key.public_key.to_string();
469+
let hex_chain_code = key.chain_code.to_string();
470+
471+
let extended_public_key = format!("{hex_public_key}{hex_chain_code}");
472+
let qr_code = if !self.no_qr {
473+
let qr_code = qrcode_or_error_string(&extended_public_key);
474+
format!("\nOr contained in the QR code:\n{qr_code}")
475+
} else {
476+
String::new()
477+
};
478+
479+
Ok(ConsoleCommand::Print(format!(
480+
"The account extended public key is: {extended_public_key}{qr_code}"
481+
)))
482+
}
483+
465484
ColdWalletCommand::ShowAddresses { include_change } => {
466485
let (wallet, selected_account) = wallet_and_selected_acc(&mut self.wallet).await?;
467486
let addresses_with_usage =

wallet/wallet-cli-commands/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,9 @@ pub enum ColdWalletCommand {
329329
#[clap(name = "staking-show-legacy-vrf-key")]
330330
GetLegacyVrfPublicKey,
331331

332+
#[clap(name = "account-extended-public-key-as-hex")]
333+
AccountExtendedPublicKey,
334+
332335
#[clap(name = "account-sign-raw-transaction")]
333336
SignRawTransaction {
334337
/// Hex encoded transaction or PartiallySignedTransaction.

wallet/wallet-controller/src/read.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ use common::{
2323
primitives::{id::WithId, Amount, Id},
2424
};
2525
use crypto::{
26-
key::hdkd::{child_number::ChildNumber, u31::U31},
26+
key::{
27+
extended::ExtendedPublicKey,
28+
hdkd::{child_number::ChildNumber, u31::U31},
29+
},
2730
vrf::VRFPublicKey,
2831
};
2932
use futures::{stream::FuturesUnordered, FutureExt, TryStreamExt};
@@ -88,6 +91,14 @@ where
8891
self.account_index
8992
}
9093

94+
pub fn account_extended_public_key(
95+
&mut self,
96+
) -> Result<&ExtendedPublicKey, ControllerError<T>> {
97+
self.wallet
98+
.account_extended_public_key(self.account_index)
99+
.map_err(ControllerError::WalletError)
100+
}
101+
91102
pub fn get_balance(
92103
&self,
93104
utxo_states: UtxoStates,

0 commit comments

Comments
 (0)