diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs index 72526a5404d6e..bd35621c0d3f9 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs @@ -6,7 +6,9 @@ use crate::{ gas_feature_versions::{RELEASE_V1_14, RELEASE_V1_8, RELEASE_V1_9_SKIPPED}, gas_schedule::NativeGasParameters, - ver::gas_feature_versions::{RELEASE_V1_12, RELEASE_V1_13, RELEASE_V1_23, RELEASE_V1_26}, + ver::gas_feature_versions::{ + RELEASE_V1_12, RELEASE_V1_13, RELEASE_V1_23, RELEASE_V1_26, RELEASE_V1_28, + }, }; use aptos_gas_algebra::{ InternalGas, InternalGasPerAbstractValueUnit, InternalGasPerArg, InternalGasPerByte, @@ -242,6 +244,30 @@ crate::gas_schedule::macros::define_gas_parameters!( [bulletproofs_per_byte_rangeproof_deserialize: InternalGasPerByte, { 11.. => "bulletproofs.per_byte_rangeproof_deserialize" }, 121], // Bulletproofs gas parameters end. + // Bulletproofs batch verify gas parameters begin. + // Generated at time 1738897425.2325199 by `scripts/algebra-gas/update_bulletproofs_batch_verify_gas_params.py` with gas_per_ns=37.59. + [bulletproofs_verify_base_batch_1_bits_8: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_1_bits_8" }, 17_099_501], + [bulletproofs_verify_base_batch_1_bits_16: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_1_bits_16" }, 25_027_962], + [bulletproofs_verify_base_batch_1_bits_32: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_1_bits_32" }, 39_739_929], + [bulletproofs_verify_base_batch_1_bits_64: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_1_bits_64" }, 67_748_218], + [bulletproofs_verify_base_batch_2_bits_8: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_2_bits_8" }, 25_645_449], + [bulletproofs_verify_base_batch_2_bits_16: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_2_bits_16" }, 40_207_383], + [bulletproofs_verify_base_batch_2_bits_32: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_2_bits_32" }, 68_498_984], + [bulletproofs_verify_base_batch_2_bits_64: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_2_bits_64" }, 118_069_899], + [bulletproofs_verify_base_batch_4_bits_8: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_4_bits_8" }, 41_471_127], + [bulletproofs_verify_base_batch_4_bits_16: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_4_bits_16" }, 69_359_728], + [bulletproofs_verify_base_batch_4_bits_32: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_4_bits_32" }, 118_697_464], + [bulletproofs_verify_base_batch_4_bits_64: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_4_bits_64" }, 196_689_638], + [bulletproofs_verify_base_batch_8_bits_8: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_8_bits_8" }, 71_932_907], + [bulletproofs_verify_base_batch_8_bits_16: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_8_bits_16" }, 120_974_478], + [bulletproofs_verify_base_batch_8_bits_32: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_8_bits_32" }, 198_670_838], + [bulletproofs_verify_base_batch_8_bits_64: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_8_bits_64" }, 339_391_615], + [bulletproofs_verify_base_batch_16_bits_8: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_16_bits_8" }, 124_950_279], + [bulletproofs_verify_base_batch_16_bits_16: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_16_bits_16" }, 202_393_357], + [bulletproofs_verify_base_batch_16_bits_32: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_16_bits_32" }, 344_222_644], + [bulletproofs_verify_base_batch_16_bits_64: InternalGas, { RELEASE_V1_28.. => "bulletproofs.verify.base_batch_16_bits_64" }, 603_952_779], + // Bulletproofs batch verify gas parameters end. + [type_info_type_of_base: InternalGas, "type_info.type_of.base", 1102], // TODO(Gas): the on-chain name is wrong... [type_info_type_of_per_byte_in_str: InternalGasPerByte, "type_info.type_of.per_abstract_memory_unit", 18], diff --git a/aptos-move/aptos-gas-schedule/src/ver.rs b/aptos-move/aptos-gas-schedule/src/ver.rs index ad23e4d850b8f..797ba5b41978c 100644 --- a/aptos-move/aptos-gas-schedule/src/ver.rs +++ b/aptos-move/aptos-gas-schedule/src/ver.rs @@ -72,7 +72,7 @@ /// global operations. /// - V1 /// - TBA -pub const LATEST_GAS_FEATURE_VERSION: u64 = gas_feature_versions::RELEASE_V1_27; +pub const LATEST_GAS_FEATURE_VERSION: u64 = gas_feature_versions::RELEASE_V1_28; pub mod gas_feature_versions { pub const RELEASE_V1_8: u64 = 11; diff --git a/aptos-move/aptos-release-builder/src/components/feature_flags.rs b/aptos-move/aptos-release-builder/src/components/feature_flags.rs index 6a652d42e1c09..d03a4e4f3ba0b 100644 --- a/aptos-move/aptos-release-builder/src/components/feature_flags.rs +++ b/aptos-move/aptos-release-builder/src/components/feature_flags.rs @@ -138,6 +138,7 @@ pub enum FeatureFlag { PermissionedSigner, AccountAbstraction, VMBinaryFormatV8, + BulletproofsBatchNatives, } fn generate_features_blob(writer: &CodeWriter, data: &[u64]) { @@ -367,6 +368,7 @@ impl From for AptosFeatureFlag { }, FeatureFlag::PermissionedSigner => AptosFeatureFlag::PERMISSIONED_SIGNER, FeatureFlag::AccountAbstraction => AptosFeatureFlag::ACCOUNT_ABSTRACTION, + FeatureFlag::BulletproofsBatchNatives => AptosFeatureFlag::BULLETPROOFS_BATCH_NATIVES, } } } @@ -523,6 +525,7 @@ impl From for FeatureFlag { }, AptosFeatureFlag::PERMISSIONED_SIGNER => FeatureFlag::PermissionedSigner, AptosFeatureFlag::ACCOUNT_ABSTRACTION => FeatureFlag::AccountAbstraction, + AptosFeatureFlag::BULLETPROOFS_BATCH_NATIVES => FeatureFlag::BulletproofsBatchNatives, } } } diff --git a/aptos-move/framework/README.md b/aptos-move/framework/README.md index 31f6e61e81b06..d7c27d0518b52 100644 --- a/aptos-move/framework/README.md +++ b/aptos-move/framework/README.md @@ -50,7 +50,7 @@ To skip the Move prover tests, run: cargo test -- --skip prover ``` -To filter and run only the tests in specific packages (e.g., `aptos_stdlib`), run: +To filter and run **all** the tests in specific packages (e.g., `aptos_stdlib`), run: ``` cargo test -- aptos_stdlib --skip prover @@ -58,6 +58,23 @@ cargo test -- aptos_stdlib --skip prover (See tests in `tests/move_unit_test.rs` to determine which filter to use; e.g., to run the tests in `aptos_framework` you must filter by `move_framework`.) +To **filter by test name or module name** in a specific package (e.g., run the `test_empty_range_proof` in `aptos_stdlib::ristretto255_bulletproofs`), run: + +``` +TEST_FILTER="test_range_proof" cargo test -- aptos_stdlib --skip prover +``` + +Or, e.g., run all the Bulletproof tests: +``` +TEST_FILTER="bulletproofs" cargo test -- aptos_stdlib --skip prover +``` + +To show the amount of time and gas used in every test, set env var `REPORT_STATS=1`. +E.g., +``` +REPORT_STATS=1 TEST_FILTER="bulletproofs" cargo test -- aptos_stdlib --skip prover +``` + Sometimes, Rust runs out of stack memory in dev build mode. You can address this by either: 1. Adjusting the stack size diff --git a/aptos-move/framework/aptos-stdlib/doc/ristretto255_bulletproofs.md b/aptos-move/framework/aptos-stdlib/doc/ristretto255_bulletproofs.md index 22fe4660646e5..d5d763b652cfe 100644 --- a/aptos-move/framework/aptos-stdlib/doc/ristretto255_bulletproofs.md +++ b/aptos-move/framework/aptos-stdlib/doc/ristretto255_bulletproofs.md @@ -9,6 +9,10 @@ A Bulletproof-based zero-knowledge range proof is a proof that a Pedersen commit $c = v G + r H$ commits to an $n$-bit value $v$ (i.e., $v \in [0, 2^n)$). Currently, this module only supports $n \in \{8, 16, 32, 64\}$ for the number of bits. +The module also supports batch range proofs, allowing verification of multiple commitments in a single proof. +Each commitment in the batch must satisfy the same range constraint $v \in [0, 2^n)$, and the supported batch +sizes are limited to $\{1, 2, 4, 8, 16\}$. + - [Struct `RangeProof`](#0x1_ristretto255_bulletproofs_RangeProof) - [Constants](#@Constants_0) @@ -17,9 +21,13 @@ $n \in \{8, 16, 32, 64\}$ for the number of bits. - [Function `range_proof_to_bytes`](#0x1_ristretto255_bulletproofs_range_proof_to_bytes) - [Function `verify_range_proof_pedersen`](#0x1_ristretto255_bulletproofs_verify_range_proof_pedersen) - [Function `verify_range_proof`](#0x1_ristretto255_bulletproofs_verify_range_proof) +- [Function `verify_batch_range_proof_pedersen`](#0x1_ristretto255_bulletproofs_verify_batch_range_proof_pedersen) +- [Function `verify_batch_range_proof`](#0x1_ristretto255_bulletproofs_verify_batch_range_proof) - [Function `verify_range_proof_internal`](#0x1_ristretto255_bulletproofs_verify_range_proof_internal) +- [Function `verify_batch_range_proof_internal`](#0x1_ristretto255_bulletproofs_verify_batch_range_proof_internal) - [Specification](#@Specification_1) - [Function `verify_range_proof_internal`](#@Specification_1_verify_range_proof_internal) + - [Function `verify_batch_range_proof_internal`](#@Specification_1_verify_batch_range_proof_internal)
use 0x1::error;
@@ -74,6 +82,16 @@ The native functions have not been rolled out yet.
 
 
 
+
+
+The range proof system only supports batch sizes of 1, 2, 4, 8, and 16.
+
+
+
const E_BATCH_SIZE_NOT_SUPPORTED: u64 = 4;
+
+ + + There was an error deserializing the range proof. @@ -84,6 +102,16 @@ There was an error deserializing the range proof. + + +The domain separation tag exceeded the 256-byte limit. + + +
const E_DST_TOO_LONG: u64 = 6;
+
+ + + The range proof system only supports proving ranges of type $[0, 2^b)$ where $b \in \{8, 16, 32, 64\}$. @@ -104,6 +132,16 @@ The committed value given to the prover is too large. + + +The vector lengths of values and blinding factors do not match. + + +
const E_VECTOR_LENGTHS_MISMATCH: u64 = 5;
+
+ + + The maximum range supported by the Bulletproofs library is $[0, 2^{64})$. @@ -204,6 +242,8 @@ tag (DST). WARNING: The DST check is VERY important for security as it prevents proofs computed for one application (a.k.a., a _domain_) with dst_1 from verifying in a different application with dst_2 != dst_1. +NOTE: currently, domain separation tags of size larger than 256 bytes are not supported. +
public fun verify_range_proof_pedersen(com: &ristretto255_pedersen::Commitment, proof: &ristretto255_bulletproofs::RangeProof, num_bits: u64, dst: vector<u8>): bool
 
@@ -215,12 +255,10 @@ WARNING: The DST check is VERY important for security as it prevents proofs comp
public fun verify_range_proof_pedersen(com: &pedersen::Commitment, proof: &RangeProof, num_bits: u64, dst: vector<u8>): bool {
-    assert!(features::bulletproofs_enabled(), error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE));
-
-    verify_range_proof_internal(
-        ristretto255::point_to_bytes(&pedersen::commitment_as_compressed_point(com)),
+    verify_range_proof(
+        pedersen::commitment_as_point(com),
         &ristretto255::basepoint(), &ristretto255::hash_to_point_base(),
-        proof.bytes,
+        proof,
         num_bits,
         dst
     )
@@ -236,7 +274,11 @@ WARNING: The DST check is VERY important for security as it prevents proofs comp
 ## Function `verify_range_proof`
 
 Verifies a zero-knowledge range proof that the value v committed in com (as v * val_base + r * rand_base,
-for some randomness r) satisfies v in [0, 2^num_bits). Only works for num_bits in {8, 16, 32, 64}.
+for some randomness r) satisfies v in [0, 2^num_bits).
+
+Only works for num_bits in {8, 16, 32, 64}.
+
+NOTE: currently, domain separation tags of size larger than 256 bytes are not supported.
 
 
 
public fun verify_range_proof(com: &ristretto255::RistrettoPoint, val_base: &ristretto255::RistrettoPoint, rand_base: &ristretto255::RistrettoPoint, proof: &ristretto255_bulletproofs::RangeProof, num_bits: u64, dst: vector<u8>): bool
@@ -254,6 +296,7 @@ for some randomness r) satisfies v in [0, 2^num_
     proof: &RangeProof, num_bits: u64, dst: vector<u8>): bool
 {
     assert!(features::bulletproofs_enabled(), error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE));
+    assert!(vector::length(&dst) <= 256, error::invalid_argument(E_DST_TOO_LONG));
 
     verify_range_proof_internal(
         ristretto255::point_to_bytes(&ristretto255::point_compress(com)),
@@ -265,6 +308,87 @@ for some randomness r) satisfies v in [0, 2^num_
 
 
 
+
+
+
+
+## Function `verify_batch_range_proof_pedersen`
+
+Verifies a zero-knowledge range proof for a batch of Pedersen commitments comms
+(under the default Bulletproofs commitment key; see pedersen::new_commitment_for_bulletproof),
+ensuring that all values v satisfy v in [0, 2^num_bits).
+Only works for num_bits in {8, 16, 32, 64} and batch size (length of comms) in {1, 2, 4, 8, 16}.
+
+NOTE: currently, domain separation tags of size larger than 256 bytes are not supported.
+
+
+
public fun verify_batch_range_proof_pedersen(comms: &vector<ristretto255_pedersen::Commitment>, proof: &ristretto255_bulletproofs::RangeProof, num_bits: u64, dst: vector<u8>): bool
+
+ + + +
+Implementation + + +
public fun verify_batch_range_proof_pedersen(
+    comms: &vector<pedersen::Commitment>, proof: &RangeProof,
+    num_bits: u64, dst: vector<u8>): bool
+{
+    verify_batch_range_proof(
+        &std::vector::map_ref(comms, |com| ristretto255::point_clone(pedersen::commitment_as_point(com))),
+        &ristretto255::basepoint(), &ristretto255::hash_to_point_base(),
+        proof,
+        num_bits,
+        dst
+    )
+}
+
+ + + +
+ + + +## Function `verify_batch_range_proof` + +v * val_base + r * rand_base), ensuring that all values v satisfy +v in [0, 2^num_bits). Only works for num_bits in {8, 16, 32, 64} and batch size +(length of the comms) in {1, 2, 4, 8, 16}. + +NOTE: currently, domain separation tags of size larger than 256 bytes are not supported. + + +
public fun verify_batch_range_proof(comms: &vector<ristretto255::RistrettoPoint>, val_base: &ristretto255::RistrettoPoint, rand_base: &ristretto255::RistrettoPoint, proof: &ristretto255_bulletproofs::RangeProof, num_bits: u64, dst: vector<u8>): bool
+
+ + + +
+Implementation + + +
public fun verify_batch_range_proof(
+    comms: &vector<RistrettoPoint>,
+    val_base: &RistrettoPoint, rand_base: &RistrettoPoint,
+    proof: &RangeProof, num_bits: u64, dst: vector<u8>): bool
+{
+    assert!(features::bulletproofs_batch_enabled(), error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE));
+    assert!(vector::length(&dst) <= 256, error::invalid_argument(E_DST_TOO_LONG));
+
+    let comms = std::vector::map_ref(comms, |com| ristretto255::point_to_bytes(&ristretto255::point_compress(com)));
+
+    verify_batch_range_proof_internal(
+        comms,
+        val_base, rand_base,
+        proof.bytes, num_bits, dst
+    )
+}
+
+ + +
@@ -296,6 +420,39 @@ Aborts with + +## Function `verify_batch_range_proof_internal` + +Aborts with error::invalid_argument(E_DESERIALIZE_RANGE_PROOF) if proof is not a valid serialization of a +range proof. +Aborts with error::invalid_argument(E_RANGE_NOT_SUPPORTED) if an unsupported num_bits is provided. +Aborts with error::invalid_argument(E_BATCH_SIZE_NOT_SUPPORTED) if an unsupported batch size is provided. +Aborts with error::invalid_argument(E_VECTOR_LENGTHS_MISMATCH) if the vector lengths of comms and proof do not match. + + +
fun verify_batch_range_proof_internal(comms: vector<vector<u8>>, val_base: &ristretto255::RistrettoPoint, rand_base: &ristretto255::RistrettoPoint, proof: vector<u8>, num_bits: u64, dst: vector<u8>): bool
+
+ + + +
+Implementation + + +
native fun verify_batch_range_proof_internal(
+    comms: vector<vector<u8>>,
+    val_base: &RistrettoPoint,
+    rand_base: &RistrettoPoint,
+    proof: vector<u8>,
+    num_bits: u64,
+    dst: vector<u8>): bool;
+
+ + +
@@ -314,6 +471,22 @@ Aborts with + +### Function `verify_batch_range_proof_internal` + + +
fun verify_batch_range_proof_internal(comms: vector<vector<u8>>, val_base: &ristretto255::RistrettoPoint, rand_base: &ristretto255::RistrettoPoint, proof: vector<u8>, num_bits: u64, dst: vector<u8>): bool
+
+ + + +
pragma opaque;
 
diff --git a/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255_bulletproofs.move b/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255_bulletproofs.move index 731ceabc74227..0ad95bc08e080 100644 --- a/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255_bulletproofs.move +++ b/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255_bulletproofs.move @@ -3,9 +3,14 @@ /// A Bulletproof-based zero-knowledge range proof is a proof that a Pedersen commitment /// $c = v G + r H$ commits to an $n$-bit value $v$ (i.e., $v \in [0, 2^n)$). Currently, this module only supports /// $n \in \{8, 16, 32, 64\}$ for the number of bits. +/// +/// The module also supports batch range proofs, allowing verification of multiple commitments in a single proof. +/// Each commitment in the batch must satisfy the same range constraint $v \in [0, 2^n)$, and the supported batch +/// sizes are limited to $\{1, 2, 4, 8, 16\}$. module aptos_std::ristretto255_bulletproofs { use std::error; use std::features; + use std::vector; use aptos_std::ristretto255_pedersen as pedersen; use aptos_std::ristretto255::{Self, RistrettoPoint}; @@ -29,6 +34,15 @@ module aptos_std::ristretto255_bulletproofs { /// The range proof system only supports proving ranges of type $[0, 2^b)$ where $b \in \{8, 16, 32, 64\}$. const E_RANGE_NOT_SUPPORTED: u64 = 3; + /// The range proof system only supports batch sizes of 1, 2, 4, 8, and 16. + const E_BATCH_SIZE_NOT_SUPPORTED: u64 = 4; + + /// The vector lengths of values and blinding factors do not match. + const E_VECTOR_LENGTHS_MISMATCH: u64 = 5; + + /// The domain separation tag exceeded the 256-byte limit. + const E_DST_TOO_LONG: u64 = 6; + /// The native functions have not been rolled out yet. const E_NATIVE_FUN_NOT_AVAILABLE: u64 = 4; @@ -71,26 +85,31 @@ module aptos_std::ristretto255_bulletproofs { /// /// WARNING: The DST check is VERY important for security as it prevents proofs computed for one application /// (a.k.a., a _domain_) with `dst_1` from verifying in a different application with `dst_2 != dst_1`. + /// + /// NOTE: currently, domain separation tags of size larger than 256 bytes are not supported. public fun verify_range_proof_pedersen(com: &pedersen::Commitment, proof: &RangeProof, num_bits: u64, dst: vector): bool { - assert!(features::bulletproofs_enabled(), error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE)); - - verify_range_proof_internal( - ristretto255::point_to_bytes(&pedersen::commitment_as_compressed_point(com)), + verify_range_proof( + pedersen::commitment_as_point(com), &ristretto255::basepoint(), &ristretto255::hash_to_point_base(), - proof.bytes, + proof, num_bits, dst ) } /// Verifies a zero-knowledge range proof that the value `v` committed in `com` (as v * val_base + r * rand_base, - /// for some randomness `r`) satisfies `v` in `[0, 2^num_bits)`. Only works for `num_bits` in `{8, 16, 32, 64}`. + /// for some randomness `r`) satisfies `v` in `[0, 2^num_bits)`. + /// + /// Only works for `num_bits` in `{8, 16, 32, 64}`. + /// + /// NOTE: currently, domain separation tags of size larger than 256 bytes are not supported. public fun verify_range_proof( com: &RistrettoPoint, val_base: &RistrettoPoint, rand_base: &RistrettoPoint, proof: &RangeProof, num_bits: u64, dst: vector): bool { assert!(features::bulletproofs_enabled(), error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE)); + assert!(vector::length(&dst) <= 256, error::invalid_argument(E_DST_TOO_LONG)); verify_range_proof_internal( ristretto255::point_to_bytes(&ristretto255::point_compress(com)), @@ -99,12 +118,66 @@ module aptos_std::ristretto255_bulletproofs { ) } + /// Verifies a zero-knowledge range proof for a batch of Pedersen commitments `comms` + /// (under the default Bulletproofs commitment key; see `pedersen::new_commitment_for_bulletproof`), + /// ensuring that all values `v` satisfy `v` in `[0, 2^num_bits)`. + /// Only works for `num_bits` in `{8, 16, 32, 64}` and batch size (length of `comms`) in `{1, 2, 4, 8, 16}`. + /// + /// NOTE: currently, domain separation tags of size larger than 256 bytes are not supported. + public fun verify_batch_range_proof_pedersen( + comms: &vector, proof: &RangeProof, + num_bits: u64, dst: vector): bool + { + verify_batch_range_proof( + &std::vector::map_ref(comms, |com| ristretto255::point_clone(pedersen::commitment_as_point(com))), + &ristretto255::basepoint(), &ristretto255::hash_to_point_base(), + proof, + num_bits, + dst + ) + } + + // Verifies a zero-knowledge range proof for a batch of commitments `comms` (each of the form + /// `v * val_base + r * rand_base`), ensuring that all values `v` satisfy + /// `v` in `[0, 2^num_bits)`. Only works for `num_bits` in `{8, 16, 32, 64}` and batch size + /// (length of the `comms`) in `{1, 2, 4, 8, 16}`. + /// + /// NOTE: currently, domain separation tags of size larger than 256 bytes are not supported. + public fun verify_batch_range_proof( + comms: &vector, + val_base: &RistrettoPoint, rand_base: &RistrettoPoint, + proof: &RangeProof, num_bits: u64, dst: vector): bool + { + assert!(features::bulletproofs_batch_enabled(), error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE)); + assert!(vector::length(&dst) <= 256, error::invalid_argument(E_DST_TOO_LONG)); + + let comms = std::vector::map_ref(comms, |com| ristretto255::point_to_bytes(&ristretto255::point_compress(com))); + + verify_batch_range_proof_internal( + comms, + val_base, rand_base, + proof.bytes, num_bits, dst + ) + } + #[test_only] /// Computes a range proof for the Pedersen commitment to 'val' with randomness 'r', under the default Bulletproofs /// commitment key; see `pedersen::new_commitment_for_bulletproof`. Returns the said commitment too. /// Only works for `num_bits` in `{8, 16, 32, 64}`. public fun prove_range_pedersen(val: &Scalar, r: &Scalar, num_bits: u64, dst: vector): (RangeProof, pedersen::Commitment) { - let (bytes, compressed_comm) = prove_range_internal(scalar_to_bytes(val), scalar_to_bytes(r), num_bits, dst, &ristretto255::basepoint(), &ristretto255::hash_to_point_base()); + prove_range(val, r, &ristretto255::basepoint(), &ristretto255::hash_to_point_base(), num_bits, dst) + } + + #[test_only] + /// Computes a range proof for a commitment to `val` with randomness `r`. + /// The commitment is of the form `val * val_base + r * rand_base`. + /// Returns both the commitment and the corresponding range proof. Only works for `num_bits` in `{8, 16, 32, 64}`. + public fun prove_range( + val: &Scalar, r: &Scalar, + val_base: &RistrettoPoint, rand_base: &RistrettoPoint, + num_bits: u64, dst: vector): (RangeProof, pedersen::Commitment) + { + let (bytes, compressed_comm) = prove_range_internal(scalar_to_bytes(val), scalar_to_bytes(r), num_bits, dst, val_base, rand_base); let point = ristretto255::new_compressed_point_from_bytes(compressed_comm); let point = &std::option::extract(&mut point); @@ -114,6 +187,43 @@ module aptos_std::ristretto255_bulletproofs { ) } + #[test_only] + /// Computes a range proof for a batch of values `vals`, each committed with the corresponding randomness in `rs`, + /// under the default Bulletproofs commitment key; see `pedersen::new_commitment_for_bulletproof`. + /// Returns a tuple containing the batch range proof and a vector of said commitments. + /// Only works for `num_bits` in `{8, 16, 32, 64}` and batch sizes (length of `vals` and `rs`) in `{1, 2, 4, 8, 16}`. + public fun prove_batch_range_pedersen( + vals: &vector, rs: &vector, + num_bits: u64, dst: vector): (RangeProof, vector) + { + prove_batch_range(vals, rs, &ristretto255::basepoint(), &ristretto255::hash_to_point_base(), num_bits, dst) + } + + #[test_only] + /// Computes a range proof for a batch of values `vals`, each committed with the corresponding randomness in `rs`, + /// using custom base points `val_base` and `rand_base`. Each commitment is of the form `val * val_base + r * rand_base`. + /// Returns a tuple containing the batch range proof and a vector of said commitments. + /// Only works for `num_bits` in `{8, 16, 32, 64}` and batch sizes (length of `vals` and `rs`) in `{1, 2, 4, 8, 16}`. + public fun prove_batch_range( + vals: &vector, rs: &vector, + val_base: &RistrettoPoint, rand_base: &RistrettoPoint, + num_bits: u64, dst: vector): (RangeProof, vector) + { + let vals = std::vector::map_ref(vals, |val| scalar_to_bytes(val)); + let rs = std::vector::map_ref(rs, |r| scalar_to_bytes(r)); + + let (bytes, compressed_comms) = prove_batch_range_internal(vals, rs, num_bits, dst, val_base, rand_base); + let comms = std::vector::map(compressed_comms, |compressed_comm| { + let comm = pedersen::new_commitment_from_bytes(compressed_comm); + std::option::extract(&mut comm) + }); + + ( + RangeProof { bytes }, + comms + ) + } + // // Native functions // @@ -129,6 +239,19 @@ module aptos_std::ristretto255_bulletproofs { num_bits: u64, dst: vector): bool; + /// Aborts with `error::invalid_argument(E_DESERIALIZE_RANGE_PROOF)` if `proof` is not a valid serialization of a + /// range proof. + /// Aborts with `error::invalid_argument(E_RANGE_NOT_SUPPORTED)` if an unsupported `num_bits` is provided. + /// Aborts with `error::invalid_argument(E_BATCH_SIZE_NOT_SUPPORTED)` if an unsupported batch size is provided. + /// Aborts with `error::invalid_argument(E_VECTOR_LENGTHS_MISMATCH)` if the vector lengths of `comms` and `proof` do not match. + native fun verify_batch_range_proof_internal( + comms: vector>, + val_base: &RistrettoPoint, + rand_base: &RistrettoPoint, + proof: vector, + num_bits: u64, + dst: vector): bool; + #[test_only] /// Returns a tuple consisting of (1) a range proof for 'val' committed with randomness 'r' under the default Bulletproofs /// commitment key and (2) the commitment itself. @@ -143,38 +266,75 @@ module aptos_std::ristretto255_bulletproofs { val_base: &RistrettoPoint, rand_base: &RistrettoPoint): (vector, vector); + #[test_only] + /// Returns a tuple consisting of (1) a range proof for each value in `vals`, where each value is committed + /// with the corresponding randomness in `rs`, and (2) the corresponding commitments. + /// + /// Each commitment has the form `val * val_base + r * rand_base`, where `val` and `r` are the corresponding + /// elements from `vals` and `rs`, respectively. + /// + /// Aborts with `error::invalid_argument(E_RANGE_NOT_SUPPORTED)` if an unsupported `num_bits` is provided. + /// Aborts with `error::invalid_argument(E_VALUE_OUTSIDE_RANGE)` if `val_base` is not `num_bits` wide. + native fun prove_batch_range_internal( + vals: vector>, + rs: vector>, + num_bits: u64, + dst: vector, + val_base: &RistrettoPoint, + rand_base: &RistrettoPoint): (vector, vector>); + // // Testing // #[test_only] - use aptos_std::ristretto255::{Scalar, scalar_to_bytes, point_equals}; + use aptos_std::ristretto255::{Scalar, scalar_to_bytes}; + #[test_only] + use aptos_std::ristretto255_pedersen::commitment_equals; #[test_only] const A_DST: vector = b"AptosBulletproofs"; #[test_only] const A_VALUE: vector = x"870c2fa1b2e9ac45000000000000000000000000000000000000000000000000"; // i.e., 5020644638028926087u64 #[test_only] + const B_VALUE: vector = x"bb9d99fb7f9e572b000000000000000000000000000000000000000000000000"; // i.e., 3123139139123912123u64 + #[test_only] const A_BLINDER: vector = x"e7c7b42b75503bfc7b1932783786d227ebf88f79da752b68f6b865a9c179640c"; + #[test_only] + const B_BLINDER: vector = x"ce224fe5e1111a394fc254ee503aa2406706ef606efac6e2d0332711c7a7bc06"; // Pedersen commitment to A_VALUE with randomness A_BLINDER #[test_only] const A_COMM: vector = x"0a665260a4e42e575882c2cdcb3d0febd6cf168834f6de1e9e61e7b2e53dbf14"; + #[test_only] + const B_COMM: vector = x"748c244d880a1de3970a3d01670a04db6b74b9741bfec8732e512312384a6515"; // Range proof for A_COMM using domain-separation tag in A_DST, and MAX_RANGE_BITS #[test_only] const A_RANGE_PROOF_PEDERSEN: vector = x"d8d422d3fb9511d1942b78e3ec1a8c82fe1c01a0a690c55a4761e7e825633a753cca816667d2cbb716fe04a9c199cad748c2d4e59de4ed04fedf5f04f4341a74ae75b63c1997fd65d5fb3a8c03ad8771abe2c0a4f65d19496c11d948d6809503eac4d996f2c6be4e64ebe2df31102c96f106695bdf489dc9290c93b4d4b5411fb6298d0c33afa57e2e1948c38ef567268a661e7b1c099272e29591e717930a06a2c6e0e2d56aedea3078fd59334634f1a4543069865409eba074278f191039083102a9a0621791a9be09212a847e22061e083d7a712b05bca7274b25e4cb1201c679c4957f0842d7661fa1d3f5456a651e89112628b456026f8ad3a7abeaba3fec8031ec8b0392c0aa6c96205f7b21b0c2d6b5d064bd5bd1a1d91c41625d910688fa0dca35ec0f0e31a45792f8d6a330be970a22e1e0773111a083de893c89419ee7de97295978de90bcdf873a2826746809e64f9143417dbed09fa1c124e673febfed65c137cc45fabda963c96b64645802d1440cba5e58717e539f55f3321ab0c0f60410fba70070c5db500fee874265a343a2a59773fd150bcae09321a5166062e176e2e76bef0e3dd1a9250bcb7f4c971c10f0b24eb2a94e009b72c1fc21ee4267881e27b4edba8bed627ddf37e0c53cd425bc279d0c50d154d136503e54882e9541820d6394bd52ca2b438fd8c517f186fec0649c4846c4e43ce845d80e503dee157ce55392188039a7efc78719107ab989db8d9363b9dfc1946f01a84dbca5e742ed5f30b07ac61cf17ce2cf2c6a49d799ed3968a63a3ccb90d9a0e50960d959f17f202dd5cf0f2c375a8a702e063d339e48c0227e7cf710157f63f13136d8c3076c672ea2c1028fc1825366a145a4311de6c2cc46d3144ae3d2bc5808819b9817be3fce1664ecb60f74733e75e97ca8e567d1b81bdd4c56c7a340ba00"; + #[test_only] + const AB_BATCH_RANGE_PROOF_PEDERSEN: vector = x"103086c56ead10712514d2807c5605cb5f3a090566196549b5f03bedd7c1f450b4619bca9b00f87b2e039e844c24f9f2512901eea7f8f322f218f58c37186e1bd40ae74942f69b18f6806a536b2ab0793ab8e646eafc6e31d5219545dfcbb21334230c4e063e682d1f37fdfe7258d1735af1ba4764ca182803ef4566ddd386143550b83b8d686514988ee05bb7b4180f3b296a0a9711976365b678b537e2190c49cecded1d209ecec733e5cb85d5427f1f2ef1a44ebac41fdbf822692bd68b012515065faab0611aaabe87c1facbe68e648f2e2a0de6e5e81490dfa178546d0e1ec7a7c7ee6eb1e72f0e62b6a81abf23d4e4f946e5c5b28ca287d7ee30c72667ec1203ea9314a4ef182e3ed8a49700cb2452c3765fd29611e2abb5d8aa1970387452cd473383707a0b8e2eb46ba6826654e03ba5f73b56a0ae30012dc723576e76b280339600decef76eda350232ee9e53b373d745b958a19c8b4e7133f4b846727dab188441bb7d2484a73a9a83c1c94e7bea0ea0253418d3d5a751e63f940106e597772d169a01d93b495d10c08725c5d8cdef24306a164a2e1fa1b19eb0217239bbc661e0f1ead2bf3ecc3f178b6b49c61aa2c45f4832ba9ebc2744b79b413081e824b0978cab1934d29760f77751450e409da17941ff693b7dbc0b45d0659aeca05e1e92572fcd4c4d5846e7963e25cce6d54fc4a963da031747695a8e2000469e22e682e1b3f141891121d189504db63b4ab40e0d4c59f0b945b8188b79f0eb4916723a757bcfc787863ff28c5555c8ad93df81bba7b2ff9c164e180331a8b24cff4a9de0d2a8b71f73d24521781f0ced1a064698af138c00160c87eb7ffca5ab1d9a1bec5144c648c5f51a6093dbe8ed88a2fcaab4d5412c60ebb25827d8cab48787f705c5781e2ecd82939d3b3f864c21701fcecbc57b196db7c055273e86ac654a24016abd8ba7c6e87610a0e1b70ff57378992b2d5d45c963829b0aa9323b0dde3f02382e583cb3733c187b46903ed629820ec8043a8c18df42dc0a"; #[test(fx = @std)] #[expected_failure(abort_code = 0x010003, location = Self)] fun test_unsupported_ranges(fx: signer) { features::change_feature_flags_for_testing(&fx, vector[ features::get_bulletproofs_feature() ], vector[]); - let comm = ristretto255::new_point_from_bytes(A_COMM); + let comm = pedersen::new_commitment_from_bytes(A_COMM); let comm = std::option::extract(&mut comm); - let comm = pedersen::commitment_from_point(comm); - assert!(verify_range_proof_pedersen( - &comm, - &range_proof_from_bytes(A_RANGE_PROOF_PEDERSEN), 10, A_DST), 1); + verify_range_proof_pedersen(&comm, &range_proof_from_bytes(A_RANGE_PROOF_PEDERSEN), 10, A_DST); + } + + #[test(fx = @std)] + #[expected_failure(abort_code = 0x010003, location = Self)] + fun test_unsupported_ranges_batch(fx: signer) { + features::change_feature_flags_for_testing(&fx, vector[ features::get_bulletproofs_batch_feature() ], vector[]); + + let comm_a = pedersen::new_commitment_from_bytes(A_COMM); + let comm_b = pedersen::new_commitment_from_bytes(B_COMM); + + let comms = vector[std::option::extract(&mut comm_a), std::option::extract(&mut comm_b)]; + + verify_batch_range_proof_pedersen(&comms, &range_proof_from_bytes(AB_BATCH_RANGE_PROOF_PEDERSEN), 10, A_DST); } #[test(fx = @std)] @@ -194,6 +354,65 @@ module aptos_std::ristretto255_bulletproofs { assert!(verify_range_proof_pedersen(&comm, &proof, num_bits, A_DST), 1); } + #[test(fx = @std)] + fun test_batch_prover(fx: signer) { + features::change_feature_flags_for_testing(&fx, vector[ features::get_bulletproofs_batch_feature() ], vector[]); + + let vs = vector[ + ristretto255::new_scalar_from_u64(59), + ristretto255::new_scalar_from_u64(60), + ]; + let rs = vector[ + std::option::extract(&mut ristretto255::new_scalar_from_bytes(A_BLINDER)), + std::option::extract(&mut ristretto255::new_scalar_from_bytes(B_BLINDER)), + ]; + let num_bits = 8; + + let (proof, comms) = prove_batch_range_pedersen(&vs, &rs, num_bits, A_DST); + + assert!(verify_batch_range_proof_pedersen(&comms, &proof, 64, A_DST) == false, 1); + assert!(verify_batch_range_proof_pedersen(&comms, &proof, 32, A_DST) == false, 1); + assert!(verify_batch_range_proof_pedersen(&comms, &proof, 16, A_DST) == false, 1); + assert!(verify_batch_range_proof_pedersen(&comms, &proof, num_bits, A_DST), 1); + } + + #[test(fx = @std)] + #[expected_failure(abort_code = 0x030004, location = Self)] + fun test_bulletproof_feature_disabled(fx: signer) { + features::change_feature_flags_for_testing(&fx, vector[ ], vector[ features::get_bulletproofs_feature()]); + + let v = ristretto255::new_scalar_from_u64(59); + let r = ristretto255::new_scalar_from_bytes(A_BLINDER); + let r = std::option::extract(&mut r); + let num_bits = 8; + + let (proof, comm) = prove_range_pedersen(&v, &r, num_bits, A_DST); + + // This will fail with error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE) + verify_range_proof_pedersen(&comm, &proof, num_bits, A_DST); + } + + #[test(fx = @std)] + #[expected_failure(abort_code = 0x030004, location = Self)] + fun test_bulletproof_batch_feature_disabled(fx: signer) { + features::change_feature_flags_for_testing(&fx, vector[ ], vector[ features::get_bulletproofs_batch_feature() ]); + + let vs = vector[ + ristretto255::new_scalar_from_u64(59), + ristretto255::new_scalar_from_u64(60), + ]; + let rs = vector[ + std::option::extract(&mut ristretto255::new_scalar_from_bytes(A_BLINDER)), + std::option::extract(&mut ristretto255::new_scalar_from_bytes(B_BLINDER)), + ]; + let num_bits = 8; + + let (proof, comms) = prove_batch_range_pedersen(&vs, &rs, num_bits, A_DST); + + // This will fail with error::invalid_state(E_NATIVE_FUN_NOT_AVAILABLE) + verify_batch_range_proof_pedersen(&comms, &proof, num_bits, A_DST); + } + #[test(fx = @std)] #[expected_failure(abort_code = 0x010001, location = Self)] fun test_empty_range_proof(fx: signer) { @@ -211,9 +430,100 @@ module aptos_std::ristretto255_bulletproofs { } #[test(fx = @std)] - fun test_valid_range_proof_verifies_against_comm(fx: signer) { + #[expected_failure(abort_code = 0x010001, location = Self)] + fun test_empty_batch_range_proof(fx: signer) { + features::change_feature_flags_for_testing(&fx, vector[ features::get_bulletproofs_batch_feature() ], vector[]); + + let proof = &range_proof_from_bytes(vector[ ]); + let num_bits = 64; + let comms = vector[pedersen::new_commitment_for_bulletproof( + &ristretto255::scalar_one(), + &ristretto255::new_scalar_from_sha2_512(b"hello random world") + )]; + + // This will fail with error::invalid_argument(E_DESERIALIZE_RANGE_PROOF) + verify_batch_range_proof_pedersen(&comms, proof, num_bits, A_DST); + } + + #[test(fx = @std)] + #[expected_failure(abort_code = 0x010002, location = Self)] + fun test_value_outside_range_range_proof(fx: signer) { features::change_feature_flags_for_testing(&fx, vector[ features::get_bulletproofs_feature() ], vector[]); + let value_a = ristretto255::new_scalar_from_bytes(A_VALUE); + let value_b = ristretto255::new_scalar_from_u128(1 << 65); + + let blinder_a = ristretto255::new_scalar_from_bytes(A_BLINDER); + let blinder_b = ristretto255::new_scalar_from_bytes(B_BLINDER); + + let values = vector[std::option::extract(&mut value_a), value_b]; + let blinders = vector[std::option::extract(&mut blinder_a), std::option::extract(&mut blinder_b)]; + + // This will fail with error::invalid_argument(E_VALUE_OUTSIDE_RANGE) + prove_batch_range_pedersen(&values, &blinders, 64, A_DST); + } + + #[test(fx = @std)] + #[expected_failure(abort_code = 0x010002, location = Self)] + fun test_value_outside_range_batch_range_proof(fx: signer) { + features::change_feature_flags_for_testing(&fx, vector[ features::get_bulletproofs_batch_feature() ], vector[]); + + let value = ristretto255::new_scalar_from_u128(1 << 65); + let blinder = std::option::extract(&mut ristretto255::new_scalar_from_bytes(A_BLINDER)); + + // This will fail with error::invalid_argument(E_VALUE_OUTSIDE_RANGE) + prove_range_pedersen(&value, &blinder, 64, A_DST); + } + + #[test(fx = @std)] + #[expected_failure(abort_code = 0x010004, location = Self)] + fun test_invalid_batch_size_range_proof(fx: signer) { + features::change_feature_flags_for_testing(&fx, vector[ features::get_bulletproofs_batch_feature() ], vector[]); + + let value_a = ristretto255::new_scalar_from_bytes(A_VALUE); + let value_b = ristretto255::new_scalar_from_bytes(B_VALUE); + let value_c = ristretto255::new_scalar_from_u32(1); + + let blinder_a = ristretto255::new_scalar_from_bytes(A_BLINDER); + let blinder_b = ristretto255::new_scalar_from_bytes(B_BLINDER); + let blinder_c = ristretto255::new_scalar_from_u32(1); + + let values = vector[ + std::option::extract(&mut value_a), + std::option::extract(&mut value_b), + value_c, + ]; + let blinders = vector[ + std::option::extract(&mut blinder_a), + std::option::extract(&mut blinder_b), + blinder_c, + ]; + + // This will fail with error::invalid_argument(E_BATCH_SIZE_NOT_SUPPORTED) + prove_batch_range_pedersen(&values, &blinders, 64, A_DST); + } + + #[test(fx = @std)] + #[expected_failure(abort_code = 0x010005, location = Self)] + fun test_invalid_args_batch_range_proof(fx: signer) { + features::change_feature_flags_for_testing(&fx, vector[ features::get_bulletproofs_batch_feature() ], vector[]); + + let value_a = ristretto255::new_scalar_from_bytes(A_VALUE); + let value_b = ristretto255::new_scalar_from_bytes(B_VALUE); + + let blinder_a = ristretto255::new_scalar_from_bytes(A_BLINDER); + + let values = vector[std::option::extract(&mut value_a), std::option::extract(&mut value_b)]; + let blinders = vector[std::option::extract(&mut blinder_a)]; + + // This will fail with error::invalid_argument(E_VECTOR_LENGTHS_MISMATCH) + prove_batch_range_pedersen(&values, &blinders, 64, A_DST); + } + + #[test(fx = @std)] + fun test_valid_range_proof_verifies_against_comm(fx: signer) { + features::change_feature_flags_for_testing(&fx, vector[ features::get_bulletproofs_batch_feature() ], vector[]); + let value = ristretto255::new_scalar_from_bytes(A_VALUE); let value = std::option::extract(&mut value); @@ -222,21 +532,45 @@ module aptos_std::ristretto255_bulletproofs { let comm = pedersen::new_commitment_for_bulletproof(&value, &blinder); - let expected_comm = std::option::extract(&mut ristretto255::new_point_from_bytes(A_COMM)); - assert!(point_equals(pedersen::commitment_as_point(&comm), &expected_comm), 1); + let expected_comm = std::option::extract(&mut pedersen::new_commitment_from_bytes(A_COMM)); + assert!(commitment_equals(&comm, &expected_comm), 1); assert!(verify_range_proof_pedersen( &comm, &range_proof_from_bytes(A_RANGE_PROOF_PEDERSEN), MAX_RANGE_BITS, A_DST), 1); } + #[test(fx = @std)] + fun test_valid_batch_range_proof_verifies_against_comm(fx: signer) { + features::change_feature_flags_for_testing(&fx, vector[ features::get_bulletproofs_batch_feature() ], vector[]); + + let value_a = ristretto255::new_scalar_from_bytes(A_VALUE); + let value_b = ristretto255::new_scalar_from_bytes(B_VALUE); + + let blinder_a = ristretto255::new_scalar_from_bytes(A_BLINDER); + let blinder_b = ristretto255::new_scalar_from_bytes(B_BLINDER); + + let values = vector[std::option::extract(&mut value_a), std::option::extract(&mut value_b)]; + let blinders = vector[std::option::extract(&mut blinder_a), std::option::extract(&mut blinder_b)]; + + let comms = std::vector::zip_map(values, blinders, |val, blinder| { + pedersen::new_commitment_for_bulletproof(&val, &blinder) + }); + + assert!(commitment_equals(std::vector::borrow(&comms, 0), &std::option::extract(&mut pedersen::new_commitment_from_bytes(A_COMM))), 1); + assert!(commitment_equals(std::vector::borrow(&comms, 1), &std::option::extract(&mut pedersen::new_commitment_from_bytes(B_COMM))), 1); + + assert!(verify_batch_range_proof_pedersen( + &comms, + &range_proof_from_bytes(AB_BATCH_RANGE_PROOF_PEDERSEN), MAX_RANGE_BITS, A_DST), 1); + } + #[test(fx = @std)] fun test_invalid_range_proof_fails_verification(fx: signer) { features::change_feature_flags_for_testing(&fx, vector[ features::get_bulletproofs_feature() ], vector[]); - let comm = ristretto255::new_point_from_bytes(A_COMM); + let comm = pedersen::new_commitment_from_bytes(A_COMM); let comm = std::option::extract(&mut comm); - let comm = pedersen::commitment_from_point(comm); // Take a valid proof... let range_proof_invalid = A_RANGE_PROOF_PEDERSEN; @@ -250,4 +584,26 @@ module aptos_std::ristretto255_bulletproofs { &comm, &range_proof_from_bytes(range_proof_invalid), MAX_RANGE_BITS, A_DST) == false, 1); } + + #[test(fx = @std)] + fun test_invalid_batch_range_proof_fails_verification(fx: signer) { + features::change_feature_flags_for_testing(&fx, vector[ features::get_bulletproofs_batch_feature() ], vector[]); + + let comm_a = pedersen::new_commitment_from_bytes(A_COMM); + let comm_b = pedersen::new_commitment_from_bytes(B_COMM); + + let comms = vector[std::option::extract(&mut comm_a), std::option::extract(&mut comm_b)]; + + // Take a valid proof... + let range_proof_invalid = AB_BATCH_RANGE_PROOF_PEDERSEN; + + // ...and modify a byte in the middle of the proof + let pos = std::vector::length(&range_proof_invalid) / 2; + let byte = std::vector::borrow_mut(&mut range_proof_invalid, pos); + *byte = *byte + 1; + + assert!(verify_batch_range_proof_pedersen( + &comms, + &range_proof_from_bytes(range_proof_invalid), MAX_RANGE_BITS, A_DST) == false, 1); + } } diff --git a/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255_bulletproofs.spec.move b/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255_bulletproofs.spec.move index 0e442a84d0b1d..4a63b2009ff24 100644 --- a/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255_bulletproofs.spec.move +++ b/aptos-move/framework/aptos-stdlib/sources/cryptography/ristretto255_bulletproofs.spec.move @@ -2,4 +2,8 @@ spec aptos_std::ristretto255_bulletproofs { spec verify_range_proof_internal { // TODO: temporary mockup. pragma opaque; } + + spec verify_batch_range_proof_internal { + pragma opaque; + } } diff --git a/aptos-move/framework/move-stdlib/doc/features.md b/aptos-move/framework/move-stdlib/doc/features.md index 69b1082ad9b59..e010c138dd13c 100644 --- a/aptos-move/framework/move-stdlib/doc/features.md +++ b/aptos-move/framework/move-stdlib/doc/features.md @@ -139,6 +139,8 @@ return true. - [Function `is_permissioned_signer_enabled`](#0x1_features_is_permissioned_signer_enabled) - [Function `get_account_abstraction_feature`](#0x1_features_get_account_abstraction_feature) - [Function `is_account_abstraction_enabled`](#0x1_features_is_account_abstraction_enabled) +- [Function `get_bulletproofs_batch_feature`](#0x1_features_get_bulletproofs_batch_feature) +- [Function `bulletproofs_batch_enabled`](#0x1_features_bulletproofs_batch_enabled) - [Function `change_feature_flags`](#0x1_features_change_feature_flags) - [Function `change_feature_flags_internal`](#0x1_features_change_feature_flags_internal) - [Function `change_feature_flags_for_next_epoch`](#0x1_features_change_feature_flags_for_next_epoch) @@ -326,6 +328,17 @@ Lifetime: transient + + +Whether the batch Bulletproofs native functions are available. This is needed because of the introduction of a new native function. +Lifetime: transient + + +
const BULLETPROOFS_BATCH_NATIVES: u64 = 87;
+
+ + + Whether the Bulletproofs zero-knowledge range proof module is enabled, and the related native function is @@ -3447,6 +3460,52 @@ Deprecated feature + + + + +## Function `get_bulletproofs_batch_feature` + + + +
public fun get_bulletproofs_batch_feature(): u64
+
+ + + +
+Implementation + + +
public fun get_bulletproofs_batch_feature(): u64 { BULLETPROOFS_BATCH_NATIVES }
+
+ + + +
+ + + +## Function `bulletproofs_batch_enabled` + + + +
public fun bulletproofs_batch_enabled(): bool
+
+ + + +
+Implementation + + +
public fun bulletproofs_batch_enabled(): bool acquires Features {
+    is_enabled(BULLETPROOFS_BATCH_NATIVES)
+}
+
+ + +
diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.move b/aptos-move/framework/move-stdlib/sources/configs/features.move index 28de7017df8d9..eb032b61f12ad 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.move @@ -634,6 +634,16 @@ module std::features { is_enabled(ACCOUNT_ABSTRACTION) } + /// Whether the batch Bulletproofs native functions are available. This is needed because of the introduction of a new native function. + /// Lifetime: transient + const BULLETPROOFS_BATCH_NATIVES: u64 = 87; + + public fun get_bulletproofs_batch_feature(): u64 { BULLETPROOFS_BATCH_NATIVES } + + public fun bulletproofs_batch_enabled(): bool acquires Features { + is_enabled(BULLETPROOFS_BATCH_NATIVES) + } + // ============================================================================================ // Feature Flag Implementation diff --git a/aptos-move/framework/src/natives/cryptography/bulletproofs.rs b/aptos-move/framework/src/natives/cryptography/bulletproofs.rs index 513da6fe504a9..6ce8140563dfb 100644 --- a/aptos-move/framework/src/natives/cryptography/bulletproofs.rs +++ b/aptos-move/framework/src/natives/cryptography/bulletproofs.rs @@ -2,15 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 #[cfg(feature = "testing")] -use crate::natives::cryptography::ristretto255::pop_scalar_from_bytes; +use crate::natives::cryptography::ristretto255::{pop_scalar_from_bytes, pop_scalars_from_bytes}; use crate::natives::cryptography::ristretto255_point::{ get_point_handle, NativeRistrettoPointContext, }; use aptos_crypto::bulletproofs::MAX_RANGE_BITS; use aptos_gas_schedule::gas_params::natives::aptos_framework::*; use aptos_native_interface::{ - safely_pop_arg, RawSafeNative, SafeNativeBuilder, SafeNativeContext, SafeNativeError, - SafeNativeResult, + safely_pop_arg, safely_pop_vec_arg, RawSafeNative, SafeNativeBuilder, SafeNativeContext, + SafeNativeError, SafeNativeResult, }; use bulletproofs::{BulletproofGens, PedersenGens}; #[cfg(feature = "testing")] @@ -39,6 +39,14 @@ pub mod abort_codes { /// Abort code when the requested range is larger than the maximum supported one. /// NOTE: This must match the code in the Move implementation pub const NFE_RANGE_NOT_SUPPORTED: u64 = 0x01_0003; + + /// Abort code when the requested batch size is larger than the maximum supported one. + /// NOTE: This must match the code in the Move implementation + pub const NFE_BATCH_SIZE_NOT_SUPPORTED: u64 = 0x01_0004; + + /// Abort code when the vector lengths of values and blinding factors do not match. + /// NOTE: This must match the code in the Move implementation + pub const NFE_VECTOR_LENGTHS_MISMATCH: u64 = 0x01_0005; } /// The Bulletproofs library only seems to support proving [0, 2^{num_bits}) ranges where num_bits is @@ -47,9 +55,14 @@ fn is_supported_number_of_bits(num_bits: usize) -> bool { matches!(num_bits, 8 | 16 | 32 | 64) } -/// Public parameters of the Bulletproof range proof system +/// The Bulletproofs library only supports batch sizes of 1, 2, 4, 8, or 16. +fn is_supported_batch_size(batch_size: usize) -> bool { + matches!(batch_size, 1 | 2 | 4 | 8 | 16) +} + +/// Public parameters of the Bulletproof range proof system, for both individual and batch proving static BULLETPROOF_GENERATORS: Lazy = - Lazy::new(|| BulletproofGens::new(MAX_RANGE_BITS, 1)); + Lazy::new(|| BulletproofGens::new(MAX_RANGE_BITS, 16)); fn native_verify_range_proof( context: &mut SafeNativeContext, @@ -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( + context: &mut SafeNativeContext, + _ty_args: Vec, + mut args: VecDeque, +) -> SafeNativeResult> { + debug_assert!(_ty_args.is_empty()); + debug_assert!(args.len() == 6); + + let dst = safely_pop_arg!(args, Vec); + let num_bits = safely_pop_arg!(args, u64) as usize; + let proof_bytes = safely_pop_arg!(args, Vec); + let rand_base_handle = get_point_handle(&safely_pop_arg!(args, StructRef))?; + let val_base_handle = get_point_handle(&safely_pop_arg!(args, StructRef))?; + let comm_bytes = safely_pop_vec_arg!(args, Vec); + + let comm_points = comm_bytes + .iter() + .map(|comm_bytes| CompressedRistretto::from_slice(comm_bytes.as_slice())) + .collect::>(); + + if !is_supported_number_of_bits(num_bits) { + return Err(SafeNativeError::Abort { + abort_code: abort_codes::NFE_RANGE_NOT_SUPPORTED, + }); + } + if !is_supported_batch_size(comm_points.len()) { + return Err(SafeNativeError::Abort { + abort_code: abort_codes::NFE_BATCH_SIZE_NOT_SUPPORTED, + }); + } + + let pg = { + let point_context = context.extensions().get::(); + let point_data = point_context.point_data.borrow_mut(); + + let rand_base = point_data.get_point(&rand_base_handle); + let val_base = point_data.get_point(&val_base_handle); + + // TODO(Perf): Is there a way to avoid this unnecessary cloning here? + PedersenGens { + B: *val_base, + B_blinding: *rand_base, + } + }; + + verify_batch_range_proof(context, &comm_points, &pg, &proof_bytes[..], num_bits, dst) +} + #[cfg(feature = "testing")] /// This is a test-only native that charges zero gas. It is only exported in testing mode. fn native_test_only_prove_range( @@ -157,6 +218,93 @@ fn native_test_only_prove_range( ]) } +#[cfg(feature = "testing")] +/// This is a test-only native that charges zero gas. It is only exported in testing mode. +fn native_test_only_batch_prove_range( + context: &mut SafeNativeContext, + _ty_args: Vec, + mut args: VecDeque, +) -> SafeNativeResult> { + debug_assert!(_ty_args.is_empty()); + debug_assert!(args.len() == 6); + + let rand_base_handle = get_point_handle(&safely_pop_arg!(args, StructRef))?; + let val_base_handle = get_point_handle(&safely_pop_arg!(args, StructRef))?; + let dst = safely_pop_arg!(args, Vec); + let num_bits = safely_pop_arg!(args, u64) as usize; + let v_blindings = pop_scalars_from_bytes(&mut args)?; + let vs = pop_scalars_from_bytes(&mut args)?; + + if !is_supported_number_of_bits(num_bits) { + return Err(SafeNativeError::Abort { + abort_code: abort_codes::NFE_RANGE_NOT_SUPPORTED, + }); + } + if !is_supported_batch_size(vs.len()) { + return Err(SafeNativeError::Abort { + abort_code: abort_codes::NFE_BATCH_SIZE_NOT_SUPPORTED, + }); + } + if vs.len() != v_blindings.len() { + return Err(SafeNativeError::Abort { + abort_code: abort_codes::NFE_VECTOR_LENGTHS_MISMATCH, + }); + } + + // Make sure only the first 64 bits are set for each Scalar. + if !vs + .iter() + .all(|v| v.as_bytes()[8..].iter().all(|&byte| byte == 0u8)) + { + return Err(SafeNativeError::Abort { + abort_code: abort_codes::NFE_VALUE_OUTSIDE_RANGE, + }); + } + + // Convert each Scalar to u64. + let vs = vs + .iter() + .map(|v| LittleEndian::read_u64(v.as_bytes())) + .collect::>(); + + let mut t = Transcript::new(dst.as_slice()); + + let pg = { + let point_context = context.extensions().get::(); + let point_data = point_context.point_data.borrow_mut(); + + let rand_base = point_data.get_point(&rand_base_handle); + let val_base = point_data.get_point(&val_base_handle); + + // TODO(Perf): Is there a way to avoid this unnecessary cloning here? + PedersenGens { + B: *val_base, + B_blinding: *rand_base, + } + }; + + // Construct a range proof. + let (proof, commitments) = bulletproofs::RangeProof::prove_multiple( + &BULLETPROOF_GENERATORS, + &pg, + &mut t, + &vs, + &v_blindings, + num_bits, + ) + .expect("Bulletproofs prover failed unexpectedly"); + + Ok(smallvec![ + Value::vector_u8(proof.to_bytes()), + Value::vector_for_testing_only( + commitments + .iter() + .map(|commitment| Value::vector_u8(commitment.as_bytes().to_vec())) + .collect::>() + ) + ]) +} + /*************************************************************************************************** * module * @@ -204,21 +352,100 @@ fn verify_range_proof( Ok(smallvec![Value::bool(success)]) } +/// Helper function to gas meter and verify a batch Bulletproof range proof for Pedersen +/// commitments with `pc_gens` as their commitment keys. +fn verify_batch_range_proof( + context: &mut SafeNativeContext, + comm_points: &[CompressedRistretto], + pc_gens: &PedersenGens, + proof_bytes: &[u8], + bit_length: usize, + dst: Vec, +) -> SafeNativeResult> { + charge_gas(context, comm_points.len(), bit_length)?; + + let range_proof = match bulletproofs::RangeProof::from_bytes(proof_bytes) { + Ok(proof) => proof, + Err(_) => { + return Err(SafeNativeError::Abort { + abort_code: abort_codes::NFE_DESERIALIZE_RANGE_PROOF, + }) + }, + }; + + let mut ver_trans = Transcript::new(dst.as_slice()); + + let success = range_proof + .verify_multiple( + &BULLETPROOF_GENERATORS, + pc_gens, + &mut ver_trans, + comm_points, + bit_length, + ) + .is_ok(); + + Ok(smallvec![Value::bool(success)]) +} + +/// Charges base gas fee for verifying and deserializing a Bulletproof range proof. +fn charge_gas( + context: &mut SafeNativeContext, + batch_size: usize, + bit_length: usize, +) -> SafeNativeResult<()> { + match (batch_size, bit_length) { + (1, 8) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_1_BITS_8), + (1, 16) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_1_BITS_16), + (1, 32) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_1_BITS_32), + (1, 64) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_1_BITS_64), + (2, 8) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_2_BITS_8), + (2, 16) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_2_BITS_16), + (2, 32) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_2_BITS_32), + (2, 64) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_2_BITS_64), + (4, 8) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_4_BITS_8), + (4, 16) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_4_BITS_16), + (4, 32) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_4_BITS_32), + (4, 64) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_4_BITS_64), + (8, 8) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_8_BITS_8), + (8, 16) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_8_BITS_16), + (8, 32) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_8_BITS_32), + (8, 64) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_8_BITS_64), + (16, 8) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_16_BITS_8), + (16, 16) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_16_BITS_16), + (16, 32) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_16_BITS_32), + (16, 64) => context.charge(BULLETPROOFS_VERIFY_BASE_BATCH_16_BITS_64), + _ => unreachable!(), + } +} + pub fn make_all( builder: &SafeNativeBuilder, ) -> impl Iterator + '_ { let mut natives = vec![]; #[cfg(feature = "testing")] - natives.extend([( - "prove_range_internal", - native_test_only_prove_range as RawSafeNative, - )]); - - natives.extend([( - "verify_range_proof_internal", - native_verify_range_proof as RawSafeNative, - )]); + natives.extend([ + ( + "prove_range_internal", + native_test_only_prove_range as RawSafeNative, + ), + ( + "prove_batch_range_internal", + native_test_only_batch_prove_range, + ), + ]); + + natives.extend([ + ( + "verify_range_proof_internal", + native_verify_range_proof as RawSafeNative, + ), + ( + "verify_batch_range_proof_internal", + native_verify_batch_range_proof, + ), + ]); builder.make_named_natives(natives) } diff --git a/aptos-move/framework/src/natives/cryptography/ristretto255.rs b/aptos-move/framework/src/natives/cryptography/ristretto255.rs index 93d3d888843af..88be28e232867 100644 --- a/aptos-move/framework/src/natives/cryptography/ristretto255.rs +++ b/aptos-move/framework/src/natives/cryptography/ristretto255.rs @@ -5,8 +5,8 @@ use crate::natives::cryptography::{ristretto255_point, ristretto255_scalar}; use aptos_gas_algebra::GasExpression; use aptos_gas_schedule::{gas_params::natives::aptos_framework::*, NativeGasParameters}; use aptos_native_interface::{ - safely_assert_eq, safely_pop_arg, RawSafeNative, SafeNativeBuilder, SafeNativeError, - SafeNativeResult, + safely_assert_eq, safely_pop_arg, safely_pop_vec_arg, RawSafeNative, SafeNativeBuilder, + SafeNativeError, SafeNativeResult, }; use aptos_types::vm_status::StatusCode; use curve25519_dalek::scalar::Scalar; @@ -174,6 +174,16 @@ pub fn pop_scalar_from_bytes(arguments: &mut VecDeque) -> SafeNativeResul scalar_from_valid_bytes(bytes) } +/// Pops a Scalars off the argument stack when the argument was a `vector>`. +pub fn pop_scalars_from_bytes(arguments: &mut VecDeque) -> SafeNativeResult> { + let bytes = safely_pop_vec_arg!(arguments, Vec); + + bytes + .into_iter() + .map(scalar_from_valid_bytes) + .collect::>>() +} + /// The 'data' field inside a Move Scalar struct is at index 0. const DATA_FIELD_INDEX: usize = 0; diff --git a/aptos-move/framework/tests/move_unit_test.rs b/aptos-move/framework/tests/move_unit_test.rs index 71af908a174fe..97229408ca802 100644 --- a/aptos-move/framework/tests/move_unit_test.rs +++ b/aptos-move/framework/tests/move_unit_test.rs @@ -28,11 +28,16 @@ fn run_tests_for_pkg(path_to_pkg: impl Into) { ..Default::default() }; + let utc = UnitTestingConfig { + filter: std::env::var("TEST_FILTER").ok(), + report_statistics: matches!(std::env::var("REPORT_STATS"), Ok(s) if s.as_str() == "1"), + ..Default::default() + }; let ok = run_move_unit_tests( &pkg_path, build_config.clone(), // TODO(Gas): double check if this is correct - UnitTestingConfig::default(), + utc, aptos_test_natives(), aptos_test_feature_flags_genesis(), /* gas limit */ Some(100_000), diff --git a/crates/aptos-crypto/Cargo.toml b/crates/aptos-crypto/Cargo.toml index 8a0822a6b4bfa..5437518b8eeae 100644 --- a/crates/aptos-crypto/Cargo.toml +++ b/crates/aptos-crypto/Cargo.toml @@ -100,6 +100,10 @@ harness = false name = "bulletproofs" harness = false +[[bench]] +name = "bulletproofs_batch_verify" +harness = false + [[bench]] name = "ed25519" harness = false diff --git a/crates/aptos-crypto/benches/bulletproofs.rs b/crates/aptos-crypto/benches/bulletproofs.rs index 95877aab593c2..493a47230e426 100644 --- a/crates/aptos-crypto/benches/bulletproofs.rs +++ b/crates/aptos-crypto/benches/bulletproofs.rs @@ -17,7 +17,7 @@ fn get_values(num_bits: usize, batch_size: usize) -> (Vec, Vec) { .map(|_| rng.gen_range(0u64, (2u128.pow(num_bits as u32) - 1u128) as u64)) .collect::>(); - // Sigh, some RngCore incompatibilites I don't want to deal with right now. + // Sigh, some RngCore incompatibilities I don't want to deal with right now. let b = (0..batch_size) .map(|_| Scalar::hash_from_bytes::(b"some random blinder")) .collect::>(); diff --git a/crates/aptos-crypto/benches/bulletproofs_batch_verify.rs b/crates/aptos-crypto/benches/bulletproofs_batch_verify.rs new file mode 100644 index 0000000000000..6c62ddf0ddc1f --- /dev/null +++ b/crates/aptos-crypto/benches/bulletproofs_batch_verify.rs @@ -0,0 +1,133 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +#[macro_use] +extern crate criterion; + +use aptos_crypto::bulletproofs::MAX_RANGE_BITS; +use bulletproofs::{BulletproofGens, PedersenGens, RangeProof}; +use criterion::{measurement::Measurement, BenchmarkGroup, BenchmarkId, Criterion, Throughput}; +use curve25519_dalek_ng::scalar::Scalar; +use merlin::Transcript; +use rand::{thread_rng, Rng}; +use rand_core::RngCore; + +fn get_values(num_bits: usize, batch_size: usize) -> (Vec, Vec) { + let mut rng = thread_rng(); + + let v = (0..batch_size) + .map(|_| rng.gen_range(0u64, (2u128.pow(num_bits as u32) - 1u128) as u64)) + .collect::>(); + + // Sigh, some RngCore incompatibilities I don't want to deal with right now. + let b = (0..batch_size) + .map(|_| { + let mut scalar = [0u8; 32]; + rng.fill_bytes(&mut scalar); + + Scalar::from_bytes_mod_order(scalar) + }) + .collect::>(); + + (v, b) +} + +fn bench_group(c: &mut Criterion) { + let mut group = c.benchmark_group("bulletproofs_batch_verify"); + + for batch_size in [1, 2, 4, 8, 16] { + for num_bits in [8, 16, 32, 64] { + range_batch_prove(&mut group, num_bits, batch_size); + range_batch_verify(&mut group, num_bits, batch_size); + } + } + + group.finish(); +} + +fn range_batch_prove( + g: &mut BenchmarkGroup, + num_bits: usize, + batch_size: usize, +) { + let pg = PedersenGens::default(); + let bg = BulletproofGens::new(MAX_RANGE_BITS, 16); + + g.throughput(Throughput::Elements(batch_size as u64)); + g.bench_function( + BenchmarkId::new( + "range_prove", + format!("batch={}/bits={}", batch_size, num_bits), + ), + move |b| { + b.iter_with_setup( + || get_values(num_bits, batch_size), + |(v, b)| { + let mut dst = [0_u8; 256]; + thread_rng().fill(&mut dst); + let mut t_prv = Transcript::new(&dst); + assert!(RangeProof::prove_multiple( + &bg, + &pg, + &mut t_prv, + v.as_slice(), + b.as_slice(), + num_bits + ) + .is_ok()); + }, + ) + }, + ); +} + +fn range_batch_verify( + g: &mut BenchmarkGroup, + num_bits: usize, + batch_size: usize, +) { + let bp_gens = BulletproofGens::new(MAX_RANGE_BITS, 16); + let pc_gens = PedersenGens::default(); + + g.throughput(Throughput::Elements(batch_size as u64)); + g.bench_function( + BenchmarkId::new( + "range_verify", + format!("batch={}/bits={}", batch_size, num_bits), + ), + move |b| { + b.iter_with_setup( + || { + let (v, b) = get_values(num_bits, batch_size); + let mut dst = [0_u8; 256]; + thread_rng().fill(&mut dst); + let mut t = Transcript::new(&dst); + + let (proof, comm) = RangeProof::prove_multiple( + &bp_gens, + &pc_gens, + &mut t, + v.as_slice(), + b.as_slice(), + num_bits, + ) + .unwrap(); + + (dst, proof.to_bytes(), comm) + }, + |(dst, proof_bytes, comm)| { + let mut t = Transcript::new(&dst); + + let proof = RangeProof::from_bytes(&proof_bytes[..]).unwrap(); + + assert!(proof + .verify_multiple(&bp_gens, &pc_gens, &mut t, &comm, num_bits) + .is_ok()); + }, + ) + }, + ); +} + +criterion_group!(bulletproofs_benches, bench_group); +criterion_main!(bulletproofs_benches); diff --git a/crates/aptos-crypto/src/secp256r1_ecdsa/secp256r1_ecdsa_sigs.rs b/crates/aptos-crypto/src/secp256r1_ecdsa/secp256r1_ecdsa_sigs.rs index aac404aaaba40..94471fbd38a3f 100644 --- a/crates/aptos-crypto/src/secp256r1_ecdsa/secp256r1_ecdsa_sigs.rs +++ b/crates/aptos-crypto/src/secp256r1_ecdsa/secp256r1_ecdsa_sigs.rs @@ -35,19 +35,6 @@ impl Signature { /// Deserialize an P256Signature, without checking for malleability /// Uses the SEC1 serialization format. - #[cfg(not(feature = "fuzzing"))] - pub(crate) fn from_bytes_unchecked( - bytes: &[u8], - ) -> std::result::Result { - match p256::ecdsa::Signature::try_from(bytes) { - Ok(p256_signature) => Ok(Signature(p256_signature)), - Err(_) => Err(CryptoMaterialError::DeserializationError), - } - } - - /// Deserialize an P256Signature, without checking for malleability - /// Uses the SEC1 serialization format. - #[cfg(any(test, feature = "fuzzing"))] pub fn from_bytes_unchecked( bytes: &[u8], ) -> std::result::Result { diff --git a/scripts/algebra-gas/README.md b/scripts/algebra-gas/README.md index 77388fa7db306..fc6bae7436f39 100644 --- a/scripts/algebra-gas/README.md +++ b/scripts/algebra-gas/README.md @@ -28,8 +28,8 @@ Value `k` and `b` should be printed. {"b": 336.51096106242346, "k": 4.868293006038344} ``` -Combined with the [pre-defined](https://github.com/aptos-labs/aptos-core/blob/28df4c1f0ea0d6c6dc6b0460257aa9086e830d1a/aptos-move/aptos-gas/src/move_stdlib.rs#L17-L18) SHA2-256 gas formula (unscaled internal gas):`g(n)=50n+3000`, -it can be calculated that `gas_per_ns = 50/k`. +Combined with the [pre-defined](https://github.com/aptos-labs/aptos-core/blob/2d6ed231ca39fc07422dfe95aa76746b2210e36d/aptos-move/aptos-gas-schedule/src/gas_schedule/move_stdlib.rs#L23-L24) SHA2-256 gas formula (unscaled internal gas):`g(n)=183n+11028`, +it can be calculated that `gas_per_ns = 183/k`. Second last, go to `scripts/algebra-gas/update_algebra_gas_params.py` and update the value of the global variable `TARGET_GAS_VERSION` if necessary. diff --git a/scripts/algebra-gas/update_bulletproofs_batch_verify_gas_params.py b/scripts/algebra-gas/update_bulletproofs_batch_verify_gas_params.py new file mode 100755 index 0000000000000..8a1377e85d00d --- /dev/null +++ b/scripts/algebra-gas/update_bulletproofs_batch_verify_gas_params.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +''' +This module automated the steps to +calculate gas parameters for `bulletproofs.move` batch verification natives from benchmarking results, +then update the gas parameter definitions in rust. +''' + +import argparse +import load_bench_ns +from pathlib import Path +from time import time +import math + +# Typically you are making a new version of gas schedule, +# so this should be larger than `LATEST_GAS_FEATURE_VERSION` in `aptos-move/aptos-gas/src/gas_meter.rs`. +TARGET_GAS_VERSION = 'RELEASE_V1_28' + +def prettify_number(x:int) -> str: + s = str(math.ceil(x)) + n = len(s) + b = n % 3 + chunks_0 = [s[:b]] if b>=1 else [] + chunks = chunks_0 + [s[i:i+3] for i in range(b,n,3)] + return '_'.join(chunks) + +def get_bulletproofs_lines(gas_per_ns): + nanoseconds = {} + + for batch_size in [1, 2, 4, 8, 16]: + for num_bits in [8, 16, 32, 64]: + ns = load_bench_ns.main(f'target/criterion/bulletproofs_batch_verify/range_verify/batch={batch_size}_bits={num_bits}') + + nanoseconds[f'bulletproofs_verify_base_batch_{batch_size}_bits_{num_bits}'] = ns + + gas_units = {k:gas_per_ns*v for k,v in nanoseconds.items()} + + lines = [] + + for batch_size in [1, 2, 4, 8, 16]: + for num_bits in [8, 16, 32, 64]: + lines.append(f' [bulletproofs_verify_base_batch_{batch_size}_bits_{num_bits}: InternalGas, {{ {TARGET_GAS_VERSION}.. => "bulletproofs.verify.base_batch_{batch_size}_bits_{num_bits}" }}, {prettify_number(gas_units[f"bulletproofs_verify_base_batch_{batch_size}_bits_{num_bits}"])}],') + + return lines + +def main(gas_per_ns): + path = Path('aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs') + lines = path.read_text().split('\n') + line_id_begin = lines.index(' // Bulletproofs batch verify gas parameters begin.') + line_id_end = lines.index(' // Bulletproofs batch verify gas parameters end.') + generator_note_line = f' // Generated at time {time()} by `scripts/algebra-gas/update_bulletproofs_batch_verify_gas_params.py` with gas_per_ns={gas_per_ns}.' + new_lines = lines[:line_id_begin+1] + [generator_note_line] + get_bulletproofs_lines(gas_per_ns) + lines[line_id_end:] + path.write_text('\n'.join(new_lines)) + +if __name__=='__main__': + parser = argparse.ArgumentParser( + description='Generate gas parameters for bulletproofs batch verification in `aptos-move/aptos-gas-schedule/src/gas_schedule/aptos_framework.rs`.') + parser.add_argument('--gas_per_ns', required=True, type=float) + args = parser.parse_args() + main(args.gas_per_ns) diff --git a/types/src/on_chain_config/aptos_features.rs b/types/src/on_chain_config/aptos_features.rs index a7b627e41d07e..a0d0f6382c3d5 100644 --- a/types/src/on_chain_config/aptos_features.rs +++ b/types/src/on_chain_config/aptos_features.rs @@ -120,6 +120,7 @@ pub enum FeatureFlag { ACCOUNT_ABSTRACTION = 85, /// Enables bytecode version v8 VM_BINARY_FORMAT_V8 = 86, + BULLETPROOFS_BATCH_NATIVES = 87, } impl FeatureFlag {