This commit is contained in:
jonesmarvin8 2026-04-27 18:47:02 -04:00
parent 2926c808d3
commit 016d063329
16 changed files with 113 additions and 162 deletions

78
Cargo.lock generated
View File

@ -1303,7 +1303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799"
dependencies = [
"clap",
"heck 0.5.0",
"heck",
"indexmap 2.13.0",
"log",
"proc-macro2",
@ -1450,7 +1450,7 @@ version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
dependencies = [
"heck 0.5.0",
"heck",
"proc-macro2",
"quote",
"syn 2.0.117",
@ -1959,7 +1959,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de"
dependencies = [
"data-encoding",
"syn 1.0.109",
"syn 2.0.117",
]
[[package]]
@ -2183,7 +2183,7 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e92f10a49176cbffacaedabfaa11d51db1ea0f80a83c26e1873b43cd1742c24"
dependencies = [
"heck 0.5.0",
"heck",
"proc-macro2",
"proc-macro2-diagnostics",
]
@ -3007,12 +3007,6 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
@ -3894,7 +3888,7 @@ version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da3f8ab5ce1bb124b6d082e62dffe997578ceaf0aeb9f3174a214589dc00f07"
dependencies = [
"heck 0.5.0",
"heck",
"proc-macro-crate",
"proc-macro2",
"quote",
@ -4015,37 +4009,9 @@ dependencies = [
name = "keycard_wallet"
version = "0.1.0"
dependencies = [
"amm_core",
"anyhow",
"async-stream",
"ata_core",
"base58",
"bip39",
"clap",
"common",
"env_logger",
"futures",
"hex",
"humantime",
"humantime-serde",
"indicatif",
"itertools 0.14.0",
"key_protocol",
"log",
"nssa",
"nssa_core",
"optfield",
"pyo3",
"rand 0.8.5",
"sequencer_service_rpc",
"serde",
"serde_json",
"sha2",
"testnet_initial_state",
"thiserror 2.0.18",
"token_core",
"tokio",
"url",
]
[[package]]
@ -6248,15 +6214,15 @@ dependencies = [
[[package]]
name = "pyo3"
version = "0.21.2"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8"
checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219"
dependencies = [
"cfg-if",
"indoc",
"libc",
"memoffset",
"parking_lot",
"once_cell",
"portable-atomic",
"pyo3-build-config",
"pyo3-ffi",
@ -6266,9 +6232,9 @@ dependencies = [
[[package]]
name = "pyo3-build-config"
version = "0.21.2"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50"
checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999"
dependencies = [
"once_cell",
"target-lexicon",
@ -6276,9 +6242,9 @@ dependencies = [
[[package]]
name = "pyo3-ffi"
version = "0.21.2"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403"
checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33"
dependencies = [
"libc",
"pyo3-build-config",
@ -6286,9 +6252,9 @@ dependencies = [
[[package]]
name = "pyo3-macros"
version = "0.21.2"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c"
checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
@ -6298,11 +6264,11 @@ dependencies = [
[[package]]
name = "pyo3-macros-backend"
version = "0.21.2"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c"
checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a"
dependencies = [
"heck 0.4.1",
"heck",
"proc-macro2",
"pyo3-build-config",
"quote",
@ -7996,7 +7962,7 @@ version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
dependencies = [
"heck 0.5.0",
"heck",
"proc-macro2",
"quote",
"syn 2.0.117",
@ -8123,9 +8089,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "target-lexicon"
version = "0.12.16"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
[[package]]
name = "tempfile"
@ -9689,7 +9655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck 0.5.0",
"heck",
"wit-parser",
]
@ -9700,7 +9666,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck 0.5.0",
"heck",
"indexmap 2.13.0",
"prettyplease",
"syn 2.0.117",

View File

@ -121,6 +121,7 @@ url = { version = "2.5.4", features = ["serde"] }
tokio-retry = "0.3.0"
schemars = "1.2"
async-stream = "0.3.6"
pyo3 = { version = "0.24", features = ["auto-initialize"] }
logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "1da154c74b911318fb853d37261f8a05ffe513b4" }
logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "1da154c74b911318fb853d37261f8a05ffe513b4" }

View File

@ -51,7 +51,7 @@ async fn main() {
.send_privacy_preserving_tx(
accounts,
Program::serialize_instruction(greeting).unwrap(),
&program.into()
&program.into(),
)
.await
.unwrap();

View File

@ -59,7 +59,7 @@ async fn main() {
.send_privacy_preserving_tx(
accounts,
Program::serialize_instruction(instruction).unwrap(),
&program_with_dependencies
&program_with_dependencies,
)
.await
.unwrap();

View File

@ -8,35 +8,6 @@ license = { workspace = true }
workspace = true
[dependencies]
nssa_core.workspace = true
nssa.workspace = true
common.workspace = true
key_protocol.workspace = true
sequencer_service_rpc = { workspace = true, features = ["client"] }
token_core.workspace = true
amm_core.workspace = true
testnet_initial_state.workspace = true
ata_core.workspace = true
bip39.workspace = true
pyo3 = { version = "0.21", features = ["auto-initialize"] }
anyhow.workspace = true
thiserror.workspace = true
serde_json.workspace = true
env_logger.workspace = true
log.workspace = true
serde.workspace = true
humantime-serde.workspace = true
humantime.workspace = true
tokio = { workspace = true, features = ["macros"] }
clap.workspace = true
base58.workspace = true
hex.workspace = true
rand.workspace = true
itertools.workspace = true
sha2.workspace = true
futures.workspace = true
async-stream.workspace = true
indicatif = { version = "0.18.3", features = ["improved_unicode"] }
optfield = "0.4.0"
url.workspace = true
pyo3.workspace = true
log.workspace = true

View File

@ -11,13 +11,13 @@ pub struct KeycardWallet {
impl KeycardWallet {
/// Create a new Python `KeycardWallet` instance.
pub fn new(py: Python) -> PyResult<Self> {
let module = py.import_bound("keycard_wallet")?;
let module = py.import("keycard_wallet")?;
let class = module.getattr("KeycardWallet")?;
let instance = class.call0()?;
Ok(Self {
instance: instance.into_py(py),
instance: instance.into(),
})
}
@ -28,12 +28,10 @@ impl KeycardWallet {
.extract()
}
pub fn setup_communication(&self, py: Python, pin: &str) -> PyResult<bool> {
let py_pin = pyo3::types::PyString::new_bound(py, pin);
pub fn setup_communication(&self, py: Python<'_>, pin: &str) -> PyResult<bool> {
self.instance
.bind(py)
.call_method1("setup_communication", (py_pin,))?
.call_method1("setup_communication", (pin,))?
.extract()
}
@ -65,14 +63,14 @@ impl KeycardWallet {
.expect("Expect a Boolean.");
if is_connected {
println!("\u{2705} Keycard is now connected to wallet.");
log::info!("\u{2705} Keycard is now connected to wallet.");
} else {
println!("\u{274c} Keycard is not connected to wallet.");
log::info!("\u{274c} Keycard is not connected to wallet.");
}
let pub_key = wallet.get_public_key_for_path(py, path);
let _ = wallet.disconnect(py);
drop(wallet.disconnect(py));
pub_key
});
pub_key.expect("Expect a valid public key2")
@ -84,24 +82,23 @@ impl KeycardWallet {
path: &str,
message: &[u8; 32],
) -> PyResult<Signature> {
let py_message = pyo3::types::PyBytes::new_bound(py, message);
let py_signature: Vec<u8> = self
.instance
.bind(py)
.call_method1("sign_message_for_path", (py_message, path))?
.call_method1("sign_message_for_path", (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(|vec: Vec<u8>| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"Invalid signature length: expected 64 bytes, got {} (bytes: {:02x?})",
vec.len(),
vec
))
})?;
println!("{signature:?}");
Ok(Signature { value: signature })
}
#[must_use]
pub fn sign_message_for_path_with_connect(
pin: &str,
path: &str,
@ -117,14 +114,14 @@ impl KeycardWallet {
.expect("Expect a Boolean.");
if is_connected {
println!("\u{2705} Keycard is now connected to wallet.");
log::info!("\u{2705} Keycard is now connected to wallet.");
} else {
println!("\u{274c} Keycard is not connected to wallet.");
log::info!("\u{274c} Keycard is not connected to wallet.");
}
let signature = wallet.sign_message_for_path(py, path, message);
let _ = wallet.disconnect(py);
drop(wallet.disconnect(py));
signature
})

View File

@ -1,9 +1,8 @@
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<()> {
pub fn add_python_path(py: Python<'_>) -> PyResult<()> {
let current_dir = env::current_dir().expect("Failed to get current working directory");
let paths_to_add: Vec<PathBuf> = vec![
@ -14,21 +13,25 @@ pub fn add_python_path(py: Python) -> PyResult<()> {
// Sanity check — warns early if a path doesn't exist
for path in &paths_to_add {
if !path.exists() {
eprintln!("Warning: Python path does not exist: {path:?}");
log::info!("Warning: Python path does not exist: {}", path.display());
}
}
let sys = py.import_bound("sys")?;
let sys_path: &PyList = sys.getattr("path")?.extract()?;
let sys = PyModule::import(py, "sys")?;
let binding = sys.getattr("path")?;
let sys_path = binding.downcast::<PyList>()?;
for path in &paths_to_add {
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)
{
let already_present = sys_path.iter().any(|p| {
p.extract::<&str>()
.map(|s| s == path_str)
.unwrap_or(false)
});
if !already_present {
sys_path.insert(0, path_str)?;
}
}

View File

@ -18,7 +18,7 @@ amm_core.workspace = true
testnet_initial_state.workspace = true
ata_core.workspace = true
bip39.workspace = true
pyo3 = { version = "0.21", features = ["auto-initialize"] }
pyo3.workspace = true
anyhow.workspace = true
thiserror.workspace = true

View File

@ -33,7 +33,12 @@ pub enum AccountSubcommand {
/// Account label (alternative to --account-id).
#[arg(long, conflicts_with = "account_id")]
account_label: Option<String>,
#[arg(long, conflicts_with = "account_id", conflicts_with = "account_label", requires = "key_path")]
#[arg(
long,
conflicts_with = "account_id",
conflicts_with = "account_label",
requires = "key_path"
)]
pin: Option<String>,
#[arg(long)]
key_path: Option<String>,
@ -206,7 +211,7 @@ impl WalletSubcommand for AccountSubcommand {
let account_id: nssa::AccountId = account_id_str.parse()?;
// Add account id to the display for keycard users.
println!("Account Id: {}", resolved);
log::info!("Account Id: {resolved}");
if let Some(label) = wallet_core.storage.labels.get(&account_id_str) {
println!("Label: {label}");

View File

@ -21,7 +21,6 @@ pub enum KeycardSubcommand {
}
impl WalletSubcommand for KeycardSubcommand {
#[expect(clippy::cognitive_complexity, reason = "TODO: fix later")]
async fn handle_subcommand(
self,
_wallet_core: &mut WalletCore,
@ -61,9 +60,12 @@ impl WalletSubcommand for KeycardSubcommand {
println!("\u{274c} Keycard is not connected to wallet.");
}
let _ = wallet.load_mnemonic(py, &mnemonic.expect("Expect a mnemonic phrase as a string"));
drop(wallet.load_mnemonic(
py,
&mnemonic.expect("Expect a mnemonic phrase as a string"),
));
let _ = wallet.disconnect(py);
drop(wallet.disconnect(py));
});
Ok(SubcommandReturnValue::Empty)

View File

@ -31,7 +31,12 @@ pub enum AuthTransferSubcommand {
/// Account label (alternative to --account-id).
#[arg(long, conflicts_with = "account_id", conflicts_with = "pin")]
account_label: Option<String>,
#[arg(long, conflicts_with = "account_id", conflicts_with = "account_label", requires = "key_path")]
#[arg(
long,
conflicts_with = "account_id",
conflicts_with = "account_label",
requires = "key_path"
)]
pin: Option<String>,
#[arg(long)]
key_path: Option<String>,
@ -168,7 +173,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
)?),
(None, None, Some(to_key_path)) => {
Some(KeycardWallet::get_account_id_for_path_with_connect(
&pin.as_ref().expect("Expect a pin as a String."),
pin.as_ref().expect("Expect a pin as a String."),
&to_key_path,
))
}
@ -226,7 +231,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
NativeTokenTransferProgramSubcommandShielded::ShieldedOwned {
from,
to,
amount
amount,
},
)
}
@ -282,7 +287,12 @@ pub enum NativeTokenTransferProgramSubcommand {
/// amount - amount of balance to move.
#[arg(long)]
amount: u128,
#[arg(long, conflicts_with = "from", conflicts_with = "from_label", requires = "key_path")]
#[arg(
long,
conflicts_with = "from",
conflicts_with = "from_label",
requires = "key_path"
)]
pin: Option<String>,
#[arg(long)]
key_path: Option<String>,
@ -325,7 +335,7 @@ pub enum NativeTokenTransferProgramSubcommandShielded {
to: String,
/// amount - amount of balance to move.
#[arg(long)]
amount: u128
amount: u128,
},
/// Send native token transfer from `from` to `to` for `amount`.
///
@ -342,7 +352,7 @@ pub enum NativeTokenTransferProgramSubcommandShielded {
to_vpk: String,
/// amount - amount of balance to move.
#[arg(long)]
amount: u128
amount: u128,
},
}
@ -463,11 +473,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
Self::ShieldedOwned {
from,
to,
amount
} => {
Self::ShieldedOwned { from, to, amount } => {
let from: AccountId = from.parse().unwrap();
let to: AccountId = to.parse().unwrap();
@ -512,9 +518,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_vpk.to_vec());
let (tx_hash, _) = NativeTokenTransfer(wallet_core)
.send_shielded_transfer_to_outer_account(
from, to_npk, to_vpk, amount
)
.send_shielded_transfer_to_outer_account(from, to_npk, to_vpk, amount)
.await?;
println!("Transaction hash is {tx_hash}");

View File

@ -22,7 +22,12 @@ pub enum PinataProgramAgnosticSubcommand {
/// To account label (alternative to --to).
#[arg(long, conflicts_with = "to")]
to_label: Option<String>,
#[arg(long, conflicts_with = "to", conflicts_with = "to_label", requires = "key_path")]
#[arg(
long,
conflicts_with = "to",
conflicts_with = "to_label",
requires = "key_path"
)]
pin: Option<String>,
#[arg(long)]
key_path: Option<String>,

View File

@ -70,8 +70,7 @@ pub fn resolve_id_or_label(
(None, None, Some(pin)) => Ok(KeycardWallet::get_account_id_for_path_with_connect(
pin,
key_path.as_ref().expect("Expect a key path String."),
)
.to_string()),
)),
_ => anyhow::bail!("provide exactly one of account id, account label or keycard path"),
}
}

View File

@ -25,9 +25,7 @@ use nssa::{
},
};
use nssa_core::{
Commitment, MembershipProof, SharedSecretKey,
account::Nonce,
program::InstructionData,
Commitment, MembershipProof, SharedSecretKey, account::Nonce, program::InstructionData,
};
pub use privacy_preserving_tx::PrivacyPreservingAccount;
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};

View File

@ -60,12 +60,12 @@ impl NativeTokenTransfer<'_> {
.expect("Expect a valid signature")
} else {
let pub_key = KeycardWallet::get_public_key_for_path_with_connect(
&pin.as_ref().expect("Expect a pin as a String."),
&key_path.as_ref().expect("Expect a key path String."),
pin.as_ref().expect("Expect a pin as a String."),
key_path.as_ref().expect("Expect a key path String."),
);
let signature = KeycardWallet::sign_message_for_path_with_connect(
&pin.as_ref().expect("Expect a pin as a String."),
&key_path.as_ref().expect("Expect a key path String."),
pin.as_ref().expect("Expect a pin as a String."),
key_path.as_ref().expect("Expect a key path String."),
&message.hash_message(),
)
.expect("Expect valid signature");

View File

@ -182,7 +182,19 @@ impl Token<'_> {
)
.unwrap();
let witness_set = if pin.is_none() {
let witness_set = if let Some(pin) = &pin {
let sender_public_key = KeycardWallet::get_public_key_for_path_with_connect(
pin,
sender_key_path.as_ref().expect("Expect a key path String."),
);
let signature = KeycardWallet::sign_message_for_path_with_connect(
pin,
sender_key_path.as_ref().expect("Expect a key path String."),
&message.hash_message(),
)
.expect("Expect a valid signature");
WitnessSet::from_list(&[signature], &[sender_public_key])
} else {
let mut private_keys = Vec::new();
let sender_sk = self
.0
@ -206,18 +218,6 @@ impl Token<'_> {
}
nssa::public_transaction::WitnessSet::for_message(&message, &private_keys)
} else {
let sender_public_key = KeycardWallet::get_public_key_for_path_with_connect(
&pin.as_ref().expect("Expect a pin as a String."),
&sender_key_path.as_ref().expect("Expect a key path String."),
);
let signature = KeycardWallet::sign_message_for_path_with_connect(
&pin.expect("Expect a pin as a String."),
&sender_key_path.expect("Expect a key path String."),
&message.hash_message(),
)
.expect("Expect a valid signature");
WitnessSet::from_list(&[signature], &[sender_public_key])
};
let tx = nssa::PublicTransaction::new(message, witness_set);