logos-execution-zone/python/keycard_wallet.py

146 lines
4.5 KiB
Python
Raw Permalink Normal View History

from smartcard.System import readers
from keycard.exceptions import APDUError, TransportError
from ecdsa import VerifyingKey, SECP256k1
from keycard.keycard import KeyCard
2026-05-01 02:44:36 -04:00
from keycard.commands.export_lee_key import export_lee_key
from mnemonic import Mnemonic
from keycard import constants
import keycard
DEFAULT_PAIRING_PASSWORD = "KeycardDefaultPairing"
class KeycardWallet:
def __init__(self):
self.card = KeyCard()
def _is_smart_card_reader_detected(self) -> bool:
try:
return len(readers()) > 0
except Exception:
return False
def _is_keycard_detected(self) -> bool:
try:
KeyCard().select()
return True
except (TransportError, APDUError, Exception):
# No readers, no card, or card doesn't respond.
return False
def is_unpaired_keycard_available(self) -> bool:
if not self._is_smart_card_reader_detected():
return False
elif not self._is_keycard_detected():
return False
return True
2026-04-30 19:02:33 -04:00
def setup_communication(self, pin: str, password = DEFAULT_PAIRING_PASSWORD) -> bool:
try:
self.card.select()
if not self.card.is_initialized:
return False
2026-04-30 19:02:33 -04:00
pairing_index, pairing_key = self.card.pair(password)
self.pairing_index = pairing_index
2026-04-22 21:23:33 -04:00
self.card.open_secure_channel(pairing_index, pairing_key)
2026-04-22 21:23:33 -04:00
self.card.verify_pin(pin)
return True
except Exception as e:
print(f"Error: {e}")
return False
2026-04-30 19:02:33 -04:00
def load_mnemonic(self, mnemonic: str) -> bool:
try:
2026-04-22 21:23:33 -04:00
# Convert mnemonic to seed
mnemo = Mnemonic("english")
2026-04-30 19:02:33 -04:00
seed = mnemo.to_seed(mnemonic)
2026-04-22 21:23:33 -04:00
# Load the LEE seed onto the card
result = self.card.load_key(
key_type = constants.LoadKeyType.LEE_SEED,
2026-04-23 09:47:09 -04:00
lee_seed = seed
2026-04-22 21:23:33 -04:00
)
2026-04-30 19:02:33 -04:00
#TODO: this appears to be the issue.
2026-04-22 21:23:33 -04:00
return True
except Exception as e:
print(f"Error during disconnect: {e}")
return False
def disconnect(self) -> bool:
try:
2026-04-22 21:23:33 -04:00
if not self.card.is_secure_channel_open:
return None
self.card.unpair(self.pairing_index)
2026-04-22 21:23:33 -04:00
return True
except Exception as e:
2026-04-22 21:23:33 -04:00
print(f"Error during unpair: {e}")
return False
2026-04-22 21:23:33 -04:00
2026-04-23 17:45:43 -04:00
def get_public_key_for_path(self, path: str = "m/44'/60'/0'/0/0") -> bytes | None:
2026-04-22 21:23:33 -04:00
try:
if not self.card.is_secure_channel_open or not self.card.is_pin_verified:
return None
2026-04-22 21:23:33 -04:00
public_key = self.card.export_key(
derivation_option = constants.DerivationOption.DERIVE,
public_only = True,
keypath = path
)
2026-04-23 17:45:43 -04:00
public_key = public_key.public_key
public_key = VerifyingKey.from_string(public_key[1:], curve=SECP256k1)
public_key = public_key.to_string("compressed")[1:]
2026-04-23 17:45:43 -04:00
return public_key
2026-04-22 21:23:33 -04:00
except Exception as e:
print(f"Error getting public key: {e}")
return None
2026-04-30 19:02:33 -04:00
def sign_message_for_path(self, message: bytes, path: str = "m/44'/60'/0'/0/0") -> bytes | None:
2026-04-22 21:23:33 -04:00
try:
if not self.card.is_secure_channel_open or not self.card.is_pin_verified:
return None
2026-04-22 21:23:33 -04:00
signature = self.card.sign_with_path(
digest = message,
2026-04-23 09:47:09 -04:00
path = path,
algorithm = constants.SigningAlgorithm.SCHNORR_BIP340,
2026-04-22 21:23:33 -04:00
make_current = False
)
2026-04-23 17:45:43 -04:00
return signature.signature
2026-04-22 21:23:33 -04:00
except Exception as e:
print(f"Error signing message: {e}")
2026-05-01 02:44:36 -04:00
return None
def get_private_keys_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
private_keys = export_lee_key(
self.card,
constants.DerivationOption.DERIVE,
path
)
nsk = private_keys.lee_nsk
vsk = private_keys.lee_vsk
return (nsk, vsk)
except Exception as e:
import traceback
traceback.print_exc()
2026-04-22 21:23:33 -04:00
return None