Skip to content

Commit

Permalink
[rooch-networkgh-1644] add groth16 verification support.
Browse files Browse the repository at this point in the history
  • Loading branch information
Feliciss committed Jul 13, 2024
1 parent 58ec3e6 commit c3fe20e
Show file tree
Hide file tree
Showing 10 changed files with 764 additions and 39 deletions.
425 changes: 387 additions & 38 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ uuid = { version = "1.10.0", features = ["v4", "fast-rng"] }
protobuf = { version = "2.28", features = ["with-bytes"] }
redb = { version = "2.1.1" }
rust-rocksdb = { version = "0.27.0", features = ["lz4", "jemalloc"] }

fastcrypto-zkp = { version = "0.1.3" }

# Note: the BEGIN and END comments below are required for external tooling. Do not remove.
# BEGIN MOVE DEPENDENCIES
Expand Down
1 change: 1 addition & 0 deletions frameworks/moveos-stdlib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ bech32 = { workspace = true }
bs58 = { workspace = true, features = ["check"] }
revm-precompile = { workspace = true }
revm-primitives = { workspace = true }
fastcrypto-zkp = { workspace = true }

move-binary-format = { workspace = true }
move-bytecode-utils = { workspace = true }
Expand Down
112 changes: 112 additions & 0 deletions frameworks/moveos-stdlib/sources/groth16.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

/// Source from https://github.com/MystenLabs/sui/blob/924c294d9b4a98d5bc50cd6c830e7c0cdbc2a2b1/crates/sui-framework/packages/sui-framework/sources/crypto/groth16.move
module moveos_std::groth16 {

#[allow(unused_const)]
// Error for input is not a valid Arkwork representation of a verifying key.
const EInvalidVerifyingKey: u64 = 0;

#[allow(unused_const)]
// Error if the given curve is not supported
const EInvalidCurve: u64 = 1;

#[allow(unused_const)]
// Error if the number of public inputs given exceeds the max.
const ETooManyPublicInputs: u64 = 2;

/// Represents an elliptic curve construction to be used in the verifier. Currently we support BLS12-381 and BN254.
/// This should be given as the first parameter to `prepare_verifying_key` or `verify_groth16_proof`.
public struct Curve has store, copy, drop {
id: u8,
}

/// Return the `Curve` value indicating that the BLS12-381 construction should be used in a given function.
public fun bls12381(): Curve { Curve { id: 0 } }

/// Return the `Curve` value indicating that the BN254 construction should be used in a given function.
public fun bn254(): Curve { Curve { id: 1 } }

/// A `PreparedVerifyingKey` consisting of four components in serialized form.
public struct PreparedVerifyingKey has store, copy, drop {
vk_gamma_abc_g1_bytes: vector<u8>,
alpha_g1_beta_g2_bytes: vector<u8>,
gamma_g2_neg_pc_bytes: vector<u8>,
delta_g2_neg_pc_bytes: vector<u8>
}

/// Creates a `PreparedVerifyingKey` from bytes.
public fun pvk_from_bytes(vk_gamma_abc_g1_bytes: vector<u8>, alpha_g1_beta_g2_bytes: vector<u8>, gamma_g2_neg_pc_bytes: vector<u8>, delta_g2_neg_pc_bytes: vector<u8>): PreparedVerifyingKey {
PreparedVerifyingKey {
vk_gamma_abc_g1_bytes,
alpha_g1_beta_g2_bytes,
gamma_g2_neg_pc_bytes,
delta_g2_neg_pc_bytes
}
}

/// Returns bytes of the four components of the `PreparedVerifyingKey`.
public fun pvk_to_bytes(pvk: PreparedVerifyingKey): vector<vector<u8>> {
vector[
pvk.vk_gamma_abc_g1_bytes,
pvk.alpha_g1_beta_g2_bytes,
pvk.gamma_g2_neg_pc_bytes,
pvk.delta_g2_neg_pc_bytes,
]
}

/// A `PublicProofInputs` wrapper around its serialized bytes.
public struct PublicProofInputs has store, copy, drop {
bytes: vector<u8>,
}

/// Creates a `PublicProofInputs` wrapper from bytes.
public fun public_proof_inputs_from_bytes(bytes: vector<u8>): PublicProofInputs {
PublicProofInputs { bytes }
}

/// A `ProofPoints` wrapper around the serialized form of three proof points.
public struct ProofPoints has store, copy, drop {
bytes: vector<u8>
}

/// Creates a Groth16 `ProofPoints` from bytes.
public fun proof_points_from_bytes(bytes: vector<u8>): ProofPoints {
ProofPoints { bytes }
}

/// @param curve: What elliptic curve construction to use. See `bls12381` and `bn254`.
/// @param verifying_key: An Arkworks canonical compressed serialization of a verifying key.
///
/// Returns four vectors of bytes representing the four components of a prepared verifying key.
/// This step computes one pairing e(P, Q), and binds the verification to one particular proof statement.
/// This can be used as inputs for the `verify_groth16_proof` function.
public fun prepare_verifying_key(curve: &Curve, verifying_key: &vector<u8>): PreparedVerifyingKey {
prepare_verifying_key_internal(curve.id, verifying_key)
}

/// Native functions that flattens the inputs into an array and passes to the Rust native function. May abort with `EInvalidVerifyingKey` or `EInvalidCurve`.
native fun prepare_verifying_key_internal(curve: u8, verifying_key: &vector<u8>): PreparedVerifyingKey;

/// @param curve: What elliptic curve construction to use. See the `bls12381` and `bn254` functions.
/// @param prepared_verifying_key: Consists of four vectors of bytes representing the four components of a prepared verifying key.
/// @param public_proof_inputs: Represent inputs that are public.
/// @param proof_points: Represent three proof points.
///
/// Returns a boolean indicating whether the proof is valid.
public fun verify_groth16_proof(curve: &Curve, prepared_verifying_key: &PreparedVerifyingKey, public_proof_inputs: &PublicProofInputs, proof_points: &ProofPoints): bool {
verify_groth16_proof_internal(
curve.id,
&prepared_verifying_key.vk_gamma_abc_g1_bytes,
&prepared_verifying_key.alpha_g1_beta_g2_bytes,
&prepared_verifying_key.gamma_g2_neg_pc_bytes,
&prepared_verifying_key.delta_g2_neg_pc_bytes,
&public_proof_inputs.bytes,
&proof_points.bytes
)
}

/// Native functions that flattens the inputs into arrays of vectors and passed to the Rust native function. May abort with `EInvalidCurve` or `ETooManyPublicInputs`.
native fun verify_groth16_proof_internal(curve: u8, vk_gamma_abc_g1_bytes: &vector<u8>, alpha_g1_beta_g2_bytes: &vector<u8>, gamma_g2_neg_pc_bytes: &vector<u8>, delta_g2_neg_pc_bytes: &vector<u8>, public_proof_inputs: &vector<u8>, proof_points: &vector<u8>): bool;
}
6 changes: 6 additions & 0 deletions frameworks/moveos-stdlib/src/natives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub struct GasParameters {
pub hash: moveos_stdlib::hash::GasParameters,
pub bls12381: moveos_stdlib::bls12381::GasParameters,
pub evm: moveos_stdlib::evm::GasParameters,
pub groth16: moveos_stdlib::groth16::GasParameters,
}

impl GasParameters {
Expand All @@ -55,6 +56,7 @@ impl GasParameters {
hash: moveos_stdlib::hash::GasParameters::zeros(),
bls12381: moveos_stdlib::bls12381::GasParameters::zeros(),
evm: moveos_stdlib::evm::GasParameters::zeros(),
groth16: moveos_stdlib::groth16::GasParameters::zeros(),
}
}
}
Expand Down Expand Up @@ -129,6 +131,10 @@ pub fn all_natives(gas_params: GasParameters) -> NativeFunctionTable {
moveos_stdlib::bls12381::make_all(gas_params.bls12381)
);
add_natives!("evm", moveos_stdlib::evm::make_all(gas_params.evm));
add_natives!(
"groth16",
moveos_stdlib::groth16::make_all(gas_params.groth16)
);

let moveos_native_fun_table = make_table_from_iter(MOVEOS_STD_ADDRESS, natives);
native_fun_table.extend(moveos_native_fun_table);
Expand Down
240 changes: 240 additions & 0 deletions frameworks/moveos-stdlib/src/natives/moveos_stdlib/groth16.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

use move_binary_format::errors::PartialVMResult;
use move_core_types::gas_algebra::{InternalGas, InternalGasPerByte, NumBytes};
use move_vm_runtime::native_functions::{NativeContext, NativeFunction};
use move_vm_types::{
loaded_data::runtime_types::Type,
natives::function::NativeResult,
pop_arg,
values::{self, Value, VectorRef},
};
use smallvec::smallvec;
use std::collections::VecDeque;

use crate::natives::helpers::{make_module_natives, make_native};

pub const INVALID_CURVE: u64 = 0;
pub const INVALID_VERIFYING_KEY: u64 = 1;
pub const TOO_MANY_PUBLIC_INPUTS: u64 = 2;

// These must match the corresponding values in sui::groth16::Curve.
pub const BLS12381: u8 = 0;
pub const BN254: u8 = 1;

// We need to set an upper bound on the number of public inputs to avoid a DoS attack
pub const MAX_PUBLIC_INPUTS: usize = 8;

/***************************************************************************************************
* native fun prepare_verifying_key_internal
* Implementation of the Move native function `prepare_verifying_key_internal(curve: u8, verifying_key: &vector<u8>): PreparedVerifyingKey`
* This function has two cost modes depending on the curve being set to `BLS12381` or `BN254`. The core formula is same but constants differ.
* If curve = 0, we use the `bls12381` cost constants, otherwise we use the `bn254` cost constants.
* gas cost: groth16_prepare_verifying_key_cost_base | covers various fixed costs in the oper
* Note: `curve` and `verifying_key` are fixed size, so their costs are included in the base cost.
**************************************************************************************************/
pub fn native_prepare_verifying_key_internal(
gas_params: &FromBytesGasParameters,
_context: &mut NativeContext,
ty_args: Vec<Type>,
mut args: VecDeque<Value>,
) -> PartialVMResult<NativeResult> {
debug_assert!(ty_args.is_empty());
debug_assert!(args.len() == 2);

let verifying_key = pop_arg!(args, VectorRef);
let curve = pop_arg!(args, u8);

let verifying_key_bytes_ref = verifying_key.as_bytes_ref();

let base_cost = match curve {
BLS12381 => {
52 // TODO: use variable?
}
BN254 => {
52 // TODO: use variable?
}
_ => {
return Ok(NativeResult::err(gas_params.base, INVALID_CURVE));
}
};

let result;
if curve == BLS12381 {
result = fastcrypto_zkp::bls12381::api::prepare_pvk_bytes(&verifying_key_bytes_ref);
} else if curve == BN254 {
result = fastcrypto_zkp::bn254::api::prepare_pvk_bytes(&verifying_key_bytes_ref);
} else {
return Ok(NativeResult::err(base_cost.into(), INVALID_CURVE));
}

match result {
Ok(pvk) => Ok(NativeResult::ok(
base_cost.into(),
smallvec![Value::struct_(values::Struct::pack(vec![
Value::vector_u8(pvk[0].to_vec()),
Value::vector_u8(pvk[1].to_vec()),
Value::vector_u8(pvk[2].to_vec()),
Value::vector_u8(pvk[3].to_vec())
]))],
)),
Err(_) => Ok(NativeResult::err(base_cost.into(), INVALID_VERIFYING_KEY)),
}
}

/***************************************************************************************************
* native fun verify_groth16_proof_internal
* Implementation of the Move native function `verify_groth16_proof_internal(curve: u8, vk_gamma_abc_g1_bytes: &vector<u8>,
* alpha_g1_beta_g2_bytes: &vector<u8>, gamma_g2_neg_pc_bytes: &vector<u8>, delta_g2_neg_pc_bytes: &vector<u8>,
* public_proof_inputs: &vector<u8>, proof_points: &vector<u8>): bool`
*
* This function has two cost modes depending on the curve being set to `BLS12381` or `BN254`. The core formula is same but constants differ.
* If curve = 0, we use the `bls12381` cost constants, otherwise we use the `bn254` cost constants.
* gas cost: groth16_prepare_verifying_key_cost_base | covers various fixed costs in the oper
* + groth16_verify_groth16_proof_internal_public_input_cost_per_byte
* * size_of(public_proof_inputs) | covers the cost of verifying each public input per byte
* + groth16_verify_groth16_proof_internal_cost_per_public_input
* * num_public_inputs) | covers the cost of verifying each public input per input
* Note: every other arg is fixed size, so their costs are included in the base cost.
**************************************************************************************************/
pub fn native_verify_groth16_proof_internal(
gas_params: &FromBytesGasParameters,
_context: &mut NativeContext,
ty_args: Vec<Type>,
mut args: VecDeque<Value>,
) -> PartialVMResult<NativeResult> {
debug_assert!(ty_args.is_empty());
debug_assert!(args.len() == 7);

let proof_points = pop_arg!(args, VectorRef);
let public_proof_inputs = pop_arg!(args, VectorRef);
let delta_g2_neg_pc = pop_arg!(args, VectorRef);
let gamma_g2_neg_pc = pop_arg!(args, VectorRef);
let alpha_g1_beta_g2 = pop_arg!(args, VectorRef);
let vk_gamma_abc_g1 = pop_arg!(args, VectorRef);
let curve = pop_arg!(args, u8);

let proof_points_bytes_ref = proof_points.as_bytes_ref();
let public_proof_inputs_bytes_ref = public_proof_inputs.as_bytes_ref();
let delta_g2_neg_pc_bytes_ref = delta_g2_neg_pc.as_bytes_ref();
let gamma_g2_neg_pc_bytes_ref = gamma_g2_neg_pc.as_bytes_ref();
let alpha_g1_beta_g2_bytes_ref = alpha_g1_beta_g2.as_bytes_ref();
let vk_gamma_abc_g1_bytes_ref = vk_gamma_abc_g1.as_bytes_ref();

let (base_cost, cost_per_public_input, num_public_inputs) = match curve {
BLS12381 => (
52, // TODO: use variable?
2, // TODO: use variable?
(public_proof_inputs_bytes_ref.len()
+ fastcrypto_zkp::bls12381::conversions::SCALAR_SIZE
- 1)
/ fastcrypto_zkp::bls12381::conversions::SCALAR_SIZE,
),
BN254 => (
52, // TODO: use variable?
2, // TODO: use variable?
(public_proof_inputs_bytes_ref.len() + fastcrypto_zkp::bn254::api::SCALAR_SIZE - 1)
/ fastcrypto_zkp::bn254::api::SCALAR_SIZE,
),
_ => {
return Ok(NativeResult::err(gas_params.base, INVALID_CURVE));
}
};

let cost = (gas_params.per_byte * NumBytes::new(public_proof_inputs_bytes_ref.len() as u64))
+ (cost_per_public_input * num_public_inputs as u64).into()
+ base_cost.into();

let result;
if curve == BLS12381 {
if public_proof_inputs_bytes_ref.len()
> fastcrypto_zkp::bls12381::conversions::SCALAR_SIZE * MAX_PUBLIC_INPUTS
{
return Ok(NativeResult::err(cost, TOO_MANY_PUBLIC_INPUTS));
}
result = fastcrypto_zkp::bls12381::api::verify_groth16_in_bytes(
&vk_gamma_abc_g1_bytes_ref,
&alpha_g1_beta_g2_bytes_ref,
&gamma_g2_neg_pc_bytes_ref,
&delta_g2_neg_pc_bytes_ref,
&public_proof_inputs_bytes_ref,
&proof_points_bytes_ref,
);
} else if curve == BN254 {
if public_proof_inputs_bytes_ref.len()
> fastcrypto_zkp::bn254::api::SCALAR_SIZE * MAX_PUBLIC_INPUTS
{
return Ok(NativeResult::err(cost, TOO_MANY_PUBLIC_INPUTS));
}
result = fastcrypto_zkp::bn254::api::verify_groth16_in_bytes(
&vk_gamma_abc_g1_bytes_ref,
&alpha_g1_beta_g2_bytes_ref,
&gamma_g2_neg_pc_bytes_ref,
&delta_g2_neg_pc_bytes_ref,
&public_proof_inputs_bytes_ref,
&proof_points_bytes_ref,
);
} else {
return Ok(NativeResult::err(cost, INVALID_CURVE));
}

Ok(NativeResult::ok(
cost,
smallvec![Value::bool(result.unwrap_or(false))],
))
}

#[derive(Debug, Clone)]
pub struct FromBytesGasParameters {
pub base: InternalGas,
pub per_byte: InternalGasPerByte,
}

impl FromBytesGasParameters {
pub fn zeros() -> Self {
Self {
base: 0.into(),
per_byte: 0.into(),
}
}
}

/***************************************************************************************************
* module
**************************************************************************************************/

#[derive(Debug, Clone)]
pub struct GasParameters {
pub prepare_verifying_key_internal: FromBytesGasParameters,
pub verify_groth16_proof_internal: FromBytesGasParameters,
}

impl GasParameters {
pub fn zeros() -> Self {
Self {
prepare_verifying_key_internal: FromBytesGasParameters::zeros(),
verify_groth16_proof_internal: FromBytesGasParameters::zeros(),
}
}
}

pub fn make_all(gas_params: GasParameters) -> impl Iterator<Item = (String, NativeFunction)> {
let natives = [
(
"prepare_verifying_key_internal",
make_native(
gas_params.prepare_verifying_key_internal,
native_prepare_verifying_key_internal,
),
),
(
"verify_groth16_proof_internal",
make_native(
gas_params.verify_groth16_proof_internal,
native_verify_groth16_proof_internal,
),
),
];
make_module_natives(natives)
}
Loading

0 comments on commit c3fe20e

Please sign in to comment.