Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/c_hash_sig/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "c_hash_sig_crust"
version = "0.1.0"
version = "0.2.0"
edition = "2021"

[lib]
Expand All @@ -11,6 +11,7 @@ crate-type = ["cdylib", "staticlib"]
hashsig = { git = "https://github.com/b-wagn/hash-sig" }
rand = "0.9"
bincode = { version = "2.0.1", features = ["serde"] }
serde_json = "1.0"

[build-dependencies]
build-helper = { path = "../build-helper" }
12 changes: 9 additions & 3 deletions crates/c_hash_sig/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,15 @@ All messages must be **exactly 32 bytes**. To sign longer messages, first use a
## Scheme Parameters

Current implementation uses:
- **Scheme**: Generalized XMSS (Winternitz encoding, w=4)
- **Hash function**: SHA-3 (SHAKE)
- **Lifetime**: 2^18 epochs (262,144 epochs)
- **Scheme**: Generalized XMSS (Target Sum encoding)
- **Scheme alias**: `SIGTopLevelTargetSumLifetime32Dim64Base8`
- **Hash function**: Poseidon2 (ZK-friendly)
- **Encoding**: Target Sum
- **Dimension**: 64
- **Base**: 8
- **Final Layer**: 77
- **Target Sum**: 375
- **Lifetime**: 2^32 epochs (4,294,967,296 epochs)
- **Message length**: 32 bytes

## Project Statistics
Expand Down
129 changes: 110 additions & 19 deletions crates/c_hash_sig/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,25 @@ use std::os::raw::{c_char, c_int};
use std::ptr;
use std::slice;

use hashsig::signature::generalized_xmss::instantiations_sha::lifetime_2_to_the_18::winternitz::SIGWinternitzLifetime18W4;
#[cfg(test)]
mod scheme_impl {
use hashsig::signature::generalized_xmss::instantiations_poseidon_top_level::lifetime_2_to_the_8::SIGTopLevelTargetSumLifetime8Dim64Base8;

pub type SignatureSchemeType = SIGTopLevelTargetSumLifetime8Dim64Base8;
}

#[cfg(not(test))]
mod scheme_impl {
use hashsig::signature::generalized_xmss::instantiations_poseidon_top_level::lifetime_2_to_the_32::hashing_optimized::SIGTopLevelTargetSumLifetime32Dim64Base8;

pub type SignatureSchemeType = SIGTopLevelTargetSumLifetime32Dim64Base8;
}

use scheme_impl::SignatureSchemeType;
use hashsig::signature::{SignatureScheme, SignatureSchemeSecretKey};
use hashsig::MESSAGE_LENGTH;

// Type aliases for convenience
type SignatureSchemeType = SIGWinternitzLifetime18W4;
type PublicKeyType = <SignatureSchemeType as SignatureScheme>::PublicKey;
type SecretKeyType = <SignatureSchemeType as SignatureScheme>::SecretKey;
type SignatureType = <SignatureSchemeType as SignatureScheme>::Signature;
Expand Down Expand Up @@ -385,7 +398,7 @@ pub unsafe extern "C" fn pq_secret_key_serialize(
let sk = &*(sk as *const PQSignatureSchemeSecretKeyInner);

// Use bincode for serialization
match bincode::serde::encode_to_vec(&*sk.inner, bincode::config::standard()) {
match bincode::serde::encode_to_vec(&*sk.inner, bincode::config::standard().with_fixed_int_encoding()) {
Ok(bytes) => {
if bytes.len() > buffer_len {
*written_len = bytes.len();
Expand Down Expand Up @@ -424,7 +437,7 @@ pub unsafe extern "C" fn pq_secret_key_deserialize(

let buffer_slice = slice::from_raw_parts(buffer, buffer_len);

match bincode::serde::decode_from_slice(buffer_slice, bincode::config::standard()) {
match bincode::serde::decode_from_slice(buffer_slice, bincode::config::standard().with_fixed_int_encoding()) {
Ok((sk, _)) => {
let sk_wrapper = Box::new(PQSignatureSchemeSecretKeyInner {
inner: Box::new(sk),
Expand Down Expand Up @@ -462,7 +475,7 @@ pub unsafe extern "C" fn pq_public_key_serialize(

let pk = &*(pk as *const PQSignatureSchemePublicKeyInner);

match bincode::serde::encode_to_vec(&*pk.inner, bincode::config::standard()) {
match bincode::serde::encode_to_vec(&*pk.inner, bincode::config::standard().with_fixed_int_encoding()) {
Ok(bytes) => {
if bytes.len() > buffer_len {
*written_len = bytes.len();
Expand Down Expand Up @@ -501,7 +514,7 @@ pub unsafe extern "C" fn pq_public_key_deserialize(

let buffer_slice = slice::from_raw_parts(buffer, buffer_len);

match bincode::serde::decode_from_slice(buffer_slice, bincode::config::standard()) {
match bincode::serde::decode_from_slice(buffer_slice, bincode::config::standard().with_fixed_int_encoding()) {
Ok((pk, _)) => {
let pk_wrapper = Box::new(PQSignatureSchemePublicKeyInner {
inner: Box::new(pk),
Expand Down Expand Up @@ -539,7 +552,7 @@ pub unsafe extern "C" fn pq_signature_serialize(

let signature = &*(signature as *const PQSignatureInner);

match bincode::serde::encode_to_vec(&*signature.inner, bincode::config::standard()) {
match bincode::serde::encode_to_vec(&*signature.inner, bincode::config::standard().with_fixed_int_encoding()) {
Ok(bytes) => {
if bytes.len() > buffer_len {
*written_len = bytes.len();
Expand Down Expand Up @@ -578,7 +591,7 @@ pub unsafe extern "C" fn pq_signature_deserialize(

let buffer_slice = slice::from_raw_parts(buffer, buffer_len);

match bincode::serde::decode_from_slice(buffer_slice, bincode::config::standard()) {
match bincode::serde::decode_from_slice(buffer_slice, bincode::config::standard().with_fixed_int_encoding()) {
Ok((signature, _)) => {
let sig_wrapper = Box::new(PQSignatureInner {
inner: Box::new(signature),
Expand All @@ -590,6 +603,84 @@ pub unsafe extern "C" fn pq_signature_deserialize(
}
}

// ============================================================================
// JSON deserialization functions
// ============================================================================

/// Deserialize public key from JSON string
///
/// # Parameters
/// - `json_str`: null-terminated JSON string
/// - `pk_out`: pointer to write public key (output)
///
/// # Returns
/// Error code
///
/// # Safety
/// json_str must be a valid null-terminated C string
#[no_mangle]
pub unsafe extern "C" fn pq_public_key_from_json(
json_str: *const c_char,
pk_out: *mut *mut PQSignatureSchemePublicKey,
) -> PQSigningError {
if json_str.is_null() || pk_out.is_null() {
return PQSigningError::InvalidPointer;
}

let c_str = match std::ffi::CStr::from_ptr(json_str).to_str() {
Ok(s) => s,
Err(_) => return PQSigningError::UnknownError,
};

match serde_json::from_str::<PublicKeyType>(c_str) {
Ok(pk) => {
let pk_wrapper = Box::new(PQSignatureSchemePublicKeyInner {
inner: Box::new(pk),
});
*pk_out = Box::into_raw(pk_wrapper) as *mut PQSignatureSchemePublicKey;
PQSigningError::Success
}
Err(_) => PQSigningError::UnknownError,
}
}

/// Deserialize secret key from JSON string
///
/// # Parameters
/// - `json_str`: null-terminated JSON string
/// - `sk_out`: pointer to write secret key (output)
///
/// # Returns
/// Error code
///
/// # Safety
/// json_str must be a valid null-terminated C string
#[no_mangle]
pub unsafe extern "C" fn pq_secret_key_from_json(
json_str: *const c_char,
sk_out: *mut *mut PQSignatureSchemeSecretKey,
) -> PQSigningError {
if json_str.is_null() || sk_out.is_null() {
return PQSigningError::InvalidPointer;
}

let c_str = match std::ffi::CStr::from_ptr(json_str).to_str() {
Ok(s) => s,
Err(_) => return PQSigningError::UnknownError,
};

match serde_json::from_str::<SecretKeyType>(c_str) {
Ok(sk) => {
let sk_wrapper = Box::new(PQSignatureSchemeSecretKeyInner {
inner: Box::new(sk),
});
*sk_out = Box::into_raw(sk_wrapper) as *mut PQSignatureSchemeSecretKey;
PQSigningError::Success
}
Err(_) => PQSigningError::UnknownError,
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -601,7 +692,7 @@ mod tests {
let mut sk: *mut PQSignatureSchemeSecretKey = ptr::null_mut();

// Key generation
let result = pq_key_gen(0, 1000, &mut pk, &mut sk);
let result = pq_key_gen(0, 200, &mut pk, &mut sk);
assert_eq!(result, PQSigningError::Success);
assert!(!pk.is_null());
assert!(!sk.is_null());
Expand Down Expand Up @@ -679,7 +770,7 @@ mod tests {
unsafe {
let mut pk: *mut PQSignatureSchemePublicKey = ptr::null_mut();
let mut sk: *mut PQSignatureSchemeSecretKey = ptr::null_mut();
pq_key_gen(0, 1000, &mut pk, &mut sk);
pq_key_gen(0, 200, &mut pk, &mut sk);

// Test with incorrect message length for signing
let short_message = [0u8; 16]; // Incorrect length
Expand Down Expand Up @@ -712,7 +803,7 @@ mod tests {
unsafe {
let mut pk: *mut PQSignatureSchemePublicKey = ptr::null_mut();
let mut sk: *mut PQSignatureSchemeSecretKey = ptr::null_mut();
pq_key_gen(0, 1000, &mut pk, &mut sk);
pq_key_gen(0, 200, &mut pk, &mut sk);

let message = [1u8; MESSAGE_LENGTH];
let mut signature: *mut PQSignature = ptr::null_mut();
Expand Down Expand Up @@ -742,7 +833,7 @@ mod tests {
unsafe {
let mut pk: *mut PQSignatureSchemePublicKey = ptr::null_mut();
let mut sk: *mut PQSignatureSchemeSecretKey = ptr::null_mut();
pq_key_gen(0, 10000, &mut pk, &mut sk);
pq_key_gen(0, 192, &mut pk, &mut sk);

let initial_prepared = pq_get_prepared_interval(sk);
assert!(initial_prepared.start < initial_prepared.end);
Expand Down Expand Up @@ -770,7 +861,7 @@ mod tests {
unsafe {
let mut pk: *mut PQSignatureSchemePublicKey = ptr::null_mut();
let mut sk: *mut PQSignatureSchemeSecretKey = ptr::null_mut();
pq_key_gen(0, 1000, &mut pk, &mut sk);
pq_key_gen(0, 200, &mut pk, &mut sk);

let message = [42u8; MESSAGE_LENGTH];
let mut signature: *mut PQSignature = ptr::null_mut();
Expand Down Expand Up @@ -868,7 +959,7 @@ mod tests {
unsafe {
let mut pk: *mut PQSignatureSchemePublicKey = ptr::null_mut();
let mut sk: *mut PQSignatureSchemeSecretKey = ptr::null_mut();
pq_key_gen(0, 1000, &mut pk, &mut sk);
pq_key_gen(0, 200, &mut pk, &mut sk);

// Sign several different messages with different epochs
for epoch in [5, 10, 15, 20, 25] {
Expand Down Expand Up @@ -896,14 +987,14 @@ mod tests {
#[test]
fn test_get_lifetime() {
let lifetime = pq_get_lifetime();
assert_eq!(lifetime, 262144); // 2^18
assert_eq!(lifetime, 256); // 2^8
}

#[test]
fn test_activation_and_prepared_intervals() {
unsafe {
let activation_epoch = 100;
let num_active_epochs = 5000;
let activation_epoch = 0;
let num_active_epochs = 200;

let mut pk: *mut PQSignatureSchemePublicKey = ptr::null_mut();
let mut sk: *mut PQSignatureSchemeSecretKey = ptr::null_mut();
Expand Down Expand Up @@ -955,7 +1046,7 @@ mod tests {
unsafe {
let mut pk: *mut PQSignatureSchemePublicKey = ptr::null_mut();
let mut sk: *mut PQSignatureSchemeSecretKey = ptr::null_mut();
pq_key_gen(0, 1000, &mut pk, &mut sk);
pq_key_gen(0, 200, &mut pk, &mut sk);

// Try to serialize into too small buffer
let mut small_buffer = [0u8; 10];
Expand All @@ -966,7 +1057,7 @@ mod tests {
small_buffer.len(),
&mut written,
);

// Should be error, but written should contain required size
assert_eq!(result, PQSigningError::UnknownError);
assert!(written > small_buffer.len());
Expand Down