# promocrypt-core
[](https://github.com/professor93/promocrypt-core/actions/workflows/release.yml)
[](https://crates.io/crates/promocrypt-core)
[](https://opensource.org/licenses/MIT)
[](https://www.rust-lang.org/)
**Core Rust library for cryptographically secure promotional code generation and validation.**
## Overview
`promocrypt-core` is a high-performance library for generating and validating promotional codes with enterprise-grade security. It uses HMAC-SHA256 for code generation and Damm algorithm for check digit validation, ensuring 100% detection of single-character errors and transpositions.
### Key Highlights
- **Cryptographically Secure**: HMAC-SHA256 based generation
- **Two-Key Encryption**: Separate keys for read (machineID) and write (secret) access
- **Machine Binding**: `.bin` files can be bound to specific machines
- **Damm Check Digit**: 100% detection of single errors and transpositions
- **Index-Based Check Position**: Hide check digit location for added security
- **Storage Encryption**: Encrypt codes before database storage
- **FFI Ready**: C-compatible interface for PHP, Python, and other languages
## Features
| Code Generation | HMAC-SHA256 based, deterministic |
| Code Validation | Damm check digit with detailed error reporting |
| Batch Operations | Generate/validate thousands of codes efficiently |
| Prefix/Suffix | Optional formatting for branded codes |
| Separators | Configurable separator positions (e.g., `XXXX-XXXX-XX`) |
| Check Position | Index-based placement (-1=end, 0=start, N=position) |
| Storage Encryption | AES-256-SIV for database storage |
| Two-Key Encryption | MachineID (read) + Secret (write) |
| Machine Binding | Bind `.bin` files to specific machines |
| Counter Modes | File-based, in-bin, or manual |
| Secret Rotation | Change password without invalidating codes |
| History Tracking | Track rotations, masterings, config changes |
| Generation Log | Record all code generation operations |
| Statistics | Capacity, utilization, generation counts |
| FFI/C API | Use from PHP, Python, Go, etc. |
## Installation
### From Source
```bash
git clone https://github.com/professor93/promocrypt-core.git
cd promocrypt-core
cargo build --release
```
### As Cargo Dependency
```toml
[dependencies]
promocrypt-core = "1.0"
```
### Pre-built Binaries
Download from [GitHub Releases](https://github.com/professor93/promocrypt-core/releases):
- `libpromocrypt-linux-x86_64.so` - Linux x86_64
- `libpromocrypt-macos-aarch64.dylib` - macOS Apple Silicon
- `promocrypt.h` - C header file
## Quick Start
### Create and Use a .bin File
```rust
use promocrypt_core::{BinFile, create_config, CounterMode};
// Create a new .bin file
let mut config = create_config("my-campaign");
config.counter_mode = CounterMode::InBin;
let mut bin = BinFile::create("campaign.bin", "my-secret-password", config)?;
// Generate codes
let codes = bin.generate_batch(1000)?;
println!("Generated {} codes", codes.len());
// Validate codes
for code in &codes {
assert!(bin.validate(code).is_valid());
}
```
### Read-Only Access (Validation Only)
```rust
use promocrypt_core::BinFile;
// Open with machine ID (read-only)
let bin = BinFile::open_readonly("campaign.bin")?;
// Validate user input
let result = bin.validate("ABC123DEF0");
if result.is_valid() {
println!("Code is valid!");
} else {
println!("Invalid code: {:?}", result);
}
```
## API Reference
### BinFile Methods
#### Creation and Opening
```rust
// Create new .bin file (requires secret)
BinFile::create(path, secret, config) -> Result<BinFile>
// Open read-only with machine ID
BinFile::open_readonly(path) -> Result<BinFile>
// Open with full access using secret
BinFile::open_with_secret(path, secret) -> Result<BinFile>
```
#### Validation (Both Access Levels)
```rust
// Validate with detailed result
bin.validate(code) -> ValidationResult
// Quick boolean check
bin.is_valid(code) -> bool
// Batch validation
bin.validate_batch(&[codes]) -> Vec<ValidationResult>
```
#### Generation (Requires Secret)
```rust
// Generate single code
bin.generate() -> Result<String>
// Generate batch
bin.generate_batch(count) -> Result<Vec<String>>
// Generate at specific counter (manual mode)
bin.generate_at(counter) -> Result<String>
// Generate batch at specific counter
bin.generate_batch_at(start_counter, count) -> Result<Vec<String>>
```
#### Storage Encryption (Requires Secret)
```rust
// Encrypt code for database storage
bin.encrypt_code(code) -> Result<String>
// Decrypt code from storage
bin.decrypt_code(encrypted) -> Result<String>
// Batch operations
bin.encrypt_codes(&[codes]) -> Result<Vec<String>>
bin.decrypt_codes(&[encrypted]) -> Result<Vec<String>>
// Enable/disable storage encryption
bin.set_storage_encryption(enabled) -> Result<()>
bin.is_storage_encryption_enabled() -> bool
```
#### Counter Management
```rust
// Get current counter
bin.get_counter() -> Result<u64>
// Set counter (requires secret)
bin.set_counter(value) -> Result<()>
// Reserve counter range atomically
bin.reserve_counter_range(count) -> Result<u64>
```
#### Configuration Updates (Requires Secret)
```rust
// Update format options
bin.set_format(CodeFormat) -> Result<()>
// Update check position
bin.set_check_position(CheckPosition) -> Result<()>
// Rotate secret password
bin.rotate_secret(old_secret, new_secret) -> Result<()>
```
#### Machine Management (Requires Secret)
```rust
// Create copy bound to another machine
bin.master_for_machine(output_path, target_machine_id) -> Result<()>
// Export unbound copy
bin.export_unbound(output_path) -> Result<()>
```
#### History and Statistics
```rust
// Get history
bin.get_history() -> &History
bin.export_history() -> String // JSON
bin.clear_history(keep_last: Option<usize>) -> Result<()>
// Get generation log
bin.get_generation_log() -> &[GenerationLogEntry]
bin.export_generation_log() -> String // JSON
bin.clear_generation_log(keep_last: Option<usize>) -> Result<()>
// Get statistics
bin.get_stats() -> BinStats
bin.total_codes_generated() -> u64
```
### Check Position (Index-Based)
The check digit can be placed at any position for added security:
```rust
use promocrypt_core::CheckPosition;
// End (default) - index 9 for 10-char code
let pos = CheckPosition::End; // XXXXXXXXX[C]
// Start - index 0
let pos = CheckPosition::Start; // [C]XXXXXXXXX
// Custom index
let pos = CheckPosition::Index(4); // XXXX[C]XXXXX
let pos = CheckPosition::Index(-3); // XXXXXXX[C]XX
```
### Code Format
```rust
use promocrypt_core::CodeFormat;
// Create format with prefix and suffix
let format = CodeFormat::new()
.with_prefix("PROMO-")
.with_suffix("-2024");
// Result: PROMO-A3KF7NP2XM-2024
// Add separators
let format = CodeFormat::new()
.with_separator('-', vec![4, 8]);
// Result: A3KF-7NP2-XM
// Combined
let format = CodeFormat::new()
.with_prefix("SALE")
.with_suffix("24")
.with_separator('-', vec![4]);
// Result: SALEA3KF-7NP2XM24
```
### Counter Modes
```rust
use promocrypt_core::CounterMode;
// File-based counter (default)
CounterMode::File { path: "./campaign.counter".to_string() }
// In-bin counter (stored in .bin file)
CounterMode::InBin
// Manual counter (caller provides values)
CounterMode::Manual
// External counter (for database-managed counters)
CounterMode::External
```
## Binary File Format
The `.bin` file contains encrypted configuration and keys:
```
Offset Size Field
------ ---- -----
0 8 Magic: "PROMOCRY"
8 1 Version: 0x02
9 1 Flags
10 4 Header CRC32
14 16 Salt
30 48 MachineEncryptedKey
78 48 SecretEncryptedKey
126 4 EncryptedDataLength
130 N EncryptedData (JSON)
130+N 16 AuthTag
146+N 4 MutableLength
150+N M MutableSection (counter)
150+N+M 16 MutableAuthTag
```
## Security
### Two-Key Encryption System
```
data_key (random 32 bytes)
│
┌────────────┴────────────┐
│ │
▼ ▼
AES-GCM(machineID) AES-GCM(secret)
│ │
▼ ▼
machine_encrypted_key secret_encrypted_key
(READ-ONLY access) (FULL access)
```
- **MachineID**: Hardware-derived key for validation only
- **Secret**: User password for generation and configuration
### Machine ID Components
1. MAC addresses (all interfaces, sorted)
2. CPU ID (if available)
3. Root disk serial (if available)
Final ID: `SHA256(components + "promocrypt-machine-id-v1")`
### Cryptographic Algorithms
| Code Generation | HMAC-SHA256 |
| Key Derivation | Argon2id (m=64MB, t=3, p=1) |
| Encryption | AES-256-GCM |
| Storage Encryption | AES-256-SIV (deterministic) |
## FFI/C API
### Basic Usage (C)
```c
#include "promocrypt.h"
int main() {
PromocryptHandle* handle = NULL;
// Open with secret
PromocryptErrorCode err = promocrypt_open_with_secret(
"campaign.bin",
"my-secret",
&handle
);
if (err != PROMOCRYPT_SUCCESS) {
return 1;
}
// Generate a code
char code[64];
err = promocrypt_generate(handle, code, sizeof(code));
if (err == PROMOCRYPT_SUCCESS) {
printf("Generated: %s\n", code);
}
// Validate
if (promocrypt_is_valid(handle, code) == 1) {
printf("Code is valid!\n");
}
// Clean up
promocrypt_close(handle);
return 0;
}
```
### PHP Usage
```php
<?php
$ffi = FFI::cdef(
file_get_contents('promocrypt.h'),
'libpromocrypt.so'
);
$handle = FFI::new('PromocryptHandle*');
$err = $ffi->promocrypt_open_with_secret(
'campaign.bin',
'my-secret',
FFI::addr($handle)
);
if ($err === 0) {
$code = FFI::new('char[64]');
$ffi->promocrypt_generate($handle, $code, 64);
echo "Generated: " . FFI::string($code) . "\n";
$ffi->promocrypt_close($handle);
}
```
### Python Usage
```python
import ctypes
lib = ctypes.CDLL('./libpromocrypt.so')
# Define types
lib.promocrypt_open_with_secret.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p)]
lib.promocrypt_open_with_secret.restype = ctypes.c_int
lib.promocrypt_generate.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint64]
lib.promocrypt_generate.restype = ctypes.c_int
lib.promocrypt_is_valid.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
lib.promocrypt_is_valid.restype = ctypes.c_int
lib.promocrypt_close.argtypes = [ctypes.c_void_p]
# Use the library
handle = ctypes.c_void_p()
err = lib.promocrypt_open_with_secret(
b'campaign.bin',
b'my-secret',
ctypes.byref(handle)
)
if err == 0:
code = ctypes.create_string_buffer(64)
lib.promocrypt_generate(handle, code, 64)
print(f"Generated: {code.value.decode()}")
is_valid = lib.promocrypt_is_valid(handle, code.value)
print(f"Valid: {is_valid == 1}")
lib.promocrypt_close(handle)
```
## Building from Source
### Prerequisites
- Rust 1.89.0 or later
- Cargo
### Build Commands
```bash
# Debug build
cargo build
# Release build
cargo build --release
# Build with FFI support
cargo build --release --features ffi
# Build shared library
cargo build --release --lib
```
### Output Files
```
target/release/
├── libpromocrypt_core.so # Linux shared library
├── libpromocrypt_core.dylib # macOS shared library
├── libpromocrypt_core.a # Static library
└── libpromocrypt_core.rlib # Rust library
```
## Running Tests
```bash
# Run all tests
cargo test
# Run with all features
cargo test --all-features
# Run specific test file
cargo test --test integration
# Run with verbose output
cargo test -- --nocapture
# Run benchmarks
cargo bench
```
## Examples
### Full Workflow Example
```rust
use promocrypt_core::{
BinFile, create_config, CounterMode, CheckPosition, CodeFormat,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Create configuration
let mut config = create_config("black-friday-2024");
config.counter_mode = CounterMode::InBin;
config.check_position = CheckPosition::Index(4); // Hide check digit
config.format = CodeFormat::new()
.with_prefix("BF24-")
.with_separator('-', vec![4, 8]);
// 2. Create .bin file
let mut bin = BinFile::create(
"black-friday.bin",
"super-secret-password",
config
)?;
// 3. Generate codes
let codes = bin.generate_batch(10000)?;
println!("Generated {} codes", codes.len());
println!("Sample: {}", codes[0]);
// Output: BF24-XXXX-XXXX-XX
// 4. Get statistics
let stats = bin.get_stats();
println!("Capacity: {}", stats.capacity);
println!("Generated: {}", stats.total_generated);
println!("Utilization: {:.4}%", stats.utilization_percent);
// 5. Save and reopen read-only
drop(bin);
let bin = BinFile::open_readonly("black-friday.bin")?;
// 6. Validate codes
for code in &codes[..10] {
let result = bin.validate(code);
assert!(result.is_valid(), "Code should be valid");
}
// 7. Test invalid code
let result = bin.validate("INVALID-CODE");
assert!(!result.is_valid());
Ok(())
}
```
### Storage Encryption Example
```rust
use promocrypt_core::{BinFile, create_config, CounterMode};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut config = create_config("encrypted-campaign");
config.counter_mode = CounterMode::InBin;
config.storage_encryption_enabled = true;
let mut bin = BinFile::create("encrypted.bin", "secret", config)?;
// Generate and encrypt for storage
let code = bin.generate()?;
let encrypted = bin.encrypt_code(&code)?;
println!("Original: {}", code);
println!("Encrypted: {}", encrypted);
// Later: decrypt from database
let decrypted = bin.decrypt_code(&encrypted)?;
assert_eq!(code, decrypted);
Ok(())
}
```
### Secret Rotation Example
```rust
use promocrypt_core::BinFile;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Open with current secret
let mut bin = BinFile::open_with_secret("campaign.bin", "old-secret")?;
// Rotate to new secret
bin.rotate_secret("old-secret", "new-secret")?;
// Save changes
drop(bin);
// Now open with new secret
let bin = BinFile::open_with_secret("campaign.bin", "new-secret")?;
// Old codes still validate!
assert!(bin.validate("EXISTINGCODE").is_valid());
Ok(())
}
```
## Performance
| Operation | Target | Notes |
|-----------|--------|-------|
| `validate()` | < 10us | Hot path optimization |
| `generate()` | < 100us | Including counter update |
| `generate_batch(1000)` | < 50ms | |
| `generate_batch(100000)` | < 5s | |
| `open_readonly()` | < 50ms | Includes Argon2 |
| `open_with_secret()` | < 100ms | Includes Argon2 |
## License
MIT License - see [LICENSE](LICENSE) file for details.
## Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to the `main` branch.
## Related Projects
- [promocrypt-cli](https://github.com/professor93/promocrypt-cli) - Command-line interface
- [php-promocrypt](https://github.com/professor93/php-promocrypt) - PHP bindings
- [pg_promocrypt](https://github.com/professor93/pg_promocrypt) - PostgreSQL extension