mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-13 19:49:29 +00:00
fixes
This commit is contained in:
parent
f892b92ee7
commit
096522ebb9
15
python/keycard_test.py
Normal file
15
python/keycard_test.py
Normal file
@ -0,0 +1,15 @@
|
||||
import keycard_wallet as keycard_wallet
|
||||
import time # For testing
|
||||
|
||||
pin = '111111'
|
||||
|
||||
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("Signature", my_wallet.sign_message_for_path())
|
||||
|
||||
print("Disconnection", my_wallet.disconnect())
|
||||
@ -10,7 +10,10 @@ from keycard import constants
|
||||
import keycard
|
||||
|
||||
PIN = '123456'
|
||||
PUK = '123456123456'
|
||||
DEFAULT_PAIRING_PASSWORD = "KeycardDefaultPairing"
|
||||
DEFAULT_MNEMONIC = "fashion degree mountain wool question damp current pond grow dolphin chronic then"
|
||||
DEFAULT_PASSPHRASE = ""
|
||||
|
||||
class KeycardWallet:
|
||||
def __init__(self):
|
||||
@ -46,144 +49,88 @@ class KeycardWallet:
|
||||
self.card.select()
|
||||
|
||||
if not self.card.is_initialized:
|
||||
# TODO: need to be able to initialize a card.
|
||||
return False
|
||||
|
||||
if self.pairing_index is None:
|
||||
pairing_index, pairing_key = self.card.pair(password) #Testing
|
||||
pairing_index, pairing_key = self.card.pair(password)
|
||||
self.pairing_index = pairing_index
|
||||
self.pairing_key = pairing_key
|
||||
|
||||
|
||||
self.card.open_secure_channel(pairing_index, pairing_key)
|
||||
self.card.verify_pin(PIN)
|
||||
self.card.verify_pin(pin)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
return False
|
||||
|
||||
"""
|
||||
# Needs to be more robust to handle card removal and reinsertion
|
||||
def is_selected_card_available(self) -> bool:
|
||||
if self.transport.connection is None:
|
||||
return False
|
||||
|
||||
def load_mnemonic(self, mnemonic = DEFAULT_MNEMONIC, passphrase = DEFAULT_PASSPHRASE) -> bool:
|
||||
try:
|
||||
#TODO: fix this up Try a lightweight operation
|
||||
# Card is present
|
||||
self.card.send_apdu(cla=0x00, ins=0xA4, p1=0x04, p2=0x00, data=b'')
|
||||
# return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# TODO: attempt to prevent a new card from being inserted
|
||||
return self.card.is_selected
|
||||
# Convert mnemonic to seed
|
||||
mnemo = Mnemonic("english")
|
||||
seed = mnemo.to_seed(mnemonic, passphrase)
|
||||
|
||||
"""
|
||||
print(f"PIN verified: {self.card.is_pin_verified}")
|
||||
print(f"Secure channel open: {self.card.is_secure_channel_open}")
|
||||
print(f"Card initialized: {self.card.status.get('initialized', False)}")
|
||||
print(f"Seed length: {len(seed)}")
|
||||
|
||||
# Load the LEE seed onto the card
|
||||
result = self.card.load_key(
|
||||
key_type = constants.LoadKeyType.BIP39_SEED,
|
||||
bip39_seed = seed
|
||||
)
|
||||
|
||||
# Wrapped
|
||||
def disconnect(self) -> bool:
|
||||
try:
|
||||
self.card.unpair(self.pairing_index)
|
||||
self.pairing_index = None
|
||||
self.pairing_key = None
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error during disconnect: {e}")
|
||||
return False
|
||||
|
||||
# TODO: add path?
|
||||
# Wrapped
|
||||
def get_public_signing_key(self):
|
||||
uncompressed_pub_key = self.card.export_current_key(public_only=True).public_key
|
||||
|
||||
# Convert to VerifyingKey object
|
||||
vk = VerifyingKey.from_string(uncompressed_pub_key, curve=SECP256k1)
|
||||
|
||||
return vk.to_string("compressed")[1:]
|
||||
|
||||
"""
|
||||
# TODO: don't think this possible; blocked by firmware
|
||||
def get_private_signing_key(self):
|
||||
def disconnect(self) -> bool:
|
||||
try:
|
||||
exported = self.card.export_current_key(public_only=False)
|
||||
print(f"Exported key: {exported}")
|
||||
print(f"Public key: {exported.public_key.hex() if exported.public_key else 'None'}")
|
||||
print(f"Private key: {exported.private_key.hex() if exported.private_key else 'None'}")
|
||||
print(f"Chain code: {exported.chain_code.hex() if exported.chain_code else 'None'}")
|
||||
if not self.card.is_secure_channel_open:
|
||||
return None
|
||||
|
||||
if exported.private_key is None:
|
||||
raise ValueError("No private key returned - key may not be loaded on card")
|
||||
|
||||
return exported.private_key
|
||||
except Exception as e:
|
||||
print(f"Error exporting key: {e}")
|
||||
raise
|
||||
"""
|
||||
# TODO: delete this function
|
||||
def debug_key_export(self):
|
||||
"""Debug why key export fails with SW=6985"""
|
||||
|
||||
# 1. Check if a key exists
|
||||
try:
|
||||
status = self.card.status
|
||||
print(f"Status: {status}")
|
||||
except Exception as e:
|
||||
print(f"Cannot get status: {e}")
|
||||
|
||||
# 2. Try public key export first
|
||||
try:
|
||||
exported = self.card.export_current_key(public_only=True)
|
||||
print(f"Public key export: {exported.public_key.hex() if exported.public_key else 'None'}")
|
||||
except Exception as e:
|
||||
print(f"Public key export failed: {e}")
|
||||
|
||||
# 3. Check if key needs to be generated
|
||||
try:
|
||||
key_uid = self. card.generate_key()
|
||||
print(f"Generated key UID: {key_uid.hex()}")
|
||||
except Exception as e:
|
||||
print(f"Key generation failed: {e}")
|
||||
|
||||
# 4. Try private export again
|
||||
try:
|
||||
exported = self.card.export_current_key(public_only=False)
|
||||
if exported.private_key:
|
||||
print(f"Private key: {exported.private_key.hex()}")
|
||||
else:
|
||||
print("Private key is None - key may not allow export")
|
||||
except Exception as e:
|
||||
print(f"Private key export failed: {e}")
|
||||
|
||||
#TODO: check well formed?
|
||||
# Wrapped
|
||||
def change_path(self, path):
|
||||
self.card.derive_key(path)
|
||||
self.card.unpair(self.pairing_index)
|
||||
self.pairing_index = None
|
||||
self.pairing_key = None
|
||||
|
||||
# Message must be 32 bytes
|
||||
# TODO: rename to current_path
|
||||
# Wrapped
|
||||
def sign_message_current_key(self, message = b"TestMessageMustBe32Bytes!\x00\x00\x00\x00\x00\x00\x00"):
|
||||
# Message must be sent bytes
|
||||
return self.card.sign(message, constants.SigningAlgorithm.SCHNORR_BIP340)
|
||||
|
||||
|
||||
# Does not update the path
|
||||
# Wrapped
|
||||
def sign_message_with_path(self, path, message = b"TestMessageMustBe32Bytes!\x00\x00\x00\x00\x00\x00\x00"):
|
||||
# must be sent bytes
|
||||
return self.card.sign_with_path(message, path, False, constants.SigningAlgorithm.SCHNORR_BIP340)
|
||||
|
||||
# Wrapped
|
||||
def remove_account_keys(self):
|
||||
self.card.remove_key()
|
||||
|
||||
# TODO: update to accept a different language?
|
||||
def load_account_keys(self, mnemonic) :
|
||||
mnemo = Mnemonic("english")
|
||||
seed = mnemo.to_seed(mnemonic, passphrase="")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error during unpair: {e}")
|
||||
return False
|
||||
|
||||
# Load the seed onto the card
|
||||
result = self.card.load_key(
|
||||
key_type= constants.LoadKeyType.BIP39_SEED,
|
||||
lee_seed=seed
|
||||
)
|
||||
def get_public_key_for_path(self, path: str = "m/44'/60'/0'/0/0") -> str | None:
|
||||
try:
|
||||
if not self.card.is_secure_channel_open or not self.card.is_pin_verified:
|
||||
return None
|
||||
|
||||
public_key = self.card.export_key(
|
||||
derivation_option = constants.DerivationOption.DERIVE,
|
||||
public_only = True,
|
||||
keypath = path
|
||||
)
|
||||
|
||||
return public_key.public_key.hex()
|
||||
|
||||
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:
|
||||
try:
|
||||
if not self.card.is_secure_channel_open or not self.card.is_pin_verified:
|
||||
return None
|
||||
|
||||
signature = self.card.sign_with_path(
|
||||
digest = message,
|
||||
path= path,
|
||||
make_current = False
|
||||
)
|
||||
|
||||
return signature.signature.hex()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error signing message: {e}")
|
||||
return None
|
||||
@ -25,121 +25,8 @@ pub enum KeycardSubcommand {
|
||||
)]
|
||||
mnemonic: Option<String>,
|
||||
},
|
||||
Remove,
|
||||
}
|
||||
|
||||
/// Represents generic register CLI subcommand.
|
||||
/*
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum NewSubcommand {
|
||||
/// Register new public account.
|
||||
Public {
|
||||
#[arg(long)]
|
||||
/// Chain index of a parent node.
|
||||
cci: Option<ChainIndex>,
|
||||
#[arg(short, long)]
|
||||
/// Label to assign to the new account.
|
||||
label: Option<String>,
|
||||
},
|
||||
/// Register new private account.
|
||||
Private {
|
||||
#[arg(long)]
|
||||
/// Chain index of a parent node.
|
||||
cci: Option<ChainIndex>,
|
||||
#[arg(short, long)]
|
||||
/// Label to assign to the new account.
|
||||
label: Option<String>,
|
||||
},
|
||||
}
|
||||
*/
|
||||
/*
|
||||
impl WalletSubcommand for NewSubcommand {
|
||||
async fn handle_subcommand(
|
||||
self,
|
||||
wallet_core: &mut WalletCore,
|
||||
) -> Result<SubcommandReturnValue> {
|
||||
match self {
|
||||
Self::Public { cci, label } => {
|
||||
if let Some(label) = &label
|
||||
&& wallet_core
|
||||
.storage
|
||||
.labels
|
||||
.values()
|
||||
.any(|l| l.to_string() == *label)
|
||||
{
|
||||
anyhow::bail!("Label '{label}' is already in use by another account");
|
||||
}
|
||||
|
||||
let (account_id, chain_index) = wallet_core.create_new_account_public(cci);
|
||||
|
||||
let private_key = wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.get_pub_account_signing_key(account_id)
|
||||
.unwrap();
|
||||
|
||||
let public_key = PublicKey::new_from_private_key(private_key);
|
||||
|
||||
if let Some(label) = label {
|
||||
wallet_core
|
||||
.storage
|
||||
.labels
|
||||
.insert(account_id.to_string(), Label::new(label));
|
||||
}
|
||||
|
||||
println!(
|
||||
"Generated new account with account_id Public/{account_id} at path {chain_index}"
|
||||
);
|
||||
println!("With pk {}", hex::encode(public_key.value()));
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
Ok(SubcommandReturnValue::RegisterAccount { account_id })
|
||||
}
|
||||
Self::Private { cci, label } => {
|
||||
if let Some(label) = &label
|
||||
&& wallet_core
|
||||
.storage
|
||||
.labels
|
||||
.values()
|
||||
.any(|l| l.to_string() == *label)
|
||||
{
|
||||
anyhow::bail!("Label '{label}' is already in use by another account");
|
||||
}
|
||||
|
||||
let (account_id, chain_index) = wallet_core.create_new_account_private(cci);
|
||||
|
||||
if let Some(label) = label {
|
||||
wallet_core
|
||||
.storage
|
||||
.labels
|
||||
.insert(account_id.to_string(), Label::new(label));
|
||||
}
|
||||
|
||||
let (key, _) = wallet_core
|
||||
.storage
|
||||
.user_data
|
||||
.get_private_account(account_id)
|
||||
.unwrap();
|
||||
|
||||
println!(
|
||||
"Generated new account with account_id Private/{account_id} at path {chain_index}",
|
||||
);
|
||||
println!("With npk {}", hex::encode(key.nullifier_public_key.0));
|
||||
println!(
|
||||
"With vpk {}",
|
||||
hex::encode(key.viewing_public_key.to_bytes())
|
||||
);
|
||||
|
||||
wallet_core.store_persistent_data().await?;
|
||||
|
||||
Ok(SubcommandReturnValue::RegisterAccount { account_id })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl WalletSubcommand for KeycardSubcommand {
|
||||
#[expect(clippy::cognitive_complexity, reason = "TODO: fix later")]
|
||||
async fn handle_subcommand(
|
||||
|
||||
@ -23,7 +23,7 @@ impl KeycardWallet {
|
||||
/// Calls Python: is_unpaired_keycard_available()
|
||||
pub fn is_unpaired_keycard_available(&self, py: Python) -> PyResult<bool> {
|
||||
self.instance
|
||||
.bind(py) // replaces as_ref(py)
|
||||
.bind(py)
|
||||
.call_method0("is_unpaired_keycard_available")?
|
||||
.extract()
|
||||
}
|
||||
@ -44,22 +44,6 @@ impl KeycardWallet {
|
||||
.extract()
|
||||
}
|
||||
|
||||
pub fn get_public_signing_key(&self, py: Python) -> PyResult<[u8; 32]> {
|
||||
self.instance
|
||||
.bind(py)
|
||||
.call_method0("get_public_signing_key")?
|
||||
.extract()
|
||||
}
|
||||
|
||||
pub fn derive_path(&self, py: Python, path: Vec<u32>) -> PyResult<()> {
|
||||
let path = Self::convert_path_to_string(path);
|
||||
|
||||
self.instance
|
||||
.bind(py)
|
||||
.call_method1("change_path", (path,))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_path_to_string(path: Vec<u32>) -> String {
|
||||
format!(
|
||||
"m/{}",
|
||||
@ -70,23 +54,19 @@ impl KeycardWallet {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn sign_message_current_key(&self, py: Python, message: &[u8; 32]) -> PyResult<[u8; 64]> {
|
||||
let py_message = pyo3::types::PyBytes::new_bound(py, message);
|
||||
|
||||
let py_signature: Vec<u8> = self.instance
|
||||
pub fn get_public_key_for_path(
|
||||
&self,
|
||||
py: Python,
|
||||
path: &str,
|
||||
) -> PyResult<Option<[u8;32]>> {
|
||||
let public_key: Vec<u8> = self.instance
|
||||
.bind(py)
|
||||
.call_method1("sign_message_current_key", (py_message,))?
|
||||
.getattr("signature")? // or "bytes", "data", "value", etc.
|
||||
.call_method1("get_public_key_for_path", (py_message, path))?
|
||||
.getattr("public_key")?
|
||||
.extract()?;
|
||||
|
||||
let signature: [u8; 64] = py_signature
|
||||
.try_into()
|
||||
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>(
|
||||
"Expected signature of exactly 64 bytes"
|
||||
))?;
|
||||
|
||||
Ok(signature)
|
||||
}
|
||||
Ok(Some(public_key.bytes()))
|
||||
}
|
||||
|
||||
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);
|
||||
@ -94,8 +74,8 @@ impl KeycardWallet {
|
||||
|
||||
let py_signature: Vec<u8> = self.instance
|
||||
.bind(py)
|
||||
.call_method1("sign_message_with_path", (path, py_message))?
|
||||
.getattr("signature")? // or "bytes", "data", "value", etc.
|
||||
.call_method1("sign_message_with_path", (py_message, path))?
|
||||
.getattr("signature")?
|
||||
.extract()?;
|
||||
|
||||
let signature: [u8; 64] = py_signature
|
||||
@ -107,17 +87,10 @@ impl KeycardWallet {
|
||||
Ok(signature)
|
||||
}
|
||||
|
||||
pub fn remove_account_keys(&self, py: Python) -> PyResult<()> {
|
||||
pub fn load_mnemonic(&self, py: Python, mnemonic: &str) -> PyResult<()> {
|
||||
self.instance
|
||||
.bind(py)
|
||||
.call_method0("remove_account_keys")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_account_keys(&self, py: Python, mnemonic: &str) -> PyResult<()> {
|
||||
self.instance
|
||||
.bind(py)
|
||||
.call_method1("load_account_keys", (mnemonic,))?;
|
||||
.call_method1("load_mnemonic", (mnemonic,))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user