A type-safe, template-based blockchain library for C++20 with generic participant authentication and ledger tracking capabilities.
- Header-Only Design: Fully inline functions, no separate compilation required
- Generic Template Design: Create blockchains for any data type that has a
to_string()
method - Type Safety: Compile-time enforcement that data types implement required methods
- Transaction Management: Create, sign, and validate transactions with custom data types
- Block Structure: Organize transactions into blocks with cryptographic hashing
- Blockchain: Chain blocks together with hash references and validation
- Digital Signatures: RSA-based signing and verification using the Lockey library
- Timestamp Precision: Nanosecond-precision timestamps for ordering
- Priority System: Transaction priority levels (0-255)
- SFINAE Type Checking: Ensures data types have required
to_string()
method at compile time - 🆕 Generic Authenticator: Universal participant management for any use case
- 🆕 Capability-Based Authorization: Flexible permission system for different roles
- 🆕 Metadata Management: Store and retrieve participant specifications and parameters
- 🆕 State Tracking: Monitor participant states (active, maintenance, idle, etc.)
- 🆕 Double-Spend Prevention: Duplicate transaction detection and prevention
- 🆕 Merkle Trees: Efficient transaction verification with cryptographic proofs
- 🆕 Enhanced Block Validation: Comprehensive cryptographic integrity checks
- 🆕 Unified Serialization System: Dual-format serialization supporting both binary and JSON
- 🆕 Binary Serialization: High-performance, compact binary format with 37% size reduction
- 🆕 JSON Serialization: Human-readable format for debugging and interoperability
- 🆕 Format Auto-Detection: Automatic detection and handling of both serialization formats
- 🆕 Persistent Storage: Complete file I/O for blockchain persistence in both formats
- 🆕 Backward Compatibility: Existing JSON-based code continues to work unchanged
- 🆕 Performance Optimization: Binary format offers 3.7x faster serialization speed
The library features a comprehensive dual-format serialization architecture that provides both high-performance binary and human-readable JSON serialization:
- 🚀 High Performance: Binary format offers 37% size reduction and 3.7x faster serialization
- 🔄 Dual Format Support: Choose JSON for readability or binary for performance
- 🤖 Auto-Detection: Automatic format detection and handling
- 🔧 Type-Aware: SFINAE detection automatically selects optimal serialization method
- ⬅️ Backward Compatible: Existing JSON-based code continues to work unchanged
- 🛠️ Endian-Safe: Cross-platform binary format with proper byte ordering
Format | Size | Serialization | Deserialization |
---|---|---|---|
JSON | 5,596 bytes | 11 μs | 23 μs |
Binary | 3,481 bytes | 3 μs | 1 μs |
Improvement | -37.8% | +267% | +2300% |
This library is designed for ledger tracking and coordination systems across various industries:
- 🤖 Robotics: Multi-robot coordination with command authorization
- 🚜 Agriculture: Equipment tracking, sensor networks, maintenance logs
- 🏭 Manufacturing: Production line coordination and quality control
- 🏥 Healthcare: Medical device coordination and patient data integrity
- ⚡ Energy: Smart grid management and meter readings
- 🚛 Supply Chain: Asset tracking and verification
- 🏙️ Smart Cities: Infrastructure monitoring and management
- 🔬 Research: Data integrity and experiment tracking
The library uses templates throughout - Transaction<T>
, Block<T>
, and Chain<T>
where T
must have a to_string()
method.
#include "blokit/blokit.hpp"
#include <memory>
// First, create a wrapper for types that don't have to_string()
class StringWrapper {
private:
std::string value_;
public:
StringWrapper(const std::string& str) : value_(str) {}
StringWrapper(const char* str) : value_(str) {}
std::string to_string() const { return value_; }
};
// Create a crypto instance for signing
auto privateKey = std::make_shared<chain::Crypto>("key_file");
// Create a blockchain using StringWrapper
chain::Chain<StringWrapper> blockchain("my-chain", "genesis-tx", StringWrapper("genesis_data"), privateKey);
// Register participants in the blockchain
blockchain.registerParticipant("device-001", "active", {{"type", "sensor"}, {"location", "field_A"}});
blockchain.registerParticipant("device-002", "active", {{"type", "actuator"}, {"location", "field_B"}});
// Grant capabilities to participants
blockchain.grantCapability("device-001", "READ_DATA");
blockchain.grantCapability("device-002", "WRITE_DATA");
// Create a transaction
chain::Transaction<StringWrapper> tx("tx-001", StringWrapper("transfer_funds"), 100);
tx.signTransaction(privateKey);
// Create a block with transactions
std::vector<chain::Transaction<StringWrapper>> transactions = {tx};
chain::Block<StringWrapper> block(transactions);
// Add block to blockchain (with duplicate detection)
blockchain.addBlock(block);
// Save blockchain to file
blockchain.saveToFile("my_blockchain.json");
// Load blockchain from file
chain::Chain<StringWrapper> loadedChain;
loadedChain.loadFromFile("my_blockchain.json");
// Validate the chain (with enhanced cryptographic checks)
bool isValid = blockchain.isValid();
Any type can be used as long as it implements a to_string()
method. For enhanced serialization capabilities, optionally implement both serialize()
/deserialize()
methods for JSON and serializeBinary()
/deserializeBinary()
methods for binary format:
// Custom data type for payments with dual serialization support
struct PaymentData {
std::string from, to;
double amount;
// Required: must have to_string() method
std::string to_string() const {
return "Payment{from:" + from + ",to:" + to + ",amount:" + std::to_string(amount) + "}";
}
// Optional: JSON serialization for human-readable format
std::string serialize() const {
return R"({"from": ")" + from + R"(", "to": ")" + to +
R"(", "amount": )" + std::to_string(amount) + "}";
}
static PaymentData deserialize(const std::string& data) {
// Parse JSON data and return PaymentData instance
PaymentData result;
// ... JSON parsing implementation ...
return result;
}
// Optional: Binary serialization for high-performance format
std::vector<uint8_t> serializeBinary() const {
std::vector<uint8_t> buffer;
chain::BinarySerializer::writeString(buffer, from);
chain::BinarySerializer::writeString(buffer, to);
chain::BinarySerializer::writeDouble(buffer, amount);
return buffer;
}
static PaymentData deserializeBinary(const std::vector<uint8_t>& data) {
PaymentData result;
size_t offset = 0;
result.from = chain::BinarySerializer::readString(data, offset);
result.to = chain::BinarySerializer::readString(data, offset);
result.amount = chain::BinarySerializer::readDouble(data, offset);
return result;
}
};
// Create a blockchain for custom payment data
PaymentData payment{"Alice", "Bob", 100.50};
chain::Chain<PaymentData> paymentChain("payment-chain", "pay-genesis", payment, privateKey);
// Create transaction with custom data
chain::Transaction<PaymentData> payTx("pay-001", PaymentData{"Bob", "Charlie", 50.25}, 150);
payTx.signTransaction(privateKey);
// Save using JSON format (default, backward compatible)
paymentChain.saveToFile("payments.json");
// Transactions and blocks can use either format:
std::string jsonTx = payTx.serialize(); // JSON format
std::vector<uint8_t> binaryTx = payTx.serializeBinary(); // Binary format
// Auto-detection works with both formats
auto deserializedTx = chain::Transaction<PaymentData>::deserializeAuto(binaryTx);
The template system uses SFINAE to ensure your type has a to_string()
method:
// ✅ This works - has to_string() method
struct ValidType {
int value;
std::string to_string() const { return std::to_string(value); }
};
// ❌ This fails at compile time - no to_string() method
struct InvalidType {
int value;
// Missing to_string() method!
};
// Compile-time error with helpful message:
// "Type T must have a to_string() method"
chain::Transaction<InvalidType> tx; // Won't compile!
The library provides a comprehensive dual-format serialization system supporting both high-performance binary and human-readable JSON formats:
// Create and populate a blockchain
chain::Chain<StringWrapper> blockchain("my-chain", "genesis", StringWrapper("data"), privateKey);
// Add some transactions and blocks
// ... blockchain operations ...
// Traditional JSON serialization (backward compatible)
bool saved = blockchain.saveToFile("blockchain.json");
// Individual components support both formats:
std::string jsonData = transaction.serialize(); // JSON (default)
std::string explicitJson = transaction.serializeJson(); // Explicit JSON
std::vector<uint8_t> binaryData = transaction.serializeBinary(); // Binary
// Auto-detection handles both formats seamlessly
auto tx1 = chain::Transaction<T>::deserializeAuto(binaryData); // Detects binary
auto tx2 = chain::Transaction<T>::deserializeAuto(jsonAsBytes); // Detects JSON
The binary format offers significant performance advantages:
Metric | JSON Format | Binary Format | Improvement |
---|---|---|---|
File Size | 5,596 bytes | 3,481 bytes | 37.8% smaller |
Serialization Speed | 11 μs | 3 μs | 3.7x faster |
Deserialization Speed | 23 μs | 1 μs | 23x faster |
Choose Binary Format When:
- ✅ Performance is critical
- ✅ Storage space is limited
- ✅ High-frequency operations
- ✅ Network transmission efficiency matters
Choose JSON Format When:
- ✅ Human readability is important
- ✅ Debugging and inspection needed
- ✅ Interoperability with other systems
- ✅ Legacy system compatibility
// Type-aware serialization system
chain::TypeSerializer<PaymentData> serializer;
// Check format capabilities at runtime
bool supportsBinary = serializer.supportsBinary(); // true if serializeBinary() exists
bool supportsJson = serializer.supportsJson(); // true if serialize() exists
// Serialize using the optimal format for the type
if (supportsBinary) {
auto binaryData = serializer.serializeBinary(payment);
// Binary format: ~37% smaller, much faster
} else {
auto jsonData = serializer.serializeJson(payment);
// Fallback to JSON format
}
// Format auto-detection with error handling
try {
auto payment = chain::Transaction<PaymentData>::deserializeAuto(data);
// Automatically detects and handles both binary and JSON formats
} catch (const std::exception& e) {
// Handle unsupported or corrupted format
}
// Load blockchain from file (auto-detects format)
chain::Chain<StringWrapper> loadedChain;
bool loaded = loadedChain.loadFromFile("blockchain.json");
// Verify data integrity
bool isValid = loadedChain.isValid();
All blockchain components support both serialization formats:
// JSON serialization (default for backward compatibility)
std::string txJson = transaction.serialize();
std::string blockJson = block.serialize();
std::string chainJson = blockchain.serialize();
// Binary serialization (new high-performance format)
std::vector<uint8_t> txBinary = transaction.serializeBinary();
std::vector<uint8_t> blockBinary = block.serializeBinary();
// Mixed deserialization with auto-detection
auto txFromJson = chain::Transaction<T>::deserialize(txJson);
auto txFromBinary = chain::Transaction<T>::deserializeBinary(txBinary);
auto txAutoDetected = chain::Transaction<T>::deserializeAuto(binaryData);
// Both formats produce identical results
assert(txFromJson.uuid_ == txFromBinary.uuid_);
For optimal performance and flexibility, implement both serialization methods in your custom types:
struct SensorData {
std::string sensor_id;
double temperature;
int64_t timestamp;
// Required for basic blockchain operations
std::string to_string() const {
return "Sensor{" + sensor_id + ":" + std::to_string(temperature) + "}";
}
// JSON serialization (human-readable, interoperable)
std::string serialize() const {
return R"({"sensor_id": ")" + sensor_id +
R"(", "temperature": )" + std::to_string(temperature) +
R"(, "timestamp": )" + std::to_string(timestamp) + "}";
}
static SensorData deserialize(const std::string& data) {
SensorData result;
// Parse JSON and populate fields
// ... JSON parsing implementation ...
return result;
}
// Binary serialization (high-performance, compact)
std::vector<uint8_t> serializeBinary() const {
std::vector<uint8_t> buffer;
chain::BinarySerializer::writeString(buffer, sensor_id);
chain::BinarySerializer::writeDouble(buffer, temperature);
chain::BinarySerializer::writeInt64(buffer, timestamp);
return buffer;
}
static SensorData deserializeBinary(const std::vector<uint8_t>& data) {
SensorData result;
size_t offset = 0;
result.sensor_id = chain::BinarySerializer::readString(data, offset);
result.temperature = chain::BinarySerializer::readDouble(data, offset);
result.timestamp = chain::BinarySerializer::readInt64(data, offset);
return result;
}
};
The library provides a comprehensive BinarySerializer
class for endian-safe binary operations:
// Writing data
std::vector<uint8_t> buffer;
chain::BinarySerializer::writeString(buffer, "hello"); // Strings with length prefix
chain::BinarySerializer::writeDouble(buffer, 3.14159); // IEEE 754 double
chain::BinarySerializer::writeUInt32(buffer, 42); // 32-bit unsigned integer
chain::BinarySerializer::writeBytes(buffer, binaryData); // Raw byte arrays
// Reading data
size_t offset = 0;
std::string str = chain::BinarySerializer::readString(buffer, offset);
double value = chain::BinarySerializer::readDouble(buffer, offset);
uint32_t number = chain::BinarySerializer::readUInt32(buffer, offset);
auto bytes = chain::BinarySerializer::readBytes(buffer, offset, length);
- Dual Format Support: Choose between JSON (human-readable) and binary (high-performance)
- Format Auto-Detection: Automatic detection and handling of both serialization formats
- Backward Compatibility: Existing JSON-based code continues to work unchanged
- Performance Optimization: Binary format offers 37% size reduction and 3.7x speed improvement
- Endian Safety: Cross-platform binary format with proper byte ordering
- Type Safety: SFINAE detection automatically selects appropriate serialization methods
- Binary Signatures: Base64 encoding for cryptographic signatures in JSON format
- Data Integrity: Validation on load to ensure blockchain consistency
- Error Handling: Comprehensive error reporting for file operations
- Memory Efficient: Streaming approach for large blockchain files
- Cross-Platform: Compatible with all major operating systems
The library includes comprehensive serialization tests:
cd build
./test_storage # Run all serialization and storage tests
The serialization test suite covers:
- Binary vs JSON Transaction Serialization: Format comparison and data integrity
- Binary vs JSON Block Serialization: Complete block state preservation
- Performance Comparison: Speed and size benchmarks between formats
- Format Auto-Detection: Automatic format identification and handling
- Unified Serialization System Integration: End-to-end testing with type detection
- Basic serialization/deserialization
- File save and load operations
- Database integration simulation
- Block pruning and archival
- State snapshot management
// Farming operation data structure with dual serialization
struct FarmingOperation {
std::string equipment_id, operation_type, field_location, crop_type;
double area_covered;
std::string to_string() const { /* implementation */<
5D32
/span> }
// JSON serialization for human-readable logs
std::string serialize() const { /* JSON implementation */ }
static FarmingOperation deserialize(const std::string& data) { /* implementation */ }
// Binary serialization for high-frequency sensor data
std::vector<uint8_t> serializeBinary() const {
std::vector<uint8_t> buffer;
chain::BinarySerializer::writeString(buffer, equipment_id);
chain::BinarySerializer::writeString(buffer, operation_type);
chain::BinarySerializer::writeString(buffer, field_location);
chain::BinarySerializer::writeString(buffer, crop_type);
chain::BinarySerializer::writeDouble(buffer, area_covered);
return buffer;
}
static FarmingOperation deserializeBinary(const std::vector<uint8_t>& data) {
FarmingOperation result;
size_t offset = 0;
result.equipment_id = chain::BinarySerializer::readString(data, offset);
result.operation_type = chain::BinarySerializer::readString(data, offset);
result.field_location = chain::BinarySerializer::readString(data, offset);
result.crop_type = chain::BinarySerializer::readString(data, offset);
result.area_covered = chain::BinarySerializer::readDouble(data, offset);
return result;
}
};
// Create blockchain for farming operations
chain::Chain<FarmingOperation> farmChain("farm-ops", "genesis", genesis_op, privateKey);
// Register equipment with metadata
farmChain.registerParticipant("tractor-001", "ready", {
{"model", "John_Deere_8370R"}, {"fuel_capacity", "680L"}
});
// Grant capabilities
farmChain.grantCapability("tractor-001", "TILLAGE");
farmChain.grantCapability("tractor-001", "SEEDING");
// Save using appropriate format based on use case
farmChain.saveToFile("farm_operations.json"); // JSON for reports
// Binary format for high-frequency sensor data would be handled at transaction level
// Sensor reading data structure with optimized dual serialization
struct SensorReading {
std::string sensor_id, sensor_type, location;
double value;
std::string unit;
int64_t timestamp;
std::string to_string() const { /* implementation */ }
// JSON serialization for configuration and debugging
std::string serialize() const {
return R"({"sensor_id": ")" + sensor_id +
R"(", "sensor_type": ")" + sensor_type +
R"(", "location": ")" + location +
R"(", "value": )" + std::to_string(value) +
R"(, "unit": ")" + unit +
R"(", "timestamp": )" + std::to_string(timestamp) + R"("})";
}
static SensorReading deserialize(const std::string& data) { /* implementation */ }
// Binary serialization for high-frequency data logging (37% smaller, 23x faster)
std::vector<uint8_t> serializeBinary() const {
std::vector<uint8_t> buffer;
chain::BinarySerializer::writeString(buffer, sensor_id);
chain::BinarySerializer::writeString(buffer, sensor_type);
chain::BinarySerializer::writeString(buffer, location);
chain::BinarySerializer::writeDouble(buffer, value);
chain::BinarySerializer::writeString(buffer, unit);
chain::BinarySerializer::writeInt64(buffer, timestamp);
return buffer;
}
static SensorReading deserializeBinary(const std::vector<uint8_t>& data) {
SensorReading result;
size_t offset = 0;
result.sensor_id = chain::BinarySerializer::readString(data, offset);
result.sensor_type = chain::BinarySerializer::readString(data, offset);
result.location = chain::BinarySerializer::readString(data, offset);
result.value = chain::BinarySerializer::readDouble(data, offset);
result.unit = chain::BinarySerializer::readString(data, offset);
result.timestamp = chain::BinarySerializer::readInt64(data, offset);
return result;
}
};
// Create blockchain for sensor data
chain::Chain<SensorReading> sensorChain("sensors", "init", init_reading, privateKey);
// Register sensors with metadata
sensorChain.registerParticipant("soil-sensor-001", "active", {
{"type", "soil_moisture"}, {"field", "Field_A"}, {"depth", "15cm"}
});
sensorChain.grantCapability("soil-sensor-001", "READ_SOIL_MOISTURE");
// Flexible serialization based on use case:
// JSON for configuration files and human-readable reports
sensorChain.saveToFile("sensor_config.json");
// Binary format for high-frequency sensor data transactions
chain::Transaction<SensorReading> sensorTx("reading-001", highFrequencyReading, 100);
std::vector<uint8_t> compactData = sensorTx.serializeBinary(); // 37% smaller, 23x faster
// Robot command data structure
struct RobotCommand {
std::string issuer_robot, target_robot, command;
int priority_level;
std::string to_string() const { /* implementation */ }
};
// Create blockchain for robot coordination
chain::Chain<RobotCommand> robotChain("robots", "init", init_cmd, privateKey);
// Register robots and grant capabilities
robotChain.registerParticipant("robot-001", "idle");
robotChain.grantCapability("robot-001", "MOVE");
robotChain.grantCapability("robot-001", "PICK");
// Save coordination state for system recovery
robotChain.saveToFile("robot_coordination_state.json");
mkdir build && cd build
cmake ..
make
cd build
./main # Original basic demo with persistent storage
./enhanced_demo # Robot coordination demo
./farming_demo # Agricultural industry demo
./test_storage # Comprehensive storage functionality tests
The original example demonstrates:
- Generic template usage with
StringWrapper
- Transaction creation and signing with custom data types
- Block creation and validation
- Blockchain construction and management
- Cryptographic operations
- Persistent Storage: Save and load blockchain to/from JSON files
- Data Serialization: Complete serialize/deserialize functionality
- Various advanced scenarios
- Type safety and compile-time checking
The enhanced example demonstrates:
- Robot Coordination: Multiple robots working together through blockchain authorization
- Entity Management: Registration, permissions, and state tracking
- Ledger Tracking: Immutable logging for sensors, actuators, and controllers
- Double-Spend Prevention: Automatic detection and rejection of duplicate commands
- Merkle Trees: Efficient verification of individual transactions in large blocks
The farming example demonstrates:
- Equipment Operations: Tractors, sprayers, harvesters with metadata and capabilities
- Sensor Networks: Soil moisture, weather stations, drone data collection
- Maintenance Ledger: Technician records and equipment service tracking
- Generic Authenticator: Same system adapted for different agricultural use cases
- Capability-Based Authorization: Role-based permissions for different equipment types
- Data Persistence: Long-term storage of agricultural operation records
The storage tests demonstrate:
- Binary vs JSON Transaction Serialization: Complete comparison of both formats with data integrity verification
- Binary vs JSON Block 7296 Serialization: Full block state preservation in both formats
- Performance Comparison: Comprehensive benchmarks showing 37% size reduction and 3.7x speed improvement with binary format
- Format Auto-Detection: Automatic format identification and seamless handling of both formats
- Unified Serialization System Integration: End-to-end testing of the complete dual-format system
- Type Capability Detection: SFINAE-based detection of serialization capabilities
- Transaction Serialization: Individual transaction save/load with signature preservation
- Block Serialization: Complete block state persistence including Merkle trees
- Blockchain Serialization: Full chain state with participant data and metadata
- File I/O Operations: Robust error handling and data validation
- Database Simulation: Integration patterns for production database systems
- Archival Systems: Block pruning and long-term storage strategies
- State Snapshots: Point-in-time blockchain state capture and restoration
This implementation is still not production ready and lacks several critical features for production use:
- ❌ No Proof of Work or consensus mechanism
- ❌ No network layer for distributed operation
- ✅
No persistent storageComplete unified serialization system with both binary and JSON formats implemented - ✅
Limited transaction validationEnhanced transaction validation with entity permissions - ✅
No Merkle trees for efficient verificationMerkle trees implemented for efficient verification - ✅
No protection against double spendingDouble-spend prevention implemented - ❌ No difficulty adjustment
- Fork resolution
- Lockey: Cryptographic library for signing and verification
- C++20: Modern C++ features
- CMake 3.15+: Build system
This project is open source. See the license file for details.