mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-30 11:59:41 +00:00
addressed deferred comments
This commit is contained in:
parent
ada4bf3e0a
commit
b0593b34fb
3
.gitignore
vendored
3
.gitignore
vendored
@ -12,3 +12,6 @@ result
|
|||||||
wallet-ffi/wallet_ffi.h
|
wallet-ffi/wallet_ffi.h
|
||||||
bedrock_signing_key
|
bedrock_signing_key
|
||||||
integration_tests/configs/debug/
|
integration_tests/configs/debug/
|
||||||
|
venv/
|
||||||
|
keycard_wallet/python/__pycache__/
|
||||||
|
keycard_wallet/python/keycard-py/
|
||||||
|
|||||||
@ -60,6 +60,29 @@ Unset it when done:
|
|||||||
unset KEYCARD_PIN
|
unset KEYCARD_PIN
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Pairing password
|
||||||
|
|
||||||
|
The pairing password is used to establish a secure channel between the wallet and the card. It is set permanently on the card during `wallet keycard init` and must match on every subsequent re-pair.
|
||||||
|
|
||||||
|
The default password (`KeycardDefaultPairing`) is [recommended](https://docs.keycard.tech/en/developers/core) for most users. Wallet CLI allows advance users the flexibility to set their own pairing password.
|
||||||
|
|
||||||
|
To use a custom pairing password, set it before `init`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export KEYCARD_PAIRING_PASSWORD=my-custom-password
|
||||||
|
wallet keycard init
|
||||||
|
```
|
||||||
|
|
||||||
|
After a successful initializaation, subsequent commands (`connect`, transfers) use the cached pairing index and key — the pairing password is not needed again until the pairing is cleared.
|
||||||
|
|
||||||
|
**Important:** if you initialized with a custom password, `KEYCARD_PAIRING_PASSWORD` must be set in every session where re-pairing can occur (after `disconnect`, or on a new machine). If the env var is missing then wallet CLI will attempt to use the default password. As a result, pairing will fail.
|
||||||
|
|
||||||
|
Unset the pairing password variable when done:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unset KEYCARD_PAIRING_PASSWORD
|
||||||
|
```
|
||||||
|
|
||||||
## Keycard Commands
|
## Keycard Commands
|
||||||
|
|
||||||
### Keycard
|
### Keycard
|
||||||
@ -473,22 +496,34 @@ Transaction data is ...
|
|||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Tests for Keycard commands are in `keycard_wallet/tests/keycard_tests.sh`. Run from the repo root with a Keycard connected:
|
Tests for Keycard commands are in `keycard_wallet/tests/`.
|
||||||
|
|
||||||
|
| Test file | Description |
|
||||||
|
|---|---|
|
||||||
|
| `keycard_tests.sh` | Core Keycard wallet commands and `auth-transfer` commands |
|
||||||
|
| `keycard_tests_2.sh` | Tests Keycard wallet commands for `amma`, `token` and `ata` programs |
|
||||||
|
| `keycard_test_3.sh` | Demonstrates retrieving private account keys from keycard |
|
||||||
|
| `keycard_power_recovery_tests.sh` | Modified test file of `keycard_tests.sh` to test power recovery paths |
|
||||||
|
|
||||||
|
Run from the repo root with a Keycard connected:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash keycard_wallet/tests/keycard_tests.sh
|
bash keycard_wallet/tests/keycard_tests.sh
|
||||||
|
bash keycard_wallet/tests/keycard_tests_2.sh
|
||||||
|
bash keycard_wallet/tests/keycard_test_3.sh
|
||||||
|
bash keycard_wallet/tests/keycard_power_recovery_tests.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## SigningGroups
|
## SigningGroup
|
||||||
|
|
||||||
`SigningGroups` (`wallet/src/signing.rs`) partitions a transaction's signers into two buckets — local accounts and Keycard accounts. This ensures that Python GIL is only used at most once per transaction, regardless of how many Keycard accounts are involved.
|
`SigningGroup` (`wallet/src/signing.rs`) partitions a transaction's signers into two buckets — local accounts and Keycard accounts. This ensures that Python GIL is only used at most once per transaction, regardless of how many Keycard accounts are involved.
|
||||||
|
|
||||||
Local signers are resolved and signed in pure Rust. Keycard signers store only their BIP32 key path; all of them are signed inside a single Python session (`connect` / `close_session`) when `sign_all` is called. The command calls `needs_pin` to decide whether to prompt for a PIN before signing.
|
Local signers are resolved and signed in pure Rust. Keycard signers store only their BIP32 key path; all of them are signed inside a single Python session (`connect` / `close_session`) when `sign_all` is called. The command calls `needs_pin` to decide whether to prompt for a PIN before signing.
|
||||||
|
|
||||||
Foreign recipient accounts — those with no local key and no Keycard path — are silently skipped and require neither a signature nor a nonce.
|
Foreign recipient accounts — those with no local key and no Keycard path — are silently skipped and require neither a signature nor a nonce.
|
||||||
|
|
||||||
```
|
```
|
||||||
SigningGroups {
|
SigningGroup {
|
||||||
local: [(AccountId, PrivateKey)], // signed in pure Rust
|
local: [(AccountId, PrivateKey)], // signed in pure Rust
|
||||||
keycard: [(AccountId, BIP32Path)], // signed via a single Python/Keycard session
|
keycard: [(AccountId, BIP32Path)], // signed via a single Python/Keycard session
|
||||||
}
|
}
|
||||||
|
|||||||
134
keycard_tests.sh
134
keycard_tests.sh
@ -1,134 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Run wallet_with_keycard.sh first
|
|
||||||
|
|
||||||
source venv/bin/activate # Load the appropriate virtual environment
|
|
||||||
|
|
||||||
source venv/bin/activate
|
|
||||||
export KEYCARD_PIN=111111
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Keycard setup
|
|
||||||
# =============================================================================
|
|
||||||
echo "=== Test: wallet keycard available ==="
|
|
||||||
wallet keycard available
|
|
||||||
|
|
||||||
# Install a new mnemonic phrase to keycard
|
|
||||||
echo "Test: wallet keycard load"
|
|
||||||
export KEYCARD_MNEMONIC="fashion degree mountain wool question damp current pond grow dolphin chronic then"
|
|
||||||
wallet keycard load
|
|
||||||
unset KEYCARD_MNEMONIC
|
|
||||||
|
|
||||||
echo "Test: wallet auth-transfer init --account-id \"m/44'/60'/0'/0/0\""
|
|
||||||
wallet auth-transfer init --account-id "m/44'/60'/0'/0/0"
|
|
||||||
|
|
||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/0"
|
|
||||||
|
|
||||||
echo "Test: wallet pinata claim --to \"m/44'/60'/0'/0/0\""
|
|
||||||
wallet pinata claim --to "m/44'/60'/0'/0/0"
|
|
||||||
|
|
||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/0"
|
|
||||||
|
|
||||||
echo "Test: wallet auth-transfer init and send between two keycard accounts"
|
|
||||||
wallet auth-transfer init --account-id "m/44'/60'/0'/0/1"
|
|
||||||
wallet auth-transfer send --amount 40 --from "m/44'/60'/0'/0/0" --to "m/44'/60'/0'/0/1"
|
|
||||||
|
|
||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/0"
|
|
||||||
|
|
||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\""
|
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/1"
|
|
||||||
|
|
||||||
# Send from keycard account to a local wallet account
|
|
||||||
echo "Test: create local wallet account"
|
|
||||||
LOCAL_ACCOUNT_ID=$(wallet account new public 2>&1 | grep -oP '(?<=Public/)\S+')
|
|
||||||
echo "Created local account: Public/${LOCAL_ACCOUNT_ID}"
|
|
||||||
|
|
||||||
echo "Test: wallet auth-transfer init local account"
|
|
||||||
wallet auth-transfer init --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
|
||||||
|
|
||||||
|
|
||||||
echo "Test: wallet auth-transfer send from keycard to local account"
|
|
||||||
wallet auth-transfer send --amount 10 --from "m/44'/60'/0'/0/0" --to "Public/${LOCAL_ACCOUNT_ID}"
|
|
||||||
|
|
||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/0"
|
|
||||||
|
|
||||||
echo "Test: wallet account get --account-id \"Public/${LOCAL_ACCOUNT_ID}\""
|
|
||||||
wallet account get --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
|
||||||
|
|
||||||
# Create a local wallet account, fund it, and send to keycard account (co-signed: local key + keycard)
|
|
||||||
|
|
||||||
echo "Test: wallet auth-transfer send from local account to keycard account"
|
|
||||||
wallet auth-transfer send --amount 10 --from "Public/${LOCAL_ACCOUNT_ID}" --to "m/44'/60'/0'/0/1"
|
|
||||||
|
|
||||||
echo "Test: wallet account get --account-id \"Public/${LOCAL_ACCOUNT_ID}\""
|
|
||||||
wallet account get --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
|
||||||
|
|
||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\""
|
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/1"
|
|
||||||
|
|
||||||
# Send from keycard account to a local wallet account (foreign recipient — no signature needed)
|
|
||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
|
||||||
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
|
||||||
|
|
||||||
echo "Test: wallet auth-transfer send from keycard to local account"
|
|
||||||
wallet auth-transfer send --amount 10 --from "m/44'/60'/0'/0/0" --to "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
|
||||||
|
|
||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/0"
|
|
||||||
|
|
||||||
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 --account-id "m/44'/60'/0'/0/0"
|
|
||||||
echo "=== Test: account get path 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 "m/44'/60'/0'/0/0" \
|
|
||||||
--to-npk "55204e2934045b044f06d8222b454d46b54788f33c7dec4f6733d441703bb0e6" \
|
|
||||||
--to-vpk "02a8626b0c0ad9383c5678dad48c3969b4174fb377cdb03a6259648032c774cec8"
|
|
||||||
echo "Shielded auth-transfer sent"
|
|
||||||
|
|
||||||
sleep 15
|
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/0"
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Test (2): Deshielded auth-transfer — private account → keycard recipient
|
|
||||||
# A fresh private account is funded, then sends native tokens to keycard
|
|
||||||
# path 1. The private sender is handled by the ZK circuit; the keycard
|
|
||||||
# recipient does not sign — resolve() derives its account ID from the card.
|
|
||||||
# =============================================================================
|
|
||||||
echo ""
|
|
||||||
echo "=== Test (2): Deshielded auth-transfer: private account → keycard path 1 ==="
|
|
||||||
|
|
||||||
PRIV_SENDER=$(wallet account new private | grep -o 'Private/[^[:space:]]*' | head -1)
|
|
||||||
echo "Fresh private sender account: $PRIV_SENDER"
|
|
||||||
|
|
||||||
wallet auth-transfer init --account-id "$PRIV_SENDER"
|
|
||||||
|
|
||||||
echo "Test: wallet pinata claim to private sender"
|
|
||||||
wallet pinata claim --to "$PRIV_SENDER"
|
|
||||||
|
|
||||||
sleep 15
|
|
||||||
|
|
||||||
echo "priv-sender state after claim:"
|
|
||||||
wallet account get --account-id "$PRIV_SENDER"
|
|
||||||
|
|
||||||
wallet auth-transfer send \
|
|
||||||
--from "$PRIV_SENDER" \
|
|
||||||
--to "m/44'/60'/0'/0/1" \
|
|
||||||
--amount 5
|
|
||||||
echo "Deshielded transfer of 5: $PRIV_SENDER → keycard path 1"
|
|
||||||
|
|
||||||
sleep 15
|
|
||||||
|
|
||||||
echo "priv-sender state (balance should have decreased by 5):"
|
|
||||||
wallet account get --account-id "$PRIV_SENDER"
|
|
||||||
echo "Keycard path 1 state (balance should have increased by 5):"
|
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/1"
|
|
||||||
@ -8,10 +8,14 @@ from mnemonic import Mnemonic
|
|||||||
from keycard import constants
|
from keycard import constants
|
||||||
|
|
||||||
import keycard
|
import keycard
|
||||||
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
DEFAULT_PAIRING_PASSWORD = "KeycardDefaultPairing"
|
DEFAULT_PAIRING_PASSWORD = "KeycardDefaultPairing"
|
||||||
|
|
||||||
|
def _pairing_password() -> str:
|
||||||
|
return os.environ.get("KEYCARD_PAIRING_PASSWORD", DEFAULT_PAIRING_PASSWORD)
|
||||||
|
|
||||||
class KeycardWallet:
|
class KeycardWallet:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.card = KeyCard()
|
self.card = KeyCard()
|
||||||
@ -37,7 +41,7 @@ class KeycardWallet:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def initialize(self, pin: str) -> bool:
|
def initialize(self, pin: str, pairing_password: str | None = None) -> bool:
|
||||||
try:
|
try:
|
||||||
self.card.select()
|
self.card.select()
|
||||||
|
|
||||||
@ -45,14 +49,18 @@ class KeycardWallet:
|
|||||||
raise RuntimeError("Card is already initialized")
|
raise RuntimeError("Card is already initialized")
|
||||||
|
|
||||||
puk = ''.join(secrets.choice('0123456789') for _ in range(12))
|
puk = ''.join(secrets.choice('0123456789') for _ in range(12))
|
||||||
self.card.init(pin, puk, DEFAULT_PAIRING_PASSWORD)
|
self.card.init(pin, puk, pairing_password or _pairing_password())
|
||||||
print(f"Keycard PUK: {puk}")
|
print(f"Keycard PUK: {puk}")
|
||||||
print("Record this PUK and store it somewhere safe. It cannot be recovered.")
|
print("Record this PUK and store it somewhere safe. It cannot be recovered.")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise RuntimeError(f"Error initializing keycard: {e}") from e
|
raise RuntimeError(f"Error initializing keycard: {e}") from e
|
||||||
|
|
||||||
def setup_communication(self, pin: str, password = DEFAULT_PAIRING_PASSWORD) -> bool:
|
def _reconnect(self) -> None:
|
||||||
|
self.card = KeyCard()
|
||||||
|
self.card.select()
|
||||||
|
|
||||||
|
def _pair(self, pin: str, password: str) -> tuple[int, bytes]:
|
||||||
self.card.select()
|
self.card.select()
|
||||||
|
|
||||||
if not self.card.is_initialized:
|
if not self.card.is_initialized:
|
||||||
@ -70,14 +78,28 @@ class KeycardWallet:
|
|||||||
self.card.unpair(pairing_index)
|
self.card.unpair(pairing_index)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
raise RuntimeError(f"Error setting up communication: {e}") from e
|
raise RuntimeError(f"Error opening secure channel after fresh pair: {e}") from e
|
||||||
|
|
||||||
return True
|
return pairing_index, pairing_key
|
||||||
|
|
||||||
def get_pairing_data(self) -> tuple[int, bytes]:
|
def pair(self, pin: str, password: str | None = None) -> tuple[int, bytes]:
|
||||||
return (self.pairing_index, self.pairing_key)
|
password = password or _pairing_password()
|
||||||
|
try:
|
||||||
|
return self._pair(pin, password)
|
||||||
|
except TransportError as e:
|
||||||
|
print(f"Transport error during fresh pair ({e}), attempting card reset and retry...")
|
||||||
|
try:
|
||||||
|
self._reconnect()
|
||||||
|
result = self._pair(pin, password)
|
||||||
|
print("Retry succeeded after card reset.")
|
||||||
|
return result
|
||||||
|
except TransportError as e2:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Card lost power and did not recover after reset. "
|
||||||
|
"Try reseating the card in the reader."
|
||||||
|
) from e2
|
||||||
|
|
||||||
def setup_communication_with_pairing(self, pin: str, pairing_index: int, pairing_key: bytes) -> bool:
|
def _setup_communication_with_pairing(self, pin: str, pairing_index: int, pairing_key: bytes) -> bool:
|
||||||
self.card.select()
|
self.card.select()
|
||||||
|
|
||||||
if not self.card.is_initialized:
|
if not self.card.is_initialized:
|
||||||
@ -94,6 +116,22 @@ class KeycardWallet:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def setup_communication_with_pairing(self, pin: str, pairing_index: int, pairing_key: bytes) -> bool:
|
||||||
|
try:
|
||||||
|
return self._setup_communication_with_pairing(pin, pairing_index, pairing_key)
|
||||||
|
except TransportError as e:
|
||||||
|
print(f"Transport error during stored pairing ({e}), attempting card reset and retry...")
|
||||||
|
try:
|
||||||
|
self._reconnect()
|
||||||
|
result = self._setup_communication_with_pairing(pin, pairing_index, pairing_key)
|
||||||
|
print("Retry succeeded after card reset.")
|
||||||
|
return result
|
||||||
|
except TransportError as e2:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Card lost power and did not recover after reset. "
|
||||||
|
"Try reseating the card in the reader."
|
||||||
|
) from e2
|
||||||
|
|
||||||
def close_session(self) -> bool:
|
def close_session(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@ -56,10 +56,10 @@ impl KeycardWallet {
|
|||||||
.extract()
|
.extract()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_pairing_data(&self, py: Python<'_>) -> PyResult<(u8, Vec<u8>)> {
|
pub fn pair(&self, py: Python<'_>, pin: &str) -> PyResult<(u8, Vec<u8>)> {
|
||||||
self.instance
|
self.instance
|
||||||
.bind(py)
|
.bind(py)
|
||||||
.call_method0("get_pairing_data")?
|
.call_method1("pair", (pin,))?
|
||||||
.extract()
|
.extract()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,20 +96,11 @@ impl KeycardWallet {
|
|||||||
{
|
{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
self.setup_communication(py, pin)?;
|
let (index, key) = self.pair(py, pin)?;
|
||||||
if let Ok((index, key)) = self.get_pairing_data(py) {
|
save_pairing(&KeycardPairingData { index, key });
|
||||||
save_pairing(&KeycardPairingData { index, key });
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_communication(&self, py: Python<'_>, pin: &str) -> PyResult<bool> {
|
|
||||||
self.instance
|
|
||||||
.bind(py)
|
|
||||||
.call_method1("setup_communication", (pin,))?
|
|
||||||
.extract()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disconnect(&self, py: Python) -> PyResult<bool> {
|
pub fn disconnect(&self, py: Python) -> PyResult<bool> {
|
||||||
self.instance.bind(py).call_method0("disconnect")?.extract()
|
self.instance.bind(py).call_method0("disconnect")?.extract()
|
||||||
}
|
}
|
||||||
@ -269,19 +260,9 @@ impl KeycardWallet {
|
|||||||
) -> PyResult<PrivateKeyPair> {
|
) -> PyResult<PrivateKeyPair> {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
python_path::add_python_path(py)?;
|
python_path::add_python_path(py)?;
|
||||||
|
|
||||||
let wallet = Self::new(py)?;
|
let wallet = Self::new(py)?;
|
||||||
|
wallet.connect(py, pin)?;
|
||||||
let is_connected = wallet.setup_communication(py, pin)?;
|
|
||||||
|
|
||||||
if is_connected {
|
|
||||||
log::info!("\u{2705} Keycard is now connected to wallet.");
|
|
||||||
} else {
|
|
||||||
log::info!("\u{274c} Keycard is not connected to wallet.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = wallet.get_private_keys_for_path(py, path);
|
let result = wallet.get_private_keys_for_path(py, path);
|
||||||
|
|
||||||
drop(wallet.disconnect(py));
|
drop(wallet.disconnect(py));
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
|
|||||||
40
keycard_wallet/tests/force_unpower.py
Executable file
40
keycard_wallet/tests/force_unpower.py
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Forces the card in the first available reader into the unpowered state via
|
||||||
|
PC/SC SCARD_UNPOWER_CARD. Run immediately before a wallet command to simulate
|
||||||
|
the power-loss condition reported on some USB reader/driver combinations.
|
||||||
|
|
||||||
|
Either:
|
||||||
|
- pcscd re-powers the card on the next SCardConnect, so wallet
|
||||||
|
commands will succeed without triggering the retry path.
|
||||||
|
- the card stays unpowered, triggering TransportError
|
||||||
|
and exercising the retry wrapper in pair() / setup_communication_with_pairing().
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from smartcard.scard import (
|
||||||
|
SCardEstablishContext, SCardListReaders, SCardConnect, SCardDisconnect,
|
||||||
|
SCARD_SCOPE_USER, SCARD_SHARE_SHARED,
|
||||||
|
SCARD_PROTOCOL_T0, SCARD_PROTOCOL_T1,
|
||||||
|
SCARD_UNPOWER_CARD,
|
||||||
|
)
|
||||||
|
|
||||||
|
hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||||
|
hresult, reader_list = SCardListReaders(hcontext, [])
|
||||||
|
|
||||||
|
if not reader_list:
|
||||||
|
print("force_unpower: no readers found, skipping.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
hresult, hcard, _ = SCardConnect(
|
||||||
|
hcontext,
|
||||||
|
reader_list[0],
|
||||||
|
SCARD_SHARE_SHARED,
|
||||||
|
SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
|
||||||
|
)
|
||||||
|
|
||||||
|
if hresult != 0:
|
||||||
|
print(f"force_unpower: SCardConnect failed (hresult={hresult:#010x}), skipping.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
SCardDisconnect(hcard, SCARD_UNPOWER_CARD)
|
||||||
|
print("force_unpower: card powered down.")
|
||||||
129
keycard_wallet/tests/keycard_power_recovery_tests.sh
Executable file
129
keycard_wallet/tests/keycard_power_recovery_tests.sh
Executable file
@ -0,0 +1,129 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Power-recovery variant of keycard_tests.sh.
|
||||||
|
#
|
||||||
|
# Forces a card power cycle before each keycard-backed wallet command to verify
|
||||||
|
# commands survive mid-session power loss.
|
||||||
|
#
|
||||||
|
# On Linux: pcscd re-powers the card automatically on the next connection, so
|
||||||
|
# commands will succeed without triggering the retry path. This tests resilience
|
||||||
|
# to power cycles, not the retry wrapper itself.
|
||||||
|
#
|
||||||
|
# On Mac (affected driver): the card stays unpowered after force_unpower.py,
|
||||||
|
# triggering TransportError. You should see "Retry succeeded after card reset."
|
||||||
|
# in the output of each command, confirming the retry wrapper fires and recovers.
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - Run wallet_with_keycard.sh first to set up the venv and dependencies.
|
||||||
|
# - Run `wallet keycard init` and `wallet keycard connect` before this script.
|
||||||
|
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
export KEYCARD_PIN=111111
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
unpower() {
|
||||||
|
python "$SCRIPT_DIR/force_unpower.py"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Test: wallet keycard available"
|
||||||
|
wallet keycard available
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet keycard load (after power cycle)"
|
||||||
|
export KEYCARD_MNEMONIC="fashion degree mountain wool question damp current pond grow dolphin chronic then"
|
||||||
|
unpower
|
||||||
|
wallet keycard load
|
||||||
|
unset KEYCARD_MNEMONIC
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet auth-transfer init --account-id \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet auth-transfer init --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet pinata claim --to \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet pinata claim --to "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet auth-transfer init and send between two keycard accounts (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet auth-transfer init --account-id "m/44'/60'/0'/0/1"
|
||||||
|
unpower
|
||||||
|
wallet auth-transfer send --amount 40 --from "m/44'/60'/0'/0/0" --to "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: create local wallet account"
|
||||||
|
LOCAL_ACCOUNT_ID=$(wallet account new public 2>&1 | grep -oP '(?<=Public/)\S+')
|
||||||
|
echo "Created local account: Public/${LOCAL_ACCOUNT_ID}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet auth-transfer init local account"
|
||||||
|
wallet auth-transfer init --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet auth-transfer send from keycard to local account (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet auth-transfer send --amount 10 --from "m/44'/60'/0'/0/0" --to "Public/${LOCAL_ACCOUNT_ID}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"Public/${LOCAL_ACCOUNT_ID}\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet auth-transfer send from local account to keycard account (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet auth-transfer send --amount 10 --from "Public/${LOCAL_ACCOUNT_ID}" --to "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"Public/${LOCAL_ACCOUNT_ID}\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet auth-transfer send from keycard to foreign account (after power cycle)"
|
||||||
|
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
||||||
|
unpower
|
||||||
|
wallet auth-transfer send --amount 10 --from "m/44'/60'/0'/0/0" --to "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\" (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Test: wallet account get foreign account (after power cycle)"
|
||||||
|
unpower
|
||||||
|
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
||||||
0
keycard_test_3.sh → keycard_wallet/tests/keycard_test_3.sh
Normal file → Executable file
0
keycard_test_3.sh → keycard_wallet/tests/keycard_test_3.sh
Normal file → Executable file
@ -28,7 +28,8 @@ wallet pinata claim --to "m/44'/60'/0'/0/0"
|
|||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/0"
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
echo "Test: wallet auth-transfer init and send between two keycard accounts"
|
echo ""
|
||||||
|
echo "=== Test: Keycard account to Keycard account ==="
|
||||||
wallet auth-transfer init --account-id "m/44'/60'/0'/0/1"
|
wallet auth-transfer init --account-id "m/44'/60'/0'/0/1"
|
||||||
wallet auth-transfer send --amount 40 --from "m/44'/60'/0'/0/0" --to "m/44'/60'/0'/0/1"
|
wallet auth-transfer send --amount 40 --from "m/44'/60'/0'/0/0" --to "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
@ -38,7 +39,8 @@ wallet account get --account-id "m/44'/60'/0'/0/0"
|
|||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\""
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\""
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/1"
|
wallet account get --account-id "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
# Send from keycard account to a local wallet account
|
echo ""
|
||||||
|
echo "=== Test: Keycard account to public local account ==="
|
||||||
echo "Test: create local wallet account"
|
echo "Test: create local wallet account"
|
||||||
LOCAL_ACCOUNT_ID=$(wallet account new public 2>&1 | grep -oP '(?<=Public/)\S+')
|
LOCAL_ACCOUNT_ID=$(wallet account new public 2>&1 | grep -oP '(?<=Public/)\S+')
|
||||||
echo "Created local account: Public/${LOCAL_ACCOUNT_ID}"
|
echo "Created local account: Public/${LOCAL_ACCOUNT_ID}"
|
||||||
@ -56,7 +58,8 @@ wallet account get --account-id "m/44'/60'/0'/0/0"
|
|||||||
echo "Test: wallet account get --account-id \"Public/${LOCAL_ACCOUNT_ID}\""
|
echo "Test: wallet account get --account-id \"Public/${LOCAL_ACCOUNT_ID}\""
|
||||||
wallet account get --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
wallet account get --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
||||||
|
|
||||||
# Create a local wallet account, fund it, and send to keycard account (co-signed: local key + keycard)
|
echo ""
|
||||||
|
echo "=== Test: public local account to Keycard account ==="
|
||||||
|
|
||||||
echo "Test: wallet auth-transfer send from local account to keycard account"
|
echo "Test: wallet auth-transfer send from local account to keycard account"
|
||||||
wallet auth-transfer send --amount 10 --from "Public/${LOCAL_ACCOUNT_ID}" --to "m/44'/60'/0'/0/1"
|
wallet auth-transfer send --amount 10 --from "Public/${LOCAL_ACCOUNT_ID}" --to "m/44'/60'/0'/0/1"
|
||||||
@ -67,7 +70,8 @@ wallet account get --account-id "Public/${LOCAL_ACCOUNT_ID}"
|
|||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\""
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/1\""
|
||||||
wallet account get --account-id "m/44'/60'/0'/0/1"
|
wallet account get --account-id "m/44'/60'/0'/0/1"
|
||||||
|
|
||||||
# Send from keycard account to a local wallet account (foreign recipient — no signature needed)
|
echo ""
|
||||||
|
echo "=== Test: Keycard account to foreign recipient (no signature required) ==="
|
||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
||||||
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
||||||
|
|
||||||
@ -79,3 +83,44 @@ wallet account get --account-id "m/44'/60'/0'/0/0"
|
|||||||
|
|
||||||
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
echo "Test: wallet account get --account-id \"m/44'/60'/0'/0/0\""
|
||||||
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
wallet account get --account-id "Public/7wHg9sbJwc6h3NP1S9bekfAzB8CHifEcxKswCKUt3YQo"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test: Shielded auth-transfer to owned private account ==="
|
||||||
|
|
||||||
|
wallet auth-transfer send --amount 2 \
|
||||||
|
--from "m/44'/60'/0'/0/0" \
|
||||||
|
--to-npk "55204e2934045b044f06d8222b454d46b54788f33c7dec4f6733d441703bb0e6" \
|
||||||
|
--to-vpk "02a8626b0c0ad9383c5678dad48c3969b4174fb377cdb03a6259648032c774cec8"
|
||||||
|
echo "Shielded auth-transfer sent"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/0"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test: Deshielded auth-transfer: private account → keycard path 1 ==="
|
||||||
|
|
||||||
|
PRIV_SENDER=$(wallet account new private | grep -o 'Private/[^[:space:]]*' | head -1)
|
||||||
|
echo "Fresh private sender account: $PRIV_SENDER"
|
||||||
|
|
||||||
|
wallet auth-transfer init --account-id "$PRIV_SENDER"
|
||||||
|
|
||||||
|
echo "Test: wallet pinata claim to private sender"
|
||||||
|
wallet pinata claim --to "$PRIV_SENDER"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "priv-sender state after claim:"
|
||||||
|
wallet account get --account-id "$PRIV_SENDER"
|
||||||
|
|
||||||
|
wallet auth-transfer send \
|
||||||
|
--from "$PRIV_SENDER" \
|
||||||
|
--to "m/44'/60'/0'/0/1" \
|
||||||
|
--amount 5
|
||||||
|
echo "Deshielded transfer of 5: $PRIV_SENDER → keycard path 1"
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
echo "priv-sender state (balance should have decreased by 5):"
|
||||||
|
wallet account get --account-id "$PRIV_SENDER"
|
||||||
|
echo "Keycard path 1 state (balance should have increased by 5):"
|
||||||
|
wallet account get --account-id "m/44'/60'/0'/0/1"
|
||||||
0
keycard_tests_2.sh → keycard_wallet/tests/keycard_tests_2.sh
Normal file → Executable file
0
keycard_tests_2.sh → keycard_wallet/tests/keycard_tests_2.sh
Normal file → Executable file
@ -40,7 +40,7 @@ impl WalletSubcommand for KeycardSubcommand {
|
|||||||
match self {
|
match self {
|
||||||
Self::Available => {
|
Self::Available => {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
python_path::add_python_path(py).expect("keycard_wallet.py not found");
|
python_path::add_python_path(py).expect("`wallet::keycard::available`: unable to setup python path");
|
||||||
|
|
||||||
let wallet = KeycardWallet::new(py)
|
let wallet = KeycardWallet::new(py)
|
||||||
.expect("`wallet::keycard::available`: invalid data received for pin");
|
.expect("`wallet::keycard::available`: invalid data received for pin");
|
||||||
@ -61,7 +61,7 @@ impl WalletSubcommand for KeycardSubcommand {
|
|||||||
let pin = read_pin()?;
|
let pin = read_pin()?;
|
||||||
|
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
python_path::add_python_path(py).expect("keycard_wallet.py not found");
|
python_path::add_python_path(py).expect("`wallet::keycard::connect`: unable to setup python path");
|
||||||
|
|
||||||
let wallet = KeycardWallet::new(py)
|
let wallet = KeycardWallet::new(py)
|
||||||
.expect("`wallet::keycard::connect`: invalid keycard wallet provided");
|
.expect("`wallet::keycard::connect`: invalid keycard wallet provided");
|
||||||
@ -80,7 +80,7 @@ impl WalletSubcommand for KeycardSubcommand {
|
|||||||
let pin = read_pin()?;
|
let pin = read_pin()?;
|
||||||
|
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
python_path::add_python_path(py).expect("keycard_wallet.py not found");
|
python_path::add_python_path(py).expect("`wallet::keycard::disconnect`: unable to setup python path");
|
||||||
|
|
||||||
let wallet = KeycardWallet::new(py)
|
let wallet = KeycardWallet::new(py)
|
||||||
.expect("`wallet::keycard::disconnect`: invalid keycard wallet provided");
|
.expect("`wallet::keycard::disconnect`: invalid keycard wallet provided");
|
||||||
@ -103,7 +103,7 @@ impl WalletSubcommand for KeycardSubcommand {
|
|||||||
let pin = read_pin()?;
|
let pin = read_pin()?;
|
||||||
|
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
python_path::add_python_path(py).expect("keycard_wallet.py not found");
|
python_path::add_python_path(py).expect("`wallet::keycard::init`: unable to setup python path");
|
||||||
|
|
||||||
let wallet = KeycardWallet::new(py)
|
let wallet = KeycardWallet::new(py)
|
||||||
.expect("`wallet::keycard::init`: invalid keycard wallet provided");
|
.expect("`wallet::keycard::init`: invalid keycard wallet provided");
|
||||||
@ -125,7 +125,7 @@ impl WalletSubcommand for KeycardSubcommand {
|
|||||||
let mnemonic = read_mnemonic()?;
|
let mnemonic = read_mnemonic()?;
|
||||||
|
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
python_path::add_python_path(py).expect("keycard_wallet.py not found");
|
python_path::add_python_path(py).expect("`wallet::keycard::load`: unable to setup python path");
|
||||||
|
|
||||||
let wallet = KeycardWallet::new(py)
|
let wallet = KeycardWallet::new(py)
|
||||||
.expect("`wallet::keycard::load`: invalid keycard wallet provided");
|
.expect("`wallet::keycard::load`: invalid keycard wallet provided");
|
||||||
|
|||||||
@ -199,7 +199,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
|||||||
TokenProgramSubcommand::Public(
|
TokenProgramSubcommand::Public(
|
||||||
TokenProgramSubcommandPublic::TransferToken {
|
TokenProgramSubcommandPublic::TransferToken {
|
||||||
sender_account_id: from_mention,
|
sender_account_id: from_mention,
|
||||||
recipient_account_id: to_mention.expect("matched Some branch"),
|
recipient_account_id: to_mention.expect("`wallet::cli::programs::token::Send`: Invalid to_mention account provided"),
|
||||||
balance_to_move: amount,
|
balance_to_move: amount,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -318,7 +318,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
|||||||
amount,
|
amount,
|
||||||
} => {
|
} => {
|
||||||
let def_mention = definition.clone();
|
let def_mention = definition.clone();
|
||||||
let hol_mention = holder.clone();
|
let holder_mention = holder.clone();
|
||||||
let definition = definition.resolve(wallet_core.storage())?;
|
let definition = definition.resolve(wallet_core.storage())?;
|
||||||
let holder = holder
|
let holder = holder
|
||||||
.map(|account_mention| account_mention.resolve(wallet_core.storage()))
|
.map(|account_mention| account_mention.resolve(wallet_core.storage()))
|
||||||
@ -342,7 +342,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
|||||||
TokenProgramSubcommand::Public(
|
TokenProgramSubcommand::Public(
|
||||||
TokenProgramSubcommandPublic::MintToken {
|
TokenProgramSubcommandPublic::MintToken {
|
||||||
definition_account_id: def_mention,
|
definition_account_id: def_mention,
|
||||||
holder_account_id: hol_mention.expect("matched Some branch"),
|
holder_account_id: holder_mention.expect("`wallet::cli::programs::token::Mint`: Invalid holder_mention account provided"),
|
||||||
amount,
|
amount,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -40,7 +40,7 @@ use crate::{
|
|||||||
cli::CliAccountMention,
|
cli::CliAccountMention,
|
||||||
config::WalletConfigOverrides,
|
config::WalletConfigOverrides,
|
||||||
poller::TxPoller,
|
poller::TxPoller,
|
||||||
signing::SigningGroups,
|
signing::SigningGroup,
|
||||||
storage::key_chain::SharedAccountEntry,
|
storage::key_chain::SharedAccountEntry,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ pub enum ExecutionFailureKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionFailureKind {
|
impl ExecutionFailureKind {
|
||||||
/// Convert an [`anyhow::Error`] (e.g. from [`SigningGroups`]) into a keycard error.
|
/// Convert an [`anyhow::Error`] (e.g. from [`SigningGroup`]) into a keycard error.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[expect(
|
#[expect(
|
||||||
clippy::needless_pass_by_value,
|
clippy::needless_pass_by_value,
|
||||||
@ -565,13 +565,13 @@ impl WalletCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send a public transaction, fetching nonces automatically from
|
/// Send a public transaction, fetching nonces automatically from
|
||||||
/// [`SigningGroups::signing_ids`].
|
/// [`SigningGroup::signing_ids`].
|
||||||
pub async fn send_public_tx<T: serde::Serialize>(
|
pub async fn send_public_tx<T: serde::Serialize>(
|
||||||
&self,
|
&self,
|
||||||
program: &Program,
|
program: &Program,
|
||||||
account_ids: Vec<AccountId>,
|
account_ids: Vec<AccountId>,
|
||||||
instruction: T,
|
instruction: T,
|
||||||
groups: SigningGroups,
|
groups: SigningGroup,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let nonces = self
|
let nonces = self
|
||||||
.get_accounts_nonces(groups.signing_ids())
|
.get_accounts_nonces(groups.signing_ids())
|
||||||
@ -591,7 +591,7 @@ impl WalletCore {
|
|||||||
account_ids: Vec<AccountId>,
|
account_ids: Vec<AccountId>,
|
||||||
nonces: Vec<Nonce>,
|
nonces: Vec<Nonce>,
|
||||||
instruction: T,
|
instruction: T,
|
||||||
groups: SigningGroups,
|
groups: SigningGroup,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
let message = nssa::public_transaction::Message::try_new(
|
||||||
program.id(),
|
program.id(),
|
||||||
@ -661,14 +661,14 @@ impl WalletCore {
|
|||||||
KeycardWallet::get_public_account_id_for_path_with_connect(&pin, key_path_str)?;
|
KeycardWallet::get_public_account_id_for_path_with_connect(&pin, key_path_str)?;
|
||||||
let account_id: AccountId = match account_id_str
|
let account_id: AccountId = match account_id_str
|
||||||
.parse::<AccountIdWithPrivacy>()
|
.parse::<AccountIdWithPrivacy>()
|
||||||
.expect("Valid parsing of account id")
|
.expect("`wallet::lib::send_privacy_preserving_tx_with_pre_check`: invalid account id parsed")
|
||||||
{
|
{
|
||||||
AccountIdWithPrivacy::Public(id) | AccountIdWithPrivacy::Private(id) => id,
|
AccountIdWithPrivacy::Public(id) | AccountIdWithPrivacy::Private(id) => id,
|
||||||
};
|
};
|
||||||
let account = self
|
let account = self
|
||||||
.get_account_public(account_id)
|
.get_account_public(account_id)
|
||||||
.await
|
.await
|
||||||
.expect("Expect valid account");
|
.expect("`wallet::lib::send_privacy_preserving_tx_with_pre_check`: unable to retrieve public account");
|
||||||
let pin_str = pin.as_str().to_owned();
|
let pin_str = pin.as_str().to_owned();
|
||||||
(
|
(
|
||||||
Some(AccountWithMetadata {
|
Some(AccountWithMetadata {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use common::HashType;
|
|||||||
use nssa::{AccountId, program::Program};
|
use nssa::{AccountId, program::Program};
|
||||||
use token_core::TokenHolding;
|
use token_core::TokenHolding;
|
||||||
|
|
||||||
use crate::{ExecutionFailureKind, WalletCore, cli::CliAccountMention, signing::SigningGroups};
|
use crate::{ExecutionFailureKind, WalletCore, cli::CliAccountMention, signing::SigningGroup};
|
||||||
pub struct Amm<'wallet>(pub &'wallet WalletCore);
|
pub struct Amm<'wallet>(pub &'wallet WalletCore);
|
||||||
|
|
||||||
impl Amm<'_> {
|
impl Amm<'_> {
|
||||||
@ -61,7 +61,7 @@ impl Amm<'_> {
|
|||||||
user_holding_lp,
|
user_holding_lp,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(a_mention, user_holding_a, self.0)
|
.add_required(a_mention, user_holding_a, self.0)
|
||||||
.and_then(|()| groups.add_required(b_mention, user_holding_b, self.0))
|
.and_then(|()| groups.add_required(b_mention, user_holding_b, self.0))
|
||||||
@ -133,7 +133,7 @@ impl Amm<'_> {
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(seller_mention, account_id_auth, self.0)
|
.add_required(seller_mention, account_id_auth, self.0)
|
||||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
@ -202,7 +202,7 @@ impl Amm<'_> {
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(seller_mention, account_id_auth, self.0)
|
.add_required(seller_mention, account_id_auth, self.0)
|
||||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
@ -266,7 +266,7 @@ impl Amm<'_> {
|
|||||||
user_holding_lp,
|
user_holding_lp,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(a_mention, user_holding_a, self.0)
|
.add_required(a_mention, user_holding_a, self.0)
|
||||||
.and_then(|()| groups.add_required(b_mention, user_holding_b, self.0))
|
.and_then(|()| groups.add_required(b_mention, user_holding_b, self.0))
|
||||||
@ -331,7 +331,7 @@ impl Amm<'_> {
|
|||||||
user_holding_lp,
|
user_holding_lp,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(lp_mention, user_holding_lp, self.0)
|
.add_required(lp_mention, user_holding_lp, self.0)
|
||||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
|||||||
@ -9,7 +9,7 @@ use nssa_core::SharedSecretKey;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore, cli::CliAccountMention,
|
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore, cli::CliAccountMention,
|
||||||
signing::SigningGroups,
|
signing::SigningGroup,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Ata<'wallet>(pub &'wallet WalletCore);
|
pub struct Ata<'wallet>(pub &'wallet WalletCore);
|
||||||
@ -31,7 +31,7 @@ impl Ata<'_> {
|
|||||||
let account_ids = vec![owner_id, definition_id, ata_id];
|
let account_ids = vec![owner_id, definition_id, ata_id];
|
||||||
let instruction = ata_core::Instruction::Create { ata_program_id };
|
let instruction = ata_core::Instruction::Create { ata_program_id };
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(owner_mention, owner_id, self.0)
|
.add_required(owner_mention, owner_id, self.0)
|
||||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
@ -61,7 +61,7 @@ impl Ata<'_> {
|
|||||||
amount,
|
amount,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(owner_mention, owner_id, self.0)
|
.add_required(owner_mention, owner_id, self.0)
|
||||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
@ -90,7 +90,7 @@ impl Ata<'_> {
|
|||||||
amount,
|
amount,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(owner_mention, owner_id, self.0)
|
.add_required(owner_mention, owner_id, self.0)
|
||||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use common::HashType;
|
|||||||
use nssa::{AccountId, program::Program};
|
use nssa::{AccountId, program::Program};
|
||||||
|
|
||||||
use super::NativeTokenTransfer;
|
use super::NativeTokenTransfer;
|
||||||
use crate::{ExecutionFailureKind, cli::CliAccountMention, signing::SigningGroups};
|
use crate::{ExecutionFailureKind, cli::CliAccountMention, signing::SigningGroup};
|
||||||
|
|
||||||
impl NativeTokenTransfer<'_> {
|
impl NativeTokenTransfer<'_> {
|
||||||
pub async fn send_public_transfer(
|
pub async fn send_public_transfer(
|
||||||
@ -14,7 +14,7 @@ impl NativeTokenTransfer<'_> {
|
|||||||
from_mention: &CliAccountMention,
|
from_mention: &CliAccountMention,
|
||||||
to_mention: &CliAccountMention,
|
to_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(from_mention, from, self.0)
|
.add_required(from_mention, from, self.0)
|
||||||
.and_then(|()| groups.add_optional(to_mention, to, self.0))
|
.and_then(|()| groups.add_optional(to_mention, to, self.0))
|
||||||
@ -37,7 +37,7 @@ impl NativeTokenTransfer<'_> {
|
|||||||
from: AccountId,
|
from: AccountId,
|
||||||
account_mention: &CliAccountMention,
|
account_mention: &CliAccountMention,
|
||||||
) -> Result<HashType, ExecutionFailureKind> {
|
) -> Result<HashType, ExecutionFailureKind> {
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(account_mention, from, self.0)
|
.add_required(account_mention, from, self.0)
|
||||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use token_core::Instruction;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore, cli::CliAccountMention,
|
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore, cli::CliAccountMention,
|
||||||
signing::SigningGroups,
|
signing::SigningGroup,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Token<'wallet>(pub &'wallet WalletCore);
|
pub struct Token<'wallet>(pub &'wallet WalletCore);
|
||||||
@ -23,7 +23,7 @@ impl Token<'_> {
|
|||||||
let account_ids = vec![definition_account_id, supply_account_id];
|
let account_ids = vec![definition_account_id, supply_account_id];
|
||||||
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
|
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(definition_mention, definition_account_id, self.0)
|
.add_required(definition_mention, definition_account_id, self.0)
|
||||||
.and_then(|()| groups.add_required(supply_mention, supply_account_id, self.0))
|
.and_then(|()| groups.add_required(supply_mention, supply_account_id, self.0))
|
||||||
@ -147,7 +147,7 @@ impl Token<'_> {
|
|||||||
amount_to_transfer: amount,
|
amount_to_transfer: amount,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(sender_mention, sender_account_id, self.0)
|
.add_required(sender_mention, sender_account_id, self.0)
|
||||||
.and_then(|()| groups.add_optional(recipient_mention, recipient_account_id, self.0))
|
.and_then(|()| groups.add_optional(recipient_mention, recipient_account_id, self.0))
|
||||||
@ -350,7 +350,7 @@ impl Token<'_> {
|
|||||||
amount_to_burn: amount,
|
amount_to_burn: amount,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(holder_mention, holder_account_id, self.0)
|
.add_required(holder_mention, holder_account_id, self.0)
|
||||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||||
@ -476,7 +476,7 @@ impl Token<'_> {
|
|||||||
amount_to_mint: amount,
|
amount_to_mint: amount,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut groups = SigningGroups::new();
|
let mut groups = SigningGroup::new();
|
||||||
groups
|
groups
|
||||||
.add_required(definition_mention, definition_account_id, self.0)
|
.add_required(definition_mention, definition_account_id, self.0)
|
||||||
.and_then(|()| groups.add_optional(holder_mention, holder_account_id, self.0))
|
.and_then(|()| groups.add_optional(holder_mention, holder_account_id, self.0))
|
||||||
|
|||||||
@ -10,12 +10,12 @@ use crate::{WalletCore, cli::CliAccountMention};
|
|||||||
/// Local signers are signed in pure Rust; all keycard signers share a single Python session
|
/// Local signers are signed in pure Rust; all keycard signers share a single Python session
|
||||||
/// with one `connect` / `close_session` pair.
|
/// with one `connect` / `close_session` pair.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SigningGroups {
|
pub struct SigningGroup {
|
||||||
local: Vec<(AccountId, PrivateKey)>,
|
local: Vec<(AccountId, PrivateKey)>,
|
||||||
keycard: Vec<(AccountId, String)>,
|
keycard: Vec<(AccountId, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SigningGroups {
|
impl SigningGroup {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
cargo install --path wallet --force
|
|
||||||
|
|
||||||
# Install appropriate version of `keycard-py`.
|
|
||||||
git clone --branch lee-schnorr --single-branch https://github.com/bitgamma/keycard-py.git keycard_wallet/python/keycard-py
|
|
||||||
|
|
||||||
# Set up virtual environment.
|
|
||||||
python3 -m venv venv
|
|
||||||
source venv/bin/activate
|
|
||||||
pip install pyscard mnemonic ecdsa pyaes
|
|
||||||
pip install -e keycard_wallet/python/keycard-py
|
|
||||||
Loading…
x
Reference in New Issue
Block a user