|
1 | 1 | // Copyright (c) 2025 rust-cktap contributors |
2 | 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 |
3 | 3 |
|
| 4 | +use crate::apdu::tap_signer::{XpubCommand, XpubResponse}; |
4 | 5 | use crate::apdu::{ |
5 | 6 | CommandApdu as _, DeriveCommand, DeriveResponse, NewCommand, NewResponse, SignCommand, |
6 | 7 | SignResponse, StatusCommand, StatusResponse, |
7 | 8 | tap_signer::{BackupCommand, BackupResponse, ChangeCommand, ChangeResponse}, |
8 | 9 | }; |
9 | | -use crate::error::{ChangeError, DeriveError, ReadError, SignPsbtError, StatusError}; |
| 10 | +use crate::error::{ChangeError, DeriveError, ReadError, SignPsbtError, StatusError, XpubError}; |
10 | 11 | use crate::shared::{Authentication, Certificate, CkTransport, Nfc, Read, Wait, transmit}; |
11 | 12 | use crate::{BIP32_HARDENED_MASK, CkTapError}; |
12 | 13 | use async_trait::async_trait; |
13 | 14 | use bitcoin::PublicKey; |
14 | | -use bitcoin::bip32::ChainCode; |
| 15 | +use bitcoin::bip32::{ChainCode, Xpub}; |
15 | 16 | use bitcoin::hex::DisplayHex; |
16 | 17 | use bitcoin::secp256k1::{self, All, Message, Secp256k1, ecdsa::Signature}; |
17 | 18 | use bitcoin_hashes::sha256; |
@@ -314,6 +315,15 @@ pub trait TapSignerShared: Authentication { |
314 | 315 | self.set_card_nonce(change_response.card_nonce); |
315 | 316 | Ok(()) |
316 | 317 | } |
| 318 | + |
| 319 | + async fn xpub(&mut self, cvc: &str, master: bool) -> Result<Xpub, XpubError> { |
| 320 | + let (_, epubkey, xcvc) = self.calc_ekeys_xcvc(cvc, XpubCommand::name()); |
| 321 | + let xpub_command = XpubCommand::new(master, epubkey, xcvc); |
| 322 | + let xpub_response: XpubResponse = transmit(self.transport(), &xpub_command).await?; |
| 323 | + self.set_card_nonce(xpub_response.card_nonce); |
| 324 | + let xpub = Xpub::decode(xpub_response.xpub.as_slice())?; |
| 325 | + Ok(xpub) |
| 326 | + } |
317 | 327 | } |
318 | 328 |
|
319 | 329 | #[async_trait] |
@@ -390,3 +400,31 @@ impl core::fmt::Debug for TapSigner { |
390 | 400 | .finish() |
391 | 401 | } |
392 | 402 | } |
| 403 | + |
| 404 | +#[cfg(feature = "emulator")] |
| 405 | +#[cfg(test)] |
| 406 | +mod test { |
| 407 | + use crate::emulator::find_emulator; |
| 408 | + use crate::emulator::test::{CardTypeOption, EcardSubprocess}; |
| 409 | + use crate::tap_signer::TapSignerShared; |
| 410 | + use crate::{CkTapCard, rand_chaincode}; |
| 411 | + use std::path::Path; |
| 412 | + |
| 413 | + // verify the xpub command works |
| 414 | + #[tokio::test] |
| 415 | + async fn test_tap_signer_xpub() { |
| 416 | + let card_type = CardTypeOption::TapSigner; |
| 417 | + let pipe_path = "/tmp/test-tapsigner-xpub-pipe"; |
| 418 | + let pipe_path = Path::new(&pipe_path); |
| 419 | + let python = EcardSubprocess::new(pipe_path, &card_type).unwrap(); |
| 420 | + let emulator = find_emulator(pipe_path).await.unwrap(); |
| 421 | + if let CkTapCard::TapSigner(mut ts) = emulator { |
| 422 | + ts.init(rand_chaincode(), "123456").await.unwrap(); |
| 423 | + let xpub = ts.xpub("123456", false).await.unwrap(); |
| 424 | + assert_eq!(xpub.depth, 3); |
| 425 | + let master_xpub = ts.xpub("123456", true).await.unwrap(); |
| 426 | + assert_eq!(master_xpub.depth, 0); |
| 427 | + } |
| 428 | + drop(python); |
| 429 | + } |
| 430 | +} |
0 commit comments