diff --git a/.gitignore b/.gitignore index ad2f0ff..7e93290 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ /target Cargo.lock -.DS_Store \ No newline at end of file +.DS_Store + +# jetbrains ide files +.idea + +# c# build files +bin +obj + diff --git a/Cargo.toml b/Cargo.toml index 4755765..4d41b7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,10 @@ members = [ "verkle-trie", "verkle-spec", "ipa-multipoint", - "banderwagon", "ffi_interface", + "banderwagon", + "ffi_interface", + "bindings/c", + "bindings/csharp/rust_code", ] resolver = "2" diff --git a/bindings/c/.gitignore b/bindings/c/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/bindings/c/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/bindings/c/Cargo.toml b/bindings/c/Cargo.toml new file mode 100644 index 0000000..9872a1e --- /dev/null +++ b/bindings/c/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "c_verkle" +version = "0.1.0" +edition = "2021" + + +[lib] +crate-type = ["staticlib", "cdylib", "rlib"] + +[dependencies] +libc = "0.2.2" +hex = "*" +banderwagon = { path = "../../banderwagon" } +ipa-multipoint = { path = "../../ipa-multipoint" } +verkle-spec = { path = "../../verkle-spec" } +verkle-trie = { path = "../../verkle-trie" } +ffi_interface = { path = "../../ffi_interface" } + +[build-dependencies] +cbindgen = "0.26.0" \ No newline at end of file diff --git a/bindings/c/build.rs b/bindings/c/build.rs new file mode 100644 index 0000000..8e26136 --- /dev/null +++ b/bindings/c/build.rs @@ -0,0 +1,31 @@ +use std::env; +use std::path::PathBuf; + +/// The directory where the generated header file will be written. +const DIR_FOR_HEADER: &str = "build"; + +fn main() { + // linker flags + // Link libm on Unix-like systems (needed due to use of num_cpus crate) + #[cfg(not(target_os = "windows"))] + println!("cargo:rustc-link-lib=m"); + + println!("cargo:rerun-if-changed=src/"); + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let package_name = env::var("CARGO_PKG_NAME").unwrap(); + + let path_to_crate_dir = PathBuf::from(&crate_dir); + + let output_file = PathBuf::from(&path_to_crate_dir) + .join(DIR_FOR_HEADER) + .join(format!("{}.h", package_name)) + .display() + .to_string(); + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_language(cbindgen::Language::C) + .generate() + .unwrap() + .write_to_file(output_file); +} diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs new file mode 100644 index 0000000..71014f7 --- /dev/null +++ b/bindings/c/src/lib.rs @@ -0,0 +1,309 @@ +use ffi_interface::{ + deserialize_proof_query, deserialize_proof_query_uncompressed, deserialize_verifier_query, + deserialize_verifier_query_uncompressed, fr_from_le_bytes, Context, +}; +use ipa_multipoint::committer::Committer; +use ipa_multipoint::multiproof::{MultiPoint, MultiPointProof, ProverQuery, VerifierQuery}; +use ipa_multipoint::transcript::Transcript; + +#[allow(deprecated)] +use ffi_interface::get_tree_key_hash; + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn context_new() -> *mut Context { + let ctx = Box::new(Context::default()); + Box::into_raw(ctx) +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn context_free(ctx: *mut Context) { + if ctx.is_null() { + return; + } + unsafe { + let _ = Box::from_raw(ctx); + } +} + +#[allow(deprecated)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn pedersen_hash( + ctx: *mut Context, + address: *const u8, + tree_index_le: *const u8, + out: *mut u8, +) { + if ctx.is_null() || address.is_null() || tree_index_le.is_null() || out.is_null() { + // TODO: We have ommited the error handling for null pointers at the moment. + // TODO: Likely will panic in this case. + return; + } + + let (tree_index, add, context) = unsafe { + let add_slice = std::slice::from_raw_parts(address, 32); + let ctx_ref = &*ctx; + let tree_index_slice = std::slice::from_raw_parts(tree_index_le, 32); + + (tree_index_slice, add_slice, ctx_ref) + }; + + let hash = get_tree_key_hash( + context, + <[u8; 32]>::try_from(add).unwrap(), + <[u8; 32]>::try_from(tree_index).unwrap(), + ); + + unsafe { + let commitment_data_slice = std::slice::from_raw_parts_mut(out, 32); + commitment_data_slice.copy_from_slice(&hash); + } +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn multi_scalar_mul( + ctx: *mut Context, + scalars: *const u8, + len: usize, + out: *mut u8, +) { + let (scalar_slice, context) = unsafe { + let scalar = std::slice::from_raw_parts(scalars, len); + let ctx_ref = &*ctx; + + (scalar, ctx_ref) + }; + + let mut inputs = Vec::with_capacity(len); + for chunk in scalar_slice.chunks_exact(32) { + inputs.push(fr_from_le_bytes(chunk).unwrap()); + } + + let data = context.committer.commit_lagrange(&inputs); + let hash = data.to_bytes(); + + unsafe { + let commitment_data_slice = std::slice::from_raw_parts_mut(out, 32); + commitment_data_slice.copy_from_slice(&hash); + } +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn create_proof(ctx: *mut Context, input: *const u8, len: usize, out: *mut u8) { + const CHUNK_SIZE: usize = 8257; // TODO: get this from ipa-multipoint + const PROOF_SIZE: usize = 576; // TODO: get this from ipa-multipoint + + let (scalar_slice, context) = unsafe { + let scalar = std::slice::from_raw_parts(input, len); + let ctx_ref = &*ctx; + + (scalar, ctx_ref) + }; + + let num_openings = len / CHUNK_SIZE; + + let proofs_bytes = scalar_slice.chunks_exact(CHUNK_SIZE); + assert!( + proofs_bytes.remainder().is_empty(), + "There should be no left over bytes when chunking the proof" + ); + + // - Deserialize proof queries + // + let mut prover_queries: Vec = Vec::with_capacity(num_openings); + + for proof_bytes in proofs_bytes { + let prover_query = deserialize_proof_query(proof_bytes); + prover_queries.push(prover_query); + } + + // - Create proofs + // + + let mut transcript = Transcript::new(b"verkle"); + + let proof = MultiPoint::open( + // TODO: This should not need to clone the CRS, but instead take a reference + context.crs.clone(), + &context.precomputed_weights, + &mut transcript, + prover_queries, + ); + + let hash = proof.to_bytes().expect("cannot serialize proof"); + unsafe { + let commitment_data_slice = std::slice::from_raw_parts_mut(out, PROOF_SIZE); + commitment_data_slice.copy_from_slice(&hash); + } +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn create_proof_uncompressed( + ctx: *mut Context, + input: *const u8, + len: usize, + out: *mut u8, +) { + // 8257 + 32 because first commitment is uncompressed as 64 bytes + const CHUNK_SIZE: usize = 8289; // TODO: get this from ipa-multipoint + const PROOF_SIZE: usize = 1120; // TODO: get this from ipa-multipoint + + let (scalar_slice, context) = unsafe { + let scalar = std::slice::from_raw_parts(input, len); + let ctx_ref = &*ctx; + + (scalar, ctx_ref) + }; + + let num_openings = len / CHUNK_SIZE; + + let proofs_bytes = scalar_slice.chunks_exact(CHUNK_SIZE); + assert!( + proofs_bytes.remainder().is_empty(), + "There should be no left over bytes when chunking the proof" + ); + + // - Deserialize proof queries + // + let mut prover_queries: Vec = Vec::with_capacity(num_openings); + + for proof_bytes in proofs_bytes { + let prover_query = deserialize_proof_query_uncompressed(proof_bytes); + prover_queries.push(prover_query); + } + + // - Create proofs + // + + let mut transcript = Transcript::new(b"verkle"); + + let proof = MultiPoint::open( + // TODO: This should not need to clone the CRS, but instead take a reference + context.crs.clone(), + &context.precomputed_weights, + &mut transcript, + prover_queries, + ); + + let hash = proof + .to_bytes_uncompressed() + .expect("cannot serialize proof"); + unsafe { + let commitment_data_slice = std::slice::from_raw_parts_mut(out, PROOF_SIZE); + commitment_data_slice.copy_from_slice(&hash); + } +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn verify_proof(ctx: *mut Context, input: *const u8, len: usize) -> bool { + const CHUNK_SIZE: usize = 65; // TODO: get this from ipa-multipoint + const PROOF_SIZE: usize = 576; // TODO: get this from ipa-multipoint + + let (proof_slice, verifier_queries_slices, context) = unsafe { + let input_slice = std::slice::from_raw_parts(input, len); + + let (proof_slice, verifier_queries_slices) = input_slice.split_at(PROOF_SIZE); + + let ctx_ref = &*ctx; + + (proof_slice, verifier_queries_slices, ctx_ref) + }; + + let verifier_queries_bytes = verifier_queries_slices.chunks_exact(CHUNK_SIZE); + assert!( + verifier_queries_bytes.remainder().is_empty(), + "There should be no left over bytes when chunking the verifier queries" + ); + + let num_openings = verifier_queries_bytes.len() / CHUNK_SIZE; + + // - Deserialize verifier queries + // + + let mut verifier_queries: Vec = Vec::with_capacity(num_openings); + + for verifier_query_bytes in verifier_queries_bytes { + let verifier_query = deserialize_verifier_query(verifier_query_bytes); + verifier_queries.push(verifier_query); + } + + // - Check proof + // + + let proof = MultiPointProof::from_bytes(proof_slice, 256).unwrap(); + + let mut transcript = Transcript::new(b"verkle"); + + // TODO: This should not need to clone the CRS, but instead take a reference + + MultiPointProof::check( + &proof, + &context.crs.clone(), + &context.precomputed_weights, + &verifier_queries, + &mut transcript, + ) +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[no_mangle] +pub extern "C" fn verify_proof_uncompressed( + ctx: *mut Context, + input: *const u8, + len: usize, +) -> bool { + // Chunk is now 65 + 32 = 97 because first commitment is uncompressed as 64 bytes + const CHUNK_SIZE: usize = 97; // TODO: get this from ipa-multipoint + const PROOF_SIZE: usize = 1120; // TODO: get this from ipa-multipoint + + let (proof_slice, verifier_queries_slices, context) = unsafe { + let input_slice = std::slice::from_raw_parts(input, len); + + let (proof_slice, verifier_queries_slices) = input_slice.split_at(PROOF_SIZE); + + let ctx_ref = &*ctx; + + (proof_slice, verifier_queries_slices, ctx_ref) + }; + + let verifier_queries_bytes = verifier_queries_slices.chunks_exact(CHUNK_SIZE); + assert!( + verifier_queries_bytes.remainder().is_empty(), + "There should be no left over bytes when chunking the verifier queries" + ); + + let num_openings = verifier_queries_bytes.len() / CHUNK_SIZE; + + // - Deserialize verifier queries + // + + let mut verifier_queries: Vec = Vec::with_capacity(num_openings); + + for verifier_query_bytes in verifier_queries_bytes { + let verifier_query = deserialize_verifier_query_uncompressed(verifier_query_bytes); + verifier_queries.push(verifier_query); + } + + // - Check proof + // + + let proof = MultiPointProof::from_bytes_unchecked_uncompressed(proof_slice, 256).unwrap(); + + let mut transcript = Transcript::new(b"verkle"); + + // TODO: This should not need to clone the CRS, but instead take a reference + + MultiPointProof::check( + &proof, + &context.crs.clone(), + &context.precomputed_weights, + &verifier_queries, + &mut transcript, + ) +} diff --git a/bindings/csharp/csharp_code/Verkle.Bindings/RustVerkle.cs b/bindings/csharp/csharp_code/Verkle.Bindings/RustVerkle.cs new file mode 100644 index 0000000..ddb60d3 --- /dev/null +++ b/bindings/csharp/csharp_code/Verkle.Bindings/RustVerkle.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using static Verkle.Bindings.NativeMethods; + +namespace Verkle.Bindings; + +public unsafe class RustVerkle: IDisposable +{ + private Context* _context = context_new(); + + public void Dispose() + { + if (_context != null) + { + context_free(_context); + _context = null; + } + } + + public void PedersenHash(byte[] address, byte[] treeIndexLe, byte[] outHash) + { + fixed (byte* addrPtr = address) + fixed (byte* indexPtr = treeIndexLe) + fixed (byte* hashPtr = outHash) + { + pedersen_hash(_context, addrPtr, indexPtr, hashPtr); + } + } +} \ No newline at end of file diff --git a/bindings/csharp/csharp_code/Verkle.Bindings/Verkle.Bindings.csproj b/bindings/csharp/csharp_code/Verkle.Bindings/Verkle.Bindings.csproj new file mode 100644 index 0000000..4a24632 --- /dev/null +++ b/bindings/csharp/csharp_code/Verkle.Bindings/Verkle.Bindings.csproj @@ -0,0 +1,10 @@ + + + + net8.0 + enable + enable + true + + + diff --git a/bindings/csharp/csharp_code/Verkle.Bindings/native_methods.g.cs b/bindings/csharp/csharp_code/Verkle.Bindings/native_methods.g.cs new file mode 100644 index 0000000..5dd05e0 --- /dev/null +++ b/bindings/csharp/csharp_code/Verkle.Bindings/native_methods.g.cs @@ -0,0 +1,50 @@ +// +// This code is generated by csbindgen. +// DON'T CHANGE THIS DIRECTLY. +// +#pragma warning disable CS8500 +#pragma warning disable CS8981 +using System; +using System.Runtime.InteropServices; + + +namespace Verkle.Bindings +{ + internal static unsafe partial class NativeMethods + { + const string __DllName = "c_verkle"; + + + + [DllImport(__DllName, EntryPoint = "context_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern Context* context_new(); + + [DllImport(__DllName, EntryPoint = "context_free", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void context_free(Context* ctx); + + [DllImport(__DllName, EntryPoint = "pedersen_hash", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void pedersen_hash(Context* ctx, byte* address, byte* tree_index_le, byte* @out); + + [DllImport(__DllName, EntryPoint = "multi_scalar_mul", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void multi_scalar_mul(Context* ctx, byte* scalars, System.UIntPtr len, byte* @out); + + [DllImport(__DllName, EntryPoint = "create_proof", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void create_proof(Context* ctx, byte* input, System.UIntPtr len, byte* @out); + + [DllImport(__DllName, EntryPoint = "create_proof_uncompressed", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void create_proof_uncompressed(Context* ctx, byte* input, System.UIntPtr len, byte* @out); + + [DllImport(__DllName, EntryPoint = "verify_proof", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + internal static extern bool verify_proof(Context* ctx, byte* input, System.UIntPtr len); + + [DllImport(__DllName, EntryPoint = "verify_proof_uncompressed", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + internal static extern bool verify_proof_uncompressed(Context* ctx, byte* input, System.UIntPtr len); + + + } + + + +} diff --git a/bindings/csharp/csharp_code/Verkle.Bindings/native_methods.loading.cs b/bindings/csharp/csharp_code/Verkle.Bindings/native_methods.loading.cs new file mode 100644 index 0000000..a0df6a7 --- /dev/null +++ b/bindings/csharp/csharp_code/Verkle.Bindings/native_methods.loading.cs @@ -0,0 +1,53 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; + +namespace Verkle.Bindings; + +internal static unsafe partial class NativeMethods +{ + + private static string? _libraryFallbackPath; + + static NativeMethods() + { + AssemblyLoadContext.Default.ResolvingUnmanagedDll += OnResolvingUnmanagedDll; + } + + private static nint OnResolvingUnmanagedDll(Assembly context, string name) + { + if (_libraryFallbackPath is null) + { + string platform; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + name = $"lib{name}.so"; + platform = "linux"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + name = $"lib{name}.dylib"; + platform = "osx"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + name = $"{name}.dll"; + platform = "win"; + } + else + throw new PlatformNotSupportedException(); + + string arch = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(); + + _libraryFallbackPath = Path.Combine("runtimes", $"{platform}-{arch}", "native", name); + } + + return NativeLibrary.Load(_libraryFallbackPath, context, default); + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe partial struct Context + { + } +} \ No newline at end of file diff --git a/bindings/csharp/csharp_code/Verkle.Tests/Verkle.Tests.csproj b/bindings/csharp/csharp_code/Verkle.Tests/Verkle.Tests.csproj new file mode 100644 index 0000000..3a63532 --- /dev/null +++ b/bindings/csharp/csharp_code/Verkle.Tests/Verkle.Tests.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/bindings/csharp/csharp_code/Verkle.csproj b/bindings/csharp/csharp_code/Verkle.csproj new file mode 100644 index 0000000..3a63532 --- /dev/null +++ b/bindings/csharp/csharp_code/Verkle.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/bindings/csharp/csharp_code/Verkle.sln b/bindings/csharp/csharp_code/Verkle.sln new file mode 100644 index 0000000..29d54ec --- /dev/null +++ b/bindings/csharp/csharp_code/Verkle.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Verkle.Tests", "Verkle.Tests\Verkle.Tests.csproj", "{BAD690AD-B37A-48B5-B8F9-9D75712E4038}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Verkle.Bindings", "Verkle.Bindings\Verkle.Bindings.csproj", "{9FC97DF6-5D68-4905-A355-F22FB168E6D1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BAD690AD-B37A-48B5-B8F9-9D75712E4038}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAD690AD-B37A-48B5-B8F9-9D75712E4038}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAD690AD-B37A-48B5-B8F9-9D75712E4038}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAD690AD-B37A-48B5-B8F9-9D75712E4038}.Release|Any CPU.Build.0 = Release|Any CPU + {9FC97DF6-5D68-4905-A355-F22FB168E6D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FC97DF6-5D68-4905-A355-F22FB168E6D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FC97DF6-5D68-4905-A355-F22FB168E6D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FC97DF6-5D68-4905-A355-F22FB168E6D1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/bindings/csharp/rust_code/Cargo.toml b/bindings/csharp/rust_code/Cargo.toml new file mode 100644 index 0000000..5725a4f --- /dev/null +++ b/bindings/csharp/rust_code/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "csharp_verkle" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[build-dependencies] +csbindgen = "1.9.3" +toml = "0.8.19" \ No newline at end of file diff --git a/bindings/csharp/rust_code/build.rs b/bindings/csharp/rust_code/build.rs new file mode 100644 index 0000000..0a63ed5 --- /dev/null +++ b/bindings/csharp/rust_code/build.rs @@ -0,0 +1,60 @@ +use std::{env, fs, path::PathBuf}; + +use toml::Value; + +/// The path where the generated bindings file will be written, relative to the bindings folder. +const PATH_FOR_CSHARP_BINDINGS_FILE: &str = + "csharp/csharp_code/Verkle.Bindings/native_methods.g.cs"; + +fn main() { + let package_name_of_c_crate = get_package_name_of_c_crate(); + println!( + "cargo:rerun-if-changed={}", + path_to_bindings_folder().display() + ); + + let parent = path_to_bindings_folder(); + let path_to_output_file = parent.join(PATH_FOR_CSHARP_BINDINGS_FILE); + + let path_to_c_crates_lib_file = path_to_c_crate().join("src/lib.rs"); + + csbindgen::Builder::default() + .input_extern_file(path_to_c_crates_lib_file) + .csharp_namespace("Verkle.Bindings") + .csharp_dll_name(package_name_of_c_crate) + .csharp_class_name("NativeMethods") + .csharp_use_nint_types(false) + .generate_csharp_file(path_to_output_file) + .expect("csharp bindgen failed to generate bindgen file"); +} + +fn path_to_bindings_folder() -> PathBuf { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let crate_dir = PathBuf::from(crate_dir); + // Go up two directories to be at bindings parent directory + let parent = crate_dir.parent().unwrap().parent().unwrap().to_path_buf(); + parent +} + +fn path_to_c_crate() -> PathBuf { + let parent = path_to_bindings_folder(); + parent.join("c") +} +fn get_package_name_of_c_crate() -> String { + let path_to_c_crate = path_to_c_crate(); + let path_to_c_crate_cargo_toml = path_to_c_crate.join("Cargo.toml"); + + // Read the Cargo.toml of the other crate + let cargo_toml = + fs::read_to_string(path_to_c_crate_cargo_toml).expect("Failed to read Cargo.toml"); + + // Parse the Cargo.toml content + let cargo_toml: Value = cargo_toml.parse().expect("Failed to parse Cargo.toml"); + + // Access the package name from the parsed Cargo.toml + let package_name = cargo_toml["package"]["name"] + .as_str() + .expect("Failed to get package name"); + + package_name.to_string() +} diff --git a/bindings/csharp/rust_code/src/lib.rs b/bindings/csharp/rust_code/src/lib.rs new file mode 100644 index 0000000..4e4b7bc --- /dev/null +++ b/bindings/csharp/rust_code/src/lib.rs @@ -0,0 +1 @@ +// This is a dummy crate being used to generate the csharp bindings file with build.rs diff --git a/ffi_interface/src/lib.rs b/ffi_interface/src/lib.rs index c0a497c..a56b3ac 100644 --- a/ffi_interface/src/lib.rs +++ b/ffi_interface/src/lib.rs @@ -1,4 +1,4 @@ -mod serialization; +pub mod serialization; // TODO: These are re-exported to not break the java code // TODO: we ideally don't want to export these. @@ -14,10 +14,13 @@ use ipa_multipoint::crs::CRS; use ipa_multipoint::lagrange_basis::PrecomputedWeights; use ipa_multipoint::multiproof::{MultiPoint, MultiPointProof, ProverQuery, VerifierQuery}; use ipa_multipoint::transcript::Transcript; -use serialization::{fr_from_le_bytes, fr_to_le_bytes}; +pub use serialization::{fr_from_le_bytes, fr_to_le_bytes}; use verkle_trie::proof::golang_proof_format::{bytes32_to_element, hex_to_bytes32, VerkleProofGo}; -use crate::serialization::{deserialize_proof_query, deserialize_verifier_query}; +pub use crate::serialization::{ + deserialize_proof_query, deserialize_proof_query_uncompressed, deserialize_verifier_query, + deserialize_verifier_query_uncompressed, +}; /// Context holds all of the necessary components needed for cryptographic operations /// in the Verkle Trie. This includes: diff --git a/ffi_interface/src/serialization.rs b/ffi_interface/src/serialization.rs index 6a40277..0087bd8 100644 --- a/ffi_interface/src/serialization.rs +++ b/ffi_interface/src/serialization.rs @@ -82,7 +82,7 @@ pub fn deserialize_commitment(serialized_commitment: [u8; 32]) -> Result ProverQuery { +pub fn deserialize_proof_query(bytes: &[u8]) -> ProverQuery { // Commitment let (commitment, mut bytes) = take_group_element(bytes); @@ -112,7 +112,37 @@ pub(crate) fn deserialize_proof_query(bytes: &[u8]) -> ProverQuery { } #[must_use] -pub(crate) fn deserialize_verifier_query(bytes: &[u8]) -> VerifierQuery { +pub fn deserialize_proof_query_uncompressed(bytes: &[u8]) -> ProverQuery { + // Commitment + let (commitment, mut bytes) = take_uncompressed_group_element(bytes); + + // f_x is a polynomial of degree 255, so we have 256 Fr elements + const NUMBER_OF_EVALUATIONS: usize = 256; + let mut collect_lagrange_basis: Vec = Vec::with_capacity(NUMBER_OF_EVALUATIONS); + for _ in 0..NUMBER_OF_EVALUATIONS { + let (scalar, offsetted_bytes) = take_scalar(bytes); + collect_lagrange_basis.push(scalar); + bytes = offsetted_bytes; + } + + // The input point is a single byte + let (z_i, bytes) = take_byte(bytes); + + // The evaluation is a single scalar + let (y_i, bytes) = take_scalar(bytes); + + assert!(bytes.is_empty(), "we should have consumed all the bytes"); + + ProverQuery { + commitment, + poly: LagrangeBasis::new(collect_lagrange_basis), + point: z_i, + result: y_i, + } +} + +#[must_use] +pub fn deserialize_verifier_query(bytes: &[u8]) -> VerifierQuery { // Commitment let (commitment, bytes) = take_group_element(bytes); @@ -131,6 +161,37 @@ pub(crate) fn deserialize_verifier_query(bytes: &[u8]) -> VerifierQuery { } } +#[must_use] +pub fn deserialize_verifier_query_uncompressed(bytes: &[u8]) -> VerifierQuery { + // Commitment + let (commitment, bytes) = take_uncompressed_group_element(bytes); + + // The input point is a single byte + let (z_i, bytes) = take_byte(bytes); + + // The evaluation is a single scalar + let (y_i, bytes) = take_scalar(bytes); + + assert!(bytes.is_empty(), "we should have consumed all the bytes"); + + VerifierQuery { + commitment, + point: Fr::from(z_i as u128), + result: y_i, + } +} + +#[must_use] +pub(crate) fn take_uncompressed_group_element(bytes: &[u8]) -> (Element, &[u8]) { + let commitment: CommitmentBytes = bytes[..64] + .try_into() + .expect("Expected a slice of exactly 64 bytes"); + + let element = Element::from_bytes_unchecked_uncompressed(commitment); + // Increment the slice by 64 bytes + (element, &bytes[64..]) +} + #[must_use] pub(crate) fn take_group_element(bytes: &[u8]) -> (Element, &[u8]) { let element = Element::from_bytes(&bytes[0..32]).expect("could not deserialize element"); @@ -151,13 +212,13 @@ pub(crate) fn take_scalar(bytes: &[u8]) -> (Fr, &[u8]) { (y_i, &bytes[32..]) } -pub(crate) fn fr_to_le_bytes(fr: banderwagon::Fr) -> [u8; 32] { +pub fn fr_to_le_bytes(fr: banderwagon::Fr) -> [u8; 32] { let mut bytes = [0u8; 32]; fr.serialize_compressed(&mut bytes[..]) .expect("Failed to serialize scalar to bytes"); bytes } -pub(crate) fn fr_from_le_bytes(bytes: &[u8]) -> Result { +pub fn fr_from_le_bytes(bytes: &[u8]) -> Result { banderwagon::Fr::deserialize_uncompressed(bytes).map_err(|_| Error::FailedToDeserializeScalar { bytes: bytes.to_vec(), }) diff --git a/ipa-multipoint/src/ipa.rs b/ipa-multipoint/src/ipa.rs index 89df784..2435140 100644 --- a/ipa-multipoint/src/ipa.rs +++ b/ipa-multipoint/src/ipa.rs @@ -25,6 +25,10 @@ impl IPAProof { pub(crate) fn serialized_size(&self) -> usize { (self.L_vec.len() * 2 + 1) * 32 } + // might be : self.L_vec.len() * 2 * 64 + 32 or something similar + pub(crate) fn uncompressed_size(&self) -> usize { + (self.L_vec.len() * 2 * 64) + 32 + } pub fn from_bytes(bytes: &[u8], poly_degree: usize) -> IOResult { // Given the polynomial degree, we will have log2 * 2 points let num_points = log2(poly_degree); @@ -58,6 +62,43 @@ impl IPAProof { Ok(IPAProof { L_vec, R_vec, a }) } + pub fn from_bytes_unchecked_uncompressed( + bytes: &[u8], + poly_degree: usize, + ) -> IOResult { + // Given the polynomial degree, we will have log2 * 2 points + let num_points = log2(poly_degree); + let mut L_vec = Vec::with_capacity(num_points as usize); + let mut R_vec = Vec::with_capacity(num_points as usize); + + assert_eq!(((num_points * 2) * 64) + 32, bytes.len() as u32); + + let (points_bytes, a_bytes) = bytes.split_at(bytes.len() - 32); + + assert!(a_bytes.len() == 32); + + // Chunk the byte slice into 64 bytes + let mut chunks = points_bytes.chunks_exact(64); + + for _ in 0..num_points { + let chunk = chunks.next().unwrap(); + let L_bytes: [u8; 64] = chunk.try_into().unwrap(); + let point: Element = Element::from_bytes_unchecked_uncompressed(L_bytes); + L_vec.push(point) + } + + for _ in 0..num_points { + let chunk = chunks.next().unwrap(); + let R_bytes: [u8; 64] = chunk.try_into().unwrap(); + let point: Element = Element::from_bytes_unchecked_uncompressed(R_bytes); + R_vec.push(point) + } + + let a: Fr = CanonicalDeserialize::deserialize_compressed(a_bytes) + .map_err(|_| IOError::from(IOErrorKind::InvalidData))?; + + Ok(IPAProof { L_vec, R_vec, a }) + } pub fn to_bytes(&self) -> IOResult> { // We do not serialize the length. We assume that the deserializer knows this. let mut bytes = Vec::with_capacity(self.serialized_size()); @@ -75,6 +116,22 @@ impl IPAProof { .map_err(|_| IOError::from(IOErrorKind::InvalidData))?; Ok(bytes) } + pub fn to_bytes_uncompressed(&self) -> IOResult> { + let mut bytes = Vec::with_capacity(self.uncompressed_size()); + + for L in &self.L_vec { + bytes.extend(L.to_bytes_uncompressed()); + } + + for R in &self.R_vec { + bytes.extend(R.to_bytes_uncompressed()); + } + + self.a + .serialize_uncompressed(&mut bytes) + .map_err(|_| IOError::from(IOErrorKind::InvalidData))?; + Ok(bytes) + } } pub fn create( diff --git a/ipa-multipoint/src/multiproof.rs b/ipa-multipoint/src/multiproof.rs index e44812c..03d3720 100644 --- a/ipa-multipoint/src/multiproof.rs +++ b/ipa-multipoint/src/multiproof.rs @@ -186,6 +186,24 @@ impl MultiPointProof { g_x_comm, }) } + + pub fn from_bytes_unchecked_uncompressed( + bytes: &[u8], + poly_degree: usize, + ) -> crate::IOResult { + let g_x_comm_bytes: [u8; 64] = bytes[..64] + .try_into() + .expect("Expected a slice of exactly 64 bytes"); + let ipa_bytes = &bytes[64..]; // TODO: we should return a Result here incase the user gives us bad bytes + + let g_x_comm = Element::from_bytes_unchecked_uncompressed(g_x_comm_bytes); + + let open_proof = IPAProof::from_bytes_unchecked_uncompressed(ipa_bytes, poly_degree)?; + Ok(MultiPointProof { + open_proof, + g_x_comm, + }) + } pub fn to_bytes(&self) -> crate::IOResult> { let mut bytes = Vec::with_capacity(self.open_proof.serialized_size() + 32); bytes.extend(self.g_x_comm.to_bytes()); @@ -193,6 +211,13 @@ impl MultiPointProof { bytes.extend(self.open_proof.to_bytes()?); Ok(bytes) } + pub fn to_bytes_uncompressed(&self) -> crate::IOResult> { + let mut bytes = Vec::with_capacity(self.open_proof.uncompressed_size() + 64); + bytes.extend(self.g_x_comm.to_bytes_uncompressed()); + + bytes.extend(self.open_proof.to_bytes_uncompressed()?); + Ok(bytes) + } } impl MultiPointProof {