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};
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")
}
}

View File

@ -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<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]
pub fn signatures_are_valid_for(&self, message: &Message) -> bool {
let message_bytes = message.to_bytes();

View File

@ -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())

View File

@ -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}")

View File

@ -12,16 +12,10 @@ use crate::{
pub enum KeycardSubcommand {
Available,
Load {
#[arg(
short,
long,
)]
#[arg(short, long)]
mnemonic: Option<String>,
#[arg(
short,
long,
)]
pin: Option<String>
#[arg(short, long)]
pin: Option<String>,
},
}
@ -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)
}
}
}
}

View File

@ -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<bool> {
let py_pin = pyo3::types::PyString::new_bound(py, &pin);
pub fn setup_communication(&self, py: Python, pin: &String) -> PyResult<bool> {
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<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)
.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<u32>) -> String {
format!(
"m/{}",
path.iter()
.map(|n| n.to_string())
.collect::<Vec<_>>()
.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<u32>,
) -> PyResult<Option<[u8;32]>> {
let py_path = Self::convert_path_to_string(path);
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]> {
path: &String,
message: &[u8; 32],
) -> PyResult<Signature> {
let py_message = pyo3::types::PyBytes::new_bound(py, message);
let path = Self::convert_path_to_string(path);
let py_signature: Vec<u8> = self.instance
let py_signature: Vec<u8> = 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::<pyo3::exceptions::PyValueError, _>(
"Expected signature of exactly 64 bytes"
))?;
let signature: [u8; 64] = py_signature.try_into().map_err(|_| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(
"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<()> {
@ -94,4 +138,10 @@ impl KeycardWallet {
.call_method1("load_mnemonic", (mnemonic,))?;
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 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)

View File

@ -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<String>,
/// Account label (alternative to --account-id).
#[arg(long, conflicts_with = "account_id")]
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.
///
@ -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<String>,
#[arg(long, conflicts_with = "from", conflicts_with = "from_label")]
key_path: Option<String>,
},
}
@ -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<String>,
#[arg(long, conflicts_with = "from", conflicts_with = "from_label")]
key_path: Option<String>,
},
/// 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<String>,
#[arg(long)]
key_path: Option<String>,
},
/// 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<String>,
#[arg(long)]
key_path: Option<String>,
},
}
@ -427,7 +474,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
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}");

View File

@ -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<PathBuf> = 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(())
}
}

View File

@ -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<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,
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<String>,
key_path: &Option<String>,
) -> Result<HashType, ExecutionFailureKind> {
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<String>, // Used by Keycard.
key_path: &Option<String>, // Used by Keycard.
) -> Result<HashType, ExecutionFailureKind> {
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