A complete blockchain implementation featuring: - synord: Full node with GHOSTDAG consensus - explorer-web: Modern React blockchain explorer with 3D DAG visualization - CLI wallet and tools - Smart contract SDK and example contracts (DEX, NFT, token) - WASM crypto library for browser/mobile
225 lines
7.1 KiB
Rust
225 lines
7.1 KiB
Rust
//! Signature types for Synor blockchain.
|
|
//!
|
|
//! All signatures in Synor use the hybrid Ed25519+Dilithium3 scheme for
|
|
//! quantum-resistant security. Both components must be valid for a signature
|
|
//! to be considered valid.
|
|
|
|
use borsh::{BorshDeserialize, BorshSerialize};
|
|
use serde::{Deserialize, Serialize};
|
|
use thiserror::Error;
|
|
|
|
/// Ed25519 signature size in bytes.
|
|
pub const ED25519_SIGNATURE_SIZE: usize = 64;
|
|
|
|
/// A hybrid signature combining Ed25519 and Dilithium3.
|
|
///
|
|
/// Both signatures must be valid for the overall signature to be valid.
|
|
/// This provides security against both classical and quantum attacks:
|
|
/// - Ed25519: Fast, compact, proven security against classical computers
|
|
/// - Dilithium3: NIST-standardized, quantum-resistant lattice-based signature
|
|
///
|
|
/// An attacker must break BOTH algorithms to forge a signature.
|
|
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
|
pub struct HybridSignature {
|
|
/// Ed25519 signature (64 bytes).
|
|
pub ed25519_signature: Vec<u8>,
|
|
/// Dilithium3 signature (variable length, ~3293 bytes).
|
|
pub dilithium_signature: Vec<u8>,
|
|
}
|
|
|
|
impl HybridSignature {
|
|
/// Creates a new hybrid signature from components.
|
|
pub fn new(ed25519: [u8; 64], dilithium: Vec<u8>) -> Self {
|
|
HybridSignature {
|
|
ed25519_signature: ed25519.to_vec(),
|
|
dilithium_signature: dilithium,
|
|
}
|
|
}
|
|
|
|
/// Creates a new hybrid signature from Vec components.
|
|
pub fn from_vecs(ed25519: Vec<u8>, dilithium: Vec<u8>) -> Self {
|
|
HybridSignature {
|
|
ed25519_signature: ed25519,
|
|
dilithium_signature: dilithium,
|
|
}
|
|
}
|
|
|
|
/// Returns the total size of the signature in bytes.
|
|
pub fn size(&self) -> usize {
|
|
self.ed25519_signature.len() + self.dilithium_signature.len()
|
|
}
|
|
|
|
/// Returns the Ed25519 signature component.
|
|
pub fn ed25519(&self) -> &[u8] {
|
|
&self.ed25519_signature
|
|
}
|
|
|
|
/// Returns the Dilithium signature component.
|
|
pub fn dilithium(&self) -> &[u8] {
|
|
&self.dilithium_signature
|
|
}
|
|
|
|
/// Returns the Ed25519 component bytes (for compatibility).
|
|
pub fn ed25519_bytes(&self) -> &[u8] {
|
|
&self.ed25519_signature
|
|
}
|
|
|
|
/// Returns the Ed25519 component as a fixed-size array if valid.
|
|
pub fn ed25519_array(&self) -> Option<[u8; 64]> {
|
|
if self.ed25519_signature.len() != 64 {
|
|
return None;
|
|
}
|
|
let mut arr = [0u8; 64];
|
|
arr.copy_from_slice(&self.ed25519_signature);
|
|
Some(arr)
|
|
}
|
|
|
|
/// Returns the Dilithium component bytes.
|
|
pub fn dilithium_bytes(&self) -> &[u8] {
|
|
&self.dilithium_signature
|
|
}
|
|
|
|
/// Serializes the signature to bytes.
|
|
pub fn to_bytes(&self) -> Vec<u8> {
|
|
let mut bytes = Vec::with_capacity(self.size());
|
|
bytes.extend_from_slice(&self.ed25519_signature);
|
|
bytes.extend_from_slice(&self.dilithium_signature);
|
|
bytes
|
|
}
|
|
|
|
/// Deserializes a signature from bytes.
|
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignatureError> {
|
|
if bytes.len() < ED25519_SIGNATURE_SIZE {
|
|
return Err(SignatureError::InvalidLength {
|
|
expected: ED25519_SIGNATURE_SIZE,
|
|
got: bytes.len(),
|
|
});
|
|
}
|
|
|
|
let ed25519 = bytes[..ED25519_SIGNATURE_SIZE].to_vec();
|
|
let dilithium = bytes[ED25519_SIGNATURE_SIZE..].to_vec();
|
|
|
|
// Validate that we have a Dilithium signature
|
|
if dilithium.is_empty() {
|
|
return Err(SignatureError::MissingComponent(
|
|
"Dilithium signature required for hybrid signature".to_string(),
|
|
));
|
|
}
|
|
|
|
Ok(HybridSignature {
|
|
ed25519_signature: ed25519,
|
|
dilithium_signature: dilithium,
|
|
})
|
|
}
|
|
|
|
/// Returns true (all signatures are hybrid).
|
|
pub fn is_hybrid(&self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for HybridSignature {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let ed_preview = if self.ed25519_signature.len() >= 8 {
|
|
hex::encode(&self.ed25519_signature[..8])
|
|
} else {
|
|
hex::encode(&self.ed25519_signature)
|
|
};
|
|
f.debug_struct("HybridSignature")
|
|
.field("ed25519", &ed_preview)
|
|
.field("dilithium_len", &self.dilithium_signature.len())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for HybridSignature {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let ed_hex = if self.ed25519_signature.len() >= 8 {
|
|
hex::encode(&self.ed25519_signature[..8])
|
|
} else {
|
|
hex::encode(&self.ed25519_signature)
|
|
};
|
|
write!(f, "HybridSig({}...)", ed_hex)
|
|
}
|
|
}
|
|
|
|
/// Type alias for signature - all Synor signatures are hybrid.
|
|
///
|
|
/// This alias exists for API clarity and future compatibility.
|
|
/// All signatures must contain both Ed25519 and Dilithium components.
|
|
pub type Signature = HybridSignature;
|
|
|
|
/// Errors related to signatures.
|
|
#[derive(Debug, Error)]
|
|
pub enum SignatureError {
|
|
#[error("Invalid signature length: expected at least {expected}, got {got}")]
|
|
InvalidLength { expected: usize, got: usize },
|
|
|
|
#[error("Invalid signature format")]
|
|
InvalidFormat,
|
|
|
|
#[error("Signature verification failed")]
|
|
VerificationFailed,
|
|
|
|
#[error("Missing required component: {0}")]
|
|
MissingComponent(String),
|
|
|
|
#[error("Ed25519 verification failed")]
|
|
Ed25519VerificationFailed,
|
|
|
|
#[error("Dilithium verification failed")]
|
|
DilithiumVerificationFailed,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_hybrid_signature_roundtrip() {
|
|
let sig = HybridSignature::new([42u8; 64], vec![1, 2, 3, 4, 5]);
|
|
|
|
let bytes = sig.to_bytes();
|
|
let recovered = HybridSignature::from_bytes(&bytes).unwrap();
|
|
|
|
assert_eq!(sig.ed25519_signature, recovered.ed25519_signature);
|
|
assert_eq!(sig.dilithium_signature, recovered.dilithium_signature);
|
|
}
|
|
|
|
#[test]
|
|
fn test_signature_borsh() {
|
|
let sig = HybridSignature::new([1u8; 64], vec![2u8; 100]);
|
|
let serialized = borsh::to_vec(&sig).unwrap();
|
|
let deserialized: HybridSignature = borsh::from_slice(&serialized).unwrap();
|
|
|
|
assert_eq!(sig.ed25519_bytes(), deserialized.ed25519_bytes());
|
|
assert_eq!(sig.dilithium_bytes(), deserialized.dilithium_bytes());
|
|
}
|
|
|
|
#[test]
|
|
fn test_signature_size() {
|
|
let sig = HybridSignature::new([0u8; 64], vec![0u8; 3293]);
|
|
assert_eq!(sig.size(), 64 + 3293);
|
|
}
|
|
|
|
#[test]
|
|
fn test_signature_is_always_hybrid() {
|
|
let sig = HybridSignature::new([0u8; 64], vec![0u8; 100]);
|
|
assert!(sig.is_hybrid());
|
|
}
|
|
|
|
#[test]
|
|
fn test_missing_dilithium_fails() {
|
|
// Only Ed25519 bytes, no Dilithium
|
|
let bytes = [0u8; 64];
|
|
let result = HybridSignature::from_bytes(&bytes);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_type_alias() {
|
|
// Signature is now a type alias for HybridSignature
|
|
let sig: Signature = HybridSignature::new([1u8; 64], vec![2u8; 50]);
|
|
assert_eq!(sig.size(), 64 + 50);
|
|
}
|
|
}
|