Skip to content

Add a wallet command to get the account extended public key #1926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions crypto/src/key/extended.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use crate::key::secp256k1::extended_keys::{
use crate::key::{PrivateKey, PublicKey};
use randomness::{make_true_rng, CryptoRng, Rng};

use super::hdkd::chain_code::ChainCode;

#[derive(Debug, PartialEq, Eq, Clone, Decode, Encode)]
pub enum ExtendedKeyKind {
#[codec(index = 0)]
Expand Down Expand Up @@ -133,6 +135,15 @@ impl ExtendedPublicKey {
ExtendedPublicKeyHolder::Secp256k1Schnorr(k) => k.into_public_key().into(),
}
}

pub fn into_public_key_and_chain_code(self) -> (PublicKey, ChainCode) {
match self.pub_key {
ExtendedPublicKeyHolder::Secp256k1Schnorr(k) => {
let chain_code = k.chain_code();
(k.into_public_key().into(), chain_code)
}
}
}
}

impl Derivable for ExtendedPrivateKey {
Expand Down
4 changes: 4 additions & 0 deletions crypto/src/key/secp256k1/extended_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ impl Secp256k1ExtendedPublicKey {
self.public_key
}

pub fn chain_code(&self) -> ChainCode {
self.chain_code
}

pub fn from_private_key(private_key: &Secp256k1ExtendedPrivateKey) -> Self {
let secp: secp256k1::Secp256k1<secp256k1::All> = secp256k1::Secp256k1::new();
Secp256k1ExtendedPublicKey {
Expand Down
3 changes: 3 additions & 0 deletions test/functional/test_framework/wallet_cli_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ async def new_public_key(self, address: Optional[str] = None) -> bytes:
async def reveal_public_key_as_address(self, address: str) -> str:
return await self._write_command(f"address-reveal-public-key-as-address {address}\n")

async def account_extended_public_key(self) -> str:
return await self._write_command(f"account-extended-public-key-as-hex\n")

async def new_address(self) -> str:
return await self._write_command(f"address-new\n")

Expand Down
3 changes: 3 additions & 0 deletions test/functional/wallet_get_address_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ async def async_test(self):
best_block_height = await wallet.get_best_block_height()
assert_equal(best_block_height, '0')

result = await wallet.account_extended_public_key()
assert_in("The account extended public key is: 00028b42cd4776376c82791b494155151f56c2d7b471e0c7a526a7ce60dd872e38676b22c5123ba10adeaf4bfcbb45d1a02d828f25bf8646957a98d06287c4e2b850", result)

# new address
for _ in range(4):
pub_key_bytes = await wallet.new_public_key()
Expand Down
5 changes: 5 additions & 0 deletions wallet/src/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use common::size_estimation::{
tx_size_with_num_inputs_and_outputs, DestinationInfoProvider,
};
use common::Uint256;
use crypto::key::extended::ExtendedPublicKey;
use crypto::key::hdkd::child_number::ChildNumber;
use mempool::FeeRate;
use serialization::hex_encoded::HexEncoded;
Expand Down Expand Up @@ -1696,6 +1697,10 @@ impl<K: AccountKeyChains> Account<K> {
.ok_or(WalletError::AddressNotFound)
}

pub fn get_extended_public_key(&self) -> &ExtendedPublicKey {
self.account_info.account_key()
}

pub fn get_all_issued_addresses(
&self,
key_purpose: KeyPurpose,
Expand Down
8 changes: 8 additions & 0 deletions wallet/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ use common::primitives::id::{hash_encoded, WithId};
use common::primitives::{Amount, BlockHeight, Id, H256};
use common::size_estimation::SizeEstimationError;
use consensus::PoSGenerateBlockInputData;
use crypto::key::extended::ExtendedPublicKey;
use crypto::key::hdkd::child_number::ChildNumber;
use crypto::key::hdkd::derivable::Derivable;
use crypto::key::hdkd::u31::U31;
Expand Down Expand Up @@ -1436,6 +1437,13 @@ where
}
}

pub fn account_extended_public_key(
&self,
account_index: U31,
) -> WalletResult<&ExtendedPublicKey> {
Ok(self.get_account(account_index)?.get_extended_public_key())
}

pub fn get_transaction_list(
&self,
account_index: U31,
Expand Down
15 changes: 15 additions & 0 deletions wallet/src/wallet/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,21 @@ fn wallet_seed_phrase_check_address() {
.wallet()
.unwrap();

let (public_key, chain_code) = wallet
.account_extended_public_key(DEFAULT_ACCOUNT_INDEX)
.unwrap()
.clone()
.into_public_key_and_chain_code();
// m/44'/19788'/0' for MNEMONIC
let expected_pk = "029103888be8638b733d54eba6c5a96ae12583881dfab4b9585366548b54e3f8fd";
assert_eq!(
expected_pk,
public_key.hex_encode().strip_prefix("00").unwrap()
);
// m/44'/19788'/0' chain code for MNEMONIC
let expected_chain_code = "0b71f99e82c97a4c8f75d8d215e7260bcf9e964d437ec252af26877adf7e8683";
assert_eq!(expected_chain_code, chain_code.hex_encode());

let address = wallet.get_new_address(DEFAULT_ACCOUNT_INDEX).unwrap();
let pk = wallet.find_public_key(DEFAULT_ACCOUNT_INDEX, address.1.into_object()).unwrap();

Expand Down
19 changes: 19 additions & 0 deletions wallet/wallet-cli-commands/src/command_handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,25 @@ where
))
}

ColdWalletCommand::AccountExtendedPublicKey => {
let (wallet, selected_account) = wallet_and_selected_acc(&mut self.wallet).await?;
let key = wallet.get_account_extended_public_key(selected_account).await?;
let hex_public_key = key.public_key.to_string();
let hex_chain_code = key.chain_code.to_string();

let extended_public_key = format!("{hex_public_key}{hex_chain_code}");
let qr_code = if !self.no_qr {
let qr_code = qrcode_or_error_string(&extended_public_key);
format!("\nOr contained in the QR code:\n{qr_code}")
} else {
String::new()
};

Ok(ConsoleCommand::Print(format!(
"The account extended public key is: {extended_public_key}{qr_code}"
)))
}

ColdWalletCommand::ShowAddresses { include_change } => {
let (wallet, selected_account) = wallet_and_selected_acc(&mut self.wallet).await?;
let addresses_with_usage =
Expand Down
3 changes: 3 additions & 0 deletions wallet/wallet-cli-commands/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ pub enum ColdWalletCommand {
#[clap(name = "staking-show-legacy-vrf-key")]
GetLegacyVrfPublicKey,

#[clap(name = "account-extended-public-key-as-hex")]
AccountExtendedPublicKey,

#[clap(name = "account-sign-raw-transaction")]
SignRawTransaction {
/// Hex encoded transaction or PartiallySignedTransaction.
Expand Down
13 changes: 12 additions & 1 deletion wallet/wallet-controller/src/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ use common::{
primitives::{id::WithId, Amount, Id},
};
use crypto::{
key::hdkd::{child_number::ChildNumber, u31::U31},
key::{
extended::ExtendedPublicKey,
hdkd::{child_number::ChildNumber, u31::U31},
},
vrf::VRFPublicKey,
};
use futures::{stream::FuturesUnordered, FutureExt, TryStreamExt};
Expand Down Expand Up @@ -88,6 +91,14 @@ where
self.account_index
}

pub fn account_extended_public_key(
&mut self,
) -> Result<&ExtendedPublicKey, ControllerError<T>> {
self.wallet
.account_extended_public_key(self.account_index)
.map_err(ControllerError::WalletError)
}

pub fn get_balance(
&self,
utxo_states: UtxoStates,
Expand Down
12 changes: 12 additions & 0 deletions wallet/wallet-controller/src/runtime_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use common::{
};
use crypto::{
key::{
extended::ExtendedPublicKey,
hdkd::{child_number::ChildNumber, u31::U31},
PrivateKey, PublicKey,
},
Expand Down Expand Up @@ -598,6 +599,17 @@ impl<B: storage::Backend + 'static> RuntimeWallet<B> {
}
}

pub fn account_extended_public_key(
&self,
account_index: U31,
) -> WalletResult<&ExtendedPublicKey> {
match self {
RuntimeWallet::Software(w) => w.account_extended_public_key(account_index),
#[cfg(feature = "trezor")]
RuntimeWallet::Trezor(w) => w.account_extended_public_key(account_index),
}
}

pub fn get_vrf_key(
&mut self,
account_index: U31,
Expand Down
27 changes: 19 additions & 8 deletions wallet/wallet-rpc-client/src/handles_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ use wallet_controller::{
};
use wallet_rpc_lib::{
types::{
AddressInfo, AddressWithUsageInfo, Balances, BlockInfo, ComposedTransaction, CreatedWallet,
DelegationInfo, HardwareWalletType, LegacyVrfPublicKeyInfo, NewAccountInfo,
NewDelegationTransaction, NewOrderTransaction, NewSubmittedTransaction,
NewTokenTransaction, NftMetadata, NodeVersion, OpenedWallet, PoolInfo, PublicKeyInfo,
RpcHashedTimelockContract, RpcInspectTransaction, RpcNewTransaction,
RpcPreparedTransaction, RpcStandaloneAddresses, SendTokensFromMultisigAddressResult,
StakePoolBalance, StakingStatus, StandaloneAddressWithDetails, TokenMetadata,
TxOptionsOverrides, UtxoInfo, VrfPublicKeyInfo,
AccountExtendedPublicKey, AddressInfo, AddressWithUsageInfo, Balances, BlockInfo,
ComposedTransaction, CreatedWallet, DelegationInfo, HardwareWalletType,
LegacyVrfPublicKeyInfo, NewAccountInfo, NewDelegationTransaction, NewOrderTransaction,
NewSubmittedTransaction, NewTokenTransaction, NftMetadata, NodeVersion, OpenedWallet,
PoolInfo, PublicKeyInfo, RpcHashedTimelockContract, RpcInspectTransaction,
RpcNewTransaction, RpcPreparedTransaction, RpcStandaloneAddresses,
SendTokensFromMultisigAddressResult, StakePoolBalance, StakingStatus,
StandaloneAddressWithDetails, TokenMetadata, TxOptionsOverrides, UtxoInfo,
VrfPublicKeyInfo,
},
RpcError, WalletRpc,
};
Expand Down Expand Up @@ -850,6 +851,16 @@ where
.map_err(WalletRpcHandlesClientError::WalletRpcError)
}

async fn get_account_extended_public_key(
&self,
account_index: U31,
) -> Result<AccountExtendedPublicKey, Self::Error> {
self.wallet_rpc
.get_account_extended_public_key(account_index)
.await
.map_err(WalletRpcHandlesClientError::WalletRpcError)
}

async fn issue_new_nft(
&self,
account_index: U31,
Expand Down
30 changes: 21 additions & 9 deletions wallet/wallet-rpc-client/src/rpc_client/client_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ use wallet_controller::{
};
use wallet_rpc_lib::{
types::{
AddressInfo, AddressWithUsageInfo, BlockInfo, ComposedTransaction, CreatedWallet,
DelegationInfo, HardwareWalletType, LegacyVrfPublicKeyInfo, NewAccountInfo,
NewDelegationTransaction, NewOrderTransaction, NewSubmittedTransaction,
NewTokenTransaction, NftMetadata, NodeVersion, OpenedWallet, PoolInfo, PublicKeyInfo,
RpcHashedTimelockContract, RpcInspectTransaction, RpcNewTransaction,
RpcPreparedTransaction, RpcStandaloneAddresses, SendTokensFromMultisigAddressResult,
StakePoolBalance, StakingStatus, StandaloneAddressWithDetails, TokenMetadata,
TransactionOptions, TransactionRequestOptions, TxOptionsOverrides, UtxoInfo,
VrfPublicKeyInfo,
AccountExtendedPublicKey, AddressInfo, AddressWithUsageInfo, BlockInfo,
ComposedTransaction, CreatedWallet, DelegationInfo, HardwareWalletType,
LegacyVrfPublicKeyInfo, NewAccountInfo, NewDelegationTransaction, NewOrderTransaction,
NewSubmittedTransaction, NewTokenTransaction, NftMetadata, NodeVersion, OpenedWallet,
PoolInfo, PublicKeyInfo, RpcHashedTimelockContract, RpcInspectTransaction,
RpcNewTransaction, RpcPreparedTransaction, RpcStandaloneAddresses,
SendTokensFromMultisigAddressResult, StakePoolBalance, StakingStatus,
StandaloneAddressWithDetails, TokenMetadata, TransactionOptions, TransactionRequestOptions,
TxOptionsOverrides, UtxoInfo, VrfPublicKeyInfo,
},
ColdWalletRpcClient, WalletRpcClient,
};
Expand Down Expand Up @@ -751,6 +751,18 @@ impl WalletInterface for ClientWalletRpc {
.map_err(WalletRpcError::ResponseError)
}

async fn get_account_extended_public_key(
&self,
account_index: U31,
) -> Result<AccountExtendedPublicKey, Self::Error> {
ColdWalletRpcClient::get_account_extended_public_key(
&self.http_client,
account_index.into(),
)
.await
.map_err(WalletRpcError::ResponseError)
}

async fn issue_new_nft(
&self,
account_index: U31,
Expand Down
20 changes: 13 additions & 7 deletions wallet/wallet-rpc-client/src/wallet_rpc_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ use wallet_controller::{
ConnectedPeer, ControllerConfig, UtxoState, UtxoType,
};
use wallet_rpc_lib::types::{
AddressInfo, AddressWithUsageInfo, Balances, BlockInfo, ComposedTransaction, CreatedWallet,
DelegationInfo, HardwareWalletType, LegacyVrfPublicKeyInfo, NewAccountInfo,
NewDelegationTransaction, NewOrderTransaction, NewSubmittedTransaction, NewTokenTransaction,
NftMetadata, NodeVersion, OpenedWallet, PoolInfo, PublicKeyInfo, RpcHashedTimelockContract,
RpcInspectTransaction, RpcNewTransaction, RpcPreparedTransaction, RpcSignatureStatus,
RpcStandaloneAddresses, SendTokensFromMultisigAddressResult, StakePoolBalance, StakingStatus,
StandaloneAddressWithDetails, TokenMetadata, TxOptionsOverrides, UtxoInfo, VrfPublicKeyInfo,
AccountExtendedPublicKey, AddressInfo, AddressWithUsageInfo, Balances, BlockInfo,
ComposedTransaction, CreatedWallet, DelegationInfo, HardwareWalletType, LegacyVrfPublicKeyInfo,
NewAccountInfo, NewDelegationTransaction, NewOrderTransaction, NewSubmittedTransaction,
NewTokenTransaction, NftMetadata, NodeVersion, OpenedWallet, PoolInfo, PublicKeyInfo,
RpcHashedTimelockContract, RpcInspectTransaction, RpcNewTransaction, RpcPreparedTransaction,
RpcSignatureStatus, RpcStandaloneAddresses, SendTokensFromMultisigAddressResult,
StakePoolBalance, StakingStatus, StandaloneAddressWithDetails, TokenMetadata,
TxOptionsOverrides, UtxoInfo, VrfPublicKeyInfo,
};
use wallet_types::{
partially_signed_transaction::PartiallySignedTransaction, with_locked::WithLocked,
Expand Down Expand Up @@ -184,6 +185,11 @@ pub trait WalletInterface {
address: String,
) -> Result<PublicKeyInfo, Self::Error>;

async fn get_account_extended_public_key(
&self,
account_index: U31,
) -> Result<AccountExtendedPublicKey, Self::Error>;

async fn get_balance(
&self,
account_index: U31,
Expand Down
20 changes: 20 additions & 0 deletions wallet/wallet-rpc-daemon/docs/RPC.md
Original file line number Diff line number Diff line change
Expand Up @@ -3691,6 +3691,26 @@ Returns:
}, .. ]
```

### Method `account_extended_public_key`

Shows the account's extended public key.
The returned extended public key can be used to derive receiving or change addresses for
this account.


Parameters:
```
{ "account_arg": number }
```

Returns:
```
{
"public_key": hex string,
"chain_code": hex string,
}
```

### Method `account_sign_raw_transaction`

Signs the inputs that are not yet signed.
Expand Down
28 changes: 18 additions & 10 deletions wallet/wallet-rpc-lib/src/rpc/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,16 @@ use wallet_types::{
partially_signed_transaction::PartiallySignedTransaction, with_locked::WithLocked,
};

use super::types::{NewTokenTransaction, UtxoInfo};
use crate::types::{
AccountArg, AddressInfo, AddressWithUsageInfo, Balances, ChainInfo, ComposedTransaction,
CreatedWallet, DelegationInfo, HardwareWalletType, HexEncoded, LegacyVrfPublicKeyInfo,
MaybeSignedTransaction, NewAccountInfo, NewDelegationTransaction, NewOrderTransaction,
NewSubmittedTransaction, NftMetadata, NodeVersion, OpenedWallet, PoolInfo, PublicKeyInfo,
RpcAmountIn, RpcHashedTimelockContract, RpcInspectTransaction, RpcNewTransaction,
RpcPreparedTransaction, RpcStandaloneAddresses, RpcUtxoOutpoint, RpcUtxoState, RpcUtxoType,
SendTokensFromMultisigAddressResult, StakePoolBalance, StakingStatus,
StandaloneAddressWithDetails, TokenMetadata, TransactionOptions, TransactionRequestOptions,
TxOptionsOverrides, VrfPublicKeyInfo,
AccountArg, AccountExtendedPublicKey, AddressInfo, AddressWithUsageInfo, Balances, ChainInfo,
ComposedTransaction, CreatedWallet, DelegationInfo, HardwareWalletType, HexEncoded,
LegacyVrfPublicKeyInfo, MaybeSignedTransaction, NewAccountInfo, NewDelegationTransaction,
NewOrderTransaction, NewSubmittedTransaction, NewTokenTransaction, NftMetadata, NodeVersion,
OpenedWallet, PoolInfo, PublicKeyInfo, RpcAmountIn, RpcHashedTimelockContract,
RpcInspectTransaction, RpcNewTransaction, RpcPreparedTransaction, RpcStandaloneAddresses,
RpcUtxoOutpoint, RpcUtxoState, RpcUtxoType, SendTokensFromMultisigAddressResult,
StakePoolBalance, StakingStatus, StandaloneAddressWithDetails, TokenMetadata,
TransactionOptions, TransactionRequestOptions, TxOptionsOverrides, UtxoInfo, VrfPublicKeyInfo,
};

#[rpc::rpc(server)]
Expand Down Expand Up @@ -216,6 +215,15 @@ trait ColdWalletRpc {
account: AccountArg,
) -> rpc::RpcResult<Vec<VrfPublicKeyInfo>>;

/// Shows the account's extended public key.
/// The returned extended public key can be used to derive receiving or change addresses for
/// this account.
#[method(name = "account_extended_public_key")]
async fn get_account_extended_public_key(
&self,
account_arg: AccountArg,
) -> rpc::RpcResult<AccountExtendedPublicKey>;

#[method(name = "account_sign_raw_transaction")]
/// Signs the inputs that are not yet signed.
/// The input is a special format of the transaction serialized to hex. This format is automatically used in this wallet
Expand Down
Loading