diff --git a/test/functional/test_framework/wallet_cli_controller.py b/test/functional/test_framework/wallet_cli_controller.py index abece5210b..cfc98d1702 100644 --- a/test/functional/test_framework/wallet_cli_controller.py +++ b/test/functional/test_framework/wallet_cli_controller.py @@ -168,10 +168,11 @@ async def set_lookahead_size(self, size: int, force_reduce: bool) -> str: return await self._write_command(f"wallet-set-lookahead-size {size} {i_know_what_i_am_doing}\n") async def new_public_key(self) -> bytes: - public_key = await self._write_command("address-new-public-key\n") + addr = await self.new_address() + public_key = await self._write_command(f"address-reveal-public-key {addr}\n") # remove the pub key enum value, the first one byte - pub_key_bytes = bytes.fromhex(public_key)[1:] + pub_key_bytes = bytes.fromhex(public_key.split('\n')[1])[1:] return pub_key_bytes async def new_address(self) -> str: diff --git a/test/functional/test_framework/wallet_rpc_controller.py b/test/functional/test_framework/wallet_rpc_controller.py index 0f6067a617..a6ec411f39 100644 --- a/test/functional/test_framework/wallet_rpc_controller.py +++ b/test/functional/test_framework/wallet_rpc_controller.py @@ -144,7 +144,8 @@ async def select_account(self, account_index: int) -> str: return "Success" async def new_public_key(self) -> bytes: - public_key = self._write_command("address_new_public_key", [self.account])['result']['public_key'] + addr = await self.new_address() + public_key = self._write_command("address_reveal_public_key", [self.account, addr])['result']['public_key_hex'] # remove the pub key enum value, the first one byte pub_key_bytes = bytes.fromhex(public_key)[1:] diff --git a/wallet/src/account/mod.rs b/wallet/src/account/mod.rs index 9816b2a5cc..cad7cf58c4 100644 --- a/wallet/src/account/mod.rs +++ b/wallet/src/account/mod.rs @@ -1188,13 +1188,14 @@ impl Account { Ok(self.key_chain.issue_address(db_tx, purpose)?) } - /// Get a new public key that hasn't been used before - pub fn get_new_public_key( - &mut self, - db_tx: &mut StoreTxRw, - purpose: KeyPurpose, + /// Get the corresponding public key for a given public key hash + pub fn find_corresponding_pub_key( + &self, + public_key_hash: &PublicKeyHash, ) -> WalletResult { - Ok(self.key_chain.issue_key(db_tx, purpose)?.into_public_key()) + self.key_chain + .get_public_key_from_public_key_hash(public_key_hash) + .ok_or(WalletError::AddressNotFound) } pub fn get_all_issued_addresses(&self) -> BTreeMap> { diff --git a/wallet/src/key_chain/account_key_chain/mod.rs b/wallet/src/key_chain/account_key_chain/mod.rs index 19a3a97eab..5d770ca28c 100644 --- a/wallet/src/key_chain/account_key_chain/mod.rs +++ b/wallet/src/key_chain/account_key_chain/mod.rs @@ -386,6 +386,17 @@ impl AccountKeyChain { .any(|purpose| self.get_leaf_key_chain(*purpose).is_public_key_hash_mine(pubkey_hash)) } + /// Find the corresponding public key for a given public key hash + pub fn get_public_key_from_public_key_hash( + &self, + pubkey_hash: &PublicKeyHash, + ) -> Option { + KeyPurpose::ALL.iter().find_map(|purpose| { + self.get_leaf_key_chain(*purpose) + .get_public_key_from_public_key_hash(pubkey_hash) + }) + } + /// Derive addresses until there are lookahead unused ones pub fn top_up_all(&mut self, db_tx: &mut impl WalletStorageWriteLocked) -> KeyChainResult<()> { let lookahead_size = self.lookahead_size(); diff --git a/wallet/src/key_chain/leaf_key_chain/mod.rs b/wallet/src/key_chain/leaf_key_chain/mod.rs index ea91d5baa3..d64f336a26 100644 --- a/wallet/src/key_chain/leaf_key_chain/mod.rs +++ b/wallet/src/key_chain/leaf_key_chain/mod.rs @@ -388,6 +388,14 @@ impl LeafKeySoftChain { self.public_key_hash_to_index.get(pkh).copied() } + /// Get public key for public key hash or None if no key found + pub fn get_public_key_from_public_key_hash(&self, pkh: &PublicKeyHash) -> Option { + let child_number = self.public_key_hash_to_index.get(pkh)?; + self.derived_public_keys + .get(child_number) + .map(|pk| pk.clone().into_public_key()) + } + /// Mark a specific key as used in the key pool. This will update the last used key index if /// necessary. Returns false if a key was found and set to used. fn mark_child_key_as_used( diff --git a/wallet/src/wallet/mod.rs b/wallet/src/wallet/mod.rs index 31a4ab07ad..1a7e608915 100644 --- a/wallet/src/wallet/mod.rs +++ b/wallet/src/wallet/mod.rs @@ -201,6 +201,8 @@ pub enum WalletError { InputCannotBeSigned, #[error("Failed to convert partially signed tx to signed")] FailedToConvertPartiallySignedTx(PartiallySignedTransaction), + #[error("The specified address is not found in this wallet")] + AddressNotFound, } /// Result type used for the wallet @@ -924,10 +926,19 @@ impl Wallet { }) } - pub fn get_new_public_key(&mut self, account_index: U31) -> WalletResult { - self.for_account_rw(account_index, |account, db_tx| { - account.get_new_public_key(db_tx, KeyPurpose::ReceiveFunds) - }) + pub fn find_public_key( + &mut self, + account_index: U31, + address: Destination, + ) -> WalletResult { + let account = self.get_account(account_index)?; + match address { + Destination::Address(addr) => account.find_corresponding_pub_key(&addr), + Destination::PublicKey(pk) => Ok(pk), + Destination::ScriptHash(_) + | Destination::AnyoneCanSpend + | Destination::ClassicMultisig(_) => Err(WalletError::NoUtxos), + } } pub fn get_transaction_list( diff --git a/wallet/src/wallet/tests.rs b/wallet/src/wallet/tests.rs index ae19885af5..32509d3c58 100644 --- a/wallet/src/wallet/tests.rs +++ b/wallet/src/wallet/tests.rs @@ -806,13 +806,13 @@ fn wallet_accounts_creation() { let error = wallet.create_next_account(None).err().unwrap(); assert_eq!(error, WalletError::EmptyLastAccount); - let acc1_pk = wallet.get_new_public_key(res.0).unwrap(); + let acc1_pk = wallet.get_new_address(res.0).unwrap().1; let tx = wallet .create_transaction_to_addresses( DEFAULT_ACCOUNT_INDEX, [TxOutput::Transfer( OutputValue::Coin(Amount::from_atoms(1)), - Destination::PublicKey(acc1_pk), + acc1_pk.decode_object(&chain_config).unwrap(), )], vec![], FeeRate::from_amount_per_kb(Amount::ZERO), @@ -1303,7 +1303,7 @@ fn create_stake_pool_and_list_pool_ids(#[case] seed: Seed) { let pool_amount = block1_amount; - let decommission_key = wallet.get_new_public_key(DEFAULT_ACCOUNT_INDEX).unwrap(); + let decommission_key = wallet.get_new_address(DEFAULT_ACCOUNT_INDEX).unwrap().1; let stake_pool_transaction = wallet .create_stake_pool_tx( @@ -1314,7 +1314,7 @@ fn create_stake_pool_and_list_pool_ids(#[case] seed: Seed) { amount: pool_amount, margin_ratio_per_thousand: PerThousand::new_from_rng(&mut rng), cost_per_block: Amount::ZERO, - decommission_key: Destination::PublicKey(decommission_key.clone()), + decommission_key: decommission_key.decode_object(&chain_config).unwrap(), }, ) .unwrap(); @@ -1346,7 +1346,7 @@ fn create_stake_pool_and_list_pool_ids(#[case] seed: Seed) { let (pool_id, pool_data) = pool_ids.first().unwrap(); assert_eq!( pool_data.decommission_key, - Destination::PublicKey(decommission_key) + decommission_key.decode_object(&chain_config).unwrap() ); assert_eq!( &pool_data.utxo_outpoint, @@ -3666,7 +3666,7 @@ fn decommission_pool_wrong_account(#[case] seed: Seed) { let res = wallet.create_next_account(Some("name".into())).unwrap(); assert_eq!(res, (U31::from_u32(1).unwrap(), Some("name".into()))); - let decommission_key = wallet.get_new_public_key(acc_1_index).unwrap(); + let decommission_key = wallet.get_new_address(acc_1_index).unwrap().1; let stake_pool_transaction = wallet .create_stake_pool_tx( @@ -3677,7 +3677,7 @@ fn decommission_pool_wrong_account(#[case] seed: Seed) { amount: pool_amount, margin_ratio_per_thousand: PerThousand::new_from_rng(&mut rng), cost_per_block: Amount::ZERO, - decommission_key: Destination::PublicKey(decommission_key), + decommission_key: decommission_key.decode_object(&chain_config).unwrap(), }, ) .unwrap(); @@ -3767,7 +3767,7 @@ fn decommission_pool_request_wrong_account(#[case] seed: Seed) { let res = wallet.create_next_account(Some("name".into())).unwrap(); assert_eq!(res, (U31::from_u32(1).unwrap(), Some("name".into()))); - let decommission_key = wallet.get_new_public_key(acc_1_index).unwrap(); + let decommission_key = wallet.get_new_address(acc_1_index).unwrap().1; let stake_pool_transaction = wallet .create_stake_pool_tx( @@ -3778,7 +3778,7 @@ fn decommission_pool_request_wrong_account(#[case] seed: Seed) { amount: pool_amount, margin_ratio_per_thousand: PerThousand::new_from_rng(&mut rng), cost_per_block: Amount::ZERO, - decommission_key: Destination::PublicKey(decommission_key), + decommission_key: decommission_key.decode_object(&chain_config).unwrap(), }, ) .unwrap(); @@ -3851,7 +3851,7 @@ fn sign_decommission_pool_request_between_accounts(#[case] seed: Seed) { let res = wallet.create_next_account(Some("name".into())).unwrap(); assert_eq!(res, (U31::from_u32(1).unwrap(), Some("name".into()))); - let decommission_key = wallet.get_new_public_key(acc_1_index).unwrap(); + let decommission_key = wallet.get_new_address(acc_1_index).unwrap().1; let stake_pool_transaction = wallet .create_stake_pool_tx( @@ -3862,7 +3862,7 @@ fn sign_decommission_pool_request_between_accounts(#[case] seed: Seed) { amount: pool_amount, margin_ratio_per_thousand: PerThousand::new_from_rng(&mut rng), cost_per_block: Amount::ZERO, - decommission_key: Destination::PublicKey(decommission_key), + decommission_key: decommission_key.decode_object(&chain_config).unwrap(), }, ) .unwrap(); @@ -3930,7 +3930,7 @@ fn sign_decommission_pool_request_cold_wallet(#[case] seed: Seed) { let another_mnemonic = "legal winner thank year wave sausage worth useful legal winner thank yellow"; let mut cold_wallet = create_wallet_with_mnemonic(chain_config.clone(), another_mnemonic); - let decommission_key = cold_wallet.get_new_public_key(DEFAULT_ACCOUNT_INDEX).unwrap(); + let decommission_key = cold_wallet.get_new_address(DEFAULT_ACCOUNT_INDEX).unwrap().1; let coin_balance = get_coin_balance(&hot_wallet); assert_eq!(coin_balance, Amount::ZERO); @@ -3959,7 +3959,7 @@ fn sign_decommission_pool_request_cold_wallet(#[case] seed: Seed) { amount: pool_amount, margin_ratio_per_thousand: PerThousand::new_from_rng(&mut rng), cost_per_block: Amount::ZERO, - decommission_key: Destination::PublicKey(decommission_key), + decommission_key: decommission_key.decode_object(&chain_config).unwrap(), }, ) .unwrap(); @@ -4026,7 +4026,7 @@ fn filter_pools(#[case] seed: Seed) { let another_mnemonic = "legal winner thank year wave sausage worth useful legal winner thank yellow"; let mut wallet2 = create_wallet_with_mnemonic(chain_config.clone(), another_mnemonic); - let decommission_key = wallet2.get_new_public_key(DEFAULT_ACCOUNT_INDEX).unwrap(); + let decommission_key = wallet2.get_new_address(DEFAULT_ACCOUNT_INDEX).unwrap().1; let coin_balance = get_coin_balance(&wallet1); assert_eq!(coin_balance, Amount::ZERO); @@ -4053,7 +4053,7 @@ fn filter_pools(#[case] seed: Seed) { amount: pool_amount, margin_ratio_per_thousand: PerThousand::new_from_rng(&mut rng), cost_per_block: Amount::ZERO, - decommission_key: Destination::PublicKey(decommission_key), + decommission_key: decommission_key.decode_object(&chain_config).unwrap(), }, ) .unwrap(); diff --git a/wallet/wallet-cli-lib/src/commands/mod.rs b/wallet/wallet-cli-lib/src/commands/mod.rs index d7a650b167..bbf55a572b 100644 --- a/wallet/wallet-cli-lib/src/commands/mod.rs +++ b/wallet/wallet-cli-lib/src/commands/mod.rs @@ -133,8 +133,8 @@ pub enum ColdWalletCommand { NewAddress, /// Generate a new unused public key - #[clap(name = "address-new-public-key")] - NewPublicKey, + #[clap(name = "address-reveal-public-key")] + RevealPublicKey { public_key_hash: String }, /// Show receive-addresses with their usage state. /// Note that whether an address is used isn't based on the wallet, @@ -870,11 +870,14 @@ where Ok(ConsoleCommand::Print(address.address)) } - ColdWalletCommand::NewPublicKey => { + ColdWalletCommand::RevealPublicKey { public_key_hash } => { let selected_account = self.get_selected_acc()?; let public_key = - self.wallet_rpc.issue_public_key(selected_account).await?.public_key; - Ok(ConsoleCommand::Print(public_key)) + self.wallet_rpc.find_public_key(selected_account, public_key_hash).await?; + Ok(ConsoleCommand::Print(format!( + "Public key as hex and as address:\n{}\n{}", + public_key.public_key_hex, public_key.public_key_address + ))) } ColdWalletCommand::ShowReceiveAddresses => { diff --git a/wallet/wallet-controller/src/synced_controller.rs b/wallet/wallet-controller/src/synced_controller.rs index 3579d6848e..30636118f6 100644 --- a/wallet/wallet-controller/src/synced_controller.rs +++ b/wallet/wallet-controller/src/synced_controller.rs @@ -142,9 +142,12 @@ impl<'a, T: NodeInterface, W: WalletEvents> SyncedController<'a, T, W> { .map_err(ControllerError::WalletError) } - pub fn new_public_key(&mut self) -> Result> { + pub fn find_public_key( + &mut self, + address: Destination, + ) -> Result> { self.wallet - .get_new_public_key(self.account_index) + .find_public_key(self.account_index, address) .map_err(ControllerError::WalletError) } diff --git a/wallet/wallet-rpc-lib/src/rpc/interface.rs b/wallet/wallet-rpc-lib/src/rpc/interface.rs index 4017d258f8..b85302d61f 100644 --- a/wallet/wallet-rpc-lib/src/rpc/interface.rs +++ b/wallet/wallet-rpc-lib/src/rpc/interface.rs @@ -98,10 +98,11 @@ trait WalletRpc { #[method(name = "address_new")] async fn issue_address(&self, account_index: AccountIndexArg) -> rpc::RpcResult; - #[method(name = "address_new_public_key")] + #[method(name = "address_reveal_public_key")] async fn issue_public_key( &self, account_index: AccountIndexArg, + address: String, ) -> rpc::RpcResult; #[method(name = "account_balance")] diff --git a/wallet/wallet-rpc-lib/src/rpc/mod.rs b/wallet/wallet-rpc-lib/src/rpc/mod.rs index 6ac6d503e1..026d005430 100644 --- a/wallet/wallet-rpc-lib/src/rpc/mod.rs +++ b/wallet/wallet-rpc-lib/src/rpc/mod.rs @@ -58,6 +58,7 @@ use self::types::{ VrfPublicKeyInfo, }; +#[derive(Clone)] pub struct WalletRpc { wallet: WalletHandle, node: N, @@ -195,17 +196,25 @@ impl WalletRpc { Ok(AddressInfo::new(child_number, destination)) } - pub async fn issue_public_key(&self, account_index: U31) -> WRpcResult { + pub async fn find_public_key( + &self, + account_index: U31, + address: String, + ) -> WRpcResult { let config = ControllerConfig { in_top_x_mb: 5 }; // irrelevant for issuing addresses + let address = Address::from_str(&self.chain_config, &address) + .and_then(|addr| addr.decode_object(&self.chain_config)) + .map_err(|_| RpcError::InvalidAddress)?; + let publick_key = self .wallet .call_async(move |w| { Box::pin(async move { - w.synced_controller(account_index, config).await?.new_public_key() + w.synced_controller(account_index, config).await?.find_public_key(address) }) }) .await??; - Ok(PublicKeyInfo::new(publick_key)) + Ok(PublicKeyInfo::new(publick_key, &self.chain_config)) } pub async fn get_legacy_vrf_public_key( diff --git a/wallet/wallet-rpc-lib/src/rpc/server_impl.rs b/wallet/wallet-rpc-lib/src/rpc/server_impl.rs index b4b565be30..eef0b9e681 100644 --- a/wallet/wallet-rpc-lib/src/rpc/server_impl.rs +++ b/wallet/wallet-rpc-lib/src/rpc/server_impl.rs @@ -136,8 +136,9 @@ impl WalletRpcServer f async fn issue_public_key( &self, account_index: AccountIndexArg, + address: String, ) -> rpc::RpcResult { - rpc::handle_result(self.issue_public_key(account_index.index::()?).await) + rpc::handle_result(self.find_public_key(account_index.index::()?, address).await) } async fn get_issued_addresses( diff --git a/wallet/wallet-rpc-lib/src/rpc/types.rs b/wallet/wallet-rpc-lib/src/rpc/types.rs index 24f0666019..c6a08d82ad 100644 --- a/wallet/wallet-rpc-lib/src/rpc/types.rs +++ b/wallet/wallet-rpc-lib/src/rpc/types.rs @@ -139,13 +139,17 @@ impl AddressWithUsageInfo { #[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub struct PublicKeyInfo { - pub public_key: String, + pub public_key_hex: String, + pub public_key_address: String, } impl PublicKeyInfo { - pub fn new(pub_key: PublicKey) -> Self { + pub fn new(pub_key: PublicKey, chain_config: &ChainConfig) -> Self { Self { - public_key: pub_key.hex_encode(), + public_key_hex: pub_key.hex_encode(), + public_key_address: Address::new(chain_config, &Destination::PublicKey(pub_key)) + .expect("addressable") + .to_string(), } } }