From 09b19e06cf60a59c50e7cdc5c829e85ea41dccae Mon Sep 17 00:00:00 2001 From: "zhoujun.ma" Date: Thu, 7 Nov 2024 13:04:01 -0800 Subject: [PATCH] exebench for keyless --- aptos-move/aptos-vm/src/keyless_validation.rs | 18 +++-- aptos-move/vm-genesis/src/lib.rs | 19 +++-- crates/aptos-genesis/src/builder.rs | 3 + crates/aptos-genesis/src/lib.rs | 4 + crates/aptos-genesis/src/mainnet.rs | 2 + .../src/account_generator.rs | 14 ++-- .../executor-benchmark/src/db_generator.rs | 11 ++- execution/executor-benchmark/src/lib.rs | 10 ++- execution/executor-benchmark/src/main.rs | 6 ++ .../src/transaction_generator.rs | 36 +++++---- sdk/Cargo.toml | 1 + sdk/src/transaction_builder.rs | 12 +++ sdk/src/types.rs | 80 ++++++++++++++++++- types/src/jwks/rsa/insecure_test_jwk.json | 2 +- types/src/keyless/bn254_circom.rs | 71 +++++++++++++++- types/src/keyless/circuit_testcases.rs | 27 ++++++- types/src/keyless/configuration.rs | 2 +- types/src/keyless/mod.rs | 4 +- types/src/keyless/proof_simulation.rs | 25 ++++-- types/src/keyless/test_utils.rs | 22 ++++- 20 files changed, 316 insertions(+), 53 deletions(-) diff --git a/aptos-move/aptos-vm/src/keyless_validation.rs b/aptos-move/aptos-vm/src/keyless_validation.rs index 338ac4386115c2..7e0a8018d0d32b 100644 --- a/aptos-move/aptos-vm/src/keyless_validation.rs +++ b/aptos-move/aptos-vm/src/keyless_validation.rs @@ -248,16 +248,17 @@ pub(crate) fn validate_authenticators( get_jwk_for_authenticator(&federated_jwks.jwks, pk.inner_keyless_pk(), sig)? }, // 2.b: If this is not a federated keyless account, then we fail the validation. - AnyKeylessPublicKey::Normal(_) => return Err(e), + AnyKeylessPublicKey::Normal(_) => { + return Err(e) + }, } }, }; - match &sig.cert { EphemeralCertificate::ZeroKnowledgeSig(zksig) => match jwk { JWK::RSA(rsa_jwk) => { if zksig.exp_horizon_secs > config.max_exp_horizon_secs { - // println!("[aptos-vm][groth16] Expiration horizon is too long"); + println!("[aptos-vm][groth16] Expiration horizon is too long"); return Err(invalid_signature!("The expiration horizon is too long")); } @@ -277,7 +278,7 @@ pub(crate) fn validate_authenticators( config, ) .map_err(|_| { - // println!("[aptos-vm][groth16] PIH computation failed"); + println!("[aptos-vm][groth16] PIH computation failed"); invalid_signature!("Could not compute public inputs hash") })?; // println!("Public inputs hash time: {:?}", start.elapsed()); @@ -295,26 +296,27 @@ pub(crate) fn validate_authenticators( training_wheels_pk.as_ref().unwrap(), ) .map_err(|_| { - // println!("[aptos-vm][groth16] TW sig verification failed"); + println!("[aptos-vm][groth16] TW sig verification failed"); invalid_signature!( "Could not verify training wheels signature" ) })?; }, None => { - // println!("[aptos-vm][groth16] Expected TW sig to be set"); + println!("[aptos-vm][groth16] Expected TW sig to be set"); return Err(invalid_signature!( "Training wheels signature expected but it is missing" )); }, } } - + println!("exeing, pub_inputs_hash={}", public_inputs_hash.0); + println!("exeing, pvk_a1b2={}", pvk.as_ref().unwrap().alpha_g1_beta_g2); let result = zksig .verify_groth16_proof(public_inputs_hash, pvk.as_ref().unwrap()); result.map_err(|_| { - // println!("[aptos-vm][groth16] ZKP verification failed"); + println!("[aptos-vm][groth16] ZKP verification failed"); // println!("[aptos-vm][groth16] PIH: {}", public_inputs_hash); // match zksig.proof { // ZKP::Groth16(proof) => { diff --git a/aptos-move/vm-genesis/src/lib.rs b/aptos-move/vm-genesis/src/lib.rs index 23ad6125e7d079..bcf0a58aa1913a 100644 --- a/aptos-move/vm-genesis/src/lib.rs +++ b/aptos-move/vm-genesis/src/lib.rs @@ -69,6 +69,8 @@ use once_cell::sync::Lazy; use rand::prelude::*; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +use aptos_types::jwks::jwk::JWK; +use aptos_types::keyless::proof_simulation::TEST_GROTH16_KEYS; // The seed is arbitrarily picked to produce a consistent key. XXX make this more formal? const GENESIS_SEED: [u8; 32] = [42; 32]; @@ -109,6 +111,7 @@ pub struct GenesisConfiguration { pub initial_features_override: Option, pub randomness_config_override: Option, pub jwk_consensus_config_override: Option, + pub initial_jwks: Vec, } pub static GENESIS_KEYPAIR: Lazy<(Ed25519PrivateKey, Ed25519PublicKey)> = Lazy::new(|| { @@ -304,7 +307,7 @@ pub fn encode_genesis_change_set( .unwrap_or_else(OnChainJWKConsensusConfig::default_for_genesis); initialize_jwk_consensus_config(&mut session, &module_storage, &jwk_consensus_config); initialize_jwks_resources(&mut session, &module_storage); - initialize_keyless_accounts(&mut session, &module_storage, chain_id); + initialize_keyless_accounts(&mut session, &module_storage, chain_id, genesis_config.initial_jwks.clone()); set_genesis_end(&mut session, &module_storage); // Reconfiguration should happen after all on-chain invocations. @@ -676,6 +679,7 @@ fn initialize_keyless_accounts( session: &mut SessionExt, module_storage: &impl AptosModuleStorage, chain_id: ChainId, + mut initial_jwks_for_testing: Vec, ) { let config = keyless::Configuration::new_for_devnet(); exec_function( @@ -690,7 +694,8 @@ fn initialize_keyless_accounts( ]), ); if !chain_id.is_mainnet() { - let vk = Groth16VerificationKey::from(&*DEVNET_VERIFICATION_KEY); + let pvk = &(*TEST_GROTH16_KEYS).prepared_vk; + let vk = Groth16VerificationKey::from(pvk); exec_function( session, module_storage, @@ -703,11 +708,11 @@ fn initialize_keyless_accounts( ]), ); - let patch: PatchJWKMoveStruct = PatchUpsertJWK { + let additional_jwk_patch = PatchUpsertJWK { issuer: get_sample_iss(), jwk: secure_test_rsa_jwk().into(), - } - .into(); + }; + initial_jwks_for_testing.push(additional_jwk_patch); exec_function( session, module_storage, @@ -716,7 +721,7 @@ fn initialize_keyless_accounts( vec![], serialize_values(&vec![ MoveValue::Signer(CORE_CODE_ADDRESS), - MoveValue::Vector(vec![patch.as_move_value()]), + initial_jwks_for_testing.into_iter().map(PatchJWKMoveStruct::from).collect::>().as_move_value(), ]), ); } @@ -1229,6 +1234,7 @@ pub fn generate_test_genesis( initial_features_override: None, randomness_config_override: None, jwk_consensus_config_override: None, + initial_jwks: vec![], }, &OnChainConsensusConfig::default_for_genesis(), &OnChainExecutionConfig::default_for_genesis(), @@ -1279,6 +1285,7 @@ fn mainnet_genesis_config() -> GenesisConfiguration { initial_features_override: None, randomness_config_override: None, jwk_consensus_config_override: None, + initial_jwks: vec![], } } diff --git a/crates/aptos-genesis/src/builder.rs b/crates/aptos-genesis/src/builder.rs index 4117f9a4f2fe54..3a04d924cf14d6 100644 --- a/crates/aptos-genesis/src/builder.rs +++ b/crates/aptos-genesis/src/builder.rs @@ -45,6 +45,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; +use aptos_types::jwks::patch::PatchUpsertJWK; const VALIDATOR_IDENTITY: &str = "validator-identity.yaml"; const VFN_IDENTITY: &str = "vfn-identity.yaml"; @@ -441,6 +442,7 @@ pub struct GenesisConfiguration { pub initial_features_override: Option, pub randomness_config_override: Option, pub jwk_consensus_config_override: Option, + pub initial_jwks: Vec, } pub type InitConfigFn = Arc; @@ -662,6 +664,7 @@ impl Builder { initial_features_override: None, randomness_config_override: None, jwk_consensus_config_override: None, + initial_jwks: vec![], }; if let Some(init_genesis_config) = &self.init_genesis_config { (init_genesis_config)(&mut genesis_config); diff --git a/crates/aptos-genesis/src/lib.rs b/crates/aptos-genesis/src/lib.rs index 3a0cb846965459..005e24f5db5d66 100644 --- a/crates/aptos-genesis/src/lib.rs +++ b/crates/aptos-genesis/src/lib.rs @@ -33,6 +33,7 @@ use aptos_types::{ use aptos_vm::AptosVM; use aptos_vm_genesis::Validator; use std::convert::TryInto; +use aptos_types::jwks::patch::PatchUpsertJWK; /// Holder object for all pieces needed to generate a genesis transaction #[derive(Clone)] @@ -76,6 +77,7 @@ pub struct GenesisInfo { pub initial_features_override: Option, pub randomness_config_override: Option, pub jwk_consensus_config_override: Option, + pub initial_jwks: Vec, } impl GenesisInfo { @@ -115,6 +117,7 @@ impl GenesisInfo { initial_features_override: genesis_config.initial_features_override.clone(), randomness_config_override: genesis_config.randomness_config_override.clone(), jwk_consensus_config_override: genesis_config.jwk_consensus_config_override.clone(), + initial_jwks: genesis_config.initial_jwks.clone(), }) } @@ -150,6 +153,7 @@ impl GenesisInfo { initial_features_override: self.initial_features_override.clone(), randomness_config_override: self.randomness_config_override.clone(), jwk_consensus_config_override: self.jwk_consensus_config_override.clone(), + initial_jwks: self.initial_jwks.clone(), }, &self.consensus_config, &self.execution_config, diff --git a/crates/aptos-genesis/src/mainnet.rs b/crates/aptos-genesis/src/mainnet.rs index a3a1d27b6048d7..51db32cef7b384 100644 --- a/crates/aptos-genesis/src/mainnet.rs +++ b/crates/aptos-genesis/src/mainnet.rs @@ -16,6 +16,7 @@ use aptos_types::{ transaction::Transaction, waypoint::Waypoint, }; +use aptos_types::jwks::patch::PatchUpsertJWK; use aptos_vm::AptosVM; use aptos_vm_genesis::{AccountBalance, EmployeePool, ValidatorWithCommissionRate}; @@ -143,6 +144,7 @@ impl MainnetGenesisInfo { initial_features_override: self.initial_features_override.clone(), randomness_config_override: self.randomness_config_override.clone(), jwk_consensus_config_override: self.jwk_consensus_config_override.clone(), + initial_jwks: vec![], }, ) } diff --git a/execution/executor-benchmark/src/account_generator.rs b/execution/executor-benchmark/src/account_generator.rs index 4e7defd846868b..ca7b2a6df4bc6c 100644 --- a/execution/executor-benchmark/src/account_generator.rs +++ b/execution/executor-benchmark/src/account_generator.rs @@ -17,15 +17,15 @@ impl AccountGenerator { const SEED_ACCOUNTS_ROOT_SEED: u64 = u64::max_value(); const USER_ACCOUNTS_ROOT_SEED: u64 = 0; - pub fn new_for_seed_accounts() -> Self { - Self::new(Self::SEED_ACCOUNTS_ROOT_SEED, 0) + pub fn new_for_seed_accounts(is_keyless: bool) -> Self { + Self::new(Self::SEED_ACCOUNTS_ROOT_SEED, 0, is_keyless) } - pub fn new_for_user_accounts(num_to_skip: u64) -> Self { - Self::new(Self::USER_ACCOUNTS_ROOT_SEED, num_to_skip) + pub fn new_for_user_accounts(num_to_skip: u64, is_keyless: bool) -> Self { + Self::new(Self::USER_ACCOUNTS_ROOT_SEED, num_to_skip, is_keyless) } - fn new(root_seed: u64, num_to_skip: u64) -> Self { + fn new(root_seed: u64, num_to_skip: u64, is_keyless: bool) -> Self { let mut root_rng = StdRng::seed_from_u64(root_seed); let num_rngs_to_skip = num_to_skip / Self::MAX_ACCOUNT_GEN_PER_RNG; for _ in 0..num_rngs_to_skip { @@ -35,14 +35,14 @@ impl AccountGenerator { let mut active_rng_quota = Self::MAX_ACCOUNT_GEN_PER_RNG - active_rng_to_skip; let mut active_rng = StdRng::seed_from_u64(root_rng.next_u64()); for _ in 0..active_rng_to_skip { - LocalAccount::generate(&mut active_rng); + LocalAccount::generate_v2(&mut active_rng, is_keyless); } let (sender, receiver) = mpsc::sync_channel(100 /* bound */); std::thread::Builder::new() .name("account_generator".to_string()) .spawn(move || { - while sender.send(LocalAccount::generate(&mut active_rng)).is_ok() { + while sender.send(LocalAccount::generate_v2(&mut active_rng, is_keyless)).is_ok() { active_rng_quota -= 1; if active_rng_quota == 0 { active_rng = StdRng::seed_from_u64(root_rng.next_u64()); diff --git a/execution/executor-benchmark/src/db_generator.rs b/execution/executor-benchmark/src/db_generator.rs index 626b07687205df..20b0cf308a39e8 100644 --- a/execution/executor-benchmark/src/db_generator.rs +++ b/execution/executor-benchmark/src/db_generator.rs @@ -19,6 +19,9 @@ use aptos_storage_interface::DbReaderWriter; use aptos_types::on_chain_config::Features; use aptos_vm::AptosVM; use std::{fs, path::Path}; +use std::sync::Arc; +use aptos_types::jwks::patch::PatchUpsertJWK; +use aptos_types::keyless::test_utils::{get_sample_iss, get_sample_jwk}; pub fn create_db_with_accounts( num_accounts: usize, @@ -30,6 +33,7 @@ pub fn create_db_with_accounts( enable_storage_sharding: bool, pipeline_config: PipelineConfig, init_features: Features, + is_keyless: bool, ) where V: TransactionBlockExecutor + 'static, { @@ -59,6 +63,7 @@ pub fn create_db_with_accounts( enable_storage_sharding, pipeline_config, init_features, + is_keyless, ); } @@ -67,8 +72,10 @@ fn bootstrap_with_genesis( enable_storage_sharding: bool, init_features: Features, ) { - let (config, _genesis_key) = - aptos_genesis::test_utils::test_config_with_custom_features(init_features); + let (config, _genesis_key) = aptos_genesis::test_utils::test_config_with_custom_onchain(Some(Arc::new(move |config| { + config.initial_features_override = Some(init_features.clone()); + config.initial_jwks = vec![PatchUpsertJWK { issuer: get_sample_iss(), jwk: get_sample_jwk().into() }]; + }))); let mut rocksdb_configs = RocksdbConfigs::default(); rocksdb_configs.state_merkle_db_config.max_open_files = -1; diff --git a/execution/executor-benchmark/src/lib.rs b/execution/executor-benchmark/src/lib.rs index 09f2a5b91558bf..8ddb40710820c6 100644 --- a/execution/executor-benchmark/src/lib.rs +++ b/execution/executor-benchmark/src/lib.rs @@ -110,6 +110,7 @@ pub fn run_benchmark( enable_storage_sharding: bool, pipeline_config: PipelineConfig, init_features: Features, + is_keyless: bool, ) where V: TransactionBlockExecutor + 'static, { @@ -147,7 +148,7 @@ pub fn run_benchmark( } let accounts_cache = - TransactionGenerator::gen_user_account_cache(db.reader.clone(), num_accounts_to_be_loaded, num_accounts_to_skip); + TransactionGenerator::gen_user_account_cache(db.reader.clone(), num_accounts_to_be_loaded, num_accounts_to_skip, is_keyless); let (main_signer_accounts, burner_accounts) = accounts_cache.split(num_main_signer_accounts); @@ -195,6 +196,7 @@ pub fn run_benchmark( source_dir, Some(num_accounts_to_load), pipeline_config.num_generator_workers, + is_keyless, ); let mut overall_measuring = OverallMeasuring::start(); @@ -306,6 +308,7 @@ pub fn add_accounts( enable_storage_sharding: bool, pipeline_config: PipelineConfig, init_features: Features, + is_keyless: bool, ) where V: TransactionBlockExecutor + 'static, { @@ -326,6 +329,7 @@ pub fn add_accounts( enable_storage_sharding, pipeline_config, init_features, + is_keyless, ); } @@ -340,6 +344,7 @@ fn add_accounts_impl( enable_storage_sharding: bool, pipeline_config: PipelineConfig, init_features: Features, + is_keyless: bool, ) where V: TransactionBlockExecutor + 'static, { @@ -366,6 +371,7 @@ fn add_accounts_impl( &source_dir, None, pipeline_config.num_generator_workers, + is_keyless, ); let start_time = Instant::now(); @@ -375,6 +381,7 @@ fn add_accounts_impl( num_new_accounts, init_account_balance, block_size, + is_keyless, ); pipeline.start_execution(); generator.drop_sender(); @@ -780,6 +787,7 @@ mod tests { false, PipelineConfig::default(), Features::default(), + false, ); } diff --git a/execution/executor-benchmark/src/main.rs b/execution/executor-benchmark/src/main.rs index 9d3e23722e68bf..2363c83d476bf1 100644 --- a/execution/executor-benchmark/src/main.rs +++ b/execution/executor-benchmark/src/main.rs @@ -217,6 +217,9 @@ pub struct VmSelectionOpt { #[derive(Parser, Debug)] struct Opt { + #[clap(long)] + use_keyless_accounts: bool, + #[clap(long, default_value_t = 10000)] block_size: usize, @@ -416,6 +419,7 @@ where opt.enable_storage_sharding, opt.pipeline_opt.pipeline_config(), get_init_features(enable_feature, disable_feature), + opt.use_keyless_accounts, ); }, Command::RunExecutor { @@ -476,6 +480,7 @@ where opt.enable_storage_sharding, opt.pipeline_opt.pipeline_config(), get_init_features(enable_feature, disable_feature), + opt.use_keyless_accounts, ); }, Command::AddAccounts { @@ -495,6 +500,7 @@ where opt.enable_storage_sharding, opt.pipeline_opt.pipeline_config(), Features::default(), + opt.use_keyless_accounts, ); }, } diff --git a/execution/executor-benchmark/src/transaction_generator.rs b/execution/executor-benchmark/src/transaction_generator.rs index dde5b1a40454f1..008e1ce0bfa698 100644 --- a/execution/executor-benchmark/src/transaction_generator.rs +++ b/execution/executor-benchmark/src/transaction_generator.rs @@ -154,11 +154,12 @@ impl TransactionGenerator { reader: Arc, num_accounts: usize, num_to_skip: usize, + is_keyless: bool, ) -> AccountCache { Self::resync_sequence_numbers( reader, Self::gen_account_cache( - AccountGenerator::new_for_user_accounts(num_to_skip as u64), + AccountGenerator::new_for_user_accounts(num_to_skip as u64, is_keyless), num_accounts, "user", ), @@ -166,11 +167,11 @@ impl TransactionGenerator { ) } - fn gen_seed_account_cache(reader: Arc, num_accounts: usize) -> AccountCache { + fn gen_seed_account_cache(reader: Arc, num_accounts: usize, is_keyless: bool) -> AccountCache { Self::resync_sequence_numbers( reader, Self::gen_account_cache( - AccountGenerator::new_for_seed_accounts(), + AccountGenerator::new_for_seed_accounts(is_keyless), num_accounts, "seed", ), @@ -185,6 +186,7 @@ impl TransactionGenerator { db_dir: P, num_main_signer_accounts: Option, num_workers: usize, + is_keyless: bool, ) -> Self { let num_existing_accounts = TransactionGenerator::read_meta(&db_dir); @@ -194,7 +196,7 @@ impl TransactionGenerator { main_signer_accounts: num_main_signer_accounts.map(|num_main_signer_accounts| { let num_cached_accounts = std::cmp::min(num_existing_accounts, num_main_signer_accounts); - Self::gen_user_account_cache(db.reader.clone(), num_cached_accounts, 0) + Self::gen_user_account_cache(db.reader.clone(), num_cached_accounts, 0, is_keyless) }), num_existing_accounts, block_sender: Some(block_sender), @@ -256,6 +258,7 @@ impl TransactionGenerator { num_new_accounts: usize, init_account_balance: u64, block_size: usize, + is_keyless: bool, ) { assert!(self.block_sender.is_some()); // Ensure that seed accounts have enough balance to transfer money to at least 10000 account with @@ -265,12 +268,14 @@ impl TransactionGenerator { num_new_accounts, block_size, init_account_balance * 10_000, + is_keyless, ); self.create_and_fund_accounts( num_existing_accounts, num_new_accounts, init_account_balance, block_size, + is_keyless, ); } @@ -351,11 +356,12 @@ impl TransactionGenerator { num_new_accounts: usize, block_size: usize, seed_account_balance: u64, + is_keyless: bool, ) { // We don't store the # of existing seed accounts now. Thus here we just blindly re-create // and re-mint seed accounts here. let num_seed_accounts = (num_new_accounts / 1000).clamp(1, 100000); - let seed_accounts_cache = Self::gen_seed_account_cache(reader, num_seed_accounts); + let seed_accounts_cache = Self::gen_seed_account_cache(reader, num_seed_accounts, is_keyless); println!( "[{}] Generating {} seed account creation txns, with {} coins.", @@ -374,13 +380,12 @@ impl TransactionGenerator { let transactions: Vec<_> = chunk .iter() .map(|new_account| { - let txn = self.root_account.sign_with_transaction_builder( - self.transaction_factory - .implicitly_create_user_account_and_transfer( - new_account.public_key(), - seed_account_balance, - ), - ); + let builder = self.transaction_factory + .implicitly_create_user_account_and_transfer_v2( + new_account, + seed_account_balance, + ); + let txn = self.root_account.sign_with_transaction_builder(builder); Transaction::UserTransaction(txn) }) .collect(); @@ -401,13 +406,14 @@ impl TransactionGenerator { num_new_accounts: usize, init_account_balance: u64, block_size: usize, + is_keyless: bool, ) { println!( "[{}] Generating {} account creation txns.", now_fmt!(), num_new_accounts ); - let mut generator = AccountGenerator::new_for_user_accounts(num_existing_accounts as u64); + let mut generator = AccountGenerator::new_for_user_accounts(num_existing_accounts as u64, is_keyless); println!("Skipped first {} existing accounts.", num_existing_accounts); let bar = get_progress_bar(num_new_accounts); @@ -433,8 +439,8 @@ impl TransactionGenerator { let sender = &account_cache.accounts[sender_idx]; let txn = sender.sign_with_transaction_builder( self.transaction_factory - .implicitly_create_user_account_and_transfer( - new_account.public_key(), + .implicitly_create_user_account_and_transfer_v2( + &new_account, init_account_balance, ), ); diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index ed87ebb322a599..8368daa9be6222 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -26,6 +26,7 @@ ed25519-dalek-bip32 = { workspace = true } hex = { workspace = true } move-core-types = { workspace = true } rand_core = { workspace = true } +rand = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tiny-bip39 = { workspace = true } diff --git a/sdk/src/transaction_builder.rs b/sdk/src/transaction_builder.rs index 3ba9c981735d15..5aef41fb23748f 100644 --- a/sdk/src/transaction_builder.rs +++ b/sdk/src/transaction_builder.rs @@ -13,6 +13,7 @@ pub use aptos_cached_packages::aptos_stdlib; use aptos_crypto::{ed25519::Ed25519PublicKey, HashValue}; use aptos_global_constants::{GAS_UNIT_PRICE, MAX_GAS_AMOUNT}; use aptos_types::transaction::{EntryFunction, Script}; +use crate::types::{KeylessAccount, LocalAccount}; pub struct TransactionBuilder { sender: Option, @@ -155,6 +156,17 @@ impl TransactionFactory { )) } + pub fn implicitly_create_user_account_and_transfer_v2( + &self, + account: &LocalAccount, + amount: u64, + ) -> TransactionBuilder { + self.payload(aptos_stdlib::aptos_account_transfer( + account.authentication_key().account_address(), + amount, + )) + } + pub fn implicitly_create_user_account_and_transfer( &self, public_key: &Ed25519PublicKey, diff --git a/sdk/src/types.rs b/sdk/src/types.rs index 8bf98ac47f4416..61e7d9386d28ef 100644 --- a/sdk/src/types.rs +++ b/sdk/src/types.rs @@ -36,6 +36,16 @@ use std::{ sync::atomic::{AtomicU64, Ordering}, time::{Duration, SystemTime, UNIX_EPOCH}, }; +use std::ops::Add; +use base64::{encode_config, URL_SAFE_NO_PAD}; +use ed25519_dalek_bip32::ed25519_dalek::ed25519::signature; +use ed25519_dalek_bip32::ed25519_dalek::ed25519::signature::SignerMut; +use rand::Rng; +use aptos_types::keyless::{base64url_encode_str, ZKP}; +use aptos_types::keyless::bn254_circom::get_public_inputs_hash_v2; +use aptos_types::keyless::circuit_testcases::{render_jwt_payload_json, SAMPLE_JWK_SK}; +use aptos_types::keyless::proof_simulation::{Groth16SimulatorBn254, TEST_GROTH16_KEYS}; +use aptos_types::keyless::test_utils::{get_sample_epk_blinder, get_sample_esk, get_sample_iss, get_sample_jwk, get_sample_jwt_header_json, get_sample_jwt_token, get_sample_pepper, get_sample_zk_sig, oidc_provider_sign}; #[derive(Debug)] enum LocalAccountAuthenticator { @@ -88,6 +98,13 @@ impl LocalAccountAuthenticator { ephemeral_signature, } } + + fn as_keyless_account(&self) -> &KeylessAccount { + match self { + LocalAccountAuthenticator::Keyless(acc) => &acc, + _ => panic!("") + } + } } impl> From for LocalAccountAuthenticator { fn from(key: T) -> Self { @@ -181,6 +198,66 @@ impl LocalAccount { Ok(Self::new(address, key, sequence_number)) } + pub fn generate_v2(rng: &mut R1, is_keyless: bool) -> Self + where + R1: Rng + rand_core::CryptoRng, + { + if is_keyless { + let now_secs = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); + let esk = EphemeralPrivateKey::Ed25519 { inner_private_key: get_sample_esk() }; + let exp_timestamp_secs = now_secs + 7 * 86400; // + 7 days + let exp_horizon_secs = 100 * 86400; + let eph_key_pair = EphemeralKeyPair::new(esk, exp_timestamp_secs, get_sample_epk_blinder()).unwrap(); + let pepper = get_sample_pepper(); + let aud = format!("aud_{}", hex::encode(rng.gen::<[u8; 4]>())); + let uid_key = "sub".to_string(); + let uid_val = format!("uid_{}", hex::encode(rng.gen::<[u8; 4]>())); + let idc = IdCommitment::new_from_preimage(&pepper, &aud, &uid_key, &uid_val).unwrap(); + let config = Configuration::new_for_testing(); + let iss = get_sample_iss(); + let jwt_header = get_sample_jwt_header_json(); + let jwt_header_b64 = base64url_encode_str(&jwt_header); + let jwt_payload = render_jwt_payload_json(&iss, &aud, &uid_key, &uid_val, "", now_secs, &eph_key_pair.nonce, now_secs + 86400); + let jwt_payload_b64 = base64url_encode_str(&jwt_payload); + let jwt_msg = format!("{}.{}", jwt_header_b64, jwt_payload_b64); + + let sk = *SAMPLE_JWK_SK; + let jwt_sig = oidc_provider_sign(sk, jwt_msg.as_bytes()); + let jwt_sig_b64 = encode_config(jwt_sig.clone(), URL_SAFE_NO_PAD); + let jwt = format!("{}.{}", jwt_msg, jwt_sig_b64); + let jwk = get_sample_jwk(); + let public_inputs = get_public_inputs_hash_v2(&eph_key_pair.public_key, &idc, exp_timestamp_secs, exp_horizon_secs, &iss, None, &jwt_header, &jwk, None, &config).unwrap(); + let groth16_proof = Groth16SimulatorBn254::create_random_proof_with_trapdoor(&[public_inputs], &(*TEST_GROTH16_KEYS).pk, rng).unwrap(); + let zk_sig = ZeroKnowledgeSig { + proof: ZKP::Groth16(groth16_proof), + exp_horizon_secs, + extra_field: None, + override_aud_val: None, + training_wheels_signature: None, + }; + zk_sig.verify_groth16_proof(public_inputs, &TEST_GROTH16_KEYS.prepared_vk).unwrap(); + println!("gening, pvk_a1b2={}", TEST_GROTH16_KEYS.prepared_vk.alpha_g1_beta_g2); + println!("gening, pub_input_hash={}", public_inputs.0); + let keyless_account = KeylessAccount::new_from_jwt( + &jwt, + eph_key_pair, + Some(&uid_key), + Some(pepper), + Some(zk_sig), + ).unwrap(); + Self::new_keyless( + keyless_account.authentication_key().account_address(), + keyless_account, + 0 + ) + } else { + Self::generate(rng) + } + } + + pub fn as_keyless_account(&self) -> &KeylessAccount { + self.auth.as_keyless_account() + } /// Generate a new account locally. Note: This function does not actually /// create an account on the Aptos blockchain, it just generates a new /// account locally. @@ -467,7 +544,7 @@ pub struct AccountKey { impl AccountKey { pub fn generate(rng: &mut R) -> Self where - R: ::rand_core::RngCore + ::rand_core::CryptoRng, + R: rand_core::RngCore + rand_core::CryptoRng, { let private_key = Ed25519PrivateKey::generate(rng); Self::from_private_key(private_key) @@ -554,7 +631,6 @@ impl EphemeralPrivateKey { pub struct EphemeralKeyPair { private_key: EphemeralPrivateKey, public_key: EphemeralPublicKey, - #[allow(dead_code)] nonce: String, expiry_date_secs: u64, #[allow(dead_code)] diff --git a/types/src/jwks/rsa/insecure_test_jwk.json b/types/src/jwks/rsa/insecure_test_jwk.json index 0e6524dccd8d94..2e109850809b84 100644 --- a/types/src/jwks/rsa/insecure_test_jwk.json +++ b/types/src/jwks/rsa/insecure_test_jwk.json @@ -4,7 +4,7 @@ "kty": "RSA", "n": "6S7asUuzq5Q_3U9rbs-PkDVIdjgmtgWreG5qWPsC9xXZKiMV1AiV9LXyqQsAYpCqEDM3XbfmZqGb48yLhb_XqZaKgSYaC_h2DjM7lgrIQAp9902Rr8fUmLN2ivr5tnLxUUOnMOc2SQtr9dgzTONYW5Zu3PwyvAWk5D6ueIUhLtYzpcB-etoNdL3Ir2746KIy_VUsDwAM7dhrqSK8U2xFCGlau4ikOTtvzDownAMHMrfE7q1B6WZQDAQlBmxRQsyKln5DIsKv6xauNsHRgBAKctUxZG8M4QJIx3S6Aughd3RZC4Ca5Ae9fd8L8mlNYBCrQhOZ7dS0f4at4arlLcajtw", "e": "AQAB", - "kid": "test-rsa", + "kid": "test-rsa-insecure", "alg": "RS256" } ] diff --git a/types/src/keyless/bn254_circom.rs b/types/src/keyless/bn254_circom.rs index 1c37c1d8b176bf..20c9762e869270 100644 --- a/types/src/keyless/bn254_circom.rs +++ b/types/src/keyless/bn254_circom.rs @@ -20,6 +20,7 @@ use once_cell::sync::Lazy; use quick_cache::sync::Cache; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_big_array::BigArray; +use crate::transaction::authenticator::EphemeralPublicKey; // TODO(keyless): Some of this stuff, if not all, belongs to the aptos-crypto crate @@ -252,7 +253,7 @@ static PAD_AND_HASH_STRING_CACHE: Lazy> = static JWK_HASH_CACHE: Lazy> = Lazy::new(|| Cache::new(100)); -pub fn cached_pad_and_hash_string(str: &String, max_bytes: usize) -> anyhow::Result { +pub fn cached_pad_and_hash_string(str: &str, max_bytes: usize) -> anyhow::Result { let key = (str.to_string(), max_bytes); match PAD_AND_HASH_STRING_CACHE.get(&key) { None => { @@ -275,6 +276,74 @@ pub fn cached_jwk_hash(jwk: &RSA_JWK) -> anyhow::Result { } } +pub fn get_public_inputs_hash_v2( + epk: &EphemeralPublicKey, + idc: &IdCommitment, + exp_timestamp_secs: u64, + exp_horizon_secs: u64, + iss: &str, + extra_field: Option, + jwt_header: &str, + jwk: &RSA_JWK, + override_aud_val: Option, + config: &Configuration, +) -> anyhow::Result { + let mut frs = vec![]; + + let mut epk_frs = poseidon_bn254::keyless::pad_and_pack_bytes_to_scalars_with_len( + epk.to_bytes().as_slice(), + config.max_commited_epk_bytes as usize, + )?; + frs.append(&mut epk_frs); + + let idc = Fr::from_le_bytes_mod_order(&idc.0); + frs.push(idc); + + let exp_timestamp_secs = Fr::from(exp_timestamp_secs); + frs.push(exp_timestamp_secs); + + let exp_horizon_secs = Fr::from(exp_horizon_secs); + frs.push(exp_horizon_secs); + + let iss_field_hash = + cached_pad_and_hash_string(&iss, config.max_iss_val_bytes as usize)?; + frs.push(iss_field_hash); + + let (has_extra_field, extra_field_hash) = match &extra_field { + None => (Fr::zero(), *EMPTY_EXTRA_FIELD_HASH), + Some(extra_field) => ( + Fr::one(), + poseidon_bn254::keyless::pad_and_hash_string( + extra_field, + config.max_extra_field_bytes as usize, + )?, + ), + }; + frs.push(has_extra_field); + frs.push(extra_field_hash); + + let jwt_header_b64_with_separator = format!("{}.", base64url_encode_str(&jwt_header)); + let jwt_header_hash = cached_pad_and_hash_string( + &jwt_header_b64_with_separator, + config.max_jwt_header_b64_bytes as usize, + )?; + frs.push(jwt_header_hash); + + let jwk_hash = cached_jwk_hash(jwk)?; + frs.push(jwk_hash); + + let (override_aud_val_hash, use_override_aud) = match &override_aud_val { + Some(override_aud_val) => ( + cached_pad_and_hash_string(override_aud_val, IdCommitment::MAX_AUD_VAL_BYTES)?, + ark_bn254::Fr::from(1), + ), + None => (*EMPTY_OVERRIDE_AUD_FIELD_HASH, ark_bn254::Fr::from(0)), + }; + frs.push(override_aud_val_hash); + frs.push(use_override_aud); + poseidon_bn254::hash_scalars(frs) +} + pub fn get_public_inputs_hash( sig: &KeylessSignature, pk: &KeylessPublicKey, diff --git a/types/src/keyless/circuit_testcases.rs b/types/src/keyless/circuit_testcases.rs index b542d269652328..bb6896bb45bc0e 100644 --- a/types/src/keyless/circuit_testcases.rs +++ b/types/src/keyless/circuit_testcases.rs @@ -61,6 +61,31 @@ pub fn sample_jwt_payload_json() -> String { ) } +pub fn render_jwt_payload_json( + iss: &str, + aud: &str, + uid_key: &str, + uid_val: &str, + extra_field: &str, + iat: u64, + nonce: &str, + exp: u64, +) -> String { + format!( + r#"{{ + "iss":"{}", + "aud":"{}", + "{}":"{}", + {} + "iat":{}, + "nonce":"{}", + "exp":{} + }} + "#, + iss, aud, uid_key, uid_val, extra_field, iat, nonce, exp + ) +} + pub fn sample_jwt_payload_json_overrides( iss: &str, uid_val: &str, @@ -109,7 +134,7 @@ pub(crate) static SAMPLE_JWK: Lazy = Lazy::new(insecure_test_rsa_jwk); /// This is the SK from https://token.dev/. /// To convert it into a JSON, you can use https://irrte.ch/jwt-js-decode/pem2jwk.html -pub(crate) static SAMPLE_JWK_SK: Lazy<&RsaKeyPair> = Lazy::new(|| &*INSECURE_TEST_RSA_KEY_PAIR); +pub static SAMPLE_JWK_SK: Lazy<&RsaKeyPair> = Lazy::new(|| &*INSECURE_TEST_RSA_KEY_PAIR); pub(crate) const SAMPLE_UID_KEY: &str = "sub"; diff --git a/types/src/keyless/configuration.rs b/types/src/keyless/configuration.rs index b5b444110670a5..76a0d52e0a2b24 100644 --- a/types/src/keyless/configuration.rs +++ b/types/src/keyless/configuration.rs @@ -60,7 +60,7 @@ impl Configuration { Configuration { override_aud_vals: vec![Self::OVERRIDE_AUD_FOR_TESTING.to_owned()], max_signatures_per_txn: 3, - max_exp_horizon_secs: 10_000_000, // ~115.74 days + max_exp_horizon_secs: 999_999_999_999, training_wheels_pubkey: None, max_commited_epk_bytes: circuit_constants::MAX_COMMITED_EPK_BYTES, max_iss_val_bytes: circuit_constants::MAX_ISS_VAL_BYTES, diff --git a/types/src/keyless/mod.rs b/types/src/keyless/mod.rs index 1e92aa1abf6297..7afb4bfe0bccec 100644 --- a/types/src/keyless/mod.rs +++ b/types/src/keyless/mod.rs @@ -21,7 +21,7 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; -mod bn254_circom; +pub mod bn254_circom; mod circuit_constants; pub mod circuit_testcases; mod configuration; @@ -420,7 +420,7 @@ pub fn get_authenticators( Ok(authenticators) } -pub(crate) fn base64url_encode_str(data: &str) -> String { +pub fn base64url_encode_str(data: &str) -> String { base64::encode_config(data.as_bytes(), URL_SAFE_NO_PAD) } diff --git a/types/src/keyless/proof_simulation.rs b/types/src/keyless/proof_simulation.rs index fc472a05e32406..ff2a10598d5bc7 100644 --- a/types/src/keyless/proof_simulation.rs +++ b/types/src/keyless/proof_simulation.rs @@ -4,15 +4,14 @@ use crate::keyless::{Bn254, G1Bytes, G2Bytes, Groth16Proof}; use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, Group}; use ark_ff::{Field, PrimeField}; -use ark_groth16::{ - data_structures::{Proof, VerifyingKey}, - r1cs_to_qap::{LibsnarkReduction, R1CSToQAP}, -}; +use ark_groth16::{data_structures::{Proof, VerifyingKey}, PreparedVerifyingKey, r1cs_to_qap::{LibsnarkReduction, R1CSToQAP}}; use ark_relations::r1cs::SynthesisError; use ark_serialize::*; use ark_std::{marker::PhantomData, vec::Vec}; -use rand::{Rng, RngCore}; +use rand::{Rng, RngCore, SeedableRng, thread_rng}; use std::ops::AddAssign; +use once_cell::sync::Lazy; +use rand::rngs::StdRng; pub type Groth16SimulatorBn254 = Groth16Simulator; @@ -23,6 +22,22 @@ pub struct Groth16Simulator { _p: PhantomData<(E, QAP)>, } +pub struct Groth16Keys { + pub pk: Trapdoor, + pub vk: VerifyingKey, + pub prepared_vk: PreparedVerifyingKey, +} + +pub static TEST_GROTH16_KEYS: Lazy = Lazy::new(||{ + let mut rng = StdRng::seed_from_u64(999); + let (pk, vk) = Groth16SimulatorBn254::circuit_agnostic_setup_with_trapdoor(&mut rng, 1).unwrap(); + let prepared_vk = PreparedVerifyingKey::from(vk.clone()); + Groth16Keys { + pk, + vk, + prepared_vk, + } +}); /// The simulation prover key for for the Groth16 zkSNARK, used only for simulating proofs with the /// secret trapdoor information #[derive(Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize)] diff --git a/types/src/keyless/test_utils.rs b/types/src/keyless/test_utils.rs index c8babbd5913afc..c62b655b161cf0 100644 --- a/types/src/keyless/test_utils.rs +++ b/types/src/keyless/test_utils.rs @@ -24,12 +24,19 @@ use crate::{ use aptos_crypto::{ ed25519::Ed25519PrivateKey, poseidon_bn254::keyless::fr_to_bytes_le, SigningKey, Uniform, }; -use ark_bn254::Bn254; +use ark_bn254::{Bn254, Fr}; use ark_groth16::{prepare_verifying_key, PreparedVerifyingKey}; use base64::{encode_config, URL_SAFE_NO_PAD}; use move_core_types::account_address::AccountAddress; use once_cell::sync::Lazy; +use rand::Rng; use ring::signature; +use ring::signature::RsaKeyPair; +use rsa::signature::SignerMut; +use crate::keyless::bn254_circom::get_public_inputs_hash_v2; +use crate::keyless::IdCommitment; +use crate::keyless::proof_simulation::TEST_GROTH16_KEYS; +use crate::transaction::authenticator::EphemeralPublicKey; static DUMMY_EPHEMERAL_SIGNATURE: Lazy = Lazy::new(|| { let sk = Ed25519PrivateKey::generate_for_testing(); @@ -308,6 +315,19 @@ pub fn get_sample_jwt_token_from_payload(payload: &str) -> String { format!("{}.{}", msg, base64url_string) } +pub fn oidc_provider_sign(sk: &RsaKeyPair, msg: &[u8]) -> Vec { + let mut jwt_sig = vec![0u8; sk.public_modulus_len()]; + let rng = ring::rand::SystemRandom::new(); + sk.sign( + &signature::RSA_PKCS1_SHA256, + &rng, + msg, + jwt_sig.as_mut_slice(), + ).unwrap(); + + jwt_sig +} + /// Note: Does not have a valid ephemeral signature. Use the SAMPLE_ESK to compute one over the /// desired TXN. pub fn get_sample_openid_sig_and_pk() -> (KeylessSignature, KeylessPublicKey) {