TAPLE Core supports multiple cryptographic schemes for key generation, digital signatures, and content addressing.
Key Derivation Algorithms
TAPLE supports multiple key derivation algorithms for node identity and subject ownership.
KeyDerivator Enum
From the codebase, TAPLE supports these key derivation schemes:
pub enum KeyDerivator {
Ed25519,
Secp256k1,
}
Ed25519 (Default)
Edwards-curve Digital Signature Algorithm using Curve25519.
Characteristics:
- Fast signature generation and verification
- Small signatures (64 bytes)
- Deterministic (same message always produces same signature)
- Widely supported and audited
Use cases:
- Node identity keys
- Subject ownership keys
- Protocol message signing
Secp256k1 (Optional)
Elliptic Curve Digital Signature Algorithm used by Bitcoin and Ethereum.
Characteristics:
- Compatible with blockchain ecosystems
- Recoverable signatures
- Slightly slower than Ed25519
Use cases:
- Blockchain integration
- Cross-chain identity
- Ethereum-compatible addresses
Secp256k1 support requires the secp256k1 feature flag. Enable it in your Cargo.toml:taple-core = { version = "*", features = ["secp256k1"] }
KeyPair Implementation
The KeyPair enum wraps different cryptographic key types.
KeyPair Structure
From core/src/commons/crypto/mod.rs:24-49:
pub enum KeyPair {
Ed25519(Ed25519KeyPair),
#[cfg(feature = "secp256k1")]
Secp256k1(Secp256k1KeyPair),
}
impl KeyPair {
pub fn get_key_derivator(&self) -> KeyDerivator {
match self {
KeyPair::Ed25519(_) => KeyDerivator::Ed25519,
KeyPair::Secp256k1(_) => KeyDerivator::Secp256k1,
}
}
pub fn from_hex(derivator: &KeyDerivator, hex_key: &str) -> Result<KeyPair, Error> {
match derivator {
KeyDerivator::Ed25519 => Ok(KeyPair::Ed25519(Ed25519KeyPair::from_secret_key(
&hex::decode(hex_key).unwrap(),
))),
KeyDerivator::Secp256k1 => Ok(KeyPair::Secp256k1(Secp256k1KeyPair::from_secret_key(
&hex::decode(hex_key).unwrap(),
))),
}
}
}
Key Generation
KeyGenerator Trait
From core/src/commons/crypto/mod.rs:88-113:
pub trait KeyGenerator: KeyMaterial {
/// Generates random keys
fn new() -> Self
where
Self: Sized,
{
Self::from_seed(vec![].as_slice())
}
/// Generates keys deterministically using a given seed
fn from_seed(seed: &[u8]) -> Self
where
Self: Sized;
/// Generates keys from existing public key
fn from_public_key(public_key: &[u8]) -> Self
where
Self: Sized;
/// Generate keys from existing secret key
fn from_secret_key(private_key: &[u8]) -> Self
where
Self: Sized;
}
Generate Random Keys
use taple_core::crypto::{Ed25519KeyPair, KeyGenerator};
// Random key generation
let keypair = Ed25519KeyPair::new();
Generate from Seed
// Deterministic generation
let seed = b"my secure seed";
let keypair = Ed25519KeyPair::from_seed(seed);
Seed Requirements
From core/src/commons/crypto/mod.rs:266-277:
pub fn create_seed(initial_seed: &[u8]) -> Result<[u8; 32], Error> {
let mut seed = [0u8; 32];
if initial_seed.is_empty() {
// Generate random seed
getrandom::getrandom(&mut seed)
.map_err(|_| Error::SeedError("couldn't generate random seed".to_owned()))?;
} else if initial_seed.len() <= 32 {
seed[..initial_seed.len()].copy_from_slice(initial_seed);
} else {
return Err(Error::SeedError("seed is greater than 32".to_owned()));
}
Ok(seed)
}
Rules:
- Empty seed generates random keys
- Seeds must be ≤ 32 bytes
- Shorter seeds are zero-padded
- Use cryptographically secure random seeds in production
Digital Signatures
DSA Trait
From core/src/commons/crypto/mod.rs:116-122:
pub trait DSA {
/// Performs sign operation
fn sign(&self, payload: Payload) -> Result<Vec<u8>, Error>;
/// Performs verify operation
fn verify(&self, payload: Payload, signature: &[u8]) -> Result<(), Error>;
}
Payload Types
From core/src/commons/crypto/mod.rs:258-263:
pub enum Payload {
Buffer(Vec<u8>),
BufferArray(Vec<Vec<u8>>),
}
Signing Example
use taple_core::crypto::{KeyPair, Ed25519KeyPair, DSA, Payload};
let keypair = KeyPair::Ed25519(Ed25519KeyPair::new());
let message = b"Hello, TAPLE!";
// Sign
let signature = keypair.sign(Payload::Buffer(message.to_vec()))?;
// Verify
keypair.verify(Payload::Buffer(message.to_vec()), &signature)?;
Signature Implementation
From core/src/commons/crypto/mod.rs:179-202:
impl DSA for KeyPair {
fn sign(&self, payload: Payload) -> Result<Vec<u8>, Error> {
match self {
KeyPair::Ed25519(x) => x.sign(payload),
#[cfg(feature = "secp256k1")]
KeyPair::Secp256k1(x) => x.sign(payload),
}
}
fn verify(&self, payload: Payload, signature: &[u8]) -> Result<(), Error> {
match self {
KeyPair::Ed25519(x) => x.verify(payload, signature),
#[cfg(feature = "secp256k1")]
KeyPair::Secp256k1(x) => x.verify(payload, signature),
}
}
}
Key Material Access
KeyMaterial Trait
From core/src/commons/crypto/mod.rs:72-86:
pub trait KeyMaterial {
/// Returns the public key bytes as slice
fn public_key_bytes(&self) -> Vec<u8>;
/// Returns the secret key bytes as slice
fn secret_key_bytes(&self) -> Vec<u8>;
/// Returns bytes from key pair
fn to_bytes(&self) -> Vec<u8>;
/// Returns String from key pair encoded in base64
fn to_str(&self) -> String {
encode_config(self.to_bytes(), base64::URL_SAFE_NO_PAD)
}
}
let keypair = Ed25519KeyPair::new();
// Get public key (32 bytes for Ed25519)
let public_key = keypair.public_key_bytes();
// Get secret key (32 bytes for Ed25519)
let secret_key = keypair.secret_key_bytes();
// Get as base64 string
let key_string = keypair.to_str();
Digest Derivation
TAPLE uses cryptographic hash functions for content addressing and identifiers.
DigestDerivator
Supported hash algorithms:
pub enum DigestDerivator {
Blake3_256,
Blake3_512,
SHA2_256,
SHA2_512,
SHA3_256,
SHA3_512,
}
Blake3 (Default)
Characteristics:
- Extremely fast (optimized for modern CPUs)
- Cryptographically secure
- Built-in keyed hashing and key derivation
- Parallizable
Use cases:
- Subject IDs
- Event IDs
- Content addressing
SHA-2 and SHA-3
Well-established alternatives with different security properties:
- SHA2-256/512: NIST standard, widely supported
- SHA3-256/512: Keccak-based, resistant to length extension attacks
Node Settings
Configure cryptographic schemes in node settings (from core/src/commons/settings/mod.rs:234-253):
pub struct NodeSettings {
/// KeyDerivator to be used by the secret key
pub key_derivator: KeyDerivator,
/// Secret key to be used by the node
pub secret_key: String,
/// DigestDerivator for event and subject identifiers
pub digest_derivator: DigestDerivator,
// ... other settings
}
impl Default for NodeSettings {
fn default() -> Self {
Self {
key_derivator: KeyDerivator::Ed25519,
secret_key: String::from(""),
digest_derivator: DigestDerivator::Blake3_256,
// ...
}
}
}
Configuration Examples
Ed25519 with Blake3 (Default)
use taple_core::{Settings, KeyDerivator, DigestDerivator};
let mut settings = Settings::default();
// Default is Ed25519 + Blake3_256
Secp256k1 with SHA2
use taple_core::{Settings, KeyDerivator, DigestDerivator, crypto::*};
// Generate Secp256k1 key
let keypair = Secp256k1KeyPair::new();
let mut settings = Settings::default();
settings.node.key_derivator = KeyDerivator::Secp256k1;
settings.node.secret_key = hex::encode(keypair.secret_key_bytes());
settings.node.digest_derivator = DigestDerivator::SHA2_256;
Import Existing Key
let secret_key_hex = "a1b2c3d4e5f6...";
let mut settings = Settings::default();
settings.node.key_derivator = KeyDerivator::Ed25519;
settings.node.secret_key = secret_key_hex.to_string();
Serialization
KeyPair implements Borsh serialization for efficient storage.
From core/src/commons/crypto/mod.rs:204-241:
impl BorshSerialize for KeyPair {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
match &self {
KeyPair::Ed25519(x) => {
BorshSerialize::serialize(&0u8, writer)?;
let a: [u8; 32] = x.secret_key_bytes().try_into().unwrap();
BorshSerialize::serialize(&a, writer)
}
KeyPair::Secp256k1(x) => {
BorshSerialize::serialize(&1u8, writer)?;
let a: [u8; 32] = x.secret_key_bytes().try_into().unwrap();
BorshSerialize::serialize(&a, writer)
}
}
}
}
impl BorshDeserialize for KeyPair {
fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
let order: u8 = BorshDeserialize::deserialize_reader(reader)?;
match order {
0 => {
let data: [u8; 32] = BorshDeserialize::deserialize_reader(reader)?;
Ok(KeyPair::Ed25519(Ed25519KeyPair::from_secret_key(&data)))
}
1 => {
let data: [u8; 32] = BorshDeserialize::deserialize_reader(reader)?;
Ok(KeyPair::Secp256k1(Secp256k1KeyPair::from_secret_key(&data)))
}
_ => Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("Invalid KeyPair variant: {}", order),
)),
}
}
}
Key Identifiers
Generate identifiers from public keys:
use taple_core::{KeyIdentifier, Derivable};
use taple_core::crypto::{KeyPair, Ed25519KeyPair, KeyMaterial};
let keypair = KeyPair::Ed25519(Ed25519KeyPair::new());
let key_id = KeyIdentifier::new(
keypair.get_key_derivator(),
&keypair.public_key_bytes()
);
println!("Key ID: {}", key_id.to_str());
Security Best Practices
Key Generation
- Use cryptographically secure random number generators
- Never hardcode keys in source code
- Use environment variables or secure key management systems
- Rotate keys periodically
Key Storage
- Store private keys encrypted at rest
- Use hardware security modules (HSM) for production
- Implement proper access controls
- Back up keys securely
Algorithm Selection
- Ed25519: Best for most use cases (fast, secure, compact)
- Secp256k1: When blockchain compatibility is required
- Blake3: Default for hashing (fastest)
- SHA-2/SHA-3: When regulatory compliance requires NIST standards
Signature Verification
- Always verify signatures before processing
- Validate public keys are on the curve
- Use constant-time comparison to prevent timing attacks
- Implement replay attack prevention
Signature Generation (approximate)
- Ed25519: ~30,000 signatures/sec
- Secp256k1: ~10,000 signatures/sec
Signature Verification (approximate)
- Ed25519: ~15,000 verifications/sec
- Secp256k1: ~3,000 verifications/sec
Hash Generation (Blake3)
- Single-threaded: ~1-3 GB/s
- Multi-threaded: ~10+ GB/s (depends on CPU cores)
Performance numbers are approximate and vary based on hardware. Benchmark your specific use case for accurate measurements.
Migration Between Schemes
Changing cryptographic schemes requires careful planning:
- Key derivator changes: Requires new node identity
- Digest derivator changes: New subjects use new scheme, existing subjects keep original
- Backward compatibility: Old signatures remain valid with original scheme
// Check what derivator was used for a subject
let subject = api.get_subject(subject_id).await?;
let derivator = subject.subject_id.derivator;