diff --git a/crates/c_hash_sig/Cargo.toml b/crates/c_hash_sig/Cargo.toml index 5cbc7ef..468eacd 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] @@ -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/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..d0c41ff 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_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 = ::PublicKey; type SecretKeyType = ::SecretKey; type SignatureType = ::Signature; @@ -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(); @@ -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), @@ -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(); @@ -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), @@ -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(); @@ -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), @@ -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::(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::*; @@ -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()); @@ -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 @@ -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(); @@ -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); @@ -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(); @@ -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] { @@ -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(); @@ -950,12 +1041,73 @@ 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 { 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]; @@ -966,7 +1118,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());