Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[move][natives] added batch bulletproof natives #15832

Open
wants to merge 22 commits into
base: main
Choose a base branch
from

Conversation

dovgopoly
Copy link

@dovgopoly dovgopoly commented Jan 28, 2025

Description

Adds Move native functions for batch verification of Bulletproof ZK range proofs.

What

This pull request introduces native Move functions that wrap the batch operations provided by the underlying dalek-cryptography's Bulletproofs library.

Why

The primary goal is to reduce gas overhead when multiple range proofs of the same structure are needed to be verified at once. By batching the proofs together, the system minimizes repetitive computational costs, thereby enhancing performance.

How

The implementation leverages the existing Rust logic from the Bulletproofs library by wrapping its prove_multiple and verify_multiple functions as native functions in Move.

Other fixes/contributions:

  • Fixes an outstanding issue in aptos-crypto that this PR was about to fix.
  • Adds a TEST_FILTER environment variable to filter unit tests when testing the Move framework code with cargo test inside aptos-move/framework

How Has This Been Tested?

Key Areas to Review

Type of Change

  • New feature
  • Bug fix
  • Breaking change
  • Performance improvement
  • Refactoring
  • Dependency update
  • Documentation update
  • Tests

Which Components or Systems Does This Change Impact?

  • Validator Node
  • Full Node (API, Indexer, etc.)
  • Move/Aptos Virtual Machine
  • Aptos Framework
  • Aptos CLI/SDK
  • Developer Infrastructure
  • Move Compiler
  • Other (specify)

Checklist

  • I have read and followed the CONTRIBUTING doc
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I identified and added all stakeholders and component owners affected by this change as reviewers
  • I tested both happy and unhappy path of the functionality
  • I have made corresponding changes to the documentation

Copy link

trunk-io bot commented Jan 28, 2025

⏱️ 9m total CI duration on this PR
Job Cumulative Duration Recent Runs
check-dynamic-deps 3m 🟩
permission-check 2m 🟥🟥🟩🟩🟩 (+28 more)
rust-cargo-deny 2m 🟩
permission-check 2m 🟥🟥🟩🟩🟩 (+28 more)
general-lints 27s 🟩
semgrep/ci 21s 🟩
file_change_determinator 13s 🟩

settingsfeedbackdocs ⋅ learn more about trunk.io

@dovgopoly dovgopoly force-pushed the distributedlab/batch-rp-natives branch from cc4a57b to b07b143 Compare January 29, 2025 16:56
@dovgopoly dovgopoly changed the title [move][natives] added move bulletproof natives and rust wrappers (draft, wip) [move][natives] added batch bulletproof natives Jan 29, 2025
@dovgopoly dovgopoly marked this pull request as ready for review January 29, 2025 16:57
@dovgopoly dovgopoly force-pushed the distributedlab/batch-rp-natives branch from b07b143 to 49abefc Compare January 29, 2025 18:20
) -> SafeNativeResult<SmallVec<[Value; 1]>> {
charge_gas(context, comm_points.len(), bit_length)?;

let range_proof = match bulletproofs::RangeProof::from_bytes(proof_bytes) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abort here with the same error code as before.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, in the native for individual proof verification, we charge for the deserialization.

We should do it here too.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added error code. Need to discuss charging.

@@ -91,6 +104,54 @@ fn native_verify_range_proof(
verify_range_proof(context, &comm_point, &pg, &proof_bytes[..], num_bits, dst)
}

fn native_verify_batch_range_proof(
Copy link
Contributor

@alinush alinush Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to also charge the BULLETPROOFS_BASE base cost (see the native for individual proof verification).

@zjma do you know why this is missing / what happened here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the batch verification implementation is multivariate and non-linear. I suggested we use a value table for gas charging, so BULLETPROOFS_BASE is not needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you double check with @vgao1996. I recall he always advises to have a base cost.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @vgao1996's point was: every native needs to have a non-zero cost, but that's it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to *_verify.rs

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// Generated at time 1738278510.3867955 by `scripts/algebra-gas/update_bulletproofs_batch_vrfy_gas_params.py` with gas_per_ns=295.16.
[bulletproofs_verify_base_batch_1_bits_8: InternalGas, { RELEASE_V1_27.. => "bulletproofs.verify.base_batch_1_bits_8" }, 133_731_503],
[bulletproofs_verify_base_batch_1_bits_16: InternalGas, { RELEASE_V1_27.. => "bulletproofs.verify.base_batch_1_bits_16" }, 195_542_132],
[bulletproofs_verify_base_batch_1_bits_32: InternalGas, { RELEASE_V1_27.. => "bulletproofs.verify.base_batch_1_bits_32" }, 310_976_813],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be (somewhat) consistent with the cost of individually verifying a Bulletproof for a 32-bit committed value via verify_range_proof_internal, no?

There, the cost would be:

bulletproofs_base + 32 * bulletproofs_per_bit_rangeproof_verify + (some very small deserialization cost)
= 
11_794_651 + 32 * 1_004_253
=
43_930_747

But here the cost is 310_976_813 which is 7x larger.

Copy link
Contributor

@alinush alinush left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this PR!

I requested a bunch of changes (see comments), amongst which:

  1. Please revert back the error code numbers. Otherwise, backwards compatibility issues.
  2. Please simplify the Move code. There is unnecessarily repeated code (some of it ours).
  3. Please make sure the batch verification native aborts when proof deserialization fails with the same code as the native for individual proof verification.
  4. Please make sure the new batch verification native always charges BULLETPROOFS_BASE like the individual one (e.g., all the BLS12-381 natives charge the same base cost; see here)

Most importantly, this is a non-trivial change that requires a proper PR description. Please describe/include:

  1. The what
  2. The why
  3. The how
  4. Explain how gas costs were calibrated.
  5. Benchmarks for the (Rust) batch proving times and verification times, for all possible batch-size / num-bits combinations. (e.g., see "Benchmarks" here)

Comment on lines +68 to +93
let test_filter = std::env::var("TEST_FILTER").ok();
run_tests_for_pkg("aptos-framework", test_filter);
}

#[test]
fn move_aptos_stdlib_unit_tests() {
run_tests_for_pkg("aptos-stdlib");
let test_filter = std::env::var("TEST_FILTER").ok();
run_tests_for_pkg("aptos-stdlib", test_filter);
}

#[test]
fn move_stdlib_unit_tests() {
run_tests_for_pkg("move-stdlib");
let test_filter = std::env::var("TEST_FILTER").ok();
run_tests_for_pkg("move-stdlib", test_filter);
}

#[test]
fn move_token_unit_tests() {
run_tests_for_pkg("aptos-token");
let test_filter = std::env::var("TEST_FILTER").ok();
run_tests_for_pkg("aptos-token", test_filter);
}

#[test]
fn move_token_objects_unit_tests() {
run_tests_for_pkg("aptos-token-objects");
let test_filter = std::env::var("TEST_FILTER").ok();
run_tests_for_pkg("aptos-token-objects", test_filter);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better load the env inside run_tests_for_pkg then?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -28,11 +28,14 @@ fn run_tests_for_pkg(path_to_pkg: impl Into<String>) {
..Default::default()
};

let mut utc = UnitTestingConfig::default();
utc.filter = test_filter;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alinush
utc.report_statistics, if set to true, prints the amount of gas used. Can also be very useful and worth a separate envvar.

@zjma
Copy link
Contributor

zjma commented Feb 4, 2025

i can take care of the rebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants