From 2645e074f3eb5cbf158f085552e53f012dc89aa5 Mon Sep 17 00:00:00 2001 From: "Ya-wen, Jeng" Date: Thu, 18 Jan 2024 11:42:40 +0800 Subject: [PATCH] feat(core,ffi): convert proof and input for smart contract --- .../src/middleware/circom/serialization.rs | 9 + mopro-ffi/src/lib.rs | 59 +++++- mopro-ffi/src/mopro.udl | 20 ++ mopro-ffi/tests/bindings/test_mopro.kts | 13 ++ mopro-ffi/tests/bindings/test_mopro.swift | 6 + mopro-ios/MoproKit/Bindings/mopro.swift | 197 ++++++++++++++++++ mopro-ios/MoproKit/Include/moproFFI.h | 10 + 7 files changed, 312 insertions(+), 2 deletions(-) diff --git a/mopro-core/src/middleware/circom/serialization.rs b/mopro-core/src/middleware/circom/serialization.rs index 47fa4008..94905f98 100644 --- a/mopro-core/src/middleware/circom/serialization.rs +++ b/mopro-core/src/middleware/circom/serialization.rs @@ -1,4 +1,5 @@ use ark_bn254::Bn254; +use ark_circom::ethereum; use ark_ec::pairing::Pairing; use ark_groth16::{Proof, ProvingKey}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; @@ -49,6 +50,14 @@ pub fn deserialize_inputs(data: Vec) -> SerializableInputs { SerializableInputs::deserialize_uncompressed(&mut &data[..]).expect("Deserialization failed") } +pub fn convert_proof(proof: &SerializableProof) -> ethereum::Proof { + ethereum::Proof::from(proof.0.clone()) +} + +pub fn convert_inputs(inputs: &SerializableInputs) -> ethereum::Inputs { + ethereum::Inputs::from(&inputs.0[..]) +} + #[cfg(test)] mod tests { use super::*; diff --git a/mopro-ffi/src/lib.rs b/mopro-ffi/src/lib.rs index 94c5c9b1..421ef357 100644 --- a/mopro-ffi/src/lib.rs +++ b/mopro-ffi/src/lib.rs @@ -19,6 +19,25 @@ pub struct GenerateProofResult { pub inputs: Vec, } +#[derive(Debug, Clone)] +pub struct G1 { + pub x: String, + pub y: String, +} + +#[derive(Debug, Clone)] +pub struct G2 { + pub x: Vec, + pub y: Vec, +} + +#[derive(Debug, Clone)] +pub struct ProofCalldata { + pub a: G1, + pub b: G2, + pub c: G1, +} + // NOTE: Make UniFFI and Rust happy, can maybe do some renaming here #[allow(non_snake_case)] #[derive(Debug, Clone)] @@ -104,6 +123,30 @@ pub fn verify_proof2(proof: Vec, public_input: Vec) -> Result) -> ProofCalldata { + let deserialized_proof = circom::serialization::deserialize_proof(proof); + let proof = circom::serialization::convert_proof(&deserialized_proof); + let a = G1 { + x: proof.a.x.to_string(), + y: proof.a.y.to_string() + }; + let b = G2 { + x: proof.b.x.iter().map(|x| x.to_string()).collect(), + y: proof.b.y.iter().map(|x| x.to_string()).collect(), + }; + let c = G1 { + x: proof.c.x.to_string(), + y: proof.c.y.to_string() + }; + ProofCalldata { a, b, c } +} + +pub fn convert_inputs(inputs: Vec) -> Vec { + let deserialized_inputs = circom::serialization::deserialize_inputs(inputs); + let inputs = deserialized_inputs.0.iter().map(|x| x.to_string()).collect(); + inputs +} + // TODO: Use FFIError::SerializationError instead impl MoproCircom { pub fn new() -> Self { @@ -242,9 +285,15 @@ mod tests { assert_eq!(serialized_inputs, serialized_outputs); // Step 3: Verify Proof - let is_valid = mopro_circom.verify_proof(serialized_proof, serialized_inputs)?; + let is_valid = mopro_circom.verify_proof(serialized_proof.clone(), serialized_inputs.clone())?; assert!(is_valid); + // Step 4: Convert Proof + let proof_calldata = convert_proof(serialized_proof); + let inputs_calldata = convert_inputs(serialized_inputs); + assert!(proof_calldata.a.x.len() > 0); + assert!(inputs_calldata.len() > 0); + Ok(()) } @@ -287,9 +336,15 @@ mod tests { // Step 3: Verify Proof - let is_valid = mopro_circom.verify_proof(serialized_proof, serialized_inputs)?; + let is_valid = mopro_circom.verify_proof(serialized_proof.clone(), serialized_inputs.clone())?; assert!(is_valid); + // Step 4: Convert Proof + let proof_calldata = convert_proof(serialized_proof); + let inputs_calldata = convert_inputs(serialized_inputs); + assert!(proof_calldata.a.x.len() > 0); + assert!(inputs_calldata.len() > 0); + Ok(()) } } diff --git a/mopro-ffi/src/mopro.udl b/mopro-ffi/src/mopro.udl index 875a5b1b..b7ef5e51 100644 --- a/mopro-ffi/src/mopro.udl +++ b/mopro-ffi/src/mopro.udl @@ -13,6 +13,9 @@ namespace mopro { [Throws=MoproError] boolean verify_proof2(bytes proof, bytes public_input); + + ProofCalldata convert_proof(bytes proof); + sequence convert_inputs(bytes inputs); }; dictionary SetupResult { @@ -24,6 +27,23 @@ dictionary GenerateProofResult { bytes inputs; }; +dictionary G1 { + string x; + string y; +}; + +dictionary G2 { + sequence x; + sequence y; +}; + +dictionary ProofCalldata { + G1 a; + G2 b; + G1 c; +}; + + [Error] enum MoproError { "CircomError", diff --git a/mopro-ffi/tests/bindings/test_mopro.kts b/mopro-ffi/tests/bindings/test_mopro.kts index 838e72fe..f8df1f17 100644 --- a/mopro-ffi/tests/bindings/test_mopro.kts +++ b/mopro-ffi/tests/bindings/test_mopro.kts @@ -4,18 +4,31 @@ var wasmPath = "../mopro-core/examples/circom/multiplier2/target/multiplier2_js/ var r1csPath = "../mopro-core/examples/circom/multiplier2/target/multiplier2.r1cs" try { + // Setup var moproCircom = MoproCircom() var setupResult = moproCircom.setup(wasmPath, r1csPath) assert(setupResult.provingKey.size > 0) { "Proving key should not be empty" } + // Prepare inputs val inputs = mutableMapOf>() inputs["a"] = listOf("3") inputs["b"] = listOf("5") + // Generate proof var generateProofResult = moproCircom.generateProof(inputs) assert(generateProofResult.proof.size > 0) { "Proof is empty" } + + // Verify proof var isValid = moproCircom.verifyProof(generateProofResult.proof, generateProofResult.inputs) assert(isValid) { "Proof is invalid" } + + // Convert proof + var convertProofResult = convertProof(generateProofResult.proof) + var convertInputsResult = convertInputs(generateProofResult.inputs) + assert(convertProofResult.a.x.isNotEmpty()) { "Proof is empty" } + assert(convertInputsResult.size > 0) { "Inputs are empty" } + + } catch (e: Exception) { println(e) } diff --git a/mopro-ffi/tests/bindings/test_mopro.swift b/mopro-ffi/tests/bindings/test_mopro.swift index 7193618a..280128fa 100644 --- a/mopro-ffi/tests/bindings/test_mopro.swift +++ b/mopro-ffi/tests/bindings/test_mopro.swift @@ -58,6 +58,12 @@ do { let isValid = try moproCircom.verifyProof(proof: generateProofResult.proof, publicInput: generateProofResult.inputs) assert(isValid, "Proof verification should succeed") + // Convert Proof + let convertProofResult = convertProof(proof: generateProofResult.proof) + let convertInputsResult = convertInputs(inputs: generateProofResult.inputs) + assert(convertProofResult.a.x.count > 0, "Proof should not be empty") + assert(convertInputsResult.count > 0, "Inputs should not be empty") + } catch let error as MoproError { print("MoproError: \(error)") } catch { diff --git a/mopro-ios/MoproKit/Bindings/mopro.swift b/mopro-ios/MoproKit/Bindings/mopro.swift index 32127caf..e7a9b2d1 100644 --- a/mopro-ios/MoproKit/Bindings/mopro.swift +++ b/mopro-ios/MoproKit/Bindings/mopro.swift @@ -492,6 +492,116 @@ public func FfiConverterTypeMoproCircom_lower(_ value: MoproCircom) -> UnsafeMut } +public struct G1 { + public var x: String + public var y: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(x: String, y: String) { + self.x = x + self.y = y + } +} + + +extension G1: Equatable, Hashable { + public static func ==(lhs: G1, rhs: G1) -> Bool { + if lhs.x != rhs.x { + return false + } + if lhs.y != rhs.y { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(x) + hasher.combine(y) + } +} + + +public struct FfiConverterTypeG1: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> G1 { + return try G1( + x: FfiConverterString.read(from: &buf), + y: FfiConverterString.read(from: &buf) + ) + } + + public static func write(_ value: G1, into buf: inout [UInt8]) { + FfiConverterString.write(value.x, into: &buf) + FfiConverterString.write(value.y, into: &buf) + } +} + + +public func FfiConverterTypeG1_lift(_ buf: RustBuffer) throws -> G1 { + return try FfiConverterTypeG1.lift(buf) +} + +public func FfiConverterTypeG1_lower(_ value: G1) -> RustBuffer { + return FfiConverterTypeG1.lower(value) +} + + +public struct G2 { + public var x: [String] + public var y: [String] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(x: [String], y: [String]) { + self.x = x + self.y = y + } +} + + +extension G2: Equatable, Hashable { + public static func ==(lhs: G2, rhs: G2) -> Bool { + if lhs.x != rhs.x { + return false + } + if lhs.y != rhs.y { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(x) + hasher.combine(y) + } +} + + +public struct FfiConverterTypeG2: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> G2 { + return try G2( + x: FfiConverterSequenceString.read(from: &buf), + y: FfiConverterSequenceString.read(from: &buf) + ) + } + + public static func write(_ value: G2, into buf: inout [UInt8]) { + FfiConverterSequenceString.write(value.x, into: &buf) + FfiConverterSequenceString.write(value.y, into: &buf) + } +} + + +public func FfiConverterTypeG2_lift(_ buf: RustBuffer) throws -> G2 { + return try FfiConverterTypeG2.lift(buf) +} + +public func FfiConverterTypeG2_lower(_ value: G2) -> RustBuffer { + return FfiConverterTypeG2.lower(value) +} + + public struct GenerateProofResult { public var proof: Data public var inputs: Data @@ -547,6 +657,69 @@ public func FfiConverterTypeGenerateProofResult_lower(_ value: GenerateProofResu } +public struct ProofCalldata { + public var a: G1 + public var b: G2 + public var c: G1 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(a: G1, b: G2, c: G1) { + self.a = a + self.b = b + self.c = c + } +} + + +extension ProofCalldata: Equatable, Hashable { + public static func ==(lhs: ProofCalldata, rhs: ProofCalldata) -> Bool { + if lhs.a != rhs.a { + return false + } + if lhs.b != rhs.b { + return false + } + if lhs.c != rhs.c { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(a) + hasher.combine(b) + hasher.combine(c) + } +} + + +public struct FfiConverterTypeProofCalldata: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ProofCalldata { + return try ProofCalldata( + a: FfiConverterTypeG1.read(from: &buf), + b: FfiConverterTypeG2.read(from: &buf), + c: FfiConverterTypeG1.read(from: &buf) + ) + } + + public static func write(_ value: ProofCalldata, into buf: inout [UInt8]) { + FfiConverterTypeG1.write(value.a, into: &buf) + FfiConverterTypeG2.write(value.b, into: &buf) + FfiConverterTypeG1.write(value.c, into: &buf) + } +} + + +public func FfiConverterTypeProofCalldata_lift(_ buf: RustBuffer) throws -> ProofCalldata { + return try FfiConverterTypeProofCalldata.lift(buf) +} + +public func FfiConverterTypeProofCalldata_lower(_ value: ProofCalldata) -> RustBuffer { + return FfiConverterTypeProofCalldata.lower(value) +} + + public struct SetupResult { public var provingKey: Data @@ -700,6 +873,24 @@ public func add(a: UInt32, b: UInt32) -> UInt32 { ) } +public func convertInputs(inputs: Data) -> [String] { + return try! FfiConverterSequenceString.lift( + try! rustCall() { + uniffi_mopro_ffi_fn_func_convert_inputs( + FfiConverterData.lower(inputs),$0) +} + ) +} + +public func convertProof(proof: Data) -> ProofCalldata { + return try! FfiConverterTypeProofCalldata.lift( + try! rustCall() { + uniffi_mopro_ffi_fn_func_convert_proof( + FfiConverterData.lower(proof),$0) +} + ) +} + public func generateProof2(circuitInputs: [String: [String]]) throws -> GenerateProofResult { return try FfiConverterTypeGenerateProofResult.lift( try rustCallWithError(FfiConverterTypeMoproError.lift) { @@ -762,6 +953,12 @@ private var initializationResult: InitializationResult { if (uniffi_mopro_ffi_checksum_func_add() != 8411) { return InitializationResult.apiChecksumMismatch } + if (uniffi_mopro_ffi_checksum_func_convert_inputs() != 59191) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mopro_ffi_checksum_func_convert_proof() != 6119) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_mopro_ffi_checksum_func_generate_proof2() != 40187) { return InitializationResult.apiChecksumMismatch } diff --git a/mopro-ios/MoproKit/Include/moproFFI.h b/mopro-ios/MoproKit/Include/moproFFI.h index 745221d8..984f73e3 100644 --- a/mopro-ios/MoproKit/Include/moproFFI.h +++ b/mopro-ios/MoproKit/Include/moproFFI.h @@ -76,6 +76,10 @@ int8_t uniffi_mopro_ffi_fn_method_moprocircom_verify_proof(void*_Nonnull ptr, Ru ); uint32_t uniffi_mopro_ffi_fn_func_add(uint32_t a, uint32_t b, RustCallStatus *_Nonnull out_status ); +RustBuffer uniffi_mopro_ffi_fn_func_convert_inputs(RustBuffer inputs, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_mopro_ffi_fn_func_convert_proof(RustBuffer proof, RustCallStatus *_Nonnull out_status +); RustBuffer uniffi_mopro_ffi_fn_func_generate_proof2(RustBuffer circuit_inputs, RustCallStatus *_Nonnull out_status ); RustBuffer uniffi_mopro_ffi_fn_func_hello(RustCallStatus *_Nonnull out_status @@ -204,6 +208,12 @@ void ffi_mopro_ffi_rust_future_complete_void(void* _Nonnull handle, RustCallStat ); uint16_t uniffi_mopro_ffi_checksum_func_add(void +); +uint16_t uniffi_mopro_ffi_checksum_func_convert_inputs(void + +); +uint16_t uniffi_mopro_ffi_checksum_func_convert_proof(void + ); uint16_t uniffi_mopro_ffi_checksum_func_generate_proof2(void