Skip to content

Commit

Permalink
Add keyless to rust sdk and txn-emitter (#12976)
Browse files Browse the repository at this point in the history
* Add smoke test using keyless rust sdk

* add keyless account generator

* reorder import

* fix

* fix build

* fmt

* lint

* update txn emitter

* update

* update

* fix panic

* update proofs

* xclippy

* group the account type args

* reorder arg

* sort deps

* fix test
  • Loading branch information
heliuchuan authored Oct 1, 2024
1 parent a7ef111 commit d10449d
Show file tree
Hide file tree
Showing 15 changed files with 1,181 additions and 177 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
!crates/aptos/src/move_tool/*.bpl
!crates/aptos/src/node/local_testnet/hasura_metadata.json
!crates/aptos-faucet/doc/
!crates/transaction-emitter-lib/src/emitter/test_proofs_for_localnet_txn_emitter.txt
!api/doc/
!crates/indexer/migrations/**/*.sql
!ecosystem/indexer-grpc/indexer-grpc-parser/migrations/**/*.sql
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/transaction-emitter-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ aptos-sdk = { workspace = true }
aptos-transaction-generator-lib = { workspace = true }
aptos-types = { workspace = true }
async-trait = { workspace = true }
base64 = { workspace = true }
clap = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
itertools = { workspace = true }
once_cell = { workspace = true }
rand = { workspace = true }
rand_core = { workspace = true }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }
url = { workspace = true }
30 changes: 27 additions & 3 deletions crates/transaction-emitter-lib/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,6 @@ pub struct EmitArgs {
)]
pub transaction_type: Vec<TransactionTypeArg>,

#[clap(long, value_enum, default_value = "local", ignore_case = true)]
pub account_type: AccountType,

/// Number of copies of the modules that will be published,
/// under separate accounts, creating independent contracts,
/// removing contention.
Expand Down Expand Up @@ -218,6 +215,27 @@ pub struct EmitArgs {

#[clap(long)]
pub coins_per_account_override: Option<u64>,

#[clap(flatten)]
pub account_type_args: AccountTypeArgs,
}

#[derive(Clone, Debug, Default, Deserialize, Parser, Serialize)]
pub struct AccountTypeArgs {
#[clap(long, value_enum, default_value = "local", ignore_case = true)]
pub account_type: AccountType,

#[clap(long, value_parser = ConfigKey::<Ed25519PrivateKey>::from_encoded_string, requires = "proof_file_path", requires = "epk_expiry_date_secs", requires = "keyless_jwt")]
pub keyless_ephem_secret_key: Option<ConfigKey<Ed25519PrivateKey>>,

#[clap(long)]
pub proof_file_path: Option<String>,

#[clap(long)]
pub epk_expiry_date_secs: Option<u64>,

#[clap(long)]
pub keyless_jwt: Option<String>,
}

#[derive(Clone, Debug, Default, Deserialize, Parser, Serialize)]
Expand All @@ -234,6 +252,12 @@ pub struct CreateAccountsArgs {
/// used and printed.
#[clap(long)]
pub account_minter_seed: Option<String>,

#[clap(long)]
pub keyless_jwt: Option<String>,

#[clap(long)]
pub proof_file_path: Option<String>,
}

fn parse_target(target: &str) -> Result<Url> {
Expand Down
29 changes: 11 additions & 18 deletions crates/transaction-emitter-lib/src/emitter/account_minter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,14 @@ use super::{
local_account_generator::LocalAccountGenerator, parse_seed,
transaction_executor::RestApiReliableTransactionSubmitter,
};
use crate::EmitJobRequest;
use crate::{emitter::create_private_key_account_generator, EmitJobRequest};
use anyhow::{anyhow, bail, format_err, Context, Result};
use aptos_config::config::DEFAULT_MAX_SUBMIT_TRANSACTION_BATCH_SIZE;
use aptos_crypto::{
ed25519::{Ed25519PrivateKey, Ed25519PublicKey},
encoding_type::EncodingType,
};
use aptos_crypto::{ed25519::Ed25519PrivateKey, encoding_type::EncodingType};
use aptos_logger::{error, info};
use aptos_sdk::{
transaction_builder::{aptos_stdlib, TransactionFactory},
types::{
transaction::{authenticator::AuthenticationKey, SignedTransaction},
AccountKey, LocalAccount,
},
types::{transaction::SignedTransaction, AccountKey, LocalAccount},
};
use aptos_transaction_generator_lib::{
CounterState, ReliableTransactionSubmitter, RootAccountHandle,
Expand Down Expand Up @@ -394,7 +388,7 @@ impl<'t> AccountMinter<'t> {
create_and_fund_account_request(
source_account.clone(),
coins_per_seed_account,
account.public_key(),
account.address(),
txn_factory,
)
})
Expand Down Expand Up @@ -449,7 +443,7 @@ impl<'t> AccountMinter<'t> {
let txn = create_and_fund_account_request(
root_account.clone(),
coins_for_source,
new_source_account.public_key(),
new_source_account.address(),
&self.txn_factory,
);
if let Err(e) = txn_executor.execute_transactions(&[txn]).await {
Expand Down Expand Up @@ -506,7 +500,7 @@ async fn create_and_fund_new_accounts(
create_and_fund_account_request(
source_account.clone(),
coins_per_new_account,
account.public_key(),
account.address(),
txn_factory,
)
})
Expand All @@ -523,13 +517,12 @@ async fn create_and_fund_new_accounts(
pub fn create_and_fund_account_request(
creation_account: Arc<LocalAccount>,
amount: u64,
pubkey: &Ed25519PublicKey,
address: AccountAddress,
txn_factory: &TransactionFactory,
) -> SignedTransaction {
let auth_key = AuthenticationKey::ed25519(pubkey);
creation_account.sign_with_transaction_builder(txn_factory.payload(
aptos_stdlib::aptos_account_transfer(auth_key.account_address(), amount),
))
creation_account.sign_with_transaction_builder(
txn_factory.payload(aptos_stdlib::aptos_account_transfer(address, amount)),
)
}

const CREATION_PARALLELISM: usize = 500;
Expand Down Expand Up @@ -635,7 +628,7 @@ pub async fn bulk_create_accounts(
let mut rng = StdRng::from_seed(seed);

let num_seed_accounts = (num_accounts / 50).clamp(1, (num_accounts as f32).sqrt() as usize + 1);
let seed_accounts = account_generator
let seed_accounts = create_private_key_account_generator()
.gen_local_accounts(txn_executor, num_seed_accounts, &mut rng)
.await?;

Expand Down
136 changes: 127 additions & 9 deletions crates/transaction-emitter-lib/src/emitter/local_account_generator.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
use anyhow::bail;
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0
use aptos_sdk::types::{AccountKey, LocalAccount};
use aptos_transaction_generator_lib::{AccountType, ReliableTransactionSubmitter};
use aptos_crypto::ed25519::Ed25519PrivateKey;
use aptos_sdk::types::{AccountKey, EphemeralKeyPair, KeylessAccount, LocalAccount};
use aptos_transaction_generator_lib::ReliableTransactionSubmitter;
use aptos_types::keyless::{Claims, OpenIdSig, Pepper, ZeroKnowledgeSig};
use async_trait::async_trait;
use futures::future::try_join_all;
use rand::rngs::StdRng;
use std::{
fs::File,
io::{self, BufRead},
};

#[async_trait]
pub trait LocalAccountGenerator: Send + Sync {
Expand All @@ -16,13 +23,31 @@ pub trait LocalAccountGenerator: Send + Sync {
) -> anyhow::Result<Vec<LocalAccount>>;
}

pub fn create_account_generator(account_type: AccountType) -> Box<dyn LocalAccountGenerator> {
match account_type {
AccountType::Local => Box::new(PrivateKeyAccountGenerator),
_ => {
unimplemented!("Account type {:?} is not supported", account_type)
},
}
pub fn create_private_key_account_generator() -> Box<dyn LocalAccountGenerator> {
Box::new(PrivateKeyAccountGenerator)
}

pub fn create_keyless_account_generator(
ephemeral_secret_key: Ed25519PrivateKey,
epk_expiry_date_secs: u64,
jwt: &str,
proof_file_path: Option<&str>,
) -> anyhow::Result<Box<dyn LocalAccountGenerator>> {
let parts: Vec<&str> = jwt.split('.').collect();
let header_bytes = base64::decode(parts[0]).unwrap();
let jwt_header_json = String::from_utf8(header_bytes).unwrap();
let jwt_payload_json = base64::decode_config(parts[1], base64::URL_SAFE).unwrap();
let claims: Claims = serde_json::from_slice(&jwt_payload_json)?;
Ok(Box::new(KeylessAccountGenerator {
proof_file_path: proof_file_path.map(|s| s.to_string()),
ephemeral_secret_key,
epk_expiry_date_secs,
iss: claims.oidc_claims.iss,
aud: claims.oidc_claims.aud,
uid_key: "sub".to_owned(),
uid_val: claims.oidc_claims.sub,
jwt_header_json,
}))
}

pub struct PrivateKeyAccountGenerator;
Expand Down Expand Up @@ -64,3 +89,96 @@ impl LocalAccountGenerator for PrivateKeyAccountGenerator {
Ok(accounts)
}
}

pub struct KeylessAccountGenerator {
proof_file_path: Option<String>,
ephemeral_secret_key: Ed25519PrivateKey,
epk_expiry_date_secs: u64,
iss: String,
aud: String,
uid_key: String,
uid_val: String,
jwt_header_json: String,
}

#[async_trait]
impl LocalAccountGenerator for KeylessAccountGenerator {
async fn gen_local_accounts(
&self,
txn_executor: &dyn ReliableTransactionSubmitter,
num_accounts: usize,
_rng: &mut StdRng,
) -> anyhow::Result<Vec<LocalAccount>> {
let mut keyless_accounts = vec![];
let mut addresses = vec![];
let mut i = 0;
let lines: Box<dyn Iterator<Item = Result<String, io::Error>>> = match &self.proof_file_path
{
None => {
let proofs = include_str!("test_proofs_for_localnet_txn_emitter.txt");
let lines = proofs.lines().map(|line| Ok(line.to_string()));
Box::new(lines)
},
Some(path) => {
let file = File::open(path).unwrap();
let reader = io::BufReader::new(file);
Box::new(reader.lines())
},
};

for line in lines {
let serialized_proof = line?;
let zk_sig_bytes = hex::decode(serialized_proof)?;
let zk_sig = ZeroKnowledgeSig::try_from(zk_sig_bytes.as_slice())?;

// Cloning is disabled outside #[cfg(test)]
let serialized: &[u8] = &(self.ephemeral_secret_key.to_bytes());
let esk = Ed25519PrivateKey::try_from(serialized)?;

let keyless_account = KeylessAccount::new(
&self.iss,
&self.aud,
&self.uid_key,
&self.uid_val,
&self.jwt_header_json,
EphemeralKeyPair::new(
esk,
self.epk_expiry_date_secs,
vec![0; OpenIdSig::EPK_BLINDER_NUM_BYTES],
)?,
Pepper::from_number(i.try_into().unwrap()),
zk_sig,
)?;
addresses.push(keyless_account.authentication_key().account_address());
keyless_accounts.push(keyless_account);
i += 1;

if i == num_accounts {
break;
}
}

if i != num_accounts {
bail!("not enough proofs - {num_accounts} num_accounts, {i} found")
}

let result_futures = addresses
.iter()
.map(|address| txn_executor.query_sequence_number(*address))
.collect::<Vec<_>>();
let seq_nums: Vec<_> = try_join_all(result_futures).await?.into_iter().collect();

let accounts = keyless_accounts
.into_iter()
.zip(seq_nums)
.map(|(keyless_account, sequence_number)| {
LocalAccount::new_keyless(
keyless_account.authentication_key().account_address(),
keyless_account,
sequence_number,
)
})
.collect();
Ok(accounts)
}
}
Loading

0 comments on commit d10449d

Please sign in to comment.