Skip to content

Commit 3b78955

Browse files
committed
Add a wallet command to get the account extended public key
1 parent 5234be0 commit 3b78955

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;
@@ -1436,6 +1437,13 @@ where
14361437
}
14371438
}
14381439

1440+
pub fn account_extended_public_key(
1441+
&self,
1442+
account_index: U31,
1443+
) -> WalletResult<&ExtendedPublicKey> {
1444+
Ok(self.get_account(account_index)?.get_extended_public_key())
1445+
}
1446+
14391447
pub fn get_transaction_list(
14401448
&self,
14411449
account_index: U31,

wallet/src/wallet/tests.rs

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

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

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,

wallet/wallet-controller/src/runtime_wallet.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use common::{
3131
};
3232
use crypto::{
3333
key::{
34+
extended::ExtendedPublicKey,
3435
hdkd::{child_number::ChildNumber, u31::U31},
3536
PrivateKey, PublicKey,
3637
},
@@ -598,6 +599,17 @@ impl<B: storage::Backend + 'static> RuntimeWallet<B> {
598599
}
599600
}
600601

602+
pub fn account_extended_public_key(
603+
&self,
604+
account_index: U31,
605+
) -> WalletResult<&ExtendedPublicKey> {
606+
match self {
607+
RuntimeWallet::Software(w) => w.account_extended_public_key(account_index),
608+
#[cfg(feature = "trezor")]
609+
RuntimeWallet::Trezor(w) => w.account_extended_public_key(account_index),
610+
}
611+
}
612+
601613
pub fn get_vrf_key(
602614
&mut self,
603615
account_index: U31,

wallet/wallet-rpc-client/src/handles_client/mod.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,15 @@ use wallet_controller::{
4040
};
4141
use wallet_rpc_lib::{
4242
types::{
43-
AddressInfo, AddressWithUsageInfo, Balances, BlockInfo, ComposedTransaction, CreatedWallet,
44-
DelegationInfo, HardwareWalletType, LegacyVrfPublicKeyInfo, NewAccountInfo,
45-
NewDelegationTransaction, NewOrderTransaction, NewSubmittedTransaction,
46-
NewTokenTransaction, NftMetadata, NodeVersion, OpenedWallet, PoolInfo, PublicKeyInfo,
47-
RpcHashedTimelockContract, RpcInspectTransaction, RpcNewTransaction,
48-
RpcPreparedTransaction, RpcStandaloneAddresses, SendTokensFromMultisigAddressResult,
49-
StakePoolBalance, StakingStatus, StandaloneAddressWithDetails, TokenMetadata,
50-
TxOptionsOverrides, UtxoInfo, VrfPublicKeyInfo,
43+
AccountExtendedPublicKey, AddressInfo, AddressWithUsageInfo, Balances, BlockInfo,
44+
ComposedTransaction, CreatedWallet, DelegationInfo, HardwareWalletType,
45+
LegacyVrfPublicKeyInfo, NewAccountInfo, NewDelegationTransaction, NewOrderTransaction,
46+
NewSubmittedTransaction, NewTokenTransaction, NftMetadata, NodeVersion, OpenedWallet,
47+
PoolInfo, PublicKeyInfo, RpcHashedTimelockContract, RpcInspectTransaction,
48+
RpcNewTransaction, RpcPreparedTransaction, RpcStandaloneAddresses,
49+
SendTokensFromMultisigAddressResult, StakePoolBalance, StakingStatus,
50+
StandaloneAddressWithDetails, TokenMetadata, TxOptionsOverrides, UtxoInfo,
51+
VrfPublicKeyInfo,
5152
},
5253
RpcError, WalletRpc,
5354
};
@@ -850,6 +851,16 @@ where
850851
.map_err(WalletRpcHandlesClientError::WalletRpcError)
851852
}
852853

854+
async fn get_account_extended_public_key(
855+
&self,
856+
account_index: U31,
857+
) -> Result<AccountExtendedPublicKey, Self::Error> {
858+
self.wallet_rpc
859+
.get_account_extended_public_key(account_index)
860+
.await
861+
.map_err(WalletRpcHandlesClientError::WalletRpcError)
862+
}
863+
853864
async fn issue_new_nft(
854865
&self,
855866
account_index: U31,

wallet/wallet-rpc-client/src/rpc_client/client_impl.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ use wallet_controller::{
4545
};
4646
use wallet_rpc_lib::{
4747
types::{
48-
AddressInfo, AddressWithUsageInfo, BlockInfo, ComposedTransaction, CreatedWallet,
49-
DelegationInfo, HardwareWalletType, LegacyVrfPublicKeyInfo, NewAccountInfo,
50-
NewDelegationTransaction, NewOrderTransaction, NewSubmittedTransaction,
51-
NewTokenTransaction, NftMetadata, NodeVersion, OpenedWallet, PoolInfo, PublicKeyInfo,
52-
RpcHashedTimelockContract, RpcInspectTransaction, RpcNewTransaction,
53-
RpcPreparedTransaction, RpcStandaloneAddresses, SendTokensFromMultisigAddressResult,
54-
StakePoolBalance, StakingStatus, StandaloneAddressWithDetails, TokenMetadata,
55-
TransactionOptions, TransactionRequestOptions, TxOptionsOverrides, UtxoInfo,
56-
VrfPublicKeyInfo,
48+
AccountExtendedPublicKey, AddressInfo, AddressWithUsageInfo, BlockInfo,
49+
ComposedTransaction, CreatedWallet, DelegationInfo, HardwareWalletType,
50+
LegacyVrfPublicKeyInfo, NewAccountInfo, NewDelegationTransaction, NewOrderTransaction,
51+
NewSubmittedTransaction, NewTokenTransaction, NftMetadata, NodeVersion, OpenedWallet,
52+
PoolInfo, PublicKeyInfo, RpcHashedTimelockContract, RpcInspectTransaction,
53+
RpcNewTransaction, RpcPreparedTransaction, RpcStandaloneAddresses,
54+
SendTokensFromMultisigAddressResult, StakePoolBalance, StakingStatus,
55+
StandaloneAddressWithDetails, TokenMetadata, TransactionOptions, TransactionRequestOptions,
56+
TxOptionsOverrides, UtxoInfo, VrfPublicKeyInfo,
5757
},
5858
ColdWalletRpcClient, WalletRpcClient,
5959
};
@@ -751,6 +751,18 @@ impl WalletInterface for ClientWalletRpc {
751751
.map_err(WalletRpcError::ResponseError)
752752
}
753753

754+
async fn get_account_extended_public_key(
755+
&self,
756+
account_index: U31,
757+
) -> Result<AccountExtendedPublicKey, Self::Error> {
758+
ColdWalletRpcClient::get_account_extended_public_key(
759+
&self.http_client,
760+
account_index.into(),
761+
)
762+
.await
763+
.map_err(WalletRpcError::ResponseError)
764+
}
765+
754766
async fn issue_new_nft(
755767
&self,
756768
account_index: U31,

wallet/wallet-rpc-client/src/wallet_rpc_traits.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@ use wallet_controller::{
3535
ConnectedPeer, ControllerConfig, UtxoState, UtxoType,
3636
};
3737
use wallet_rpc_lib::types::{
38-
AddressInfo, AddressWithUsageInfo, Balances, BlockInfo, ComposedTransaction, CreatedWallet,
39-
DelegationInfo, HardwareWalletType, LegacyVrfPublicKeyInfo, NewAccountInfo,
40-
NewDelegationTransaction, NewOrderTransaction, NewSubmittedTransaction, NewTokenTransaction,
41-
NftMetadata, NodeVersion, OpenedWallet, PoolInfo, PublicKeyInfo, RpcHashedTimelockContract,
42-
RpcInspectTransaction, RpcNewTransaction, RpcPreparedTransaction, RpcSignatureStatus,
43-
RpcStandaloneAddresses, SendTokensFromMultisigAddressResult, StakePoolBalance, StakingStatus,
44-
StandaloneAddressWithDetails, TokenMetadata, TxOptionsOverrides, UtxoInfo, VrfPublicKeyInfo,
38+
AccountExtendedPublicKey, AddressInfo, AddressWithUsageInfo, Balances, BlockInfo,
39+
ComposedTransaction, CreatedWallet, DelegationInfo, HardwareWalletType, LegacyVrfPublicKeyInfo,
40+
NewAccountInfo, NewDelegationTransaction, NewOrderTransaction, NewSubmittedTransaction,
41+
NewTokenTransaction, NftMetadata, NodeVersion, OpenedWallet, PoolInfo, PublicKeyInfo,
42+
RpcHashedTimelockContract, RpcInspectTransaction, RpcNewTransaction, RpcPreparedTransaction,
43+
RpcSignatureStatus, RpcStandaloneAddresses, SendTokensFromMultisigAddressResult,
44+
StakePoolBalance, StakingStatus, StandaloneAddressWithDetails, TokenMetadata,
45+
TxOptionsOverrides, UtxoInfo, VrfPublicKeyInfo,
4546
};
4647
use wallet_types::{
4748
partially_signed_transaction::PartiallySignedTransaction, with_locked::WithLocked,
@@ -184,6 +185,11 @@ pub trait WalletInterface {
184185
address: String,
185186
) -> Result<PublicKeyInfo, Self::Error>;
186187

188+
async fn get_account_extended_public_key(
189+
&self,
190+
account_index: U31,
191+
) -> Result<AccountExtendedPublicKey, Self::Error>;
192+
187193
async fn get_balance(
188194
&self,
189195
account_index: U31,

wallet/wallet-rpc-daemon/docs/RPC.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3691,6 +3691,26 @@ Returns:
36913691
}, .. ]
36923692
```
36933693

3694+
### Method `account_extended_public_key`
3695+
3696+
Shows the account's extended public key.
3697+
The returned extended public key can be used to derive receiving or change addresses for
3698+
this account.
3699+
3700+
3701+
Parameters:
3702+
```
3703+
{ "account_arg": number }
3704+
```
3705+
3706+
Returns:
3707+
```
3708+
{
3709+
"public_key": hex string,
3710+
"chain_code": hex string,
3711+
}
3712+
```
3713+
36943714
### Method `account_sign_raw_transaction`
36953715

36963716
Signs the inputs that are not yet signed.

wallet/wallet-rpc-lib/src/rpc/interface.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,16 @@ use wallet_types::{
4141
partially_signed_transaction::PartiallySignedTransaction, with_locked::WithLocked,
4242
};
4343

44-
use super::types::{NewTokenTransaction, UtxoInfo};
4544
use crate::types::{
46-
AccountArg, AddressInfo, AddressWithUsageInfo, Balances, ChainInfo, ComposedTransaction,
47-
CreatedWallet, DelegationInfo, HardwareWalletType, HexEncoded, LegacyVrfPublicKeyInfo,
48-
MaybeSignedTransaction, NewAccountInfo, NewDelegationTransaction, NewOrderTransaction,
49-
NewSubmittedTransaction, NftMetadata, NodeVersion, OpenedWallet, PoolInfo, PublicKeyInfo,
50-
RpcAmountIn, RpcHashedTimelockContract, RpcInspectTransaction, RpcNewTransaction,
51-
RpcPreparedTransaction, RpcStandaloneAddresses, RpcUtxoOutpoint, RpcUtxoState, RpcUtxoType,
52-
SendTokensFromMultisigAddressResult, StakePoolBalance, StakingStatus,
53-
StandaloneAddressWithDetails, TokenMetadata, TransactionOptions, TransactionRequestOptions,
54-
TxOptionsOverrides, VrfPublicKeyInfo,
45+
AccountArg, AccountExtendedPublicKey, AddressInfo, AddressWithUsageInfo, Balances, ChainInfo,
46+
ComposedTransaction, CreatedWallet, DelegationInfo, HardwareWalletType, HexEncoded,
47+
LegacyVrfPublicKeyInfo, MaybeSignedTransaction, NewAccountInfo, NewDelegationTransaction,
48+
NewOrderTransaction, NewSubmittedTransaction, NewTokenTransaction, NftMetadata, NodeVersion,
49+
OpenedWallet, PoolInfo, PublicKeyInfo, RpcAmountIn, RpcHashedTimelockContract,
50+
RpcInspectTransaction, RpcNewTransaction, RpcPreparedTransaction, RpcStandaloneAddresses,
51+
RpcUtxoOutpoint, RpcUtxoState, RpcUtxoType, SendTokensFromMultisigAddressResult,
52+
StakePoolBalance, StakingStatus, StandaloneAddressWithDetails, TokenMetadata,
53+
TransactionOptions, TransactionRequestOptions, TxOptionsOverrides, UtxoInfo, VrfPublicKeyInfo,
5554
};
5655

5756
#[rpc::rpc(server)]
@@ -216,6 +215,15 @@ trait ColdWalletRpc {
216215
account: AccountArg,
217216
) -> rpc::RpcResult<Vec<VrfPublicKeyInfo>>;
218217

218+
/// Shows the account's extended public key.
219+
/// The returned extended public key can be used to derive receiving or change addresses for
220+
/// this account.
221+
#[method(name = "account_extended_public_key")]
222+
async fn get_account_extended_public_key(
223+
&self,
224+
account_arg: AccountArg,
225+
) -> rpc::RpcResult<AccountExtendedPublicKey>;
226+
219227
#[method(name = "account_sign_raw_transaction")]
220228
/// Signs the inputs that are not yet signed.
221229
/// The input is a special format of the transaction serialized to hex. This format is automatically used in this wallet

0 commit comments

Comments
 (0)