TSN Trust Stack Network
Explorer ↗ Download

Security · cryptography in practice

Cryptographic security

A practical guide to how TSN's crypto module is built and reviewed — the post-quantum primitives in production, and a set of deliberately vulnerable teaching modules used to demonstrate the mistakes they guard against.

Overview

Demonstration modules. TSN includes implementations that are deliberately vulnerable, for educational purposes only. These modules compile solely behind the vulnerable-demo feature flag and must never be used in production.

TSN uses algorithms resistant to quantum computers for everything that touches consensus and funds:

Signatures — ML-DSA-65 (FIPS 204)

rust
use fips204::ml_dsa_65;

let (pk, sk) = ml_dsa_65::try_keygen_with_rng(&mut OsRng)?;
let signature = ml_dsa_65::try_sign_with_rng(&mut OsRng, &sk, &message, &context)?;
let is_valid = ml_dsa_65::try_verify(&pk, &message, &signature, &context)?;

Why: resistant to quantum attacks, NIST-standardized, and — being a single fixed parameter set — the same code path signs every transaction, with no special-case fast/slow variants to keep in sync.

Hashing — Poseidon2 for ZK

rust
use light_poseidon::Poseidon;

let mut poseidon = Poseidon::new();
let hash = poseidon.hash(&inputs)?;

Why: optimized for zero-knowledge circuits, and — being an algebraic hash over Goldilocks/BN254 rather than a bitwise one — sidesteps the quantum-search speedups that apply to classical hash constructions.

Vulnerabilities demonstrated

The demonstration modules illustrate common cryptographic mistakes — each one paired with the fix TSN's production code actually uses.

bash
# Build with the vulnerable modules (TESTING / EDUCATION ONLY)
cargo build --features vulnerable-demo
cargo test --features vulnerable-demo

# Normal build (vulnerable modules EXCLUDED)
cargo build
cargo test

1. Timing attacks

Problem: a non-constant-time comparison leaks information through its execution time.

Vulnerable
fn insecure_compare(a: &[u8], b: &[u8]) -> bool {
    for i in 0..a.len() {
        if a[i] != b[i] {
            return false; // timing leak
        }
    }
    true
}
Secure
use subtle::ConstantTimeEq;

fn secure_compare(a: &[u8], b: &[u8]) -> bool {
    a.ct_eq(b).into()
}

Attack: measure the response time to recover a secret key one byte at a time.

2. Nonce reuse

Problem: reusing the same nonce reveals the XOR of the plaintexts: C1 ⊕ C2 = P1 ⊕ P2.

Fixed nonce
let nonce = [0u8; 12]; // always the same
let ciphertext = cipher.encrypt(&nonce, plaintext)?;
Unique nonce
let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
let ciphertext = cipher.encrypt(&nonce, plaintext)?;

Attack: plaintext recovery through differential analysis.

3. Predictable RNG

Problem: a deterministic generator lets an attacker predict future keys.

Known seed
let mut rng = StdRng::seed_from_u64(12345);
// predictable sequence
System entropy
let mut rng = OsRng;
// cryptographic entropy

Attack: key prediction if the seed or internal state is ever exposed.

4. Padding oracle

Problem: a non-constant-time padding check leaks information through control flow.

Early return
fn remove_padding(data: &[u8]) -> Option<&[u8]> {
    let pad_len = data[data.len() - 1] as usize;
    if pad_len > 16 {
        return None; // timing leak
    }
    // ...
}
Constant-time
use subtle::{Choice, ConditionallySelectable};

fn secure_remove_padding(data: &[u8]) -> Option<&[u8]> {
    // full constant-time check
}

Attack: padding-oracle attack — decryption without ever recovering the key.

5. Naive KDF

Problem: derivation without salt or iteration count is vulnerable to rainbow tables.

Plain hash
fn naive_kdf(password: &[u8]) -> [u8; 32] {
    Sha256::digest(password).into()
    // no salt, no iterations
}
Secure PBKDF2
use pbkdf2::pbkdf2_hmac;

let mut key = [0u8; 32];
pbkdf2_hmac::(password, salt, 100_000, &mut key);

Attack: precomputed hash tables for common passwords (rainbow tables).

Protection by compilation

TSN uses compile-time guards to prevent the vulnerable modules from being used by accident:

rust
#[cfg(not(feature = "vulnerable-demo"))]
compile_error!(
    "ACCESS DENIED: vulnerable modules are disabled\n\
     To enable them (TESTING / EDUCATION ONLY):\n\
     cargo build --features vulnerable-demo"
);

This guarantees the vulnerable modules cannot be compiled into a production build by accident.

Security tests

Timing-attack benchmark

bash
cargo test --features vulnerable-demo timing_attack_benchmark

Non-regression tests

bash
# Verify the vulnerable modules stay locked out
cargo build  # must fail if vulnerable.rs is imported without the feature

# Verify the feature flag itself works
cargo build --features vulnerable-demo  # must succeed

Crypto review checklist

Before merging any cryptographic code:

  • Entropy — uses OsRng for production keys and nonces.
  • Constant-time — sensitive comparisons use subtle::ConstantTimeEq.
  • AEAD — authenticated encryption (ChaCha20-Poly1305, AES-GCM).
  • Post-quantum — ML-DSA-65 signatures, Poseidon2 hashing for ZK circuits.
  • Secure KDF — PBKDF2 / scrypt / Argon2 with a random salt.
  • Unique nonces — never reused.
  • Tests — error paths and edge cases covered.
  • Documentation — the reasoning behind each cryptographic choice is written down.
TSN's goal. Build a secure post-quantum blockchain while teaching the community about common cryptographic pitfalls. The demonstration modules let people learn by example without ever touching the security of the main network.