TAPLE uses robust cryptographic primitives to ensure security, authenticity, and integrity across the distributed ledger. The system supports multiple cryptographic schemes with a flexible architecture.
Key Pair Types
TAPLE supports multiple asymmetric cryptographic schemes:
pub enum KeyPair {
Ed25519 ( Ed25519KeyPair ),
#[cfg(feature = "secp256k1" )]
Secp256k1 ( Secp256k1KeyPair ),
}
Source: commons/crypto/mod.rs:24-29
Ed25519 is the default and recommended scheme. Secp256k1 support is available as an optional feature.
Key Derivators
Key derivators determine which cryptographic scheme to use:
pub enum KeyDerivator {
Ed25519 ,
Secp256k1 ,
}
Source: commons/identifier/derive/mod.rs (referenced)
Getting the Derivator
pub fn get_key_derivator ( & self ) -> KeyDerivator {
match self {
KeyPair :: Ed25519 ( _ ) => KeyDerivator :: Ed25519 ,
KeyPair :: Secp256k1 ( _ ) => KeyDerivator :: Secp256k1 ,
}
}
Source: commons/crypto/mod.rs:32-37
Key Generation
Keys can be generated in multiple ways:
Random Generation
let key_pair = crypto :: KeyPair :: Ed25519 ( Ed25519KeyPair :: from_seed ( & []));
Source: lib.rs:32
From Seed
pub trait KeyGenerator : KeyMaterial {
fn new () -> Self
where
Self : Sized ,
{
Self :: from_seed ( vec! [] . as_slice ())
}
fn from_seed ( seed : & [ u8 ]) -> Self
where
Self : Sized ;
fn from_public_key ( public_key : & [ u8 ]) -> Self
where
Self : Sized ;
fn from_secret_key ( private_key : & [ u8 ]) -> Self
where
Self : Sized ;
}
Source: commons/crypto/mod.rs:90-113
Seed Creation
Seeds are validated and padded to 32 bytes:
pub fn create_seed ( initial_seed : & [ u8 ]) -> Result <[ u8 ; 32 ], Error > {
let mut seed = [ 0 u8 ; 32 ];
if initial_seed . is_empty () {
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 )
}
Source: commons/crypto/mod.rs:266-277
Seeds longer than 32 bytes will be rejected. Empty seeds trigger secure random generation.
From Hex String
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 (),
))),
}
}
Source: commons/crypto/mod.rs:39-48
Ed25519 Implementation
Ed25519 is the primary cryptographic scheme:
pub type Ed25519KeyPair = BaseKeyPair < PublicKey , SecretKey >;
impl KeyGenerator for Ed25519KeyPair {
fn from_seed ( seed : & [ u8 ]) -> Self {
let secret_seed = create_seed ( seed ) . expect ( "invalid seed" );
let sk : SecretKey =
SecretKey :: from_bytes ( & secret_seed ) . expect ( "cannot generate secret key" );
let pk : PublicKey = ( & sk ) . try_into () . expect ( "cannot generate public key" );
Self {
public_key : pk ,
secret_key : Some ( sk ),
}
}
fn from_public_key ( public_key : & [ u8 ]) -> Self {
Self {
public_key : PublicKey :: from_bytes ( public_key ) . expect ( "cannot generate public key" ),
secret_key : None ,
}
}
fn from_secret_key ( secret_key : & [ u8 ]) -> Ed25519KeyPair {
let sk : SecretKey = SecretKey :: from_bytes ( secret_key ) . expect ( "cannot generate secret key" );
let pk : PublicKey = ( & sk ) . try_into () . expect ( "cannot generate public key" );
Ed25519KeyPair {
secret_key : Some ( sk ),
public_key : pk ,
}
}
}
Source: commons/crypto/ed25519.rs:16-46
Ed25519 Signing
impl DSA for Ed25519KeyPair {
fn sign ( & self , payload : Payload ) -> Result < Vec < u8 >, Error > {
let esk : ExpandedSecretKey = match & self . secret_key {
Some ( x ) => x ,
None => return Err ( Error :: SignError ( "Secret key not found" . to_owned ())),
}
. into ();
match payload {
Payload :: Buffer ( msg ) => Ok ( esk
. sign ( msg . as_slice (), & self . public_key)
. to_bytes ()
. to_vec ()),
_ => Err ( Error :: SignError (
"Payload type not supported for this key" . into (),
)),
}
}
fn verify ( & self , payload : Payload , signature : & [ u8 ]) -> Result <(), Error > {
let sig = Signature :: try_from ( signature )
. map_err ( | _ | Error :: SignError ( "Invalid signature data" . into ())) ? ;
match payload {
Payload :: Buffer ( payload ) => match self . public_key . verify ( payload . as_slice (), & sig ) {
Ok ( _ ) => Ok (()),
_ => Err ( Error :: SignError ( "Signature verify failed" . into ())),
},
_ => Err ( Error :: SignError (
"Payload type not supported for this key" . into (),
)),
}
}
}
Source: commons/crypto/ed25519.rs:67-98
Digital Signature Algorithm (DSA)
All key pairs implement the DSA trait:
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 >;
}
Source: commons/crypto/mod.rs:116-122
Payload Types
pub enum Payload {
Buffer ( Vec < u8 >),
#[allow(dead_code)]
BufferArray ( Vec < Vec < u8 >>),
}
Source: commons/crypto/mod.rs:258-263
KeyPair DSA Implementation
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 ),
}
}
}
Source: commons/crypto/mod.rs:179-202
Key Material
Extracting key bytes:
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 )
}
}
Source: commons/crypto/mod.rs:72-86
KeyPair Material Implementation
impl KeyMaterial for KeyPair {
fn public_key_bytes ( & self ) -> Vec < u8 > {
match self {
KeyPair :: Ed25519 ( x ) => x . public_key_bytes (),
#[cfg(feature = "secp256k1" )]
KeyPair :: Secp256k1 ( x ) => x . public_key_bytes (),
}
}
fn secret_key_bytes ( & self ) -> Vec < u8 > {
match self {
KeyPair :: Ed25519 ( x ) => x . secret_key_bytes (),
#[cfg(feature = "secp256k1" )]
KeyPair :: Secp256k1 ( x ) => x . secret_key_bytes (),
}
}
fn to_bytes ( & self ) -> Vec < u8 > {
match self {
KeyPair :: Ed25519 ( x ) => x . to_bytes (),
#[cfg(feature = "secp256k1" )]
KeyPair :: Secp256k1 ( x ) => x . to_bytes (),
}
}
}
Source: commons/crypto/mod.rs:147-177
Identifiers
TAPLE uses three types of cryptographic identifiers:
Key Identifier
Derived from public keys:
pub struct KeyIdentifier {
// Internal structure
}
impl KeyIdentifier {
pub fn new ( derivator : KeyDerivator , public_key : & [ u8 ]) -> Self {
// Creates identifier from public key
}
}
Source: commons/identifier/key_identifier.rs (referenced)
Digest Identifier
Cryptographic hash of data:
pub struct DigestIdentifier {
// Internal structure
}
Source: commons/identifier/digest_identifier.rs (referenced)
Signature Identifier
Represents a cryptographic signature:
pub struct SignatureIdentifier {
// Internal structure
}
Source: commons/identifier/signature_identifier.rs (referenced)
Derivable Trait
All identifiers implement the Derivable trait:
pub trait Derivable : FromStr < Err = Error > {
fn derivative ( & self ) -> Vec < u8 >;
fn derivation_code ( & self ) -> String ;
fn to_str ( & self ) -> String {
match self . derivative () . len () {
0 => "" . to_string (),
_ => [
self . derivation_code (),
encode_config ( self . derivative (), base64 :: URL_SAFE_NO_PAD ),
]
. join ( "" ),
}
}
}
Source: commons/identifier/mod.rs:46-61
Identifiers are base64-encoded with a derivation code prefix, making them self-describing and URL-safe.
Digest Derivators
TAPLE supports multiple hash algorithms:
pub enum DigestDerivator {
Blake3_256 ,
Blake3_512 ,
SHA2_256 ,
SHA2_512 ,
SHA3_256 ,
SHA3_512 ,
}
Source: commons/identifier/derive/digest.rs (referenced)
Creating Digest Identifiers
impl DigestIdentifier {
pub fn from_serializable_borsh < T : BorshSerialize >(
content : & T ,
derivator : DigestDerivator
) -> Result < Self , Error > {
// Serializes content and hashes it
}
pub fn generate_with_blake3 < T : BorshSerialize >( content : & T ) -> Result < Self , Error > {
Self :: from_serializable_borsh ( content , DigestDerivator :: Blake3_256 )
}
}
Source: commons/identifier/digest_identifier.rs (referenced)
Signatures
Creating and verifying signatures:
pub struct Signature {
pub signer : KeyIdentifier ,
pub timestamp : TimeStamp ,
pub content_hash : DigestIdentifier ,
pub value : SignatureIdentifier ,
}
impl Signature {
pub fn new < T : HashId >(
content : & T ,
key_pair : & KeyPair ,
derivator : DigestDerivator ,
) -> Result < Self , Error > {
// Creates signature
}
pub fn verify < T : HashId >( & self , content : & T ) -> Result <(), Error > {
// Verifies signature
}
}
Source: commons/models/signature.rs (referenced)
Node Key Registration
Nodes register their cryptographic identity:
fn register_node_key (
key_derivator : & KeyDerivator ,
secret_key : & str ,
db : DB < C >,
) -> Result < KeyPair , Error > {
let key = KeyPair :: from_hex ( key_derivator , secret_key )
. map_err ( | _ | Error :: InvalidHexString )
. unwrap ();
let identifier =
KeyIdentifier :: new ( key . get_key_derivator (), & key . public_key_bytes ()) . to_str ();
let stored_identifier = db . get_controller_id () . ok ();
if let Some ( stored_identifier ) = stored_identifier {
if identifier != stored_identifier {
error! ( "Invalid key. There is a differente key stored" );
return Err ( Error :: InvalidKeyPairSpecified ( stored_identifier ));
}
} else {
db . set_controller_id ( identifier )
. map_err ( | e | Error :: DatabaseError ( e . to_string ())) ? ;
}
Ok ( key )
}
Source: node.rs:418-439
Once a node key is registered, attempting to use a different key will result in an error. This prevents accidental identity changes.
Serialization
Key pairs are serializable for storage:
impl BorshSerialize for KeyPair {
#[inline]
fn serialize < W : std :: io :: Write >( & self , writer : & mut W ) -> std :: io :: Result <()> {
match & self {
KeyPair :: Ed25519 ( x ) => {
BorshSerialize :: serialize ( & 0 u8 , writer ) ? ;
let a : [ u8 ; 32 ] = x . secret_key_bytes () . try_into () . unwrap ();
BorshSerialize :: serialize ( & a , writer )
}
KeyPair :: Secp256k1 ( x ) => {
BorshSerialize :: serialize ( & 1 u8 , writer ) ? ;
let a : [ u8 ; 32 ] = x . secret_key_bytes () . try_into () . unwrap ();
BorshSerialize :: serialize ( & a , writer )
}
}
}
}
impl BorshDeserialize for KeyPair {
#[inline]
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 Value representation: {}" , order ),
)),
}
}
}
Source: commons/crypto/mod.rs:204-241
Best Practices
Key Storage Store private keys securely, never in plain text
Ed25519 Default Use Ed25519 unless you have specific Secp256k1 requirements
Seed Management Use cryptographically secure random seeds in production
Signature Verification Always verify signatures before trusting data
Example: Creating and Using Keys
use taple_core :: crypto ::* ;
use taple_core :: identifier :: derive :: KeyDerivator ;
// Generate a new Ed25519 key pair
let key_pair = KeyPair :: Ed25519 ( Ed25519KeyPair :: new ());
// Get public key bytes
let public_key = key_pair . public_key_bytes ();
// Sign data
let message = b"Important data" ;
let signature = key_pair . sign ( Payload :: Buffer ( message . to_vec ())) . unwrap ();
// Verify signature
let result = key_pair . verify ( Payload :: Buffer ( message . to_vec ()), & signature );
assert! ( result . is_ok ());
// Create key identifier
let key_id = KeyIdentifier :: new (
key_pair . get_key_derivator (),
& key_pair . public_key_bytes ()
);
println! ( "Key ID: {}" , key_id . to_str ());
Next Steps
Architecture Understand how cryptography fits in the architecture
Events Learn how events are signed and verified