diff --git a/nssa/src/encoding/public_transaction.rs b/nssa/src/encoding/public_transaction.rs index 2549cf27..db96e96f 100644 --- a/nssa/src/encoding/public_transaction.rs +++ b/nssa/src/encoding/public_transaction.rs @@ -1,7 +1,7 @@ use crate::{PublicTransaction, error::NssaError, public_transaction::Message}; impl Message { - pub(crate) fn to_bytes(&self) -> Vec { + pub fn to_bytes(&self) -> Vec { borsh::to_vec(&self).expect("Autoderived borsh serialization failure") } } diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index cdd57f88..a2989923 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -13,8 +13,6 @@ pub struct WitnessSet { impl WitnessSet { #[must_use] - // TODO: this generates signatures. - // However. we may need to get signatures from Keycard. pub fn for_message(message: &Message, proof: Proof, private_keys: &[&PrivateKey]) -> Self { let message_bytes = message.to_bytes(); let signatures_and_public_keys = private_keys @@ -32,6 +30,22 @@ impl WitnessSet { } } + #[must_use] + pub fn from_list(proof: Proof, signatures: &Vec, public_keys: &Vec) -> Self { + assert_eq!(signatures.len(), public_keys.len()); + + let signatures_and_public_keys = signatures + .iter() + .zip(public_keys.iter()) + .map(|(sig, key)| (sig.clone(), key.clone())) + .collect(); + + Self { + signatures_and_public_keys, + proof, + } + } + #[must_use] pub fn signatures_are_valid_for(&self, message: &Message) -> bool { let message_bytes = message.to_bytes(); diff --git a/python/keycard_test.py b/python/keycard_test.py index ca66aa6a..df4957aa 100644 --- a/python/keycard_test.py +++ b/python/keycard_test.py @@ -2,13 +2,22 @@ import keycard_wallet as keycard_wallet import time # For testing pin = '111111' +path0 = "m/44'/60'/0'/0/0" +path1 = "m/44'/61'/0'/0/0" +path2 = "m/44'/62'/0'/0/0" +path3 = "m/44'/63'/0'/0/0" +path4 = "m/44'/64'/0'/0/0" my_wallet = keycard_wallet.KeycardWallet() print("Setup communication with card...", my_wallet.setup_communication(pin)) print("Load mnemonic...", my_wallet.load_mnemonic()) -print("Public key", my_wallet.get_public_key_for_path()) +print("Public key", my_wallet.get_public_key_for_path(path0)) +print("Public key", my_wallet.get_public_key_for_path(path1)) +print("Public key", my_wallet.get_public_key_for_path(path2)) +print("Public key", my_wallet.get_public_key_for_path(path3)) +print("Public key", my_wallet.get_public_key_for_path(path4)) print("Signature", my_wallet.sign_message_for_path()) diff --git a/python/keycard_wallet.py b/python/keycard_wallet.py index e2214462..f1f27164 100644 --- a/python/keycard_wallet.py +++ b/python/keycard_wallet.py @@ -96,7 +96,7 @@ class KeycardWallet: print(f"Error during unpair: {e}") return False - def get_public_key_for_path(self, path: str = "m/44'/60'/0'/0/0") -> str | None: + def get_public_key_for_path(self, path: str = "m/44'/60'/0'/0/0") -> bytes | None: try: if not self.card.is_secure_channel_open or not self.card.is_pin_verified: return None @@ -106,14 +106,18 @@ class KeycardWallet: public_only = True, keypath = path ) + # TODO (marvin) clean this up + public_key = public_key.public_key + public_key = VerifyingKey.from_string(public_key[1:], curve=SECP256k1) + public_key = public_key.to_string("compressed")[1:] - return public_key.public_key.hex() + return public_key except Exception as e: print(f"Error getting public key: {e}") return None - def sign_message_for_path(self, message: bytes = b"DefaultMessageTestDefaultMessage", path: str = "m/44'/60'/0'/0/0") -> str | None: + def sign_message_for_path(self, message: bytes = b"DefaultMessageTestDefaultMessage", path: str = "m/44'/60'/0'/0/0") -> bytes | None: try: if not self.card.is_secure_channel_open or not self.card.is_pin_verified: return None @@ -125,7 +129,7 @@ class KeycardWallet: make_current = False ) - return signature.signature.hex() + return signature.signature except Exception as e: print(f"Error signing message: {e}") diff --git a/wallet/src/cli/keycard.rs b/wallet/src/cli/keycard.rs index 38137713..809d0e85 100644 --- a/wallet/src/cli/keycard.rs +++ b/wallet/src/cli/keycard.rs @@ -12,16 +12,10 @@ use crate::{ pub enum KeycardSubcommand { Available, Load { - #[arg( - short, - long, - )] + #[arg(short, long)] mnemonic: Option, - #[arg( - short, - long, - )] - pin: Option + #[arg(short, long)] + pin: Option, }, } @@ -37,7 +31,9 @@ impl WalletSubcommand for KeycardSubcommand { python_path::add_python_path(py).expect("keycard_wallet.py not found"); let wallet = KeycardWallet::new(py).expect("Expect keycard wallet"); - let available = wallet.is_unpaired_keycard_available(py).expect("Expect a Boolean."); + let available = wallet + .is_unpaired_keycard_available(py) + .expect("Expect a Boolean."); if available { println!("\u{2705} Keycard is available."); @@ -47,46 +43,30 @@ impl WalletSubcommand for KeycardSubcommand { }); Ok(SubcommandReturnValue::Empty) - },/* - Self::Connect { pin } => { - // TODO This should be persistent. + } + Self::Load { mnemonic, pin } => { Python::with_gil(|py| { python_path::add_python_path(py).expect("keycard_wallet.py not found"); let wallet = KeycardWallet::new(py).expect("Expect keycard wallet"); - let is_connected = wallet.setup_communication(py, pin.expect("TODO")).expect("Expect a Boolean."); + let is_connected = wallet + .setup_communication(py, &pin.expect("TODO")) + .expect("Expect a Boolean."); if is_connected { println!("\u{2705} Keycard is now connected to wallet."); } else { println!("\u{274c} Keycard is not connected to wallet."); } - }); - - Ok(SubcommandReturnValue::Empty) - },*/ - Self::Load { mnemonic, pin } => { - Python::with_gil(|py| { - python_path::add_python_path(py).expect("keycard_wallet.py not found"); - - let wallet = KeycardWallet::new(py).expect("Expect keycard wallet"); - - let is_connected = wallet.setup_communication(py, pin.expect("TODO")).expect("Expect a Boolean."); - - if is_connected { - println!("\u{2705} Keycard is now connected to wallet."); - } else { - println!("\u{274c} Keycard is not connected to wallet."); - } let _ = wallet.load_mnemonic(py, &mnemonic.expect("TODO")); let _ = wallet.disconnect(py); - }); + }); - Ok(SubcommandReturnValue::Empty) - }, + Ok(SubcommandReturnValue::Empty) + } } } } diff --git a/wallet/src/cli/keycard_wallet.rs b/wallet/src/cli/keycard_wallet.rs index b08483b1..0c050f76 100644 --- a/wallet/src/cli/keycard_wallet.rs +++ b/wallet/src/cli/keycard_wallet.rs @@ -1,5 +1,7 @@ -use pyo3::prelude::*; -use pyo3::types::PyAny; +use nssa::{AccountId, PublicKey, Signature}; +use pyo3::{prelude::*, types::PyAny}; + +use crate::cli::python_path; /// Rust wrapper around the Python KeycardWallet class. /// Holds a persistent Python object in memory. @@ -28,8 +30,8 @@ impl KeycardWallet { .extract() } - pub fn setup_communication(&self, py: Python, pin: String) -> PyResult { - let py_pin = pyo3::types::PyString::new_bound(py, &pin); + pub fn setup_communication(&self, py: Python, pin: &String) -> PyResult { + let py_pin = pyo3::types::PyString::new_bound(py, pin); self.instance .bind(py) @@ -38,54 +40,96 @@ impl KeycardWallet { } pub fn disconnect(&self, py: Python) -> PyResult { - self.instance + self.instance.bind(py).call_method0("disconnect")?.extract() + } + + pub fn get_public_key_for_path(&self, py: Python, path: &String) -> PyResult { + let public_key: Vec = self + .instance .bind(py) - .call_method0("disconnect")? - .extract() + .call_method1("get_public_key_for_path", (path,))? + .extract()?; + + let public_key: [u8; 32] = public_key.try_into().expect("Expect 32 bytes"); + + Ok(PublicKey::try_new(public_key).expect("Expect a valid public key1")) } - fn convert_path_to_string(path: Vec) -> String { - format!( - "m/{}", - path.iter() - .map(|n| n.to_string()) - .collect::>() - .join("'/") - ) + pub fn get_public_key_for_path_with_connect(pin: &String, path: &String) -> PublicKey { + let pub_key = Python::with_gil(|py| { + python_path::add_python_path(py).expect("keycard_wallet.py not found"); + + let wallet = KeycardWallet::new(py).expect("Expect keycard wallet"); + + let is_connected = wallet + .setup_communication(py, pin) + .expect("Expect a Boolean."); + + if is_connected { + println!("\u{2705} Keycard is now connected to wallet."); + } else { + println!("\u{274c} Keycard is not connected to wallet."); + } + + let pub_key = wallet.get_public_key_for_path(py, path); + + let _ = wallet.disconnect(py); + pub_key + }); + pub_key.expect("Expect a valid public key2") } - pub fn get_public_key_for_path( + pub fn sign_message_for_path( &self, py: Python, - path: Vec, - ) -> PyResult> { - let py_path = Self::convert_path_to_string(path); - let public_key: Vec = self.instance - .bind(py) - .call_method1("get_public_key_for_path", (py_path,))? - .getattr("public_key")? - .extract()?; - - Ok(Some(public_key.try_into().expect("TODO"))) - } - - pub fn sign_message_with_path(&self, py: Python, path: Vec, message: &[u8; 32]) -> PyResult<[u8; 64]> { + path: &String, + message: &[u8; 32], + ) -> PyResult { let py_message = pyo3::types::PyBytes::new_bound(py, message); - let path = Self::convert_path_to_string(path); - - let py_signature: Vec = self.instance + + let py_signature: Vec = self + .instance .bind(py) - .call_method1("sign_message_with_path", (py_message, path))? - .getattr("signature")? + .call_method1("sign_message_for_path", (py_message, path))? .extract()?; - let signature: [u8; 64] = py_signature - .try_into() - .map_err(|_| PyErr::new::( - "Expected signature of exactly 64 bytes" - ))?; + let signature: [u8; 64] = py_signature.try_into().map_err(|_| { + PyErr::new::( + "Expected signature of exactly 64 bytes", + ) + })?; + println!("{:?}", signature); + Ok(Signature { value: signature }) + } - Ok(signature) + pub fn sign_message_for_path_with_connection( + pin: &String, + path: &String, + message: &[u8; 32], + ) -> PyResult { + let signature = Python::with_gil(|py| { + python_path::add_python_path(py).expect("keycard_wallet.py not found"); + + let wallet = KeycardWallet::new(py).expect("Expect keycard wallet"); + + let is_connected = wallet + .setup_communication(py, pin) + .expect("Expect a Boolean."); + + if is_connected { + println!("\u{2705} Keycard is now connected to wallet."); + } else { + println!("\u{274c} Keycard is not connected to wallet."); + } + + let signature = wallet.sign_message_for_path(py, path, message); + + let _ = wallet.disconnect(py); + + signature + }); + + signature } pub fn load_mnemonic(&self, py: Python, mnemonic: &str) -> PyResult<()> { @@ -94,4 +138,10 @@ impl KeycardWallet { .call_method1("load_mnemonic", (mnemonic,))?; Ok(()) } -} \ No newline at end of file + + pub fn get_account_id_for_path_with_connect(pin: &String, key_path: &String) -> AccountId { + let public_key = KeycardWallet::get_public_key_for_path_with_connect(pin, key_path); + + AccountId::from(&public_key) + } +} diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index 362b9aab..cade0a7c 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -27,9 +27,9 @@ pub mod account; pub mod chain; pub mod config; pub mod keycard; +pub mod keycard_wallet; pub mod programs; pub mod python_path; -pub mod keycard_wallet; pub(crate) trait WalletSubcommand { async fn handle_subcommand(self, wallet_core: &mut WalletCore) diff --git a/wallet/src/cli/programs/native_token_transfer.rs b/wallet/src/cli/programs/native_token_transfer.rs index 41008eac..d3f93f40 100644 --- a/wallet/src/cli/programs/native_token_transfer.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -6,7 +6,7 @@ use nssa::AccountId; use crate::{ AccDecodeData::Decode, WalletCore, - cli::{SubcommandReturnValue, WalletSubcommand}, + cli::{SubcommandReturnValue, WalletSubcommand, keycard_wallet::KeycardWallet}, helperfunctions::{ AccountPrivacyKind, parse_addr_with_privacy_prefix, resolve_account_label, resolve_id_or_label, @@ -23,12 +23,16 @@ pub enum AuthTransferSubcommand { #[arg( long, conflicts_with = "account_label", - required_unless_present = "account_label" + // required_unless_present = "account_label" )] account_id: Option, /// Account label (alternative to --account-id). #[arg(long, conflicts_with = "account_id")] account_label: Option, + #[arg(long)] + pin: Option, + #[arg(long, conflicts_with = "account_id", conflicts_with = "account_label")] + key_path: Option, }, /// Send native tokens from one account to another with variable privacy. /// @@ -62,6 +66,10 @@ pub enum AuthTransferSubcommand { /// amount - amount of balance to move. #[arg(long)] amount: u128, + #[arg(long, conflicts_with = "from", conflicts_with = "from_label")] + pin: Option, + #[arg(long, conflicts_with = "from", conflicts_with = "from_label")] + key_path: Option, }, } @@ -74,21 +82,36 @@ impl WalletSubcommand for AuthTransferSubcommand { Self::Init { account_id, account_label, + pin, + key_path, } => { - let resolved = resolve_id_or_label( - account_id, - account_label, - &wallet_core.storage.labels, - &wallet_core.storage.user_data, - )?; - let (account_id, addr_privacy) = parse_addr_with_privacy_prefix(&resolved)?; + let resolved = if pin.is_none() { + resolve_id_or_label( + account_id, + account_label, + &wallet_core.storage.labels, + &wallet_core.storage.user_data, + )? + } else { + String::default() + }; + + let (account_id, addr_privacy) = if pin.is_none() { + parse_addr_with_privacy_prefix(&resolved)? + } else { + (String::default(), AccountPrivacyKind::Public) + }; match addr_privacy { AccountPrivacyKind::Public => { - let account_id = account_id.parse()?; + let account_id = if pin.is_none() { + account_id.parse()? + } else { + KeycardWallet::get_account_id_for_path_with_connect(&pin.as_ref().expect("TODO"), &key_path.as_ref().expect("TODO")) + }; let tx_hash = NativeTokenTransfer(wallet_core) - .register_account(account_id) + .register_account(account_id, &pin, &key_path) .await?; println!("Transaction hash is {tx_hash}"); @@ -133,13 +156,21 @@ impl WalletSubcommand for AuthTransferSubcommand { to_npk, to_vpk, amount, + pin, + key_path, } => { - let from = resolve_id_or_label( + let from = if pin.is_none() { + resolve_id_or_label( from, from_label, &wallet_core.storage.labels, &wallet_core.storage.user_data, - )?; + )? + } else { + KeycardWallet::get_account_id_for_path_with_connect(&pin.as_ref().expect("TODO"), &key_path.as_ref().expect("TODO")).to_string() + }; + + let to = match (to, to_label) { (v, None) => v, (None, Some(label)) => Some(resolve_account_label( @@ -171,7 +202,7 @@ impl WalletSubcommand for AuthTransferSubcommand { match (from_privacy, to_privacy) { (AccountPrivacyKind::Public, AccountPrivacyKind::Public) => { - NativeTokenTransferProgramSubcommand::Public { from, to, amount } + NativeTokenTransferProgramSubcommand::Public { from, to, amount, pin, key_path } } (AccountPrivacyKind::Private, AccountPrivacyKind::Private) => { NativeTokenTransferProgramSubcommand::Private( @@ -195,6 +226,8 @@ impl WalletSubcommand for AuthTransferSubcommand { from, to, amount, + pin, + key_path, }, ) } @@ -221,6 +254,8 @@ impl WalletSubcommand for AuthTransferSubcommand { to_npk, to_vpk, amount, + pin, + key_path }, ) } @@ -250,6 +285,10 @@ pub enum NativeTokenTransferProgramSubcommand { /// amount - amount of balance to move. #[arg(long)] amount: u128, + #[arg(long, conflicts_with = "from", conflicts_with = "from_label")] + pin: Option, + #[arg(long, conflicts_with = "from", conflicts_with = "from_label")] + key_path: Option, }, /// Private execution. #[command(subcommand)] @@ -290,6 +329,10 @@ pub enum NativeTokenTransferProgramSubcommandShielded { /// amount - amount of balance to move. #[arg(long)] amount: u128, + #[arg(long)] + pin: Option, + #[arg(long)] + key_path: Option, }, /// Send native token transfer from `from` to `to` for `amount`. /// @@ -307,6 +350,10 @@ pub enum NativeTokenTransferProgramSubcommandShielded { /// amount - amount of balance to move. #[arg(long)] amount: u128, + #[arg(long)] + pin: Option, + #[arg(long)] + key_path: Option, }, } @@ -427,7 +474,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { wallet_core: &mut WalletCore, ) -> Result { match self { - Self::ShieldedOwned { from, to, amount } => { + Self::ShieldedOwned { from, to, amount , pin: _, key_path: _} => { let from: AccountId = from.parse().unwrap(); let to: AccountId = to.parse().unwrap(); @@ -457,6 +504,8 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { to_npk, to_vpk, amount, + pin: _, + key_path: _ } => { let from: AccountId = from.parse().unwrap(); @@ -522,12 +571,13 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } - Self::Public { from, to, amount } => { + Self::Public { from, to, amount, pin, key_path } => { + let from: AccountId = from.parse().unwrap(); let to: AccountId = to.parse().unwrap(); let tx_hash = NativeTokenTransfer(wallet_core) - .send_public_transfer(from, to, amount) + .send_public_transfer(from, to, amount, &pin, &key_path) .await?; println!("Transaction hash is {tx_hash}"); diff --git a/wallet/src/cli/python_path.rs b/wallet/src/cli/python_path.rs index fb720e43..d03bffff 100644 --- a/wallet/src/cli/python_path.rs +++ b/wallet/src/cli/python_path.rs @@ -1,12 +1,10 @@ -use std::env; -use std::path::PathBuf; -use pyo3::prelude::*; -use pyo3::types::PyList; +use std::{env, path::PathBuf}; + +use pyo3::{prelude::*, types::PyList}; /// Adds the project's `python/` directory and venv site-packages to Python's sys.path pub fn add_python_path(py: Python) -> PyResult<()> { - let current_dir = env::current_dir() - .expect("Failed to get current working directory"); + let current_dir = env::current_dir().expect("Failed to get current working directory"); let paths_to_add: Vec = vec![ current_dir.join("python"), @@ -24,15 +22,16 @@ pub fn add_python_path(py: Python) -> PyResult<()> { let sys_path: &PyList = sys.getattr("path")?.extract()?; for path in &paths_to_add { - let path_str = path - .to_str() - .expect("Invalid path"); + let path_str = path.to_str().expect("Invalid path"); // Avoid duplicating the path - if !sys_path.iter().any(|p| p.extract::<&str>().unwrap_or("") == path_str) { + if !sys_path + .iter() + .any(|p| p.extract::<&str>().unwrap_or("") == path_str) + { sys_path.insert(0, path_str)?; } } Ok(()) -} \ No newline at end of file +} diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 2b0861e5..0ddb5a9a 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -18,11 +18,11 @@ use config::WalletConfig; use key_protocol::key_management::key_tree::{chain_index::ChainIndex, traits::KeyNode as _}; use log::info; use nssa::{ - Account, AccountId, PrivacyPreservingTransaction, - privacy_preserving_transaction::{ + PublicKey, + Account, AccountId, PrivacyPreservingTransaction, Signature, privacy_preserving_transaction::{ circuit::{ProgramWithDependencies, Proof}, message::EncryptedAccountData, - }, + } }; use nssa_core::{ Commitment, MembershipProof, SharedSecretKey, account::Nonce, program::InstructionData, @@ -32,9 +32,7 @@ use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuil use tokio::io::AsyncWriteExt as _; use crate::{ - config::{PersistentStorage, WalletConfigOverrides}, - helperfunctions::produce_data_for_storage, - poller::TxPoller, + cli::keycard_wallet::KeycardWallet, config::{PersistentStorage, WalletConfigOverrides}, helperfunctions::produce_data_for_storage, poller::TxPoller }; pub mod chain_storage; @@ -584,4 +582,36 @@ impl WalletCore { ), ) } + + pub fn sign_privacy_message_with_keycard( + message: &nssa::privacy_preserving_transaction::Message, + proof: Proof, + pin: &String, + key_paths: &[String] + ) -> Result + { + let mut signatures = Vec::::new(); + let mut public_keys = Vec::::new(); + + let message_bytes: [u8; 32] = { + let v = message.to_bytes(); + let mut bytes = [0_u8; 32]; + let len = v.len().min(32); + bytes[..len].copy_from_slice(&v[..len]); + bytes + }; + + for path in key_paths.iter() { + public_keys.push( KeycardWallet::get_public_key_for_path_with_connect(&pin, &path)); + signatures.push( KeycardWallet::sign_message_for_path_with_connection(&pin, &path, &message_bytes).expect("Expect a valid signature")); + } + + Ok( + nssa::privacy_preserving_transaction::witness_set::WitnessSet::from_list( + proof, + &signatures, + &public_keys, + ), + ) + } } diff --git a/wallet/src/program_facades/native_token_transfer/public.rs b/wallet/src/program_facades/native_token_transfer/public.rs index 7705c268..97d1609c 100644 --- a/wallet/src/program_facades/native_token_transfer/public.rs +++ b/wallet/src/program_facades/native_token_transfer/public.rs @@ -4,10 +4,14 @@ use nssa::{ program::Program, public_transaction::{Message, WitnessSet}, }; +use pyo3::Python; use sequencer_service_rpc::RpcClient as _; use super::NativeTokenTransfer; -use crate::{ExecutionFailureKind, WalletCore}; +use crate::{ + ExecutionFailureKind, WalletCore, + cli::{keycard_wallet::KeycardWallet, python_path}, +}; impl NativeTokenTransfer<'_> { pub async fn send_public_transfer( @@ -15,6 +19,8 @@ impl NativeTokenTransfer<'_> { from: AccountId, to: AccountId, balance_to_move: u128, + pin: &Option, + key_path: &Option, ) -> Result { let balance = self .0 @@ -52,8 +58,22 @@ impl NativeTokenTransfer<'_> { let message = Message::try_new(program_id, account_ids, nonces, balance_to_move).unwrap(); - let witness_set = WalletCore::sign_public_message(self.0, &message, &sign_ids) - .expect("Expect a valid signature"); + let witness_set = if pin.is_none() { + WalletCore::sign_public_message(self.0, &message, &sign_ids) + .expect("Expect a valid signature") + } else { + // TODO: maybe the issue? (Marvin) + let message_bytes: [u8; 32] = { + let v = message.to_bytes(); + let mut bytes = [0_u8; 32]; + let len = v.len().min(32); + bytes[..len].copy_from_slice(&v[..len]); + bytes + }; + let pub_key = KeycardWallet::get_public_key_for_path_with_connect(&pin.as_ref().expect("TODO"), &key_path.as_ref().expect("TODO")); + let signature = KeycardWallet::sign_message_for_path_with_connection(&pin.as_ref().expect("TODO"), &key_path.as_ref().expect("TODO"), &message_bytes).expect("Expect valid signature"); + WitnessSet::from_list(&[signature], &[pub_key]) + }; let tx = PublicTransaction::new(message, witness_set); @@ -70,6 +90,8 @@ impl NativeTokenTransfer<'_> { pub async fn register_account( &self, from: AccountId, + pin: &Option, // Used by Keycard. + key_path: &Option, // Used by Keycard. ) -> Result { let nonces = self .0 @@ -82,14 +104,61 @@ impl NativeTokenTransfer<'_> { let program_id = Program::authenticated_transfer_program().id(); let message = Message::try_new(program_id, account_ids, nonces, instruction).unwrap(); - let signing_key = self.0.storage.user_data.get_pub_account_signing_key(from); + // (Marvin): This really needs to be the ChainIndex + // But, I cannot change that due to Default Accounts. + // Instead, I had introduced a "NEW" sign...which I do not see... + // Correction: I did not need a specific function. Rather, I use `from_list` to combine + // public and signatures together for a WitnessSet. - let Some(signing_key) = signing_key else { - return Err(ExecutionFailureKind::KeyNotFoundError); + // The tricky part is that I NEED to do everything with chain-codes... This won't look nice, + // but is feasible. + let witness_set = if pin.is_none() { + let signing_key = self.0.storage.user_data.get_pub_account_signing_key(from); + + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + WitnessSet::for_message(&message, &[signing_key]) + } else { + let witness_set = Python::with_gil(|py| { + python_path::add_python_path(py).expect("keycard_wallet.py not found"); + + let wallet = KeycardWallet::new(py).expect("Expect keycard wallet"); + + let is_connected = wallet + .setup_communication(py, pin.as_ref().expect("TODO")) + .expect("Expect a Boolean."); + + if is_connected { + println!("\u{2705} Keycard is now connected to wallet."); + } else { + println!("\u{274c} Keycard is not connected to wallet."); + } + // TODO: maybe the issue? (Marvin) + let message: [u8; 32] = { + let v = message.to_bytes(); + let mut bytes = [0_u8; 32]; + let len = v.len().min(32); + bytes[..len].copy_from_slice(&v[..len]); + bytes + }; + + let pub_key = wallet + .get_public_key_for_path(py, key_path.as_ref().expect("TODO")) + .expect("Expect a valid public key"); + + let signature = wallet + .sign_message_for_path(py, key_path.as_ref().expect("TODO"), &message) + .expect("TODO"); + + let _ = wallet.disconnect(py); + + WitnessSet::from_list(&[signature], &[pub_key]) + }); + witness_set }; - let witness_set = WitnessSet::for_message(&message, &[signing_key]); - let tx = PublicTransaction::new(message, witness_set); Ok(self