Merge branch 'marvin/keycard-commands' into marvin/keycard-privacy-commands

This commit is contained in:
jonesmarvin8 2026-05-15 18:17:39 -04:00 committed by GitHub
commit c2dad4b602
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -112,38 +112,75 @@ impl SigningGroups {
Ok(sigs)
}
}
/// Lazily opens and reuses a single Keycard session for all keycard signers in one transaction.
pub struct KeycardSessionContext {
pin: String,
wallet: Option<KeycardWallet>,
}
impl KeycardSessionContext {
pub fn new(pin: impl Into<String>) -> Self {
Self {
pin: pin.into(),
wallet: None,
/// Add a recipient. Same as [`add_sender`] but silently skips accounts with no local
/// key and no keycard path — they are foreign and require neither a signature nor a nonce.
pub fn add_recipient(
&mut self,
mention: &CliAccountMention,
account_id: AccountId,
wallet_core: &WalletCore,
) -> Result<()> {
if let CliAccountMention::KeyPath(path) = mention {
self.keycard.push((account_id, path.clone()));
return Ok(());
}
if let Some(key) = wallet_core
.storage()
.key_chain()
.pub_account_signing_key(account_id)
{
self.local.push((account_id, key.clone()));
}
Ok(())
}
pub fn get_or_connect<'py>(
&'py mut self,
py: Python<'py>,
) -> pyo3::PyResult<&'py KeycardWallet> {
if self.wallet.is_none() {
python_path::add_python_path(py)?;
let wallet = KeycardWallet::new(py)?;
wallet.connect(py, &self.pin)?;
self.wallet = Some(wallet);
}
Ok(self.wallet.as_ref().unwrap())
/// Returns `true` when a PIN is required (at least one keycard signer is present).
#[must_use]
pub const fn needs_pin(&self) -> bool {
!self.keycard.is_empty()
}
pub fn close(self, py: Python<'_>) {
if let Some(w) = self.wallet {
drop(w.close_session(py));
/// Account IDs that require a nonce (every non-foreign signer).
#[must_use]
pub fn signing_ids(&self) -> Vec<AccountId> {
self.local
.iter()
.map(|(id, _)| *id)
.chain(self.keycard.iter().map(|(id, _)| *id))
.collect()
}
/// Sign `hash` for every account in the group.
///
/// Local accounts are signed in pure Rust. Keycard accounts share one Python session.
pub fn sign_all(&self, hash: &[u8; 32], pin: &str) -> Result<Vec<(Signature, PublicKey)>> {
let mut sigs: Vec<(Signature, PublicKey)> = self
.local
.iter()
.map(|(_, key)| {
(
Signature::new(key, hash),
PublicKey::new_from_private_key(key),
)
})
.collect();
if !self.keycard.is_empty() {
pyo3::Python::with_gil(|py| -> pyo3::PyResult<()> {
python_path::add_python_path(py)?;
let wallet = KeycardWallet::new(py)?;
wallet.connect(py, pin)?;
for (_, path) in &self.keycard {
sigs.push(wallet.sign_message_for_path(py, path, hash)?);
}
drop(wallet.close_session(py));
Ok(())
})
.map_err(anyhow::Error::from)?;
}
Ok(sigs)
}
}