mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-25 09:29:33 +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
|
||||
bedrock_signing_key
|
||||
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
|
||||
```
|
||||
|
||||
## 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
|
||||
@ -473,22 +496,34 @@ Transaction data is ...
|
||||
|
||||
## 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 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.
|
||||
|
||||
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
|
||||
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"
|
||||
@ -4,14 +4,18 @@ from ecdsa import VerifyingKey, SECP256k1
|
||||
|
||||
from keycard.keycard import KeyCard
|
||||
from keycard.commands.export_lee_key import export_lee_key
|
||||
from mnemonic import Mnemonic
|
||||
from keycard import constants
|
||||
|
||||
from mnemonic import Mnemonic
|
||||
from keycard import constants
|
||||
|
||||
import keycard
|
||||
import os
|
||||
import secrets
|
||||
|
||||
DEFAULT_PAIRING_PASSWORD = "KeycardDefaultPairing"
|
||||
|
||||
def _pairing_password() -> str:
|
||||
return os.environ.get("KEYCARD_PAIRING_PASSWORD", DEFAULT_PAIRING_PASSWORD)
|
||||
|
||||
class KeycardWallet:
|
||||
def __init__(self):
|
||||
self.card = KeyCard()
|
||||
@ -37,7 +41,7 @@ class KeycardWallet:
|
||||
return False
|
||||
return True
|
||||
|
||||
def initialize(self, pin: str) -> bool:
|
||||
def initialize(self, pin: str, pairing_password: str | None = None) -> bool:
|
||||
try:
|
||||
self.card.select()
|
||||
|
||||
@ -45,14 +49,18 @@ class KeycardWallet:
|
||||
raise RuntimeError("Card is already initialized")
|
||||
|
||||
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("Record this PUK and store it somewhere safe. It cannot be recovered.")
|
||||
return True
|
||||
except Exception as 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()
|
||||
|
||||
if not self.card.is_initialized:
|
||||
@ -70,14 +78,28 @@ class KeycardWallet:
|
||||
self.card.unpair(pairing_index)
|
||||
except Exception:
|
||||
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]:
|
||||
return (self.pairing_index, self.pairing_key)
|
||||
def pair(self, pin: str, password: str | None = None) -> tuple[int, bytes]:
|
||||
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()
|
||||
|
||||
if not self.card.is_initialized:
|
||||
@ -94,6 +116,22 @@ class KeycardWallet:
|
||||
|
||||
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:
|
||||
return True
|
||||
|
||||
|
||||
@ -56,10 +56,10 @@ impl KeycardWallet {
|
||||
.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
|
||||
.bind(py)
|
||||
.call_method0("get_pairing_data")?
|
||||
.call_method1("pair", (pin,))?
|
||||
.extract()
|
||||
}
|
||||
|
||||
@ -96,20 +96,11 @@ impl KeycardWallet {
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
self.setup_communication(py, pin)?;
|
||||
if let Ok((index, key)) = self.get_pairing_data(py) {
|
||||
save_pairing(&KeycardPairingData { index, key });
|
||||
}
|
||||
let (index, key) = self.pair(py, pin)?;
|
||||
save_pairing(&KeycardPairingData { index, key });
|
||||
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> {
|
||||
self.instance.bind(py).call_method0("disconnect")?.extract()
|
||||
}
|
||||
@ -269,19 +260,9 @@ impl KeycardWallet {
|
||||
) -> PyResult<PrivateKeyPair> {
|
||||
Python::with_gil(|py| {
|
||||
python_path::add_python_path(py)?;
|
||||
|
||||
let wallet = Self::new(py)?;
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
wallet.connect(py, pin)?;
|
||||
let result = wallet.get_private_keys_for_path(py, path);
|
||||
|
||||
drop(wallet.disconnect(py));
|
||||
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\""
|
||||
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 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\""
|
||||
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"
|
||||
LOCAL_ACCOUNT_ID=$(wallet account new public 2>&1 | grep -oP '(?<=Public/)\S+')
|
||||
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}\""
|
||||
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"
|
||||
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\""
|
||||
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\""
|
||||
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\""
|
||||
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 {
|
||||
Self::Available => {
|
||||
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)
|
||||
.expect("`wallet::keycard::available`: invalid data received for pin");
|
||||
@ -61,7 +61,7 @@ impl WalletSubcommand for KeycardSubcommand {
|
||||
let pin = read_pin()?;
|
||||
|
||||
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)
|
||||
.expect("`wallet::keycard::connect`: invalid keycard wallet provided");
|
||||
@ -80,7 +80,7 @@ impl WalletSubcommand for KeycardSubcommand {
|
||||
let pin = read_pin()?;
|
||||
|
||||
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)
|
||||
.expect("`wallet::keycard::disconnect`: invalid keycard wallet provided");
|
||||
@ -103,7 +103,7 @@ impl WalletSubcommand for KeycardSubcommand {
|
||||
let pin = read_pin()?;
|
||||
|
||||
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)
|
||||
.expect("`wallet::keycard::init`: invalid keycard wallet provided");
|
||||
@ -125,7 +125,7 @@ impl WalletSubcommand for KeycardSubcommand {
|
||||
let mnemonic = read_mnemonic()?;
|
||||
|
||||
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)
|
||||
.expect("`wallet::keycard::load`: invalid keycard wallet provided");
|
||||
|
||||
@ -199,7 +199,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
||||
TokenProgramSubcommand::Public(
|
||||
TokenProgramSubcommandPublic::TransferToken {
|
||||
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,
|
||||
},
|
||||
)
|
||||
@ -318,7 +318,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
||||
amount,
|
||||
} => {
|
||||
let def_mention = definition.clone();
|
||||
let hol_mention = holder.clone();
|
||||
let holder_mention = holder.clone();
|
||||
let definition = definition.resolve(wallet_core.storage())?;
|
||||
let holder = holder
|
||||
.map(|account_mention| account_mention.resolve(wallet_core.storage()))
|
||||
@ -342,7 +342,7 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand {
|
||||
TokenProgramSubcommand::Public(
|
||||
TokenProgramSubcommandPublic::MintToken {
|
||||
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,
|
||||
},
|
||||
)
|
||||
|
||||
@ -40,7 +40,7 @@ use crate::{
|
||||
cli::CliAccountMention,
|
||||
config::WalletConfigOverrides,
|
||||
poller::TxPoller,
|
||||
signing::SigningGroups,
|
||||
signing::SigningGroup,
|
||||
storage::key_chain::SharedAccountEntry,
|
||||
};
|
||||
|
||||
@ -89,7 +89,7 @@ pub enum 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]
|
||||
#[expect(
|
||||
clippy::needless_pass_by_value,
|
||||
@ -565,13 +565,13 @@ impl WalletCore {
|
||||
}
|
||||
|
||||
/// Send a public transaction, fetching nonces automatically from
|
||||
/// [`SigningGroups::signing_ids`].
|
||||
/// [`SigningGroup::signing_ids`].
|
||||
pub async fn send_public_tx<T: serde::Serialize>(
|
||||
&self,
|
||||
program: &Program,
|
||||
account_ids: Vec<AccountId>,
|
||||
instruction: T,
|
||||
groups: SigningGroups,
|
||||
groups: SigningGroup,
|
||||
) -> Result<HashType, ExecutionFailureKind> {
|
||||
let nonces = self
|
||||
.get_accounts_nonces(groups.signing_ids())
|
||||
@ -591,7 +591,7 @@ impl WalletCore {
|
||||
account_ids: Vec<AccountId>,
|
||||
nonces: Vec<Nonce>,
|
||||
instruction: T,
|
||||
groups: SigningGroups,
|
||||
groups: SigningGroup,
|
||||
) -> Result<HashType, ExecutionFailureKind> {
|
||||
let message = nssa::public_transaction::Message::try_new(
|
||||
program.id(),
|
||||
@ -661,14 +661,14 @@ impl WalletCore {
|
||||
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")
|
||||
.expect("`wallet::lib::send_privacy_preserving_tx_with_pre_check`: invalid account id parsed")
|
||||
{
|
||||
AccountIdWithPrivacy::Public(id) | AccountIdWithPrivacy::Private(id) => id,
|
||||
};
|
||||
let account = self
|
||||
.get_account_public(account_id)
|
||||
.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();
|
||||
(
|
||||
Some(AccountWithMetadata {
|
||||
|
||||
@ -3,7 +3,7 @@ use common::HashType;
|
||||
use nssa::{AccountId, program::Program};
|
||||
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);
|
||||
|
||||
impl Amm<'_> {
|
||||
@ -61,7 +61,7 @@ impl Amm<'_> {
|
||||
user_holding_lp,
|
||||
];
|
||||
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(a_mention, user_holding_a, 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
|
||||
.add_required(seller_mention, account_id_auth, self.0)
|
||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||
@ -202,7 +202,7 @@ impl Amm<'_> {
|
||||
));
|
||||
};
|
||||
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(seller_mention, account_id_auth, self.0)
|
||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||
@ -266,7 +266,7 @@ impl Amm<'_> {
|
||||
user_holding_lp,
|
||||
];
|
||||
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(a_mention, user_holding_a, self.0)
|
||||
.and_then(|()| groups.add_required(b_mention, user_holding_b, self.0))
|
||||
@ -331,7 +331,7 @@ impl Amm<'_> {
|
||||
user_holding_lp,
|
||||
];
|
||||
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(lp_mention, user_holding_lp, self.0)
|
||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||
|
||||
@ -9,7 +9,7 @@ use nssa_core::SharedSecretKey;
|
||||
|
||||
use crate::{
|
||||
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore, cli::CliAccountMention,
|
||||
signing::SigningGroups,
|
||||
signing::SigningGroup,
|
||||
};
|
||||
|
||||
pub struct Ata<'wallet>(pub &'wallet WalletCore);
|
||||
@ -31,7 +31,7 @@ impl Ata<'_> {
|
||||
let account_ids = vec![owner_id, definition_id, ata_id];
|
||||
let instruction = ata_core::Instruction::Create { ata_program_id };
|
||||
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(owner_mention, owner_id, self.0)
|
||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||
@ -61,7 +61,7 @@ impl Ata<'_> {
|
||||
amount,
|
||||
};
|
||||
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(owner_mention, owner_id, self.0)
|
||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||
@ -90,7 +90,7 @@ impl Ata<'_> {
|
||||
amount,
|
||||
};
|
||||
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(owner_mention, owner_id, self.0)
|
||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||
|
||||
@ -3,7 +3,7 @@ use common::HashType;
|
||||
use nssa::{AccountId, program::Program};
|
||||
|
||||
use super::NativeTokenTransfer;
|
||||
use crate::{ExecutionFailureKind, cli::CliAccountMention, signing::SigningGroups};
|
||||
use crate::{ExecutionFailureKind, cli::CliAccountMention, signing::SigningGroup};
|
||||
|
||||
impl NativeTokenTransfer<'_> {
|
||||
pub async fn send_public_transfer(
|
||||
@ -14,7 +14,7 @@ impl NativeTokenTransfer<'_> {
|
||||
from_mention: &CliAccountMention,
|
||||
to_mention: &CliAccountMention,
|
||||
) -> Result<HashType, ExecutionFailureKind> {
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(from_mention, from, self.0)
|
||||
.and_then(|()| groups.add_optional(to_mention, to, self.0))
|
||||
@ -37,7 +37,7 @@ impl NativeTokenTransfer<'_> {
|
||||
from: AccountId,
|
||||
account_mention: &CliAccountMention,
|
||||
) -> Result<HashType, ExecutionFailureKind> {
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(account_mention, from, self.0)
|
||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||
|
||||
@ -5,7 +5,7 @@ use token_core::Instruction;
|
||||
|
||||
use crate::{
|
||||
ExecutionFailureKind, PrivacyPreservingAccount, WalletCore, cli::CliAccountMention,
|
||||
signing::SigningGroups,
|
||||
signing::SigningGroup,
|
||||
};
|
||||
|
||||
pub struct Token<'wallet>(pub &'wallet WalletCore);
|
||||
@ -23,7 +23,7 @@ impl Token<'_> {
|
||||
let account_ids = vec![definition_account_id, supply_account_id];
|
||||
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
|
||||
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(definition_mention, definition_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,
|
||||
};
|
||||
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(sender_mention, sender_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,
|
||||
};
|
||||
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(holder_mention, holder_account_id, self.0)
|
||||
.map_err(ExecutionFailureKind::from_anyhow)?;
|
||||
@ -476,7 +476,7 @@ impl Token<'_> {
|
||||
amount_to_mint: amount,
|
||||
};
|
||||
|
||||
let mut groups = SigningGroups::new();
|
||||
let mut groups = SigningGroup::new();
|
||||
groups
|
||||
.add_required(definition_mention, definition_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
|
||||
/// with one `connect` / `close_session` pair.
|
||||
#[derive(Default)]
|
||||
pub struct SigningGroups {
|
||||
pub struct SigningGroup {
|
||||
local: Vec<(AccountId, PrivateKey)>,
|
||||
keycard: Vec<(AccountId, String)>,
|
||||
}
|
||||
|
||||
impl SigningGroups {
|
||||
impl SigningGroup {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
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