promocrypt-core

Crates.iopromocrypt-core
lib.rspromocrypt-core
version1.1.0
created_at2026-01-07 17:57:23.829715+00
updated_at2026-01-10 08:11:33.694493+00
descriptionCore library for cryptographically secure promotional code generation
homepage
repositoryhttps://github.com/professor93/promocrypt-core
max_upload_size
id2028762
size471,758
Inoyatulloh (professor93)

documentation

README

promocrypt-core

Release Crates.io License: MIT Rust

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

Feature Description
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

git clone https://github.com/professor93/promocrypt-core.git
cd promocrypt-core
cargo build --release

As Cargo Dependency

[dependencies]
promocrypt-core = "1.0"

Pre-built Binaries

Download from GitHub 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

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)

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

// 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)

// 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)

// 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)

// 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

// 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)

// 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)

// 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

// 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:

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

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

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

Component Algorithm
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)

#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
$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

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

# 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

# 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

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

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

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 file for details.

Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests to the main branch.

Related Projects

Commit count: 0

cargo fmt