Updates for signatures with keycard

This commit is contained in:
jonesmarvin8 2026-04-23 17:45:43 -04:00
parent 9f1c8bdf29
commit fac4e86e40
11 changed files with 331 additions and 126 deletions

View File

@ -1,7 +1,7 @@
use crate::{PublicTransaction, error::NssaError, public_transaction::Message}; use crate::{PublicTransaction, error::NssaError, public_transaction::Message};
impl Message { impl Message {
pub(crate) fn to_bytes(&self) -> Vec<u8> { pub fn to_bytes(&self) -> Vec<u8> {
borsh::to_vec(&self).expect("Autoderived borsh serialization failure") borsh::to_vec(&self).expect("Autoderived borsh serialization failure")
} }
} }

View File

@ -13,8 +13,6 @@ pub struct WitnessSet {
impl WitnessSet { impl WitnessSet {
#[must_use] #[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 { pub fn for_message(message: &Message, proof: Proof, private_keys: &[&PrivateKey]) -> Self {
let message_bytes = message.to_bytes(); let message_bytes = message.to_bytes();
let signatures_and_public_keys = private_keys let signatures_and_public_keys = private_keys
@ -32,6 +30,22 @@ impl WitnessSet {
} }
} }
#[must_use]
pub fn from_list(proof: Proof, signatures: &Vec<Signature>, public_keys: &Vec<PublicKey>) -> 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] #[must_use]
pub fn signatures_are_valid_for(&self, message: &Message) -> bool { pub fn signatures_are_valid_for(&self, message: &Message) -> bool {
let message_bytes = message.to_bytes(); let message_bytes = message.to_bytes();

View File

@ -2,13 +2,22 @@ import keycard_wallet as keycard_wallet
import time # For testing import time # For testing
pin = '111111' 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() my_wallet = keycard_wallet.KeycardWallet()
print("Setup communication with card...", my_wallet.setup_communication(pin)) print("Setup communication with card...", my_wallet.setup_communication(pin))
print("Load mnemonic...", my_wallet.load_mnemonic()) 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()) print("Signature", my_wallet.sign_message_for_path())

View File

@ -96,7 +96,7 @@ class KeycardWallet:
print(f"Error during unpair: {e}") print(f"Error during unpair: {e}")
return False 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: try:
if not self.card.is_secure_channel_open or not self.card.is_pin_verified: if not self.card.is_secure_channel_open or not self.card.is_pin_verified:
return None return None
@ -106,14 +106,18 @@ class KeycardWallet:
public_only = True, public_only = True,
keypath = path 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: except Exception as e:
print(f"Error getting public key: {e}") print(f"Error getting public key: {e}")
return None 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: try:
if not self.card.is_secure_channel_open or not self.card.is_pin_verified: if not self.card.is_secure_channel_open or not self.card.is_pin_verified:
return None return None
@ -125,7 +129,7 @@ class KeycardWallet:
make_current = False make_current = False
) )
return signature.signature.hex() return signature.signature
except Exception as e: except Exception as e:
print(f"Error signing message: {e}") print(f"Error signing message: {e}")

View File

@ -12,16 +12,10 @@ use crate::{
pub enum KeycardSubcommand { pub enum KeycardSubcommand {
Available, Available,
Load { Load {
#[arg( #[arg(short, long)]
short,
long,
)]
mnemonic: Option<String>, mnemonic: Option<String>,
#[arg( #[arg(short, long)]
short, pin: Option<String>,
long,
)]
pin: Option<String>
}, },
} }
@ -37,7 +31,9 @@ impl WalletSubcommand for KeycardSubcommand {
python_path::add_python_path(py).expect("keycard_wallet.py not found"); python_path::add_python_path(py).expect("keycard_wallet.py not found");
let wallet = KeycardWallet::new(py).expect("Expect keycard wallet"); 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 { if available {
println!("\u{2705} Keycard is available."); println!("\u{2705} Keycard is available.");
@ -47,46 +43,30 @@ impl WalletSubcommand for KeycardSubcommand {
}); });
Ok(SubcommandReturnValue::Empty) Ok(SubcommandReturnValue::Empty)
},/* }
Self::Connect { pin } => { Self::Load { mnemonic, pin } => {
// TODO This should be persistent.
Python::with_gil(|py| { Python::with_gil(|py| {
python_path::add_python_path(py).expect("keycard_wallet.py not found"); python_path::add_python_path(py).expect("keycard_wallet.py not found");
let wallet = KeycardWallet::new(py).expect("Expect keycard wallet"); 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 { if is_connected {
println!("\u{2705} Keycard is now connected to wallet."); println!("\u{2705} Keycard is now connected to wallet.");
} else { } else {
println!("\u{274c} Keycard is not connected to wallet."); 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.load_mnemonic(py, &mnemonic.expect("TODO"));
let _ = wallet.disconnect(py); let _ = wallet.disconnect(py);
}); });
Ok(SubcommandReturnValue::Empty) Ok(SubcommandReturnValue::Empty)
}, }
} }
} }
} }

View File

@ -1,5 +1,7 @@
use pyo3::prelude::*; use nssa::{AccountId, PublicKey, Signature};
use pyo3::types::PyAny; use pyo3::{prelude::*, types::PyAny};
use crate::cli::python_path;
/// Rust wrapper around the Python KeycardWallet class. /// Rust wrapper around the Python KeycardWallet class.
/// Holds a persistent Python object in memory. /// Holds a persistent Python object in memory.
@ -28,8 +30,8 @@ impl KeycardWallet {
.extract() .extract()
} }
pub fn setup_communication(&self, py: Python, pin: String) -> PyResult<bool> { pub fn setup_communication(&self, py: Python, pin: &String) -> PyResult<bool> {
let py_pin = pyo3::types::PyString::new_bound(py, &pin); let py_pin = pyo3::types::PyString::new_bound(py, pin);
self.instance self.instance
.bind(py) .bind(py)
@ -38,54 +40,96 @@ impl KeycardWallet {
} }
pub fn disconnect(&self, py: Python) -> PyResult<bool> { pub fn disconnect(&self, py: Python) -> PyResult<bool> {
self.instance self.instance.bind(py).call_method0("disconnect")?.extract()
}
pub fn get_public_key_for_path(&self, py: Python, path: &String) -> PyResult<PublicKey> {
let public_key: Vec<u8> = self
.instance
.bind(py) .bind(py)
.call_method0("disconnect")? .call_method1("get_public_key_for_path", (path,))?
.extract() .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<u32>) -> String { pub fn get_public_key_for_path_with_connect(pin: &String, path: &String) -> PublicKey {
format!( let pub_key = Python::with_gil(|py| {
"m/{}", python_path::add_python_path(py).expect("keycard_wallet.py not found");
path.iter()
.map(|n| n.to_string()) let wallet = KeycardWallet::new(py).expect("Expect keycard wallet");
.collect::<Vec<_>>()
.join("'/") 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, &self,
py: Python, py: Python,
path: Vec<u32>, path: &String,
) -> PyResult<Option<[u8;32]>> { message: &[u8; 32],
let py_path = Self::convert_path_to_string(path); ) -> PyResult<Signature> {
let public_key: Vec<u8> = 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<u32>, message: &[u8; 32]) -> PyResult<[u8; 64]> {
let py_message = pyo3::types::PyBytes::new_bound(py, message); let py_message = pyo3::types::PyBytes::new_bound(py, message);
let path = Self::convert_path_to_string(path);
let py_signature: Vec<u8> = self
let py_signature: Vec<u8> = self.instance .instance
.bind(py) .bind(py)
.call_method1("sign_message_with_path", (py_message, path))? .call_method1("sign_message_for_path", (py_message, path))?
.getattr("signature")?
.extract()?; .extract()?;
let signature: [u8; 64] = py_signature let signature: [u8; 64] = py_signature.try_into().map_err(|_| {
.try_into() PyErr::new::<pyo3::exceptions::PyValueError, _>(
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>( "Expected signature of exactly 64 bytes",
"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<Signature> {
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<()> { pub fn load_mnemonic(&self, py: Python, mnemonic: &str) -> PyResult<()> {
@ -94,4 +138,10 @@ impl KeycardWallet {
.call_method1("load_mnemonic", (mnemonic,))?; .call_method1("load_mnemonic", (mnemonic,))?;
Ok(()) Ok(())
} }
}
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)
}
}

View File

@ -27,9 +27,9 @@ pub mod account;
pub mod chain; pub mod chain;
pub mod config; pub mod config;
pub mod keycard; pub mod keycard;
pub mod keycard_wallet;
pub mod programs; pub mod programs;
pub mod python_path; pub mod python_path;
pub mod keycard_wallet;
pub(crate) trait WalletSubcommand { pub(crate) trait WalletSubcommand {
async fn handle_subcommand(self, wallet_core: &mut WalletCore) async fn handle_subcommand(self, wallet_core: &mut WalletCore)

View File

@ -6,7 +6,7 @@ use nssa::AccountId;
use crate::{ use crate::{
AccDecodeData::Decode, AccDecodeData::Decode,
WalletCore, WalletCore,
cli::{SubcommandReturnValue, WalletSubcommand}, cli::{SubcommandReturnValue, WalletSubcommand, keycard_wallet::KeycardWallet},
helperfunctions::{ helperfunctions::{
AccountPrivacyKind, parse_addr_with_privacy_prefix, resolve_account_label, AccountPrivacyKind, parse_addr_with_privacy_prefix, resolve_account_label,
resolve_id_or_label, resolve_id_or_label,
@ -23,12 +23,16 @@ pub enum AuthTransferSubcommand {
#[arg( #[arg(
long, long,
conflicts_with = "account_label", conflicts_with = "account_label",
required_unless_present = "account_label" // required_unless_present = "account_label"
)] )]
account_id: Option<String>, account_id: Option<String>,
/// Account label (alternative to --account-id). /// Account label (alternative to --account-id).
#[arg(long, conflicts_with = "account_id")] #[arg(long, conflicts_with = "account_id")]
account_label: Option<String>, account_label: Option<String>,
#[arg(long)]
pin: Option<String>,
#[arg(long, conflicts_with = "account_id", conflicts_with = "account_label")]
key_path: Option<String>,
}, },
/// Send native tokens from one account to another with variable privacy. /// Send native tokens from one account to another with variable privacy.
/// ///
@ -62,6 +66,10 @@ pub enum AuthTransferSubcommand {
/// amount - amount of balance to move. /// amount - amount of balance to move.
#[arg(long)] #[arg(long)]
amount: u128, amount: u128,
#[arg(long, conflicts_with = "from", conflicts_with = "from_label")]
pin: Option<String>,
#[arg(long, conflicts_with = "from", conflicts_with = "from_label")]
key_path: Option<String>,
}, },
} }
@ -74,21 +82,36 @@ impl WalletSubcommand for AuthTransferSubcommand {
Self::Init { Self::Init {
account_id, account_id,
account_label, account_label,
pin,
key_path,
} => { } => {
let resolved = resolve_id_or_label( let resolved = if pin.is_none() {
account_id, resolve_id_or_label(
account_label, account_id,
&wallet_core.storage.labels, account_label,
&wallet_core.storage.user_data, &wallet_core.storage.labels,
)?; &wallet_core.storage.user_data,
let (account_id, addr_privacy) = parse_addr_with_privacy_prefix(&resolved)?; )?
} 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 { match addr_privacy {
AccountPrivacyKind::Public => { 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) let tx_hash = NativeTokenTransfer(wallet_core)
.register_account(account_id) .register_account(account_id, &pin, &key_path)
.await?; .await?;
println!("Transaction hash is {tx_hash}"); println!("Transaction hash is {tx_hash}");
@ -133,13 +156,21 @@ impl WalletSubcommand for AuthTransferSubcommand {
to_npk, to_npk,
to_vpk, to_vpk,
amount, amount,
pin,
key_path,
} => { } => {
let from = resolve_id_or_label( let from = if pin.is_none() {
resolve_id_or_label(
from, from,
from_label, from_label,
&wallet_core.storage.labels, &wallet_core.storage.labels,
&wallet_core.storage.user_data, &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) { let to = match (to, to_label) {
(v, None) => v, (v, None) => v,
(None, Some(label)) => Some(resolve_account_label( (None, Some(label)) => Some(resolve_account_label(
@ -171,7 +202,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
match (from_privacy, to_privacy) { match (from_privacy, to_privacy) {
(AccountPrivacyKind::Public, AccountPrivacyKind::Public) => { (AccountPrivacyKind::Public, AccountPrivacyKind::Public) => {
NativeTokenTransferProgramSubcommand::Public { from, to, amount } NativeTokenTransferProgramSubcommand::Public { from, to, amount, pin, key_path }
} }
(AccountPrivacyKind::Private, AccountPrivacyKind::Private) => { (AccountPrivacyKind::Private, AccountPrivacyKind::Private) => {
NativeTokenTransferProgramSubcommand::Private( NativeTokenTransferProgramSubcommand::Private(
@ -195,6 +226,8 @@ impl WalletSubcommand for AuthTransferSubcommand {
from, from,
to, to,
amount, amount,
pin,
key_path,
}, },
) )
} }
@ -221,6 +254,8 @@ impl WalletSubcommand for AuthTransferSubcommand {
to_npk, to_npk,
to_vpk, to_vpk,
amount, amount,
pin,
key_path
}, },
) )
} }
@ -250,6 +285,10 @@ pub enum NativeTokenTransferProgramSubcommand {
/// amount - amount of balance to move. /// amount - amount of balance to move.
#[arg(long)] #[arg(long)]
amount: u128, amount: u128,
#[arg(long, conflicts_with = "from", conflicts_with = "from_label")]
pin: Option<String>,
#[arg(long, conflicts_with = "from", conflicts_with = "from_label")]
key_path: Option<String>,
}, },
/// Private execution. /// Private execution.
#[command(subcommand)] #[command(subcommand)]
@ -290,6 +329,10 @@ pub enum NativeTokenTransferProgramSubcommandShielded {
/// amount - amount of balance to move. /// amount - amount of balance to move.
#[arg(long)] #[arg(long)]
amount: u128, amount: u128,
#[arg(long)]
pin: Option<String>,
#[arg(long)]
key_path: Option<String>,
}, },
/// Send native token transfer from `from` to `to` for `amount`. /// Send native token transfer from `from` to `to` for `amount`.
/// ///
@ -307,6 +350,10 @@ pub enum NativeTokenTransferProgramSubcommandShielded {
/// amount - amount of balance to move. /// amount - amount of balance to move.
#[arg(long)] #[arg(long)]
amount: u128, amount: u128,
#[arg(long)]
pin: Option<String>,
#[arg(long)]
key_path: Option<String>,
}, },
} }
@ -427,7 +474,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
wallet_core: &mut WalletCore, wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> { ) -> Result<SubcommandReturnValue> {
match self { match self {
Self::ShieldedOwned { from, to, amount } => { Self::ShieldedOwned { from, to, amount , pin: _, key_path: _} => {
let from: AccountId = from.parse().unwrap(); let from: AccountId = from.parse().unwrap();
let to: AccountId = to.parse().unwrap(); let to: AccountId = to.parse().unwrap();
@ -457,6 +504,8 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
to_npk, to_npk,
to_vpk, to_vpk,
amount, amount,
pin: _,
key_path: _
} => { } => {
let from: AccountId = from.parse().unwrap(); let from: AccountId = from.parse().unwrap();
@ -522,12 +571,13 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) 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 from: AccountId = from.parse().unwrap();
let to: AccountId = to.parse().unwrap(); let to: AccountId = to.parse().unwrap();
let tx_hash = NativeTokenTransfer(wallet_core) let tx_hash = NativeTokenTransfer(wallet_core)
.send_public_transfer(from, to, amount) .send_public_transfer(from, to, amount, &pin, &key_path)
.await?; .await?;
println!("Transaction hash is {tx_hash}"); println!("Transaction hash is {tx_hash}");

View File

@ -1,12 +1,10 @@
use std::env; use std::{env, path::PathBuf};
use std::path::PathBuf;
use pyo3::prelude::*; use pyo3::{prelude::*, types::PyList};
use pyo3::types::PyList;
/// Adds the project's `python/` directory and venv site-packages to Python's sys.path /// Adds the project's `python/` directory and venv site-packages to Python's sys.path
pub fn add_python_path(py: Python) -> PyResult<()> { pub fn add_python_path(py: Python) -> PyResult<()> {
let current_dir = env::current_dir() let current_dir = env::current_dir().expect("Failed to get current working directory");
.expect("Failed to get current working directory");
let paths_to_add: Vec<PathBuf> = vec![ let paths_to_add: Vec<PathBuf> = vec![
current_dir.join("python"), current_dir.join("python"),
@ -24,15 +22,16 @@ pub fn add_python_path(py: Python) -> PyResult<()> {
let sys_path: &PyList = sys.getattr("path")?.extract()?; let sys_path: &PyList = sys.getattr("path")?.extract()?;
for path in &paths_to_add { for path in &paths_to_add {
let path_str = path let path_str = path.to_str().expect("Invalid path");
.to_str()
.expect("Invalid path");
// Avoid duplicating the 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)?; sys_path.insert(0, path_str)?;
} }
} }
Ok(()) Ok(())
} }

View File

@ -18,11 +18,11 @@ use config::WalletConfig;
use key_protocol::key_management::key_tree::{chain_index::ChainIndex, traits::KeyNode as _}; use key_protocol::key_management::key_tree::{chain_index::ChainIndex, traits::KeyNode as _};
use log::info; use log::info;
use nssa::{ use nssa::{
Account, AccountId, PrivacyPreservingTransaction, PublicKey,
privacy_preserving_transaction::{ Account, AccountId, PrivacyPreservingTransaction, Signature, privacy_preserving_transaction::{
circuit::{ProgramWithDependencies, Proof}, circuit::{ProgramWithDependencies, Proof},
message::EncryptedAccountData, message::EncryptedAccountData,
}, }
}; };
use nssa_core::{ use nssa_core::{
Commitment, MembershipProof, SharedSecretKey, account::Nonce, program::InstructionData, 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 tokio::io::AsyncWriteExt as _;
use crate::{ use crate::{
config::{PersistentStorage, WalletConfigOverrides}, cli::keycard_wallet::KeycardWallet, config::{PersistentStorage, WalletConfigOverrides}, helperfunctions::produce_data_for_storage, poller::TxPoller
helperfunctions::produce_data_for_storage,
poller::TxPoller,
}; };
pub mod chain_storage; 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<nssa::privacy_preserving_transaction::witness_set::WitnessSet, ExecutionFailureKind>
{
let mut signatures = Vec::<Signature>::new();
let mut public_keys = Vec::<PublicKey>::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,
),
)
}
} }

View File

@ -4,10 +4,14 @@ use nssa::{
program::Program, program::Program,
public_transaction::{Message, WitnessSet}, public_transaction::{Message, WitnessSet},
}; };
use pyo3::Python;
use sequencer_service_rpc::RpcClient as _; use sequencer_service_rpc::RpcClient as _;
use super::NativeTokenTransfer; use super::NativeTokenTransfer;
use crate::{ExecutionFailureKind, WalletCore}; use crate::{
ExecutionFailureKind, WalletCore,
cli::{keycard_wallet::KeycardWallet, python_path},
};
impl NativeTokenTransfer<'_> { impl NativeTokenTransfer<'_> {
pub async fn send_public_transfer( pub async fn send_public_transfer(
@ -15,6 +19,8 @@ impl NativeTokenTransfer<'_> {
from: AccountId, from: AccountId,
to: AccountId, to: AccountId,
balance_to_move: u128, balance_to_move: u128,
pin: &Option<String>,
key_path: &Option<String>,
) -> Result<HashType, ExecutionFailureKind> { ) -> Result<HashType, ExecutionFailureKind> {
let balance = self let balance = self
.0 .0
@ -52,8 +58,22 @@ impl NativeTokenTransfer<'_> {
let message = let message =
Message::try_new(program_id, account_ids, nonces, balance_to_move).unwrap(); Message::try_new(program_id, account_ids, nonces, balance_to_move).unwrap();
let witness_set = WalletCore::sign_public_message(self.0, &message, &sign_ids) let witness_set = if pin.is_none() {
.expect("Expect a valid signature"); 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); let tx = PublicTransaction::new(message, witness_set);
@ -70,6 +90,8 @@ impl NativeTokenTransfer<'_> {
pub async fn register_account( pub async fn register_account(
&self, &self,
from: AccountId, from: AccountId,
pin: &Option<String>, // Used by Keycard.
key_path: &Option<String>, // Used by Keycard.
) -> Result<HashType, ExecutionFailureKind> { ) -> Result<HashType, ExecutionFailureKind> {
let nonces = self let nonces = self
.0 .0
@ -82,14 +104,61 @@ impl NativeTokenTransfer<'_> {
let program_id = Program::authenticated_transfer_program().id(); let program_id = Program::authenticated_transfer_program().id();
let message = Message::try_new(program_id, account_ids, nonces, instruction).unwrap(); 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 { // The tricky part is that I NEED to do everything with chain-codes... This won't look nice,
return Err(ExecutionFailureKind::KeyNotFoundError); // 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); let tx = PublicTransaction::new(message, witness_set);
Ok(self Ok(self