This document describes security practices and policies for the Animica blockchain.
If you discover a security vulnerability, please email security@animica.org with:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if available)
We will acknowledge receipt within 48 hours and provide a timeline for resolution.
Animica's consensus, execution, and cryptographic paths are implemented in pure Python for portability and ease of deployment. However, pure Python cannot provide the same timing guarantees as constant-time implementations in C, Rust, or assembly.
This section documents our approach to mitigating timing side-channels in pure Python code.
Python is an interpreted language with several characteristics that make true constant-time code impossible:
- Interpreter Overhead: CPython's bytecode interpreter adds variable overhead depending on operation type and object types
- Dynamic Dispatch: Attribute lookups, method calls, and type checks have variable timing
- Garbage Collection: Stop-the-world GC pauses occur unpredictably
- Memory Layout: No control over object layout or CPU cache effects
- OS Scheduling: Process preemption by the OS scheduler
- JIT Effects: PyPy and other implementations may have different timing characteristics
Our timing hardening targets LOCAL timing side-channels where an attacker can:
- Measure response times with microsecond precision
- Make many repeated measurements
- Control input values to probe timing differences
We do NOT attempt to mitigate:
- Remote timing attacks over networks (network jitter dominates)
- Cache timing attacks (require same-core execution)
- Speculative execution attacks (require hardware access)
For remote APIs:
- Network jitter (milliseconds) far exceeds Python timing variance (microseconds)
- Rate limiting and quotas prevent statistical attacks
- Server load introduces additional noise
We apply defense-in-depth with the following techniques:
Use hmac.compare_digest() for all security-sensitive comparisons:
from animica.security.ct import ct_eq_bytes, ct_eq_str
# ❌ BAD: Timing leak from early exit
if password == expected_password:
return True
# ✅ GOOD: Constant-time comparison
if ct_eq_str(password, expected_password):
return TrueAvailable helpers:
ct_eq_bytes(a, b)- Compare byte stringsct_eq_str(a, b)- Compare UTF-8 stringsct_memcmp(a, b)- Compare memoryviewsct_select(mask, if_true, if_false)- Bitwise selectionct_all_checks(*checks)- Evaluate all checks without short-circuitct_any_check(*checks)- Evaluate all checks without short-circuit
Avoid secret-dependent early returns. Process all checks before deciding:
# ❌ BAD: Early return on first failure
def verify_multi_sig(sigs):
for sig in sigs:
if not verify_sig(sig):
return False # Reveals which signature failed
return True
# ✅ GOOD: Check all, then decide
def verify_multi_sig(sigs):
results = [verify_sig(sig) for sig in sigs]
return ct_all_checks(*results)Return normalized error messages to external callers. Log detailed reasons to debug logs only:
# ❌ BAD: Error message reveals failure reason
if not hmac.compare_digest(tag, computed_tag):
return "HMAC verification failed"
if not check_timestamp(data):
return "Timestamp expired"
# ✅ GOOD: Normalized message externally
valid_hmac = hmac.compare_digest(tag, computed_tag)
valid_ts = check_timestamp(data)
if not ct_all_checks(valid_hmac, valid_ts):
logger.debug("Validation failed: hmac=%s ts=%s", valid_hmac, valid_ts)
return "Authentication failed" # Generic messageUse batch verification to amortize timing variance across multiple operations:
from animica.security.batch_verify import VerifyItem, verify_batch
items = [
VerifyItem(i, messages[i], signatures[i], public_keys[i], alg_id)
for i in range(len(messages))
]
results = verify_batch(items) # Parallel verificationBenefits:
- Parallelization improves throughput
- Timing variance averaged across batch
- Statistical attacks harder with batch processing
For DoS defense, perform cheap checks before expensive crypto:
def validate_transaction(tx):
# Cheap checks first (no secrets involved)
if len(tx.data) > MAX_SIZE:
return "Transaction too large"
if tx.chain_id != EXPECTED_CHAIN_ID:
return "Wrong chain"
if tx.gas_limit == 0:
return "Invalid gas"
# Expensive crypto last
if not verify_signature(tx):
return "Invalid signature" # Normalized messageWhen writing security-sensitive code:
-
Use ct helpers for all secret comparisons
- Passwords, tokens, HMAC tags, session IDs
- Addresses (when used for authentication)
- Signatures, handshake keys, shared secrets
-
Avoid early returns based on secrets
- Evaluate all checks before returning
- Use
ct_all_checks()orct_any_check()
-
Normalize error messages
- Generic messages to external callers
- Detailed reasons in debug logs only
-
Use batch verification where possible
- Mempool admission
- Block validation
- Transaction pools
-
Order checks by cost
- Public input validation first
- Expensive crypto last
- DoS prevention
Control parallelism with environment variable:
export ANIMICA_VERIFY_WORKERS=4 # Number of worker processesDefault: max(1, cpu_count() - 1)
Enable timing variability tests (opt-in due to flakiness):
export ANIMICA_TIMING_TESTS=1
pytest python/animica/security/tests/test_timing_variability.pyThese tests are probabilistic and may fail due to OS/CPU noise.
Run security tests:
# Constant-time helpers
pytest python/animica/security/tests/test_ct.py
# Batch verification
pytest python/animica/security/tests/test_batch_verify.py
# Timing variability (opt-in)
ANIMICA_TIMING_TESTS=1 pytest python/animica/security/tests/test_timing_variability.pyRun benchmarks:
# All benchmarks
python -m animica.bench.bench_verify
# Specific benchmarks
python -m animica.bench.bench_verify --single
python -m animica.bench.bench_verify --batch --workers=4
python -m animica.bench.bench_verify --blockWhat we CAN do:
- Use
hmac.compare_digest()(implemented in C) - Avoid obvious timing leaks from early returns
- Normalize error messages
- Batch verification for throughput
What we CANNOT prevent:
- CPython interpreter timing variance
- Garbage collection pauses
- OS scheduler preemption
- CPU cache effects from Python object layout
- Dynamic dispatch overhead
Recommendation: For applications requiring hardware-level timing guarantees:
- Use dedicated HSMs or secure enclaves
- Implement critical paths in C/Rust with constant-time primitives
- Use hardware timing randomization (if available)
- hmac.compare_digest() documentation
- Timing Attack on Python String Comparison
- Cryptography Coding Rules (OpenSSL)
- libsodium constant-time comparison
For other security topics, see:
docs/security/THREAT_MODEL.md- Overall threat modeldocs/security/DOS_DEFENSES.md- DoS protectiondocs/security/AUDIT_CHECKLIST.md- Security audit checklistdocs/security/RESPONSIBLE_DISCLOSURE.md- Vulnerability disclosure process