updating logic

This commit is contained in:
jonesmarvin8 2026-05-15 18:15:54 -04:00
parent a43314a213
commit 54f6d4922b
22 changed files with 850 additions and 1211 deletions

1
Cargo.lock generated
View File

@ -4392,6 +4392,7 @@ dependencies = [
"pyo3",
"serde",
"serde_json",
"zeroize",
]
[[package]]

View File

@ -298,6 +298,7 @@ async fn claim_funds_from_vault_to_private(
],
instruction_data,
&program_with_dependencies,
&None,
)
.await
.context("Failed to submit private vault claim transaction")?;

View File

@ -9,10 +9,10 @@ source venv/bin/activate
export KEYCARD_PIN=111111
echo "=== Test: wallet keycard get-private-keys path 10 ==="
wallet keycard get-private-keys --key-path "m/44'/60'/0'/0/10"
wallet keycard get-private-keys --key-path "m/44'/60'/0'/0/10" --reveal
echo "=== Test: wallet keycard get-private-keys path 11 ==="
wallet keycard get-private-keys --key-path "m/44'/60'/0'/0/11"
wallet keycard get-private-keys --key-path "m/44'/60'/0'/0/11" --reveal
echo ""
echo "=== All get-private-keys tests finished ==="

View File

@ -83,17 +83,17 @@ echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
echo "=== Test: account get path 0 ==="
wallet account get --key-path "m/44'/60'/0'/0/0"
wallet account get --account-id "m/44'/60'/0'/0/0"
echo "=== Test: account get path 1 ==="
wallet account get --key-path "m/44'/60'/0'/0/1"
wallet account get --account-id "m/44'/60'/0'/0/1"
echo ""
echo "=== Test (1): Shielded auth-transfer to owned private account ==="
wallet auth-transfer send --amount 2 \
--from-key-path "m/44'/60'/0'/0/0" \
--from "m/44'/60'/0'/0/0" \
--to-npk "55204e2934045b044f06d8222b454d46b54788f33c7dec4f6733d441703bb0e6" \
--to-vpk "02a8626b0c0ad9383c5678dad48c3969b4174fb377cdb03a6259648032c774cec8"
echo "Shielded auth-transfer sent"
sleep 5
wallet account get --key-path "m/44'/60'/0'/0/0"
sleep 15
wallet account get --account-id "m/44'/60'/0'/0/0"

View File

@ -32,7 +32,9 @@ export KEYCARD_PIN=111111
echo ""
echo "=== Keycard setup ==="
wallet keycard available
wallet keycard load --mnemonic "fashion degree mountain wool question damp current pond grow dolphin chronic then"
export KEYCARD_MNEMONIC="fashion degree mountain wool question damp current pond grow dolphin chronic then"
wallet keycard load
unset KEYCARD_MNEMONIC
# =============================================================================
# Create non-keycard wallet accounts
@ -49,10 +51,10 @@ wallet account new public --label amm-lp-fund 2>/dev/null || true
# (1) Create LEZ token — definition AND supply via keycard paths
# =============================================================================
echo ""
echo "=== (1) Create LEZ token (keycard def=path2, supply=path1) ==="
echo "=== (1) Create LEZ token (keycard def=path2, supply=path3) ==="
wallet token new \
--definition-key-path "m/44'/60'/0'/0/2" \
--supply-key-path "m/44'/60'/0'/0/3" \
--definition-account-id "m/44'/60'/0'/0/2" \
--supply-account-id "m/44'/60'/0'/0/3" \
--name LEZ \
--total-supply 100000
echo "LEZ token created"
@ -61,29 +63,29 @@ echo "LEZ token created"
# (2) Create LEE token — definition AND supply via keycard paths
# =============================================================================
echo ""
echo "=== (2) Create LEE token (keycard def=path4, supply=path3) ==="
echo "=== (2) Create LEE token (keycard def=path4, supply=path5) ==="
wallet token new \
--definition-key-path "m/44'/60'/0'/0/4" \
--supply-key-path "m/44'/60'/0'/0/5" \
--definition-account-id "m/44'/60'/0'/0/4" \
--supply-account-id "m/44'/60'/0'/0/5" \
--name LEE \
--total-supply 100000
echo "LEE token created"
sleep 15
LEZ_DEF_ID=$(wallet account id --key-path "m/44'/60'/0'/0/2")
LEE_DEF_ID=$(wallet account id --key-path "m/44'/60'/0'/0/4")
LEZ_DEF_ID=$(wallet account id --account-id "m/44'/60'/0'/0/2")
LEE_DEF_ID=$(wallet account id --account-id "m/44'/60'/0'/0/4")
echo "LEZ definition ID: $LEZ_DEF_ID"
echo "LEE definition ID: $LEE_DEF_ID"
echo "Keycard path 2 (LEZ definition) state:"
wallet account get --key-path "m/44'/60'/0'/0/2"
wallet account get --account-id "m/44'/60'/0'/0/2"
echo "Keycard path 3 (LEZ supply) state:"
wallet account get --key-path "m/44'/60'/0'/0/3"
wallet account get --account-id "m/44'/60'/0'/0/3"
echo "Keycard path 4 (LEE definition) state:"
wallet account get --key-path "m/44'/60'/0'/0/4"
wallet account get --account-id "m/44'/60'/0'/0/4"
echo "Keycard path 5 (LEE supply) state:"
wallet account get --key-path "m/44'/60'/0'/0/5"
wallet account get --account-id "m/44'/60'/0'/0/5"
# =============================================================================
# Initialize token holding accounts
@ -91,31 +93,36 @@ wallet account get --key-path "m/44'/60'/0'/0/5"
echo ""
echo "=== Initialize token holding accounts ==="
# Keycard path 6: LEZ holding
wallet token init \
--definition-account-id "Public/$LEZ_DEF_ID" \
--holder-key-path "m/44'/60'/0'/0/6"
echo "LEZ holding initialized for keycard path 4"
# Keycard path 6: LEZ holding (mint 0 to initialize)
wallet token mint \
--definition "m/44'/60'/0'/0/2" \
--holder "m/44'/60'/0'/0/6" \
--amount 0
echo "LEZ holding initialized for keycard path 6"
# Keycard path 7: LEE holding
wallet token init \
--definition-account-id "Public/$LEE_DEF_ID" \
--holder-key-path "m/44'/60'/0'/0/7"
echo "LEE holding initialized for keycard path 5"
wallet token mint \
--definition "m/44'/60'/0'/0/4" \
--holder "m/44'/60'/0'/0/7" \
--amount 0
echo "LEE holding initialized for keycard path 7"
# pub-receiver: public LEZ holding (for token transfer test)
wallet token init \
--definition-account-id "Public/$LEZ_DEF_ID" \
--holder-account-label pub-receiver
wallet token mint \
--definition "m/44'/60'/0'/0/2" \
--holder pub-receiver \
--amount 0
echo "LEZ holding initialized for pub-receiver"
# AMM seed accounts
wallet token init \
--definition-account-id "Public/$LEZ_DEF_ID" \
--holder-account-label amm-lez-fund
wallet token init \
--definition-account-id "Public/$LEE_DEF_ID" \
--holder-account-label amm-lee-fund
wallet token mint \
--definition "m/44'/60'/0'/0/2" \
--holder amm-lez-fund \
--amount 0
wallet token mint \
--definition "m/44'/60'/0'/0/4" \
--holder amm-lee-fund \
--amount 0
echo "AMM seed holdings initialized"
# =============================================================================
@ -125,39 +132,39 @@ echo ""
echo "=== Fund keycard holdings and AMM seed accounts ==="
wallet token send \
--from-key-path "m/44'/60'/0'/0/3" \
--to-key-path "m/44'/60'/0'/0/6" \
--from "m/44'/60'/0'/0/3" \
--to "m/44'/60'/0'/0/6" \
--amount 20000
echo "Transferred 20000 LEZ → keycard path 4"
echo "Transferred 20000 LEZ → keycard path 6"
wallet token send \
--from-key-path "m/44'/60'/0'/0/5" \
--to-key-path "m/44'/60'/0'/0/7" \
--from "m/44'/60'/0'/0/5" \
--to "m/44'/60'/0'/0/7" \
--amount 20000
echo "Transferred 20000 LEE → keycard path 5"
echo "Transferred 20000 LEE → keycard path 7"
wallet token send \
--from-key-path "m/44'/60'/0'/0/3" \
--to-label amm-lez-fund \
--from "m/44'/60'/0'/0/3" \
--to amm-lez-fund \
--amount 10000
echo "Transferred 10000 LEZ → amm-lez-fund"
wallet token send \
--from-key-path "m/44'/60'/0'/0/5" \
--to-label amm-lee-fund \
--from "m/44'/60'/0'/0/5" \
--to amm-lee-fund \
--amount 10000
echo "Transferred 10000 LEE → amm-lee-fund"
sleep 15
echo "Keycard path 6 (LEZ holding) state (balance should be 20000):"
wallet account get --key-path "m/44'/60'/0'/0/6"
wallet account get --account-id "m/44'/60'/0'/0/6"
echo "Keycard path 7 (LEE holding) state (balance should be 20000):"
wallet account get --key-path "m/44'/60'/0'/0/7"
wallet account get --account-id "m/44'/60'/0'/0/7"
echo "amm-lez-fund state (balance should be 10000):"
wallet account get --account-label amm-lez-fund
wallet account get --account-id amm-lez-fund
echo "amm-lee-fund state (balance should be 10000):"
wallet account get --account-label amm-lee-fund
wallet account get --account-id amm-lee-fund
# =============================================================================
# (3) Token transfer: keycard path 6 (LEZ) → public account
@ -165,17 +172,17 @@ wallet account get --account-label amm-lee-fund
echo ""
echo "=== (3) Token transfer: keycard path 6 → pub-receiver (public) ==="
wallet token send \
--from-key-path "m/44'/60'/0'/0/6" \
--to-label pub-receiver \
--from "m/44'/60'/0'/0/6" \
--to pub-receiver \
--amount 1000
echo "Transferred 1000 LEZ: keycard path 6 → pub-receiver"
sleep 15
echo "Keycard path 6 (LEZ) state (balance should be 19000):"
wallet account get --key-path "m/44'/60'/0'/0/6"
wallet account get --account-id "m/44'/60'/0'/0/6"
echo "pub-receiver state (balance should be 1000):"
wallet account get --account-label pub-receiver
wallet account get --account-id pub-receiver
# =============================================================================
# (4) Token transfer: keycard path 6 (LEZ) → private account (shielded)
@ -186,8 +193,8 @@ PRIV_RECEIVER=$(wallet account new private | grep -o 'Private/[^[:space:]]*' | h
echo "Fresh private receiver account: $PRIV_RECEIVER"
wallet token send \
--from-key-path "m/44'/60'/0'/0/6" \
--to "$PRIV_RECEIVER" \
--from "m/44'/60'/0'/0/6" \
--to "$PRIV_RECEIVER" \
--amount 500
echo "Shielded transfer of 500 LEZ: keycard path 6 → $PRIV_RECEIVER"
@ -196,7 +203,7 @@ wallet account sync-private
sleep 15
echo "Keycard path 6 (LEZ) state (balance should be 18500):"
wallet account get --key-path "m/44'/60'/0'/0/6"
wallet account get --account-id "m/44'/60'/0'/0/6"
echo "priv-receiver state (balance should be 500):"
wallet account get --account-id "$PRIV_RECEIVER"
@ -206,17 +213,17 @@ wallet account get --account-id "$PRIV_RECEIVER"
echo ""
echo "=== (5) Token mint: keycard def path 2 mints 2000 LEZ to keycard path 6 ==="
wallet token mint \
--definition-key-path "m/44'/60'/0'/0/2" \
--holder-key-path "m/44'/60'/0'/0/6" \
--definition "m/44'/60'/0'/0/2" \
--holder "m/44'/60'/0'/0/6" \
--amount 2000
echo "Minted 2000 LEZ to keycard path 4"
echo "Minted 2000 LEZ to keycard path 6"
sleep 15
echo "Keycard path 2 (LEZ definition) state (total supply should have increased):"
wallet account get --key-path "m/44'/60'/0'/0/2"
wallet account get --account-id "m/44'/60'/0'/0/2"
echo "Keycard path 6 (LEZ holding) state (balance should be 20500):"
wallet account get --key-path "m/44'/60'/0'/0/6"
wallet account get --account-id "m/44'/60'/0'/0/6"
# =============================================================================
# (6) Token burn with keycard — holder is keycard path 6
@ -224,17 +231,17 @@ wallet account get --key-path "m/44'/60'/0'/0/6"
echo ""
echo "=== (6) Token burn: keycard path 6 burns 500 LEZ ==="
wallet token burn \
--definition "Public/$LEZ_DEF_ID" \
--holder-key-path "m/44'/60'/0'/0/6" \
--definition "Public/$LEZ_DEF_ID" \
--holder "m/44'/60'/0'/0/6" \
--amount 500
echo "Burned 500 LEZ from keycard path 4"
echo "Burned 500 LEZ from keycard path 6"
sleep 15
echo "Keycard path 2 (LEZ definition) state (total supply should reflect burn):"
wallet account get --key-path "m/44'/60'/0'/0/2"
wallet account get --account-id "m/44'/60'/0'/0/2"
echo "Keycard path 6 (LEZ holding) state (balance should be 20000):"
wallet account get --key-path "m/44'/60'/0'/0/6"
wallet account get --account-id "m/44'/60'/0'/0/6"
# =============================================================================
# (7) Create AMM pool for LEZ/LEE — without keycard
@ -243,9 +250,9 @@ echo ""
echo "=== (7) Create AMM pool for LEZ/LEE (without keycard) ==="
wallet amm new \
--user-holding-a-label amm-lez-fund \
--user-holding-b-label amm-lee-fund \
--user-holding-lp-label amm-lp-fund \
--user-holding-a amm-lez-fund \
--user-holding-b amm-lee-fund \
--user-holding-lp amm-lp-fund \
--balance-a 10000 \
--balance-b 10000
echo "AMM pool created for LEZ/LEE"
@ -253,12 +260,12 @@ echo "AMM pool created for LEZ/LEE"
sleep 15
echo "amm-lez-fund state (balance should be 0 — contributed to pool):"
wallet account get --account-label amm-lez-fund
wallet account get --account-id amm-lez-fund
echo "amm-lee-fund state (balance should be 0 — contributed to pool):"
wallet account get --account-label amm-lee-fund
wallet account get --account-id amm-lee-fund
echo "Initial LP holding state (should hold initial LP tokens):"
wallet account get --account-label amm-lp-fund
LP_DEF_ID=$(wallet account get --account-label amm-lp-fund | grep -o '"definition_id":"[^"]*"' | awk -F'"' '{print $4}')
wallet account get --account-id amm-lp-fund
LP_DEF_ID=$(wallet account get --account-id amm-lp-fund | grep -o '"definition_id":"[^"]*"' | awk -F'"' '{print $4}')
echo "LP token definition ID: $LP_DEF_ID"
# =============================================================================
@ -268,8 +275,8 @@ echo "LP token definition ID: $LP_DEF_ID"
echo ""
echo "=== (8) Swap: keycard path 7 sells 500 LEE, keycard path 6 receives LEZ ==="
wallet amm swap-exact-input \
--user-holding-a-key-path "m/44'/60'/0'/0/6" \
--user-holding-b-key-path "m/44'/60'/0'/0/7" \
--user-holding-a "m/44'/60'/0'/0/6" \
--user-holding-b "m/44'/60'/0'/0/7" \
--amount-in 500 \
--min-amount-out 1 \
--token-definition "$LEE_DEF_ID"
@ -278,31 +285,32 @@ echo "Swap LEE → LEZ complete via keycard"
sleep 15
echo "Keycard path 6 (LEZ holding) state (balance should have increased):"
wallet account get --key-path "m/44'/60'/0'/0/6"
wallet account get --account-id "m/44'/60'/0'/0/6"
echo "Keycard path 7 (LEE holding) state (balance should have decreased by 500):"
wallet account get --key-path "m/44'/60'/0'/0/7"
wallet account get --account-id "m/44'/60'/0'/0/7"
# =============================================================================
# (9) Add liquidity — keycard accounts for holding A (path 6), B (path 7), LP (path 8)
# =============================================================================
echo ""
echo "=== (9) Initialize LP holding (keycard path 8) before add-liquidity ==="
wallet token init \
--definition-account-id "Public/$LP_DEF_ID" \
--holder-key-path "m/44'/60'/0'/0/8"
wallet token mint \
--definition "Public/$LP_DEF_ID" \
--holder "m/44'/60'/0'/0/8" \
--amount 0
echo "Keycard path 8 (LP holding) initialized"
sleep 15
echo "Keycard path 8 (LP holding) state (after init):"
wallet account get --key-path "m/44'/60'/0'/0/8"
wallet account get --account-id "m/44'/60'/0'/0/8"
echo ""
echo "=== (9) Add liquidity (keycard path 6=LEZ, path 7=LEE, path 8=LP) ==="
wallet amm add-liquidity \
--user-holding-a-key-path "m/44'/60'/0'/0/6" \
--user-holding-b-key-path "m/44'/60'/0'/0/7" \
--user-holding-lp-key-path "m/44'/60'/0'/0/8" \
--user-holding-a "m/44'/60'/0'/0/6" \
--user-holding-b "m/44'/60'/0'/0/7" \
--user-holding-lp "m/44'/60'/0'/0/8" \
--max-amount-a 1000 \
--max-amount-b 1000 \
--min-amount-lp 1
@ -311,11 +319,11 @@ echo "Add liquidity complete via keycard"
sleep 15
echo "Keycard path 6 (LEZ holding) state (balance should have decreased):"
wallet account get --key-path "m/44'/60'/0'/0/6"
wallet account get --account-id "m/44'/60'/0'/0/6"
echo "Keycard path 7 (LEE holding) state (balance should have decreased):"
wallet account get --key-path "m/44'/60'/0'/0/7"
wallet account get --account-id "m/44'/60'/0'/0/7"
echo "Keycard path 8 (LP holding) state (should have received LP tokens):"
wallet account get --key-path "m/44'/60'/0'/0/8"
wallet account get --account-id "m/44'/60'/0'/0/8"
# =============================================================================
# (10) Remove liquidity — keycard accounts for holding A (path 6), B (path 7), LP (path 8)
@ -323,9 +331,9 @@ wallet account get --key-path "m/44'/60'/0'/0/8"
echo ""
echo "=== (10) Remove liquidity (keycard path 6=LEZ, path 7=LEE, path 8=LP) ==="
wallet amm remove-liquidity \
--user-holding-a-key-path "m/44'/60'/0'/0/6" \
--user-holding-b-key-path "m/44'/60'/0'/0/7" \
--user-holding-lp-key-path "m/44'/60'/0'/0/8" \
--user-holding-a "m/44'/60'/0'/0/6" \
--user-holding-b "m/44'/60'/0'/0/7" \
--user-holding-lp "m/44'/60'/0'/0/8" \
--balance-lp 500 \
--min-amount-a 1 \
--min-amount-b 1
@ -334,22 +342,22 @@ echo "Remove liquidity complete via keycard"
sleep 15
echo "Keycard path 6 (LEZ holding) state (balance should have increased):"
wallet account get --key-path "m/44'/60'/0'/0/6"
wallet account get --account-id "m/44'/60'/0'/0/6"
echo "Keycard path 7 (LEE holding) state (balance should have increased):"
wallet account get --key-path "m/44'/60'/0'/0/7"
wallet account get --account-id "m/44'/60'/0'/0/7"
echo "Keycard path 8 (LP holding) state (balance should have decreased):"
wallet account get --key-path "m/44'/60'/0'/0/8"
wallet account get --account-id "m/44'/60'/0'/0/8"
# =============================================================================
# (11) ATA create — keycard path 9 as owner for LEZ
# =============================================================================
echo ""
echo "=== (11) ATA create: keycard path 9 as owner, LEZ token ==="
ATA_OWNER_ID=$(wallet account id --key-path "m/44'/60'/0'/0/9")
ATA_OWNER_ID=$(wallet account id --account-id "m/44'/60'/0'/0/9")
echo "ATA owner (keycard path 9): $ATA_OWNER_ID"
wallet ata create \
--key-path "m/44'/60'/0'/0/9" \
--owner "m/44'/60'/0'/0/9" \
--token-definition "$LEZ_DEF_ID"
echo "ATA created for keycard path 9 / LEZ"
@ -362,8 +370,8 @@ wallet account get --account-id "Public/$LEZ_ATA_ID"
# Fund the ATA from LEZ supply (path 3) — setup for tests 12 and 13
wallet token send \
--from-key-path "m/44'/60'/0'/0/3" \
--to "Public/$LEZ_ATA_ID" \
--from "m/44'/60'/0'/0/3" \
--to "Public/$LEZ_ATA_ID" \
--amount 3000
echo "Funded keycard path 9 ATA with 3000 LEZ"
@ -373,11 +381,11 @@ echo "ATA state after funding (balance should be 3000):"
wallet account get --account-id "Public/$LEZ_ATA_ID"
# =============================================================================
# (12) ATA send — keycard path 7's ATA → pub-receiver's ATA
# (12) ATA send — keycard path 9's ATA → pub-receiver's ATA
# =============================================================================
echo ""
echo "=== (12) ATA send: keycard path 7's ATA → pub-receiver's ATA ==="
PUB_RECEIVER_ID=$(wallet account id --account-label pub-receiver)
echo "=== (12) ATA send: keycard path 9's ATA → pub-receiver's ATA ==="
PUB_RECEIVER_ID=$(wallet account id --account-id pub-receiver)
wallet ata create \
--owner "Public/$PUB_RECEIVER_ID" \
--token-definition "$LEZ_DEF_ID"
@ -391,7 +399,7 @@ echo "pub-receiver ATA state (should be initialized with zero balance):"
wallet account get --account-id "Public/$PUB_RECEIVER_ATA_ID"
wallet ata send \
--from-key-path "m/44'/60'/0'/0/9" \
--from "m/44'/60'/0'/0/9" \
--token-definition "$LEZ_DEF_ID" \
--to "$PUB_RECEIVER_ATA_ID" \
--amount 500
@ -405,12 +413,12 @@ echo "pub-receiver ATA state (balance should be 500):"
wallet account get --account-id "Public/$PUB_RECEIVER_ATA_ID"
# =============================================================================
# (13) ATA burn — keycard path 7's ATA burns 200 LEZ
# (13) ATA burn — keycard path 9's ATA burns 200 LEZ
# =============================================================================
echo ""
echo "=== (13) ATA burn: keycard path 7's ATA burns 200 LEZ ==="
echo "=== (13) ATA burn: keycard path 9's ATA burns 200 LEZ ==="
wallet ata burn \
--key-path "m/44'/60'/0'/0/9" \
--holder "m/44'/60'/0'/0/9" \
--token-definition "$LEZ_DEF_ID" \
--amount 200
echo "Burned 200 LEZ from keycard path 9 ATA"
@ -420,7 +428,7 @@ sleep 15
echo "Keycard path 9 ATA state (balance should be 2300):"
wallet account get --account-id "Public/$LEZ_ATA_ID"
echo "LEZ definition state (total supply should reflect burn):"
wallet account get --key-path "m/44'/60'/0'/0/2"
wallet account get --account-id "m/44'/60'/0'/0/2"
echo ""
echo "=== All keycard token + AMM + ATA tests finished ==="

View File

@ -13,4 +13,5 @@ nssa_core.workspace = true
pyo3.workspace = true
log.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
serde_json.workspace = true
zeroize = "1"

View File

@ -3,6 +3,7 @@ use std::path::PathBuf;
use nssa::{AccountId, PublicKey, Signature};
use nssa_core::NullifierPublicKey;
use pyo3::{prelude::*, types::PyAny};
use zeroize::Zeroizing;
use serde::{Deserialize, Serialize};
pub mod python_path;
@ -215,26 +216,39 @@ impl KeycardWallet {
&self,
py: Python,
path: &str,
) -> PyResult<([u8; 32], [u8; 32])> {
) -> PyResult<(Zeroizing<[u8; 32]>, Zeroizing<[u8; 32]>)> {
let (raw_nsk, raw_vsk): (Vec<u8>, Vec<u8>) = self
.instance
.bind(py)
.call_method1("get_private_keys_for_path", (path,))?
.extract()?;
let nsk: [u8; 32] = raw_nsk.try_into().map_err(|vec: Vec<u8>| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"expected 32-byte NSK from keycard, got {} bytes",
vec.len()
))
})?;
let raw_nsk = Zeroizing::new(raw_nsk);
let raw_vsk = Zeroizing::new(raw_vsk);
let vsk: [u8; 32] = raw_vsk.try_into().map_err(|vec: Vec<u8>| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"expected 32-byte VSK from keycard, got {} bytes",
vec.len()
))
})?;
let nsk = {
if raw_nsk.len() != 32 {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"expected 32-byte NSK from keycard, got {} bytes",
raw_nsk.len()
)));
}
let mut arr = Zeroizing::new([0u8; 32]);
arr.copy_from_slice(&raw_nsk);
arr
};
let vsk = {
if raw_vsk.len() != 32 {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"expected 32-byte VSK from keycard, got {} bytes",
raw_vsk.len()
)));
}
let mut arr = Zeroizing::new([0u8; 32]);
arr.copy_from_slice(&raw_vsk);
arr
};
Ok((nsk, vsk))
}
@ -242,7 +256,7 @@ impl KeycardWallet {
pub fn get_private_keys_for_path_with_connect(
pin: &str,
path: &str,
) -> PyResult<([u8; 32], [u8; 32])> {
) -> PyResult<(Zeroizing<[u8; 32]>, Zeroizing<[u8; 32]>)> {
Python::with_gil(|py| {
python_path::add_python_path(py)?;
@ -268,7 +282,7 @@ impl KeycardWallet {
key_path: &str,
) -> PyResult<String> {
let (nsk, _vsk) = Self::get_private_keys_for_path_with_connect(pin, key_path)?;
let npk = NullifierPublicKey::from(&nsk);
let npk = NullifierPublicKey::from(&*nsk);
Ok(format!("Private/{}", AccountId::from((&npk, 0_u128))))
}

View File

@ -15,6 +15,12 @@ use crate::{
/// Represents generic chain CLI subcommand.
#[derive(Subcommand, Debug, Clone)]
pub enum AccountSubcommand {
/// Resolve an account mention and print just the account ID (no privacy prefix).
Id {
/// Account id with privacy prefix, label, or BIP-32 key path.
#[arg(long)]
account_id: CliAccountMention,
},
/// Get account data.
Get {
/// Flag to get raw account data.
@ -261,6 +267,14 @@ impl WalletSubcommand for AccountSubcommand {
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
Self::Id { account_id } => {
let resolved = account_id.resolve(wallet_core.storage())?;
let id = match resolved {
AccountIdWithPrivacy::Public(id) | AccountIdWithPrivacy::Private(id) => id,
};
println!("{id}");
Ok(SubcommandReturnValue::Empty)
}
Self::Get {
raw,
keys,

View File

@ -16,6 +16,18 @@ pub enum KeycardSubcommand {
Disconnect,
Init,
Load,
/// Retrieve the private keys (NSK, VSK) for a given BIP-32 key path.
///
/// Prints raw key material to stdout — intended for debugging only.
/// Requires --reveal to confirm intent.
GetPrivateKeys {
/// BIP-32 derivation path, e.g. `m/44'/60'/0'/0/0`.
#[arg(long)]
key_path: String,
/// Confirm that raw NSK and VSK should be disclosed on stdout.
#[arg(long)]
reveal: bool,
},
}
impl WalletSubcommand for KeycardSubcommand {
@ -131,6 +143,26 @@ impl WalletSubcommand for KeycardSubcommand {
Ok(SubcommandReturnValue::Empty)
}
Self::GetPrivateKeys { key_path, reveal } => {
if !reveal {
eprintln!(
"WARNING: pass --reveal to print NSK and VSK. \
Disclosing either key fully compromises the account's privacy."
);
return Ok(SubcommandReturnValue::Empty);
}
eprintln!(
"WARNING: NSK and VSK are being printed to stdout. \
Any terminal log, scrollback, or screen recording captures these keys."
);
let pin = read_pin()?;
let (nsk, vsk) =
KeycardWallet::get_private_keys_for_path_with_connect(&pin, &key_path)
.map_err(anyhow::Error::from)?;
println!("NSK: {}", hex::encode(&*nsk));
println!("VSK: {}", hex::encode(&*vsk));
Ok(SubcommandReturnValue::Empty)
}
}
}
}

View File

@ -138,7 +138,7 @@ impl CliAccountMention {
Self::KeyPath(path) => {
let pin = read_pin()?;
let id_str =
keycard_wallet::KeycardWallet::get_account_id_for_path_with_connect(&pin, path)
keycard_wallet::KeycardWallet::get_public_account_id_for_path_with_connect(&pin, path)
.map_err(anyhow::Error::from)?;
AccountIdWithPrivacy::from_str(&id_str)
.map_err(|e| anyhow::anyhow!("Invalid account id from keycard: {e}"))
@ -159,46 +159,6 @@ impl CliAccountMention {
}
}
/// Resolve to an [`AccountSigner`] for a sender — must sign, never `Foreign`.
pub fn to_signer(&self, wallet_core: &WalletCore) -> Result<crate::signing::AccountSigner> {
if let Self::KeyPath(path) = self {
return Ok(crate::signing::AccountSigner::Keycard(path.clone()));
}
let account = self.resolve(wallet_core.storage())?;
match account {
AccountIdWithPrivacy::Public(id) => Ok(crate::signing::AccountSigner::Local(id)),
AccountIdWithPrivacy::Private(_) => {
anyhow::bail!("Private accounts not supported as senders here")
}
}
}
/// Resolve to an [`AccountSigner`] for a recipient — returns `Foreign` when the account
/// has no local key and no keycard path, meaning no signature or nonce is required.
pub fn to_recipient_signer(
&self,
wallet_core: &WalletCore,
) -> Result<crate::signing::AccountSigner> {
if let Self::KeyPath(path) = self {
return Ok(crate::signing::AccountSigner::Keycard(path.clone()));
}
let account = self.resolve(wallet_core.storage())?;
match account {
AccountIdWithPrivacy::Public(id) => Ok(
match wallet_core
.storage()
.key_chain()
.pub_account_signing_key(id)
{
Some(_) => crate::signing::AccountSigner::Local(id),
None => crate::signing::AccountSigner::Foreign,
},
),
AccountIdWithPrivacy::Private(_) => {
anyhow::bail!("Private accounts not supported as recipients here")
}
}
}
}
impl FromStr for CliAccountMention {

View File

@ -131,22 +131,25 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
balance_a,
balance_b,
} => {
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
let user_holding_lp = user_holding_lp.resolve(wallet_core.storage())?;
match (user_holding_a, user_holding_b, user_holding_lp) {
let a_id = user_holding_a.resolve(wallet_core.storage())?;
let b_id = user_holding_b.resolve(wallet_core.storage())?;
let lp_id = user_holding_lp.resolve(wallet_core.storage())?;
match (a_id, b_id, lp_id) {
(
AccountIdWithPrivacy::Public(user_holding_a),
AccountIdWithPrivacy::Public(user_holding_b),
AccountIdWithPrivacy::Public(user_holding_lp),
AccountIdWithPrivacy::Public(a),
AccountIdWithPrivacy::Public(b),
AccountIdWithPrivacy::Public(lp),
) => {
Amm(wallet_core)
.send_new_definition(
user_holding_a,
user_holding_b,
user_holding_lp,
a,
b,
lp,
balance_a,
balance_b,
&user_holding_a,
&user_holding_b,
&user_holding_lp,
)
.await?;
@ -165,20 +168,22 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
min_amount_out,
token_definition,
} => {
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
match (user_holding_a, user_holding_b) {
let a_id = user_holding_a.resolve(wallet_core.storage())?;
let b_id = user_holding_b.resolve(wallet_core.storage())?;
match (a_id, b_id) {
(
AccountIdWithPrivacy::Public(user_holding_a),
AccountIdWithPrivacy::Public(user_holding_b),
AccountIdWithPrivacy::Public(a),
AccountIdWithPrivacy::Public(b),
) => {
Amm(wallet_core)
.send_swap_exact_input(
user_holding_a,
user_holding_b,
a,
b,
amount_in,
min_amount_out,
token_definition,
&user_holding_a,
&user_holding_b,
)
.await?;
@ -197,20 +202,22 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
max_amount_in,
token_definition,
} => {
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
match (user_holding_a, user_holding_b) {
let a_id = user_holding_a.resolve(wallet_core.storage())?;
let b_id = user_holding_b.resolve(wallet_core.storage())?;
match (a_id, b_id) {
(
AccountIdWithPrivacy::Public(user_holding_a),
AccountIdWithPrivacy::Public(user_holding_b),
AccountIdWithPrivacy::Public(a),
AccountIdWithPrivacy::Public(b),
) => {
Amm(wallet_core)
.send_swap_exact_output(
user_holding_a,
user_holding_b,
a,
b,
exact_amount_out,
max_amount_in,
token_definition,
&user_holding_a,
&user_holding_b,
)
.await?;
@ -230,23 +237,25 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
max_amount_a,
max_amount_b,
} => {
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
let user_holding_lp = user_holding_lp.resolve(wallet_core.storage())?;
match (user_holding_a, user_holding_b, user_holding_lp) {
let a_id = user_holding_a.resolve(wallet_core.storage())?;
let b_id = user_holding_b.resolve(wallet_core.storage())?;
let lp_id = user_holding_lp.resolve(wallet_core.storage())?;
match (a_id, b_id, lp_id) {
(
AccountIdWithPrivacy::Public(user_holding_a),
AccountIdWithPrivacy::Public(user_holding_b),
AccountIdWithPrivacy::Public(user_holding_lp),
AccountIdWithPrivacy::Public(a),
AccountIdWithPrivacy::Public(b),
AccountIdWithPrivacy::Public(lp),
) => {
Amm(wallet_core)
.send_add_liquidity(
user_holding_a,
user_holding_b,
user_holding_lp,
a,
b,
lp,
min_amount_lp,
max_amount_a,
max_amount_b,
&user_holding_a,
&user_holding_b,
)
.await?;
@ -266,23 +275,24 @@ impl WalletSubcommand for AmmProgramAgnosticSubcommand {
min_amount_a,
min_amount_b,
} => {
let user_holding_a = user_holding_a.resolve(wallet_core.storage())?;
let user_holding_b = user_holding_b.resolve(wallet_core.storage())?;
let user_holding_lp = user_holding_lp.resolve(wallet_core.storage())?;
match (user_holding_a, user_holding_b, user_holding_lp) {
let a_id = user_holding_a.resolve(wallet_core.storage())?;
let b_id = user_holding_b.resolve(wallet_core.storage())?;
let lp_id = user_holding_lp.resolve(wallet_core.storage())?;
match (a_id, b_id, lp_id) {
(
AccountIdWithPrivacy::Public(user_holding_a),
AccountIdWithPrivacy::Public(user_holding_b),
AccountIdWithPrivacy::Public(user_holding_lp),
AccountIdWithPrivacy::Public(a),
AccountIdWithPrivacy::Public(b),
AccountIdWithPrivacy::Public(lp),
) => {
Amm(wallet_core)
.send_remove_liquidity(
user_holding_a,
user_holding_b,
user_holding_lp,
a,
b,
lp,
balance_lp,
min_amount_a,
min_amount_b,
&user_holding_lp,
)
.await?;

View File

@ -91,13 +91,13 @@ impl WalletSubcommand for AtaSubcommand {
owner,
token_definition,
} => {
let owner = owner.resolve(wallet_core.storage())?;
let owner_resolved = owner.resolve(wallet_core.storage())?;
let definition_id = token_definition;
match owner {
match owner_resolved {
AccountIdWithPrivacy::Public(owner_id) => {
Ata(wallet_core)
.send_create(owner_id, definition_id)
.send_create(owner_id, definition_id, &owner)
.await?;
Ok(SubcommandReturnValue::Empty)
}
@ -127,14 +127,14 @@ impl WalletSubcommand for AtaSubcommand {
to,
amount,
} => {
let from = from.resolve(wallet_core.storage())?;
let from_resolved = from.resolve(wallet_core.storage())?;
let definition_id = token_definition;
let to_id = to;
match from {
match from_resolved {
AccountIdWithPrivacy::Public(from_id) => {
Ata(wallet_core)
.send_transfer(from_id, definition_id, to_id, amount)
.send_transfer(from_id, definition_id, to_id, amount, &from)
.await?;
Ok(SubcommandReturnValue::Empty)
}
@ -163,13 +163,13 @@ impl WalletSubcommand for AtaSubcommand {
token_definition,
amount,
} => {
let holder = holder.resolve(wallet_core.storage())?;
let holder_resolved = holder.resolve(wallet_core.storage())?;
let definition_id = token_definition;
match holder {
match holder_resolved {
AccountIdWithPrivacy::Public(holder_id) => {
Ata(wallet_core)
.send_burn(holder_id, definition_id, amount)
.send_burn(holder_id, definition_id, amount, &holder)
.await?;
Ok(SubcommandReturnValue::Empty)
}

View File

@ -73,7 +73,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
}
AccountIdWithPrivacy::Private(account_id) => {
let (tx_hash, secret) = NativeTokenTransfer(wallet_core)
.register_account_private(account_id)
.register_account_private(account_id, &None)
.await?;
println!("Transaction hash is {tx_hash}");
@ -151,6 +151,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
from,
to,
amount,
from_mention: from_account,
},
)
}
@ -175,6 +176,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
to_vpk,
to_identifier,
amount,
from_mention: from_account,
},
)
}
@ -247,6 +249,8 @@ pub enum NativeTokenTransferProgramSubcommandShielded {
/// amount - amount of balance to move.
#[arg(long)]
amount: u128,
#[arg(skip)]
from_mention: CliAccountMention,
},
/// Send native token transfer from `from` to `to` for `amount`.
///
@ -267,6 +271,8 @@ pub enum NativeTokenTransferProgramSubcommandShielded {
/// amount - amount of balance to move.
#[arg(long)]
amount: u128,
#[arg(skip)]
from_mention: CliAccountMention,
},
}
@ -318,7 +324,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
match self {
Self::PrivateOwned { from, to, amount } => {
let (tx_hash, [secret_from, secret_to]) = NativeTokenTransfer(wallet_core)
.send_private_transfer_to_owned_account(from, to, amount)
.send_private_transfer_to_owned_account(from, to, amount, &None)
.await?;
println!("Transaction hash is {tx_hash}");
@ -363,6 +369,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
to_vpk,
to_identifier.unwrap_or_else(rand::random),
amount,
&None,
)
.await?;
@ -393,9 +400,9 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
Self::ShieldedOwned { from, to, amount } => {
Self::ShieldedOwned { from, to, amount, from_mention } => {
let (tx_hash, secret) = NativeTokenTransfer(wallet_core)
.send_shielded_transfer(from, to, amount)
.send_shielded_transfer(from, to, amount, &from_mention)
.await?;
println!("Transaction hash is {tx_hash}");
@ -421,6 +428,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
to_vpk,
to_identifier,
amount,
from_mention,
} => {
let to_npk_res = hex::decode(to_npk)?;
let mut to_npk = [0; 32];
@ -440,6 +448,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
to_vpk,
to_identifier.unwrap_or_else(rand::random),
amount,
&from_mention,
)
.await?;
@ -467,7 +476,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
}
Self::Deshielded { from, to, amount } => {
let (tx_hash, secret) = NativeTokenTransfer(wallet_core)
.send_deshielded_transfer(from, to, amount)
.send_deshielded_transfer(from, to, amount, &None)
.await?;
println!("Transaction hash is {tx_hash}");

View File

@ -114,16 +114,18 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
name,
total_supply,
} => {
let def_mention = definition_account_id.clone();
let sup_mention = supply_account_id.clone();
let definition_account_id = definition_account_id.resolve(wallet_core.storage())?;
let supply_account_id = supply_account_id.resolve(wallet_core.storage())?;
let underlying_subcommand = match (definition_account_id, supply_account_id) {
(
AccountIdWithPrivacy::Public(definition_account_id),
AccountIdWithPrivacy::Public(supply_account_id),
AccountIdWithPrivacy::Public(_),
AccountIdWithPrivacy::Public(_),
) => TokenProgramSubcommand::Create(
CreateNewTokenProgramSubcommand::NewPublicDefPublicSupp {
definition_account_id,
supply_account_id,
definition_account_id: def_mention,
supply_account_id: sup_mention,
name,
total_supply,
},
@ -173,6 +175,8 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
to_identifier,
amount,
} => {
let from_mention = from.clone();
let to_mention = to.clone();
let from = from.resolve(wallet_core.storage())?;
let to = to
.map(|account_mention| account_mention.resolve(wallet_core.storage()))
@ -192,11 +196,11 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
anyhow::bail!("List of public keys is uncomplete");
}
(Some(to), None, None) => match (from, to) {
(AccountIdWithPrivacy::Public(from), AccountIdWithPrivacy::Public(to)) => {
(AccountIdWithPrivacy::Public(_), AccountIdWithPrivacy::Public(_)) => {
TokenProgramSubcommand::Public(
TokenProgramSubcommandPublic::TransferToken {
sender_account_id: from,
recipient_account_id: to,
sender_account_id: from_mention,
recipient_account_id: to_mention.expect("matched Some branch"),
balance_to_move: amount,
},
)
@ -259,15 +263,16 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
holder,
amount,
} => {
let holder_mention = holder.clone();
let definition = definition.resolve(wallet_core.storage())?;
let holder = holder.resolve(wallet_core.storage())?;
let underlying_subcommand = match (definition, holder) {
(
AccountIdWithPrivacy::Public(definition),
AccountIdWithPrivacy::Public(holder),
AccountIdWithPrivacy::Public(_),
) => TokenProgramSubcommand::Public(TokenProgramSubcommandPublic::BurnToken {
definition_account_id: definition,
holder_account_id: holder,
holder_account_id: holder_mention,
amount,
}),
(
@ -312,6 +317,8 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
holder_identifier,
amount,
} => {
let def_mention = definition.clone();
let hol_mention = holder.clone();
let definition = definition.resolve(wallet_core.storage())?;
let holder = holder
.map(|account_mention| account_mention.resolve(wallet_core.storage()))
@ -332,12 +339,12 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
}
(Some(holder), None, None) => match (definition, holder) {
(
AccountIdWithPrivacy::Public(definition),
AccountIdWithPrivacy::Public(holder),
AccountIdWithPrivacy::Public(_),
AccountIdWithPrivacy::Public(_),
) => TokenProgramSubcommand::Public(
TokenProgramSubcommandPublic::MintToken {
definition_account_id: definition,
holder_account_id: holder,
definition_account_id: def_mention,
holder_account_id: hol_mention.expect("matched Some branch"),
amount,
},
),
@ -430,9 +437,9 @@ pub enum TokenProgramSubcommandPublic {
// Transfer tokens using the token program
TransferToken {
#[arg(short, long)]
sender_account_id: AccountId,
sender_account_id: CliAccountMention,
#[arg(short, long)]
recipient_account_id: AccountId,
recipient_account_id: CliAccountMention,
#[arg(short, long)]
balance_to_move: u128,
},
@ -441,16 +448,16 @@ pub enum TokenProgramSubcommandPublic {
#[arg(short, long)]
definition_account_id: AccountId,
#[arg(short, long)]
holder_account_id: AccountId,
holder_account_id: CliAccountMention,
#[arg(short, long)]
amount: u128,
},
// Transfer tokens using the token program
MintToken {
#[arg(short, long)]
definition_account_id: AccountId,
definition_account_id: CliAccountMention,
#[arg(short, long)]
holder_account_id: AccountId,
holder_account_id: CliAccountMention,
#[arg(short, long)]
amount: u128,
},
@ -620,9 +627,9 @@ pub enum CreateNewTokenProgramSubcommand {
/// Definition - public, supply - public.
NewPublicDefPublicSupp {
#[arg(short, long)]
definition_account_id: AccountId,
definition_account_id: CliAccountMention,
#[arg(short, long)]
supply_account_id: AccountId,
supply_account_id: CliAccountMention,
#[arg(short, long)]
name: String,
#[arg(short, long)]
@ -680,11 +687,18 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
recipient_account_id,
balance_to_move,
} => {
let sender = sender_account_id.resolve(wallet_core.storage())?;
let recipient = recipient_account_id.resolve(wallet_core.storage())?;
let (AccountIdWithPrivacy::Public(sender_id), AccountIdWithPrivacy::Public(recipient_id)) = (sender, recipient) else {
anyhow::bail!("Only public accounts supported for token transfer");
};
Token(wallet_core)
.send_transfer_transaction(
sender_account_id,
recipient_account_id,
sender_id,
recipient_id,
balance_to_move,
&sender_account_id,
&recipient_account_id,
)
.await?;
Ok(SubcommandReturnValue::Empty)
@ -694,8 +708,12 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
holder_account_id,
amount,
} => {
let holder = holder_account_id.resolve(wallet_core.storage())?;
let AccountIdWithPrivacy::Public(holder_id) = holder else {
anyhow::bail!("Only public holder account supported for token burn");
};
Token(wallet_core)
.send_burn_transaction(definition_account_id, holder_account_id, amount)
.send_burn_transaction(definition_account_id, holder_id, amount, &holder_account_id)
.await?;
Ok(SubcommandReturnValue::Empty)
}
@ -704,8 +722,13 @@ impl WalletSubcommand for TokenProgramSubcommandPublic {
holder_account_id,
amount,
} => {
let definition = definition_account_id.resolve(wallet_core.storage())?;
let holder = holder_account_id.resolve(wallet_core.storage())?;
let (AccountIdWithPrivacy::Public(def_id), AccountIdWithPrivacy::Public(holder_id)) = (definition, holder) else {
anyhow::bail!("Only public accounts supported for token mint");
};
Token(wallet_core)
.send_mint_transaction(definition_account_id, holder_account_id, amount)
.send_mint_transaction(def_id, holder_id, amount, &definition_account_id, &holder_account_id)
.await?;
Ok(SubcommandReturnValue::Empty)
}
@ -1307,12 +1330,19 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
name,
total_supply,
} => {
let definition = definition_account_id.resolve(wallet_core.storage())?;
let supply = supply_account_id.resolve(wallet_core.storage())?;
let (AccountIdWithPrivacy::Public(def_id), AccountIdWithPrivacy::Public(sup_id)) = (definition, supply) else {
anyhow::bail!("Only public accounts supported for new token definition");
};
Token(wallet_core)
.send_new_definition(
definition_account_id,
supply_account_id,
def_id,
sup_id,
name,
total_supply,
&definition_account_id,
&supply_account_id,
)
.await?;
Ok(SubcommandReturnValue::Empty)

View File

@ -22,70 +22,6 @@ pub fn read_pin() -> anyhow::Result<zeroize::Zeroizing<String>> {
/// Read the mnemonic phrase without echoing it.
///
/// Exactly one of `id` or `label` must be `Some`. If `id` is provided it is
/// returned as-is; if `label` is provided it is resolved via
/// [`resolve_account_label`]. Any other combination returns an error.
pub fn resolve_id_or_label(
id: Option<String>,
label: Option<String>,
labels: &HashMap<String, Label>,
user_data: &NSSAUserData,
key_path: Option<&str>,
) -> Result<String> {
match (id, label, key_path) {
(Some(id), None, None) => Ok(id),
(None, Some(label), None) => resolve_account_label(&label, labels, user_data),
(None, None, Some(key_path)) => resolve_keycard_id(key_path),
_ => anyhow::bail!("provide exactly one of account id, account label or keycard path"),
}
}
pub fn resolve_keycard_id(key_path: &str) -> Result<String> {
let pin = read_pin()?;
KeycardWallet::get_public_account_id_for_path_with_connect(&pin, key_path)
.map_err(anyhow::Error::from)
}
/// Resolve an account label to its full `Privacy/id` string representation.
///
/// Looks up the label in the labels map and determines whether the account is
/// public or private by checking the user data key trees.
pub fn resolve_account_label(
label: &str,
labels: &HashMap<String, Label>,
user_data: &NSSAUserData,
) -> Result<String> {
let account_id_str = labels
.iter()
.find(|(_, l)| l.to_string() == label)
.map(|(k, _)| k.clone())
.ok_or_else(|| anyhow::anyhow!("No account found with label '{label}'"))?;
let account_id: nssa::AccountId = account_id_str.parse()?;
let privacy = if user_data
.public_key_tree
.account_id_map
.contains_key(&account_id)
|| user_data
.default_pub_account_signing_keys
.contains_key(&account_id)
{
"Public"
} else if user_data
.private_key_tree
.account_id_map
.contains_key(&account_id)
|| user_data
.default_user_private_accounts
.contains_key(&account_id)
{
"Private"
} else {
anyhow::bail!("Account with label '{label}' not found in wallet");
};
Ok(format!("{privacy}/{account_id_str}"))
/// Checks `KEYCARD_MNEMONIC` first for non-interactive callers. Falls back to
/// a TTY prompt so the phrase never appears in argv, shell history, or `ps`.
pub fn read_mnemonic() -> anyhow::Result<zeroize::Zeroizing<String>> {

View File

@ -28,7 +28,6 @@ use nssa_core::{
program::InstructionData,
};
pub use privacy_preserving_tx::PrivacyPreservingAccount;
use pyo3::exceptions::PyRuntimeError;
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};
use storage::Storage;
use tokio::io::AsyncWriteExt as _;
@ -575,38 +574,41 @@ impl WalletCore {
let mut pre_states = acc_manager.pre_states();
let keycard_account = if let Some(key_path_str) = key_path.as_deref() {
let account_id = {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?;
KeycardWallet::get_public_account_id_for_path_with_connect(&pin, key_path_str)?
};
let (acc_id, _) =
parse_addr_with_privacy_prefix(&account_id).expect("Valid parsing of account id");
let account_id: AccountId = acc_id.parse().expect("Expect a valid Account Id");
let (keycard_account, keycard_pin, keycard_path) = if let Some(key_path_str) = key_path.as_deref() {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?;
let account_id_str =
KeycardWallet::get_public_account_id_for_path_with_connect(&pin, key_path_str)?;
let account_id: AccountId =
match account_id_str.parse::<AccountIdWithPrivacy>().expect("Valid parsing of account id") {
AccountIdWithPrivacy::Public(id) | AccountIdWithPrivacy::Private(id) => id,
};
let account = self
.get_account_public(account_id)
.await
.expect("Expect valid account");
Some(AccountWithMetadata {
account,
is_authorized: true,
account_id,
})
let pin_str = pin.as_str().to_owned();
(
Some(AccountWithMetadata {
account,
is_authorized: true,
account_id,
}),
Some(pin_str),
Some(key_path_str.to_owned()),
)
} else {
None
(None, None, None)
};
let mut nonces: Vec<Nonce> = acc_manager.public_account_nonces().into_iter().collect();
let mut account_ids: Vec<AccountId> = acc_manager.public_account_ids();
let mut visibility_mask = acc_manager.visibility_mask().to_vec();
if let Some(acc) = keycard_account.as_ref() {
if acc_manager.public_account_ids().contains(&acc.account_id) {
if let Some(pre) = pre_states
@ -619,7 +621,6 @@ impl WalletCore {
} else {
nonces.push(acc.account.nonce);
account_ids.push(acc.account_id);
visibility_mask.push(0);
pre_states.push(acc.clone());
}
}
@ -652,12 +653,35 @@ impl WalletCore {
)
.unwrap();
let witness_set =
let witness_set = if let (Some(pin), Some(path)) =
(keycard_pin.as_deref(), keycard_path.as_deref())
{
let hash = message.hash();
let local_auth = acc_manager.public_account_auth();
let mut sigs: Vec<(Signature, PublicKey)> = local_auth
.iter()
.map(|&key| (Signature::new(key, &hash), PublicKey::new_from_private_key(key)))
.collect();
let keycard_sig = pyo3::Python::with_gil(|py| {
let mut ctx = crate::signing::KeycardSessionContext::new(pin);
let result = ctx
.get_or_connect(py)
.and_then(|w| w.sign_message_for_path(py, path, &hash));
ctx.close(py);
result
})
.map_err(ExecutionFailureKind::KeycardError)?;
sigs.push(keycard_sig);
nssa::privacy_preserving_transaction::witness_set::WitnessSet::from_raw_parts(
sigs, proof,
)
} else {
nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message(
&message,
proof,
&acc_manager.public_account_auth(),
);
)
};
let tx = PrivacyPreservingTransaction::new(message, witness_set);
let shared_secrets: Vec<_> = private_account_keys

View File

@ -1,17 +1,20 @@
use amm_core::{compute_liquidity_token_pda, compute_pool_pda, compute_vault_pda};
use common::{HashType, transaction::NSSATransaction};
use nssa::{AccountId, program::Program};
use nssa::{AccountId, program::Program, public_transaction::WitnessSet};
use pyo3::exceptions::PyRuntimeError;
use sequencer_service_rpc::RpcClient as _;
use token_core::TokenHolding;
use crate::{ExecutionFailureKind, WalletCore};
use crate::{
ExecutionFailureKind, WalletCore,
cli::CliAccountMention,
helperfunctions::read_pin,
signing::SigningGroups,
};
pub struct Amm<'wallet>(pub &'wallet WalletCore);
impl Amm<'_> {
#[expect(
clippy::too_many_arguments,
reason = "each parameter is distinct; grouping into a struct would add unnecessary indirection"
)]
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
pub async fn send_new_definition(
&self,
user_holding_a: AccountId,
@ -19,8 +22,9 @@ impl Amm<'_> {
user_holding_lp: AccountId,
balance_a: u128,
balance_b: u128,
key_path_a: Option<&str>,
key_path_b: Option<&str>,
a_mention: &CliAccountMention,
b_mention: &CliAccountMention,
lp_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let program = Program::amm();
let amm_program_id = Program::amm().id();
@ -64,106 +68,40 @@ impl Amm<'_> {
user_holding_lp,
];
// Check if LP has a stored key to determine if LP nonce is needed — before message creation
let lp_sk = self
.0
.storage
.user_data
.get_pub_account_signing_key(user_holding_lp);
let mut groups = SigningGroups::new();
groups
.add_sender(a_mention, user_holding_a, self.0)
.and_then(|()| groups.add_sender(b_mention, user_holding_b, self.0))
.and_then(|()| groups.add_recipient(lp_mention, user_holding_lp, self.0))
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let mut nonces = self
.0
.get_accounts_nonces(vec![user_holding_a, user_holding_b])
.await
let mut nonces = self.0.get_accounts_nonces(vec![user_holding_a, user_holding_b]).await
.map_err(ExecutionFailureKind::SequencerError)?;
if lp_sk.is_some() {
let lp_nonces = self
.0
.get_accounts_nonces(vec![user_holding_lp])
.await
if groups.signing_ids().contains(&user_holding_lp) {
let lp_nonces = self.0.get_accounts_nonces(vec![user_holding_lp]).await
.map_err(ExecutionFailureKind::SequencerError)?;
if lp_nonces.is_empty() {
nonces.push(nssa_core::account::Nonce(0));
} else {
nonces.extend(lp_nonces);
}
nonces.push(lp_nonces.into_iter().next().unwrap_or(nssa_core::account::Nonce(0)));
} else {
println!(
"Liquidity pool tokens receiver's account ({user_holding_lp}) private key not found in wallet. Proceeding with only liquidity provider's keys."
);
}
let message = nssa::public_transaction::Message::try_new(
program.id(),
account_ids,
nonces,
instruction,
)
.unwrap();
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction).unwrap();
let msg_hash = message.hash();
let pin = if key_path_a.is_some() || key_path_b.is_some() {
Some(crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?)
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
None
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let (sig_a, pk_a) = if let Some(kp) = key_path_a {
keycard_wallet::KeycardWallet::sign_message_for_path_with_connect(
pin.as_ref().unwrap(),
kp,
&msg_hash,
)?
} else {
let sk = self
.0
.storage
.user_data
.get_pub_account_signing_key(user_holding_a)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
(
nssa::Signature::new(sk, &msg_hash),
nssa::PublicKey::new_from_private_key(sk),
)
};
let (sig_b, pk_b) = if let Some(kp) = key_path_b {
keycard_wallet::KeycardWallet::sign_message_for_path_with_connect(
pin.as_ref().unwrap(),
kp,
&msg_hash,
)?
} else {
let sk = self
.0
.storage
.user_data
.get_pub_account_signing_key(user_holding_b)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
(
nssa::Signature::new(sk, &msg_hash),
nssa::PublicKey::new_from_private_key(sk),
)
};
let mut sigs = vec![sig_a, sig_b];
let mut pks = vec![pk_a, pk_b];
if let Some(sk_lp) = lp_sk {
sigs.push(nssa::Signature::new(sk_lp, &msg_hash));
pks.push(nssa::PublicKey::new_from_private_key(sk_lp));
}
let witness_set = nssa::public_transaction::WitnessSet::from_list(&message, &sigs, &pks)
.map_err(ExecutionFailureKind::TransactionBuildError)?;
let tx = nssa::PublicTransaction::new(message, witness_set);
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
@ -172,7 +110,7 @@ impl Amm<'_> {
.await?)
}
#[expect(clippy::too_many_arguments, reason = "To fix later")]
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
pub async fn send_swap_exact_input(
&self,
user_holding_a: AccountId,
@ -180,8 +118,8 @@ impl Amm<'_> {
swap_amount_in: u128,
min_amount_out: u128,
token_definition_id_in: AccountId,
user_holding_a_key_path: Option<&str>,
user_holding_b_key_path: Option<&str>,
a_mention: &CliAccountMention,
b_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let instruction = amm_core::Instruction::SwapExactInput {
swap_amount_in,
@ -222,59 +160,36 @@ impl Amm<'_> {
user_holding_b,
];
let account_id_auth = if definition_token_a_id == token_definition_id_in {
user_holding_a
let (account_id_auth, seller_mention) = if definition_token_a_id == token_definition_id_in {
(user_holding_a, a_mention)
} else if definition_token_b_id == token_definition_id_in {
user_holding_b
(user_holding_b, b_mention)
} else {
return Err(ExecutionFailureKind::AccountDataError(
token_definition_id_in,
));
return Err(ExecutionFailureKind::AccountDataError(token_definition_id_in));
};
let nonces = self
.0
.get_accounts_nonces(vec![account_id_auth])
.await
let mut groups = SigningGroups::new();
groups
.add_sender(seller_mention, account_id_auth, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(
program.id(),
account_ids,
nonces,
instruction,
)
.unwrap();
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction).unwrap();
let msg_hash = message.hash();
let seller_key_path = if definition_token_a_id == token_definition_id_in {
user_holding_a_key_path
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
user_holding_b_key_path
};
let witness_set = if let Some(kp) = seller_key_path {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?;
let (sig, pk) = keycard_wallet::KeycardWallet::sign_message_for_path_with_connect(
&pin, kp, &msg_hash,
)?;
nssa::public_transaction::WitnessSet::from_list(&message, &[sig], &[pk])
.map_err(ExecutionFailureKind::TransactionBuildError)?
} else {
let signing_key = self
.0
.storage
.user_data
.get_pub_account_signing_key(account_id_auth)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key])
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, witness_set);
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
@ -283,7 +198,7 @@ impl Amm<'_> {
.await?)
}
#[expect(clippy::too_many_arguments, reason = "To fix later")]
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
pub async fn send_swap_exact_output(
&self,
user_holding_a: AccountId,
@ -291,8 +206,8 @@ impl Amm<'_> {
exact_amount_out: u128,
max_amount_in: u128,
token_definition_id_in: AccountId,
user_holding_a_key_path: Option<&str>,
user_holding_b_key_path: Option<&str>,
a_mention: &CliAccountMention,
b_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let instruction = amm_core::Instruction::SwapExactOutput {
exact_amount_out,
@ -333,63 +248,36 @@ impl Amm<'_> {
user_holding_b,
];
let account_id_auth = if definition_token_a_id == token_definition_id_in {
user_holding_a
let (account_id_auth, seller_mention) = if definition_token_a_id == token_definition_id_in {
(user_holding_a, a_mention)
} else if definition_token_b_id == token_definition_id_in {
user_holding_b
(user_holding_b, b_mention)
} else {
return Err(ExecutionFailureKind::AccountDataError(
token_definition_id_in,
));
return Err(ExecutionFailureKind::AccountDataError(token_definition_id_in));
};
let nonces = self
.0
.get_accounts_nonces(vec![account_id_auth])
.await
let mut groups = SigningGroups::new();
groups
.add_sender(seller_mention, account_id_auth, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(
program.id(),
account_ids,
nonces,
instruction,
)
.unwrap();
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction).unwrap();
let msg_hash = message.hash();
let witness_set = if let (Some(kp_a), Some(kp_b)) =
(user_holding_a_key_path, user_holding_b_key_path)
{
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?;
let (sig_1, pk_1) = keycard_wallet::KeycardWallet::sign_message_for_path_with_connect(
&pin, kp_a, &msg_hash,
)?;
let (sig_2, pk_2) = keycard_wallet::KeycardWallet::sign_message_for_path_with_connect(
&pin, kp_b, &msg_hash,
)?;
nssa::public_transaction::WitnessSet::from_list(
&message,
&[sig_1, sig_2],
&[pk_1, pk_2],
)
.map_err(ExecutionFailureKind::TransactionBuildError)?
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
let signing_key = self
.0
.storage
.user_data
.get_pub_account_signing_key(account_id_auth)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key])
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, witness_set);
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
@ -398,10 +286,7 @@ impl Amm<'_> {
.await?)
}
#[expect(
clippy::too_many_arguments,
reason = "each parameter is distinct; grouping into a struct would add unnecessary indirection"
)]
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
pub async fn send_add_liquidity(
&self,
user_holding_a: AccountId,
@ -410,8 +295,8 @@ impl Amm<'_> {
min_amount_liquidity: u128,
max_amount_to_add_token_a: u128,
max_amount_to_add_token_b: u128,
key_path_a: Option<&str>,
key_path_b: Option<&str>,
a_mention: &CliAccountMention,
b_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let instruction = amm_core::Instruction::AddLiquidity {
min_amount_liquidity,
@ -455,78 +340,29 @@ impl Amm<'_> {
user_holding_lp,
];
let nonces = self
.0
.get_accounts_nonces(vec![user_holding_a, user_holding_b])
.await
let mut groups = SigningGroups::new();
groups
.add_sender(a_mention, user_holding_a, self.0)
.and_then(|()| groups.add_sender(b_mention, user_holding_b, self.0))
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(
program.id(),
account_ids,
nonces,
instruction,
)
.unwrap();
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction).unwrap();
let msg_hash = message.hash();
let pin = if key_path_a.is_some() || key_path_b.is_some() {
Some(crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?)
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
None
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let (sig_a, pk_a) = if let Some(kp) = key_path_a {
keycard_wallet::KeycardWallet::sign_message_for_path_with_connect(
pin.as_ref().unwrap(),
kp,
&msg_hash,
)?
} else {
let sk = self
.0
.storage
.user_data
.get_pub_account_signing_key(user_holding_a)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
(
nssa::Signature::new(sk, &msg_hash),
nssa::PublicKey::new_from_private_key(sk),
)
};
let (sig_b, pk_b) = if let Some(kp) = key_path_b {
keycard_wallet::KeycardWallet::sign_message_for_path_with_connect(
pin.as_ref().unwrap(),
kp,
&msg_hash,
)?
} else {
let sk = self
.0
.storage
.user_data
.get_pub_account_signing_key(user_holding_b)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
(
nssa::Signature::new(sk, &msg_hash),
nssa::PublicKey::new_from_private_key(sk),
)
};
let witness_set = nssa::public_transaction::WitnessSet::from_list(
&message,
&[sig_a, sig_b],
&[pk_a, pk_b],
)
.map_err(ExecutionFailureKind::TransactionBuildError)?;
let tx = nssa::PublicTransaction::new(message, witness_set);
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
@ -535,10 +371,7 @@ impl Amm<'_> {
.await?)
}
#[expect(
clippy::too_many_arguments,
reason = "each parameter is distinct; grouping into a struct would add unnecessary indirection"
)]
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
pub async fn send_remove_liquidity(
&self,
user_holding_a: AccountId,
@ -547,7 +380,7 @@ impl Amm<'_> {
remove_liquidity_amount: u128,
min_amount_to_remove_token_a: u128,
min_amount_to_remove_token_b: u128,
key_path_lp: Option<&str>,
lp_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let instruction = amm_core::Instruction::RemoveLiquidity {
remove_liquidity_amount,
@ -591,44 +424,28 @@ impl Amm<'_> {
user_holding_lp,
];
let nonces = self
.0
.get_accounts_nonces(vec![user_holding_lp])
.await
let mut groups = SigningGroups::new();
groups
.add_sender(lp_mention, user_holding_lp, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(
program.id(),
account_ids,
nonces,
instruction,
)
.unwrap();
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction).unwrap();
let msg_hash = message.hash();
let witness_set = if let Some(kp) = key_path_lp {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?;
let (sig, pk) = keycard_wallet::KeycardWallet::sign_message_for_path_with_connect(
&pin, kp, &msg_hash,
)?;
nssa::public_transaction::WitnessSet::from_list(&message, &[sig], &[pk])
.map_err(ExecutionFailureKind::TransactionBuildError)?
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
let signing_key_lp = self
.0
.storage
.user_data
.get_pub_account_signing_key(user_holding_lp)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key_lp])
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, witness_set);
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0

View File

@ -4,11 +4,18 @@ use ata_core::{compute_ata_seed, get_associated_token_account_id};
use common::{HashType, transaction::NSSATransaction};
use nssa::{
AccountId, privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program,
public_transaction::WitnessSet,
};
use nssa_core::SharedSecretKey;
use pyo3::exceptions::PyRuntimeError;
use sequencer_service_rpc::RpcClient as _;
use crate::{ExecutionFailureKind, PrivacyPreservingAccount, WalletCore};
use crate::{
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore,
cli::CliAccountMention,
helperfunctions::read_pin,
signing::SigningGroups,
};
pub struct Ata<'wallet>(pub &'wallet WalletCore);
@ -17,7 +24,7 @@ impl Ata<'_> {
&self,
owner_id: AccountId,
definition_id: AccountId,
key_path: Option<&str>,
owner_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let program = Program::ata();
let ata_program_id = program.id();
@ -27,54 +34,31 @@ impl Ata<'_> {
);
let account_ids = vec![owner_id, definition_id, ata_id];
let nonces = self
.0
.get_accounts_nonces(vec![owner_id])
.await
.map_err(ExecutionFailureKind::SequencerError)?;
let instruction = ata_core::Instruction::Create { ata_program_id };
let message = nssa::public_transaction::Message::try_new(
program.id(),
account_ids,
nonces,
instruction,
)?;
let mut groups = SigningGroups::new();
groups
.add_sender(owner_mention, owner_id, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let msg_hash = message.hash();
let witness_set = if let Some(kp) = key_path {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?;
let (sig, pk) = keycard_wallet::KeycardWallet::sign_message_for_path_with_connect(
&pin, kp, &msg_hash,
)?;
nssa::public_transaction::WitnessSet::from_list(&message, &[sig], &[pk])
.map_err(ExecutionFailureKind::TransactionBuildError)?
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction)?;
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
let Some(signing_key) = self
.0
.storage
.user_data
.get_pub_account_signing_key(owner_id)
else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key])
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self.0.sequencer_client.send_transaction(NSSATransaction::Public(tx)).await?)
}
pub async fn send_transfer(
@ -83,7 +67,7 @@ impl Ata<'_> {
definition_id: AccountId,
recipient_id: AccountId,
amount: u128,
key_path: Option<&str>,
owner_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let program = Program::ata();
let ata_program_id = program.id();
@ -93,57 +77,31 @@ impl Ata<'_> {
);
let account_ids = vec![owner_id, sender_ata_id, recipient_id];
let instruction = ata_core::Instruction::Transfer { ata_program_id, amount };
let nonces = self
.0
.get_accounts_nonces(vec![owner_id])
.await
let mut groups = SigningGroups::new();
groups
.add_sender(owner_mention, owner_id, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let instruction = ata_core::Instruction::Transfer {
ata_program_id,
amount,
};
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction)?;
let message = nssa::public_transaction::Message::try_new(
program.id(),
account_ids,
nonces,
instruction,
)?;
let msg_hash = message.hash();
let witness_set = if let Some(kp) = key_path {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?;
let (sig, pk) = keycard_wallet::KeycardWallet::sign_message_for_path_with_connect(
&pin, kp, &msg_hash,
)?;
nssa::public_transaction::WitnessSet::from_list(&message, &[sig], &[pk])
.map_err(ExecutionFailureKind::TransactionBuildError)?
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
let Some(signing_key) = self
.0
.storage
.user_data
.get_pub_account_signing_key(owner_id)
else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key])
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self.0.sequencer_client.send_transaction(NSSATransaction::Public(tx)).await?)
}
pub async fn send_burn(
@ -151,7 +109,7 @@ impl Ata<'_> {
owner_id: AccountId,
definition_id: AccountId,
amount: u128,
key_path: Option<&str>,
owner_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let program = Program::ata();
let ata_program_id = program.id();
@ -161,57 +119,31 @@ impl Ata<'_> {
);
let account_ids = vec![owner_id, holder_ata_id, definition_id];
let instruction = ata_core::Instruction::Burn { ata_program_id, amount };
let nonces = self
.0
.get_accounts_nonces(vec![owner_id])
.await
let mut groups = SigningGroups::new();
groups
.add_sender(owner_mention, owner_id, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let instruction = ata_core::Instruction::Burn {
ata_program_id,
amount,
};
let message = nssa::public_transaction::Message::try_new(program.id(), account_ids, nonces, instruction)?;
let message = nssa::public_transaction::Message::try_new(
program.id(),
account_ids,
nonces,
instruction,
)?;
let msg_hash = message.hash();
let witness_set = if let Some(kp) = key_path {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?;
let (sig, pk) = keycard_wallet::KeycardWallet::sign_message_for_path_with_connect(
&pin, kp, &msg_hash,
)?;
nssa::public_transaction::WitnessSet::from_list(&message, &[sig], &[pk])
.map_err(ExecutionFailureKind::TransactionBuildError)?
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
let Some(signing_key) = self
.0
.storage
.user_data
.get_pub_account_signing_key(owner_id)
else {
return Err(ExecutionFailureKind::KeyNotFoundError);
};
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key])
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?)
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self.0.sequencer_client.send_transaction(NSSATransaction::Public(tx)).await?)
}
pub async fn send_create_private_owner(
@ -230,18 +162,15 @@ impl Ata<'_> {
Program::serialize_instruction(instruction).expect("Instruction should serialize");
let accounts = vec![
PrivacyPreservingAccount::PrivateOwned(owner_id),
self.0
.resolve_private_account(owner_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
PrivacyPreservingAccount::Public(definition_id),
PrivacyPreservingAccount::Public(ata_id),
];
self.0
.send_privacy_preserving_tx(
accounts,
instruction_data,
&ata_with_token_dependency(),
&None,
)
.send_privacy_preserving_tx(accounts, instruction_data, &ata_with_token_dependency(), &None)
.await
.map(|(hash, mut secrets)| {
let secret = secrets.pop().expect("expected owner's secret");
@ -270,18 +199,15 @@ impl Ata<'_> {
Program::serialize_instruction(instruction).expect("Instruction should serialize");
let accounts = vec![
PrivacyPreservingAccount::PrivateOwned(owner_id),
self.0
.resolve_private_account(owner_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
PrivacyPreservingAccount::Public(sender_ata_id),
PrivacyPreservingAccount::Public(recipient_id),
];
self.0
.send_privacy_preserving_tx(
accounts,
instruction_data,
&ata_with_token_dependency(),
&None,
)
.send_privacy_preserving_tx(accounts, instruction_data, &ata_with_token_dependency(), &None)
.await
.map(|(hash, mut secrets)| {
let secret = secrets.pop().expect("expected owner's secret");
@ -309,18 +235,15 @@ impl Ata<'_> {
Program::serialize_instruction(instruction).expect("Instruction should serialize");
let accounts = vec![
PrivacyPreservingAccount::PrivateOwned(owner_id),
self.0
.resolve_private_account(owner_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
PrivacyPreservingAccount::Public(holder_ata_id),
PrivacyPreservingAccount::Public(definition_id),
];
self.0
.send_privacy_preserving_tx(
accounts,
instruction_data,
&ata_with_token_dependency(),
&None,
)
.send_privacy_preserving_tx(accounts, instruction_data, &ata_with_token_dependency(), &None)
.await
.map(|(hash, mut secrets)| {
let secret = secrets.pop().expect("expected owner's secret");

View File

@ -11,7 +11,7 @@ use sequencer_service_rpc::RpcClient as _;
use super::NativeTokenTransfer;
use crate::{
ExecutionFailureKind, cli::CliAccountMention, helperfunctions::read_pin,
signing::KeycardSessionContext,
signing::SigningGroups,
};
impl NativeTokenTransfer<'_> {
@ -23,30 +23,26 @@ impl NativeTokenTransfer<'_> {
from_mention: &CliAccountMention,
to_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let from_signer = from_mention.to_signer(self.0).map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string()))
})?;
let to_signer = to_mention.to_recipient_signer(self.0).map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string()))
})?;
let account_ids = vec![from, to];
let signing_ids: Vec<AccountId> = if to_signer.needs_signature() {
vec![from, to]
} else {
vec![from]
};
let mut groups = SigningGroups::new();
groups
.add_sender(from_mention, from, self.0)
.and_then(|()| groups.add_recipient(to_mention, to, self.0))
.map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
e.to_string(),
))
})?;
let program_id = Program::authenticated_transfer_program().id();
let nonces = self
.0
.get_accounts_nonces(signing_ids)
.get_accounts_nonces(groups.signing_ids())
.await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = Message::try_new(
program_id,
account_ids,
vec![from, to],
nonces,
AuthTransferInstruction::Transfer {
amount: balance_to_move,
@ -54,7 +50,7 @@ impl NativeTokenTransfer<'_> {
)
.map_err(ExecutionFailureKind::TransactionBuildError)?;
let pin = if from_mention.is_keycard() || to_mention.is_keycard() {
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
@ -67,30 +63,11 @@ impl NativeTokenTransfer<'_> {
String::new()
};
let witness_set = pyo3::Python::with_gil(|py| -> pyo3::PyResult<WitnessSet> {
let mut ctx = KeycardSessionContext::new(&pin);
let hash = message.hash();
let sigs = groups.sign_all(&message.hash(), &pin).map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string()))
})?;
let (from_sig, from_pk) = from_signer
.sign(self.0, &mut ctx, py, &hash)
.expect("from signer always produces a signature")
.map_err(|e| pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string()))?;
let sigs_and_keys = match to_signer
.sign(self.0, &mut ctx, py, &hash)
.transpose()
.map_err(|e| pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string()))?
{
Some((to_sig, to_pk)) => vec![(from_sig, from_pk), (to_sig, to_pk)],
None => vec![(from_sig, from_pk)],
};
ctx.close(py);
Ok(WitnessSet::from_raw_parts(sigs_and_keys))
})
.map_err(ExecutionFailureKind::KeycardError)?;
let tx = PublicTransaction::new(message, witness_set);
let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client
@ -119,11 +96,16 @@ impl NativeTokenTransfer<'_> {
)
.map_err(ExecutionFailureKind::TransactionBuildError)?;
let signer = account_mention.to_signer(self.0).map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string()))
})?;
let mut groups = SigningGroups::new();
groups
.add_sender(account_mention, from, self.0)
.map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
e.to_string(),
))
})?;
let pin = if account_mention.is_keycard() {
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
@ -136,21 +118,11 @@ impl NativeTokenTransfer<'_> {
String::new()
};
let witness_set = pyo3::Python::with_gil(|py| -> pyo3::PyResult<WitnessSet> {
let mut ctx = KeycardSessionContext::new(&pin);
let hash = message.hash();
let sigs = groups.sign_all(&message.hash(), &pin).map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string()))
})?;
let (sig, pk) = signer
.sign(self.0, &mut ctx, py, &hash)
.expect("account signer always produces a signature")
.map_err(|e| pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string()))?;
ctx.close(py);
Ok(WitnessSet::from_raw_parts(vec![(sig, pk)]))
})
.map_err(ExecutionFailureKind::KeycardError)?;
let tx = PublicTransaction::new(message, witness_set);
let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
.sequencer_client

View File

@ -3,7 +3,7 @@ use nssa::AccountId;
use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
use super::{NativeTokenTransfer, auth_transfer_preparation};
use crate::{ExecutionFailureKind, PrivacyPreservingAccount};
use crate::{ExecutionFailureKind, PrivacyPreservingAccount, cli::CliAccountMention};
impl NativeTokenTransfer<'_> {
pub async fn send_shielded_transfer(
@ -11,9 +11,10 @@ impl NativeTokenTransfer<'_> {
from: AccountId,
to: AccountId,
balance_to_move: u128,
from_key_path: &Option<String>,
from_mention: &CliAccountMention,
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
let key_path = from_mention.key_path().map(str::to_owned);
self.0
.send_privacy_preserving_tx_with_pre_check(
@ -26,7 +27,7 @@ impl NativeTokenTransfer<'_> {
instruction_data,
&program.into(),
tx_pre_check,
from_key_path,
&key_path,
)
.await
.map(|(resp, secrets)| {
@ -45,9 +46,10 @@ impl NativeTokenTransfer<'_> {
to_vpk: ViewingPublicKey,
to_identifier: Identifier,
balance_to_move: u128,
from_key_path: &Option<String>,
from_mention: &CliAccountMention,
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move);
let key_path = from_mention.key_path().map(str::to_owned);
self.0
.send_privacy_preserving_tx_with_pre_check(
@ -62,7 +64,7 @@ impl NativeTokenTransfer<'_> {
instruction_data,
&program.into(),
tx_pre_check,
from_key_path,
&key_path,
)
.await
.map(|(resp, secrets)| {

View File

@ -1,90 +1,56 @@
use common::{HashType, transaction::NSSATransaction};
use keycard_wallet::KeycardWallet;
use nssa::{AccountId, PublicKey, Signature, program::Program, public_transaction::WitnessSet};
use nssa::{AccountId, program::Program, public_transaction::WitnessSet};
use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey};
use pyo3::exceptions::PyRuntimeError;
use sequencer_service_rpc::RpcClient as _;
use token_core::Instruction;
use crate::{ExecutionFailureKind, PrivacyPreservingAccount, WalletCore};
use crate::{
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore,
cli::CliAccountMention,
helperfunctions::read_pin,
signing::SigningGroups,
};
pub struct Token<'wallet>(pub &'wallet WalletCore);
impl Token<'_> {
#[expect(clippy::too_many_arguments, reason = "each parameter is distinct")]
pub async fn send_new_definition(
&self,
definition_account_id: AccountId,
supply_account_id: AccountId,
name: String,
total_supply: u128,
definition_key_path: Option<&str>,
supply_key_path: Option<&str>,
definition_mention: &CliAccountMention,
supply_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let account_ids = vec![definition_account_id, supply_account_id];
let program_id = nssa::program::Program::token().id();
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
let nonces = self
.0
.get_accounts_nonces(account_ids.clone())
.await
let mut groups = SigningGroups::new();
groups
.add_sender(definition_mention, definition_account_id, self.0)
.and_then(|()| groups.add_sender(supply_mention, supply_account_id, self.0))
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(
program_id,
account_ids,
nonces,
instruction,
)
.unwrap();
let message = nssa::public_transaction::Message::try_new(program_id, account_ids, nonces, instruction).unwrap();
let msg_hash = message.hash();
let pin = if definition_key_path.is_some() || supply_key_path.is_some() {
Some(crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
e.to_string(),
))
})?)
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
None
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let (sig_def, pk_def) = if let Some(kp) = definition_key_path {
KeycardWallet::sign_message_for_path_with_connect(pin.as_ref().unwrap(), kp, &msg_hash)?
} else {
let sk = self
.0
.storage
.user_data
.get_pub_account_signing_key(definition_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
(
Signature::new(sk, &msg_hash),
PublicKey::new_from_private_key(sk),
)
};
let (sig_sup, pk_sup) = if let Some(kp) = supply_key_path {
KeycardWallet::sign_message_for_path_with_connect(pin.as_ref().unwrap(), kp, &msg_hash)?
} else {
let sk = self
.0
.storage
.user_data
.get_pub_account_signing_key(supply_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
(
Signature::new(sk, &msg_hash),
PublicKey::new_from_private_key(sk),
)
};
let witness_set = nssa::public_transaction::WitnessSet::from_list(
&message,
&[sig_def, sig_sup],
&[pk_def, pk_sup],
)
.map_err(ExecutionFailureKind::TransactionBuildError)?;
let tx = nssa::PublicTransaction::new(message, witness_set);
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
@ -108,7 +74,9 @@ impl Token<'_> {
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::Public(definition_account_id),
PrivacyPreservingAccount::PrivateOwned(supply_account_id),
self.0
.resolve_private_account(supply_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
],
instruction_data,
&Program::token().into(),
@ -138,7 +106,9 @@ impl Token<'_> {
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
self.0
.resolve_private_account(definition_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
PrivacyPreservingAccount::Public(supply_account_id),
],
instruction_data,
@ -169,8 +139,12 @@ impl Token<'_> {
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
PrivacyPreservingAccount::PrivateOwned(supply_account_id),
self.0
.resolve_private_account(definition_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
self.0
.resolve_private_account(supply_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
],
instruction_data,
&Program::token().into(),
@ -190,79 +164,36 @@ impl Token<'_> {
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
sender_key_path: Option<String>,
sender_mention: &CliAccountMention,
recipient_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let account_ids = vec![sender_account_id, recipient_account_id];
let program_id = nssa::program::Program::token().id();
let instruction = Instruction::Transfer {
amount_to_transfer: amount,
};
let instruction = Instruction::Transfer { amount_to_transfer: amount };
let mut nonces = self
.0
.get_accounts_nonces(vec![sender_account_id])
.await
let mut groups = SigningGroups::new();
groups
.add_sender(sender_mention, sender_account_id, self.0)
.and_then(|()| groups.add_recipient(recipient_mention, recipient_account_id, self.0))
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let private_keys = if sender_key_path.is_none() {
let mut private_keys = Vec::new();
let sender_sk = self
.0
.storage
.user_data
.get_pub_account_signing_key(sender_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
private_keys.push(sender_sk);
let message = nssa::public_transaction::Message::try_new(program_id, account_ids, nonces, instruction).unwrap();
if let Some(recipient_sk) = self
.0
.storage
.user_data
.get_pub_account_signing_key(recipient_account_id)
{
private_keys.push(recipient_sk);
let recipient_nonces = self
.0
.get_accounts_nonces(vec![recipient_account_id])
.await
.map_err(ExecutionFailureKind::SequencerError)?;
nonces.extend(recipient_nonces);
} else {
println!(
"Receiver's account ({recipient_account_id}) private key not found in wallet. Proceeding with only sender's key."
);
}
private_keys
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
Vec::new()
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let message = nssa::public_transaction::Message::try_new(
program_id,
account_ids,
nonces,
instruction,
)
.unwrap();
let witness_set = if let Some(sender_key_path) = sender_key_path {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
e.to_string(),
))
})?;
let (signature, public_key) = KeycardWallet::sign_message_for_path_with_connect(
&pin,
&sender_key_path,
&message.hash(),
)?;
WitnessSet::from_list(&message, &[signature], &[public_key])
.map_err(ExecutionFailureKind::TransactionBuildError)?
} else {
nssa::public_transaction::WitnessSet::for_message(&message, &private_keys)
};
let tx = nssa::PublicTransaction::new(message, witness_set);
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
@ -286,8 +217,12 @@ impl Token<'_> {
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(sender_account_id),
PrivacyPreservingAccount::PrivateOwned(recipient_account_id),
self.0
.resolve_private_account(sender_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
self.0
.resolve_private_account(recipient_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
],
instruction_data,
&Program::token().into(),
@ -319,7 +254,9 @@ impl Token<'_> {
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(sender_account_id),
self.0
.resolve_private_account(sender_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
PrivacyPreservingAccount::PrivateForeign {
npk: recipient_npk,
vpk: recipient_vpk,
@ -354,7 +291,9 @@ impl Token<'_> {
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(sender_account_id),
self.0
.resolve_private_account(sender_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
PrivacyPreservingAccount::Public(recipient_account_id),
],
instruction_data,
@ -376,7 +315,6 @@ impl Token<'_> {
sender_account_id: AccountId,
recipient_account_id: AccountId,
amount: u128,
sender_key_path: Option<String>,
) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> {
let instruction = Instruction::Transfer {
amount_to_transfer: amount,
@ -388,11 +326,13 @@ impl Token<'_> {
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::Public(sender_account_id),
PrivacyPreservingAccount::PrivateOwned(recipient_account_id),
self.0
.resolve_private_account(recipient_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
],
instruction_data,
&Program::token().into(),
&sender_key_path,
&None,
)
.await
.map(|(resp, secrets)| {
@ -442,129 +382,40 @@ impl Token<'_> {
})
}
pub async fn send_initialize_account(
&self,
definition_account: PrivacyPreservingAccount,
holder_account: PrivacyPreservingAccount,
key_path: &Option<String>,
) -> Result<(HashType, Vec<SharedSecretKey>), ExecutionFailureKind> {
let instruction = Instruction::InitializeAccount;
if definition_account.is_public() && holder_account.is_public() {
let PrivacyPreservingAccount::Public(definition_account_id) = definition_account else {
unreachable!()
};
let PrivacyPreservingAccount::Public(holder_account_id) = holder_account else {
unreachable!()
};
let nonces = self
.0
.get_accounts_nonces(vec![holder_account_id])
.await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(
Program::token().id(),
vec![definition_account_id, holder_account_id],
nonces,
instruction,
)
.expect("Instruction should serialize");
let witness_set = if let Some(key_path) = key_path {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<
pyo3::exceptions::PyRuntimeError,
_,
>(e.to_string()))
})?;
let (signature, pub_key) =
keycard_wallet::KeycardWallet::sign_message_for_path_with_connect(
&pin,
key_path,
&message.hash(),
)?;
nssa::public_transaction::WitnessSet::from_list(&message, &[signature], &[pub_key])
.map_err(ExecutionFailureKind::TransactionBuildError)?
} else {
let signing_key = self
.0
.storage
.user_data
.get_pub_account_signing_key(holder_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key])
};
let tx = nssa::PublicTransaction::new(message, witness_set);
let hash = self
.0
.sequencer_client
.send_transaction(NSSATransaction::Public(tx))
.await?;
Ok((hash, vec![]))
} else {
let instruction_data =
Program::serialize_instruction(instruction).expect("Instruction should serialize");
self.0
.send_privacy_preserving_tx(
vec![definition_account, holder_account],
instruction_data,
&Program::token().into(),
key_path,
)
.await
}
}
pub async fn send_burn_transaction(
&self,
definition_account_id: AccountId,
holder_account_id: AccountId,
amount: u128,
holder_key_path: Option<&str>,
holder_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let account_ids = vec![definition_account_id, holder_account_id];
let instruction = Instruction::Burn {
amount_to_burn: amount,
};
let nonces = self
.0
.get_accounts_nonces(vec![holder_account_id])
.await
let mut groups = SigningGroups::new();
groups
.add_sender(holder_mention, holder_account_id, self.0)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let message = nssa::public_transaction::Message::try_new(
Program::token().id(),
account_ids,
nonces,
instruction,
)
.expect("Instruction should serialize");
let message = nssa::public_transaction::Message::try_new(Program::token().id(), account_ids, nonces, instruction)
.expect("Instruction should serialize");
let msg_hash = message.hash();
let witness_set = if let Some(kp) = holder_key_path {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
e.to_string(),
))
})?;
let (sig, pk) = KeycardWallet::sign_message_for_path_with_connect(&pin, kp, &msg_hash)?;
nssa::public_transaction::WitnessSet::from_list(&message, &[sig], &[pk])
.map_err(ExecutionFailureKind::TransactionBuildError)?
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
let signing_key = self
.0
.storage
.user_data
.get_pub_account_signing_key(holder_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key])
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let tx = nssa::PublicTransaction::new(message, witness_set);
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
@ -588,8 +439,12 @@ impl Token<'_> {
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
PrivacyPreservingAccount::PrivateOwned(holder_account_id),
self.0
.resolve_private_account(definition_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
self.0
.resolve_private_account(holder_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
],
instruction_data,
&Program::token().into(),
@ -619,7 +474,9 @@ impl Token<'_> {
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
self.0
.resolve_private_account(definition_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
PrivacyPreservingAccount::Public(holder_account_id),
],
instruction_data,
@ -652,7 +509,9 @@ impl Token<'_> {
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::Public(definition_account_id),
PrivacyPreservingAccount::PrivateOwned(holder_account_id),
self.0
.resolve_private_account(holder_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
],
instruction_data,
&Program::token().into(),
@ -673,78 +532,37 @@ impl Token<'_> {
definition_account_id: AccountId,
holder_account_id: AccountId,
amount: u128,
definition_key_path: Option<&str>,
definition_mention: &CliAccountMention,
holder_mention: &CliAccountMention,
) -> Result<HashType, ExecutionFailureKind> {
let account_ids = vec![definition_account_id, holder_account_id];
let instruction = Instruction::Mint {
amount_to_mint: amount,
};
let mut nonces = self
.0
.get_accounts_nonces(vec![definition_account_id])
.await
let mut groups = SigningGroups::new();
groups
.add_sender(definition_mention, definition_account_id, self.0)
.and_then(|()| groups.add_recipient(holder_mention, holder_account_id, self.0))
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let nonces = self.0.get_accounts_nonces(groups.signing_ids()).await
.map_err(ExecutionFailureKind::SequencerError)?;
let private_keys = if definition_key_path.is_none() {
let mut private_keys = Vec::new();
let definition_sk = self
.0
.storage
.user_data
.get_pub_account_signing_key(definition_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
private_keys.push(definition_sk);
let message = nssa::public_transaction::Message::try_new(Program::token().id(), account_ids, nonces, instruction).unwrap();
if let Some(holder_sk) = self
.0
.storage
.user_data
.get_pub_account_signing_key(holder_account_id)
{
private_keys.push(holder_sk);
let holder_nonce: Vec<nssa_core::account::Nonce> = self
.0
.get_accounts_nonces(vec![holder_account_id])
.await
.map_err(ExecutionFailureKind::SequencerError)?;
nonces.extend(holder_nonce);
} else {
println!(
"Holder's account ({holder_account_id}) private key not found in wallet. Proceeding with only definition's key."
);
}
private_keys
let pin = if groups.needs_pin() {
read_pin()
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?
.as_str()
.to_owned()
} else {
Vec::new()
String::new()
};
let sigs = groups.sign_all(&message.hash(), &pin)
.map_err(|e| ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(e.to_string())))?;
let message = nssa::public_transaction::Message::try_new(
Program::token().id(),
account_ids,
nonces,
instruction,
)
.unwrap();
let witness_set = if let Some(definition_key_path) = definition_key_path {
let pin = crate::helperfunctions::read_pin().map_err(|e| {
ExecutionFailureKind::KeycardError(pyo3::PyErr::new::<PyRuntimeError, _>(
e.to_string(),
))
})?;
let (signature, public_key) = KeycardWallet::sign_message_for_path_with_connect(
&pin,
definition_key_path,
&message.hash(),
)?;
WitnessSet::from_list(&message, &[signature], &[public_key])
.map_err(ExecutionFailureKind::TransactionBuildError)?
} else {
nssa::public_transaction::WitnessSet::for_message(&message, &private_keys)
};
let tx = nssa::PublicTransaction::new(message, witness_set);
let tx = nssa::PublicTransaction::new(message, WitnessSet::from_raw_parts(sigs));
Ok(self
.0
@ -768,8 +586,12 @@ impl Token<'_> {
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
PrivacyPreservingAccount::PrivateOwned(holder_account_id),
self.0
.resolve_private_account(definition_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
self.0
.resolve_private_account(holder_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
],
instruction_data,
&Program::token().into(),
@ -801,7 +623,9 @@ impl Token<'_> {
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
self.0
.resolve_private_account(definition_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
PrivacyPreservingAccount::PrivateForeign {
npk: holder_npk,
vpk: holder_vpk,
@ -836,7 +660,9 @@ impl Token<'_> {
self.0
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::PrivateOwned(definition_account_id),
self.0
.resolve_private_account(definition_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
PrivacyPreservingAccount::Public(holder_account_id),
],
instruction_data,
@ -869,7 +695,9 @@ impl Token<'_> {
.send_privacy_preserving_tx(
vec![
PrivacyPreservingAccount::Public(definition_account_id),
PrivacyPreservingAccount::PrivateOwned(holder_account_id),
self.0
.resolve_private_account(holder_account_id)
.ok_or(ExecutionFailureKind::KeyNotFoundError)?,
],
instruction_data,
&Program::token().into(),
@ -922,4 +750,5 @@ impl Token<'_> {
(resp, first)
})
}
}

View File

@ -1,60 +1,116 @@
use anyhow::Result;
use keycard_wallet::{KeycardWallet, python_path};
use nssa::{AccountId, PublicKey, Signature};
use nssa::{AccountId, PrivateKey, PublicKey, Signature};
use pyo3::Python;
use crate::WalletCore;
use crate::{WalletCore, cli::CliAccountMention};
/// How a single account participates in signing a transaction.
/// Groups transaction signers by type to minimise Python GIL acquisition.
///
/// Created from [`crate::cli::CliAccountMention`] via `to_signer` / `to_recipient_signer`.
/// Used inside `Python::with_gil` blocks — does not cross async boundaries.
pub enum AccountSigner {
/// Account is in the local wallet; key is looked up from storage at sign time.
Local(AccountId),
/// Account is on a Keycard at the given BIP32 path.
Keycard(String),
/// Foreign account — no signature or nonce required.
Foreign,
/// Local signers are signed in pure Rust; all keycard signers share a single Python session
/// with one `connect` / `close_session` pair.
#[derive(Default)]
pub struct SigningGroups {
local: Vec<(AccountId, PrivateKey)>,
keycard: Vec<(AccountId, String)>,
}
impl AccountSigner {
impl SigningGroups {
#[must_use]
pub const fn needs_signature(&self) -> bool {
!matches!(self, Self::Foreign)
pub fn new() -> Self {
Self::default()
}
/// Sign `hash` and return `(Signature, PublicKey)`, or `None` for `Foreign`.
pub fn sign(
&self,
/// Add a sender. Keycard paths are queued for the hardware session; local accounts
/// have their signing key resolved eagerly. Errors if no key is found.
pub fn add_sender(
&mut self,
mention: &CliAccountMention,
account_id: AccountId,
wallet_core: &WalletCore,
ctx: &mut KeycardSessionContext,
py: Python<'_>,
hash: &[u8; 32],
) -> Option<Result<(Signature, PublicKey)>> {
match self {
Self::Local(id) => {
let key = wallet_core
.storage()
.key_chain()
.pub_account_signing_key(*id);
Some(key.map_or_else(
|| Err(anyhow::anyhow!("signing key not found for account {id}")),
|key| {
Ok((
Signature::new(key, hash),
PublicKey::new_from_private_key(key),
))
},
))
}
Self::Keycard(path) => Some(
ctx.get_or_connect(py)
.and_then(|w| w.sign_message_for_path(py, path, hash))
.map_err(anyhow::Error::from),
),
Self::Foreign => None,
) -> Result<()> {
if let CliAccountMention::KeyPath(path) = mention {
self.keycard.push((account_id, path.clone()));
return Ok(());
}
let key = wallet_core
.storage()
.key_chain()
.pub_account_signing_key(account_id)
.ok_or_else(|| anyhow::anyhow!("signing key not found for account {account_id}"))?
.clone();
self.local.push((account_id, key));
Ok(())
}
/// 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(())
}
/// 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()
}
/// 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)
}
}