From 32a0511e530743db9b7f29bf8fa381dab5dc3c8b Mon Sep 17 00:00:00 2001 From: kamilsa Date: Tue, 18 Nov 2025 11:31:29 +0500 Subject: [PATCH 1/5] Update version and change signature scheme to Poseidon2 with extended lifetime --- crates/c_hash_sig/Cargo.toml | 2 +- crates/c_hash_sig/README.md | 12 +++++++++--- crates/c_hash_sig/src/lib.rs | 6 +++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/c_hash_sig/Cargo.toml b/crates/c_hash_sig/Cargo.toml index 5cbc7ef..43212ee 100644 --- a/crates/c_hash_sig/Cargo.toml +++ b/crates/c_hash_sig/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "c_hash_sig_crust" -version = "0.1.0" +version = "0.2.0" edition = "2021" [lib] diff --git a/crates/c_hash_sig/README.md b/crates/c_hash_sig/README.md index 050a9af..85ca83c 100644 --- a/crates/c_hash_sig/README.md +++ b/crates/c_hash_sig/README.md @@ -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 diff --git a/crates/c_hash_sig/src/lib.rs b/crates/c_hash_sig/src/lib.rs index 27573ce..c20f9f6 100644 --- a/crates/c_hash_sig/src/lib.rs +++ b/crates/c_hash_sig/src/lib.rs @@ -4,12 +4,12 @@ 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; +use hashsig::signature::generalized_xmss::instantiations_poseidon_top_level::lifetime_2_to_the_32::hashing_optimized::SIGTopLevelTargetSumLifetime32Dim64Base8; use hashsig::signature::{SignatureScheme, SignatureSchemeSecretKey}; use hashsig::MESSAGE_LENGTH; // Type aliases for convenience -type SignatureSchemeType = SIGWinternitzLifetime18W4; +type SignatureSchemeType = SIGTopLevelTargetSumLifetime32Dim64Base8; type PublicKeyType = ::PublicKey; type SecretKeyType = ::SecretKey; type SignatureType = ::Signature; @@ -896,7 +896,7 @@ mod tests { #[test] fn test_get_lifetime() { let lifetime = pq_get_lifetime(); - assert_eq!(lifetime, 262144); // 2^18 + assert_eq!(lifetime, 4294967296); // 2^32 } #[test] From a3aa7c7ef16fd61fcf3f9e78fe32aa9358ef3df9 Mon Sep 17 00:00:00 2001 From: kamilsa Date: Tue, 18 Nov 2025 13:41:38 +0500 Subject: [PATCH 2/5] Add serde json serialization --- crates/c_hash_sig/Cargo.toml | 1 + crates/c_hash_sig/src/lib.rs | 78 ++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/crates/c_hash_sig/Cargo.toml b/crates/c_hash_sig/Cargo.toml index 43212ee..468eacd 100644 --- a/crates/c_hash_sig/Cargo.toml +++ b/crates/c_hash_sig/Cargo.toml @@ -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" } diff --git a/crates/c_hash_sig/src/lib.rs b/crates/c_hash_sig/src/lib.rs index c20f9f6..02929a2 100644 --- a/crates/c_hash_sig/src/lib.rs +++ b/crates/c_hash_sig/src/lib.rs @@ -590,6 +590,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::(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::(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::*; From 1dcb046d5a191b31d8dfc63a1e1d88c8d16f8919 Mon Sep 17 00:00:00 2001 From: kamilsa Date: Thu, 20 Nov 2025 11:38:11 +0500 Subject: [PATCH 3/5] Speed tests --- crates/c_hash_sig/src/lib.rs | 39 ++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/crates/c_hash_sig/src/lib.rs b/crates/c_hash_sig/src/lib.rs index 02929a2..8fd7737 100644 --- a/crates/c_hash_sig/src/lib.rs +++ b/crates/c_hash_sig/src/lib.rs @@ -4,12 +4,25 @@ use std::os::raw::{c_char, c_int}; use std::ptr; use std::slice; -use hashsig::signature::generalized_xmss::instantiations_poseidon_top_level::lifetime_2_to_the_32::hashing_optimized::SIGTopLevelTargetSumLifetime32Dim64Base8; +#[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 = SIGTopLevelTargetSumLifetime32Dim64Base8; type PublicKeyType = ::PublicKey; type SecretKeyType = ::SecretKey; type SignatureType = ::Signature; @@ -679,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()); @@ -757,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 @@ -790,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(); @@ -820,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); @@ -848,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(); @@ -946,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] { @@ -974,14 +987,14 @@ mod tests { #[test] fn test_get_lifetime() { let lifetime = pq_get_lifetime(); - assert_eq!(lifetime, 4294967296); // 2^32 + 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(); @@ -1033,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]; @@ -1044,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()); From 87966163e2356ca41278c3ea248307ba3de9884a Mon Sep 17 00:00:00 2001 From: kamilsa Date: Thu, 20 Nov 2025 17:04:41 +0500 Subject: [PATCH 4/5] Update bincode serialization to use fixed integer encoding for PQSignatureScheme --- crates/c_hash_sig/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/c_hash_sig/src/lib.rs b/crates/c_hash_sig/src/lib.rs index 8fd7737..a205420 100644 --- a/crates/c_hash_sig/src/lib.rs +++ b/crates/c_hash_sig/src/lib.rs @@ -398,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(); @@ -437,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), @@ -475,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(); @@ -514,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), @@ -552,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(); @@ -591,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), From d6ba85b9675a7a5bcc750561f16c4f49bd1797d5 Mon Sep 17 00:00:00 2001 From: kamilsa Date: Fri, 21 Nov 2025 12:23:07 +0500 Subject: [PATCH 5/5] Add unit test for public key JSON deserialization and serialization --- crates/c_hash_sig/src/lib.rs | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/crates/c_hash_sig/src/lib.rs b/crates/c_hash_sig/src/lib.rs index a205420..d0c41ff 100644 --- a/crates/c_hash_sig/src/lib.rs +++ b/crates/c_hash_sig/src/lib.rs @@ -1041,6 +1041,67 @@ mod tests { } } + #[test] + // #[cfg(not(test))] + fn test_public_key_json_deserialization_lifetime32() { + use std::ffi::CString; + + let json = r#"{ + "root": [ + 227456853, + 1463530671, + 1004245254, + 894145477, + 1555036206, + 780627728, + 1559453783, + 23977525 + ], + "parameter": [ + 1732673242, + 873131288, + 391672736, + 1837524665, + 1051820738 + ] +}"#; + + let expected_bytes: [u8; 47] = [ + 0x55, 0xb7, 0x8e, 0x0d, 0xaf, 0xb4, 0x3b, 0x57, + 0x06, 0x91, 0xdb, 0x3b, 0xc5, 0x93, 0x4b, 0x35, + 0x2e, 0xf8, 0xaf, 0x5c, 0x10, 0x6f, 0x87, 0x2e, + 0x57, 0x60, 0xf3, 0x5c, 0x35, 0xde, 0x6d, 0x01, + 0xda, 0x7e, 0x46, 0x67, 0x18, 0xed, 0x0a, 0x34, + 0xa0, 0x73, 0x58, 0x17, 0xb9, 0x66, 0x86, + ]; + + unsafe { + let json_cstr = CString::new(json).unwrap(); + let mut pk: *mut PQSignatureSchemePublicKey = ptr::null_mut(); + + let result = pq_public_key_from_json(json_cstr.as_ptr(), &mut pk); + assert_eq!(result, PQSigningError::Success); + assert!(!pk.is_null()); + + // Serialize the public key to check its bytes + let mut buffer = vec![0u8; 1000]; + let mut written = 0; + let result = pq_public_key_serialize( + pk, + buffer.as_mut_ptr(), + buffer.len(), + &mut written, + ); + assert_eq!(result, PQSigningError::Success); + + // Check that the serialized key matches expected bytes + // The exact format may include additional metadata, so we check the key data + assert_eq!(&buffer[..expected_bytes.len()], &expected_bytes[..]); + + pq_public_key_free(pk); + } + } + #[test] fn test_serialization_buffer_too_small() { unsafe {