Skip to content

Commit

Permalink
Merge branch 'main' into 09-09-add_perimission_checks_to_object
Browse files Browse the repository at this point in the history
  • Loading branch information
runtian-zhou authored Jan 15, 2025
2 parents 0154651 + b1ffc17 commit eb4b8f1
Show file tree
Hide file tree
Showing 11 changed files with 392 additions and 4 deletions.
2 changes: 2 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 keyless/pepper/example-client-rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ fn get_jwt_or_path() -> String {

#[tokio::main]
async fn main() {
// if let Ok(x) = std::env::var("V0_VERIFY") {
// test_v0_verify();
// }
println!();
println!("Starting an interaction with aptos-oidb-pepper-service.");
let url = get_pepper_service_url();
Expand Down
29 changes: 29 additions & 0 deletions keyless/pepper/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,32 @@ Follow the instruction to manually complete a session with the pepper service.
Sorry for the missing examples in other programming languages.
For now please read through `example-client-rust/src/main.rs` implementation and output:
that is what your frontend needs to do.

## Extra: manual testing for endpoint `v0/verify`.
NOTE: API `v0/verify` now depends on on-chain resources
`0x1::keyless_account::Groth16VerificationKey` and `0x1::keyless_account::Configuration`,
which need to be fetched via HTTP requests.

In terminal 0, run the pepper service.
```bash
export VUF_KEY_SEED_HEX=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
export ONCHAIN_GROTH16_VK_URL=http://localhost:4444/groth16_vk.json
export ONCHAIN_KEYLESS_CONFIG_URL=http://localhost:4444/keyless_config.json
cargo run -p aptos-keyless-pepper-service
```

In terminal 1, peek the cached resources, they should currently give 404.
```
curl -v http://localhost:8000/cached/groth16-vk
curl -v http://localhost:8000/cached/keyless-config
```

In terminal 2, mock the full node with a naive HTTP server.
```bash
cd keyless/pepper/service/resources
python3 -m http.server 4444
```

Wait for 10 secs then go back to terminal 1 to retry the curl cmds. The cached data should be available.

TODO: how to generate sample request and interact with `v0/verify` endpoint?
2 changes: 2 additions & 0 deletions keyless/pepper/service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ aptos-logger = { workspace = true }
aptos-metrics-core = { workspace = true }
aptos-types = { workspace = true }
ark-bls12-381 = { workspace = true }
ark-bn254 = { workspace = true }
ark-ec = { workspace = true }
ark-ff = { workspace = true }
ark-groth16 = { workspace = true }
ark-serialize = { workspace = true }
bcs = { workspace = true }
dashmap = { workspace = true }
Expand Down
13 changes: 13 additions & 0 deletions keyless/pepper/service/resources/groth16_vk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"type": "0x1::keyless_account::Groth16VerificationKey",
"data": {
"alpha_g1": "0xe2f26dbea299f5223b646cb1fb33eadb059d9407559d7441dfd902e3a79a4d2d",
"beta_g2": "0xabb73dc17fbc13021e2471e0c08bd67d8401f52b73d6d07483794cad4778180e0c06f33bbc4c79a9cadef253a68084d382f17788f885c9afd176f7cb2f036789",
"delta_g2": "0xb106619932d0ef372c46909a2492e246d5de739aa140e27f2c71c0470662f125219049cfe15e4d140d7e4bb911284aad1cad19880efb86f2d9dd4b1bb344ef8f",
"gamma_abc_g1": [
"0x6123b6fea40de2a7e3595f9c35210da8a45a7e8c2f7da9eb4548e9210cfea81a",
"0x32a9b8347c512483812ee922dc75952842f8f3083edb6fe8d5c3c07e1340b683"
],
"gamma_g2": "0xedf692d95cbdde46ddda5ef7d422436779445c5e66006a42761e1f12efde0018c212f3aeb785e49712e7a9353349aaf1255dfb31b7bf60723a480d9293938e19"
}
}
17 changes: 17 additions & 0 deletions keyless/pepper/service/resources/keyless_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"type": "0x1::keyless_account::Configuration",
"data": {
"max_commited_epk_bytes": 93,
"max_exp_horizon_secs": "10000000",
"max_extra_field_bytes": 350,
"max_iss_val_bytes": 120,
"max_jwt_header_b64_bytes": 300,
"max_signatures_per_txn": 3,
"override_aud_vals": [],
"training_wheels_pubkey": {
"vec": [
"0x5cd926a700e3997a3b319bfd003127ec7278eff14973c8cdcfbea54f3ea3669f"
]
}
}
}
120 changes: 120 additions & 0 deletions keyless/pepper/service/src/groth16_vk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright (c) Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

use crate::watcher::{unhexlify_api_bytes, ExternalResource};
use anyhow::{anyhow, Result};
use aptos_infallible::RwLock;
use ark_bn254::{Bn254, G1Affine, G2Affine};
use ark_groth16::{PreparedVerifyingKey, VerifyingKey};
use ark_serialize::CanonicalDeserialize;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct VKeyData {
pub alpha_g1: String,
pub beta_g2: String,
pub delta_g2: String,
pub gamma_abc_g1: Vec<String>,
pub gamma_g2: String,
}

/// On-chain representation of a VK.
///
/// https://fullnode.testnet.aptoslabs.com/v1/accounts/0x1/resource/0x1::keyless_account::Groth16VerificationKey
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct OnChainGroth16VerificationKey {
/// Some type info returned by node API.
pub r#type: String,
pub data: VKeyData,
}

impl OnChainGroth16VerificationKey {
pub fn to_ark_pvk(&self) -> Result<PreparedVerifyingKey<Bn254>> {
let mut gamma_abc_g1 = Vec::with_capacity(self.data.gamma_abc_g1.len());
for (idx, onchain_ele) in self.data.gamma_abc_g1.iter().enumerate() {
let ark_ele = g1_from_api_repr(onchain_ele).map_err(|e| {
anyhow!("to_ark_pvk() failed with gamma_abc_g1[{idx}] convert err: {e}")
})?;
gamma_abc_g1.push(ark_ele);
}
let ark_vk = VerifyingKey {
alpha_g1: g1_from_api_repr(&self.data.alpha_g1)
.map_err(|e| anyhow!("to_ark_pvk() failed with alpha_g1 convert err: {e}"))?,
beta_g2: g2_from_api_repr(&self.data.beta_g2)
.map_err(|e| anyhow!("to_ark_pvk() failed with beta_g2 convert err: {e}"))?,
gamma_g2: g2_from_api_repr(&self.data.gamma_g2)
.map_err(|e| anyhow!("to_ark_pvk() failed with gamma_g2 convert err: {e}"))?,
delta_g2: g2_from_api_repr(&self.data.delta_g2)
.map_err(|e| anyhow!("to_ark_pvk() failed with delta_g2 convert err: {e}"))?,
gamma_abc_g1,
};
Ok(PreparedVerifyingKey::from(ark_vk))
}
}

/// This variable holds the cached on-chain VK. A refresh loop exists to update it periodically.
pub static ONCHAIN_GROTH16_VK: Lazy<Arc<RwLock<Option<OnChainGroth16VerificationKey>>>> =
Lazy::new(|| Arc::new(RwLock::new(None)));

fn g1_from_api_repr(api_repr: &str) -> Result<G1Affine> {
let bytes = unhexlify_api_bytes(api_repr)
.map_err(|e| anyhow!("g1_from_api_repr() failed with unhex err: {e}"))?;
let ret = G1Affine::deserialize_compressed(bytes.as_slice())
.map_err(|e| anyhow!("g1_from_api_repr() failed with g1 deser err: {e}"))?;
Ok(ret)
}

fn g2_from_api_repr(api_repr: &str) -> Result<G2Affine> {
let bytes = unhexlify_api_bytes(api_repr)
.map_err(|e| anyhow!("g2_from_api_repr() failed with unhex err: {e}"))?;
let ret = G2Affine::deserialize_compressed(bytes.as_slice())
.map_err(|e| anyhow!("g2_from_api_repr() failed with g2 deser err: {e}"))?;
Ok(ret)
}

impl ExternalResource for OnChainGroth16VerificationKey {
fn resource_name() -> String {
"OnChainGroth16VerificationKey".to_string()
}
}

#[cfg(test)]
mod tests {
use crate::groth16_vk::{OnChainGroth16VerificationKey, VKeyData};
use ark_bn254::{Bn254, Fr, G1Affine, G2Affine};
use ark_groth16::Groth16;
use ark_serialize::CanonicalDeserialize;

#[test]
fn test_to_ark_pvk() {
let api_repr = OnChainGroth16VerificationKey {
r#type: "0x1::keyless_account::Groth16VerificationKey".to_string(),
data: VKeyData {
alpha_g1: "0xe39cb24154872dbdbbdbc8056c6eb3e6cab3ad82f80ded72ed4c9301c5b3da15".to_string(),
beta_g2: "0x9a732e38644f89ad2c7bd629b84d6b81f2e83ca4b3cddfd99c0254e49332861e2fcec4f74545abdd42c8857ff8df6d3f6b3670f930d1d5ba961655ea38ded315".to_string(),
delta_g2: "0x04c7b3a2734731369a281424c2bd7af229b92496527fd0a01bfe4a5c01e0a92f256921817b6d6cf040ccd483d81738ac88571b57009f182946e8a88cced03a01".to_string(),
gamma_abc_g1: vec![
"0x2f4f4bc4acbea0c3bae9e676fb59537e2e46994d5896e286e6fcccc7e14b1b2d".to_string(),
"0x979308443fbac05f6d22a16525c26246e965a9be68e163154f44b20d6b2ddf18".to_string(),
],
gamma_g2: "0xedf692d95cbdde46ddda5ef7d422436779445c5e66006a42761e1f12efde0018c212f3aeb785e49712e7a9353349aaf1255dfb31b7bf60723a480d9293938e19".to_string(),
},
};

let ark_pvk = api_repr.to_ark_pvk().unwrap();
let proof = ark_groth16::Proof {
a: G1Affine::deserialize_compressed(hex::decode("fd6ae6c19f7eb7362e420e3e359f3d85c0030b779a815627b805276e45018817").unwrap().as_slice()).unwrap(),
b: G2Affine::deserialize_compressed(hex::decode("c4af5f1e793653d80009c19637b326f78a46d9d031e9e36a6f87e296ba04e31322af2d8d5c3d4129b6b2f3d222f741ce17145ab62f1b238f3f9e8c88831da297").unwrap().as_slice()).unwrap(),
c: G1Affine::deserialize_compressed(hex::decode("a856a923cde90ecb6f8955c9627ede579e67b7082431b965d011fca578892096").unwrap().as_slice()).unwrap(),
};
let public_inputs = vec![Fr::deserialize_compressed(
hex::decode("08aba90163b227d54013b7d1a892b20edf149a23acf810acf78e8baf8e770d11")
.unwrap()
.as_slice(),
)
.unwrap()];
assert!(Groth16::<Bn254>::verify_proof(&ark_pvk, &proof, &public_inputs).unwrap());
}
}
69 changes: 69 additions & 0 deletions keyless/pepper/service/src/keyless_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

use crate::watcher::{unhexlify_api_bytes, ExternalResource};
use anyhow::{anyhow, Result};
use aptos_infallible::RwLock;
use aptos_types::keyless::Configuration;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct TrainingWheelsPubKey {
vec: Vec<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct OnChainKeylessConfiguration {
/// Some type info returned by node API.
pub r#type: String,
pub data: ConfigData,
}

impl OnChainKeylessConfiguration {
pub fn to_rust_repr(&self) -> Result<aptos_types::keyless::Configuration> {
let training_wheels_pubkey = self
.data
.training_wheels_pubkey
.vec
.first()
.map(|v| unhexlify_api_bytes(v.as_str()))
.transpose()
.map_err(|e| anyhow!("to_rust_repr() failed with unhexlify err: {e}"))?;
let ret = Configuration {
override_aud_vals: self.data.override_aud_vals.clone(),
max_signatures_per_txn: self.data.max_signatures_per_txn,
max_exp_horizon_secs: self.data.max_exp_horizon_secs.parse().map_err(|e| {
anyhow!("to_rust_repr() failed at max_exp_horizon_secs convert: {e}")
})?,
training_wheels_pubkey,
max_commited_epk_bytes: self.data.max_commited_epk_bytes,
max_iss_val_bytes: self.data.max_iss_val_bytes,
max_extra_field_bytes: self.data.max_extra_field_bytes,
max_jwt_header_b64_bytes: self.data.max_jwt_header_b64_bytes,
};
Ok(ret)
}
}

impl ExternalResource for OnChainKeylessConfiguration {
fn resource_name() -> String {
"OnChainKeylessConfiguration".to_string()
}
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct ConfigData {
pub max_commited_epk_bytes: u16,
pub max_exp_horizon_secs: String,
pub max_extra_field_bytes: u16,
pub max_iss_val_bytes: u16,
pub max_jwt_header_b64_bytes: u32,
pub max_signatures_per_txn: u16,
pub override_aud_vals: Vec<String>,
pub training_wheels_pubkey: TrainingWheelsPubKey,
}

pub static ONCHAIN_KEYLESS_CONFIG: Lazy<Arc<RwLock<Option<OnChainKeylessConfiguration>>>> =
Lazy::new(|| Arc::new(RwLock::new(None)));
26 changes: 22 additions & 4 deletions keyless/pepper/service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
use crate::{
account_db::{init_account_db, ACCOUNT_RECOVERY_DB},
account_managers::ACCOUNT_MANAGERS,
groth16_vk::ONCHAIN_GROTH16_VK,
keyless_config::ONCHAIN_KEYLESS_CONFIG,
vuf_keys::VUF_SK,
ProcessingFailure::{BadRequest, InternalError},
};
Expand All @@ -30,7 +32,7 @@ use aptos_types::{
account_address::AccountAddress,
keyless::{
get_public_inputs_hash, Configuration, EphemeralCertificate, Groth16ProofAndStatement,
IdCommitment, KeylessPublicKey, KeylessSignature, OpenIdSig, DEVNET_VERIFICATION_KEY, ZKP,
IdCommitment, KeylessPublicKey, KeylessSignature, OpenIdSig, ZKP,
},
transaction::authenticator::{
AnyPublicKey, AnySignature, AuthenticationKey, EphemeralPublicKey,
Expand All @@ -47,9 +49,12 @@ use uuid::Uuid;
pub mod about;
pub mod account_db;
pub mod account_managers;
pub mod groth16_vk;
pub mod jwk;
pub mod keyless_config;
pub mod metrics;
pub mod vuf_keys;
pub mod watcher;

pub type Issuer = String;
pub type KeyID = String;
Expand Down Expand Up @@ -182,7 +187,13 @@ impl HandlerTrait<VerifyRequest, VerifyResponse> for V0VerifyHandler {
.map_err(|e| BadRequest(format!("JWT header decoding error: {e}")))?;
let jwk = jwk::cached_decoding_key_as_rsa(iss_val, &jwt_header.kid)
.map_err(|e| BadRequest(format!("JWK not found: {e}")))?;
let config = Configuration::new_for_devnet();
let config_api_repr =
{ ONCHAIN_KEYLESS_CONFIG.read().as_ref().cloned() }.ok_or_else(|| {
InternalError("API keyless config not cached locally.".to_string())
})?;
let config = config_api_repr
.to_rust_repr()
.map_err(|e| InternalError(format!("Could not parse API keyless config: {e}")))?;
let training_wheels_pk = match &config.training_wheels_pubkey {
None => None,
// This takes ~4.4 microseconds, so we are not too concerned about speed here.
Expand Down Expand Up @@ -247,8 +258,15 @@ impl HandlerTrait<VerifyRequest, VerifyResponse> for V0VerifyHandler {
}
}

let result = zksig
.verify_groth16_proof(public_inputs_hash, &DEVNET_VERIFICATION_KEY);
let onchain_groth16_vk =
{ ONCHAIN_GROTH16_VK.read().as_ref().cloned() }.ok_or_else(
|| InternalError("No Groth16 VK cached locally.".to_string()),
)?;
let ark_groth16_pvk = onchain_groth16_vk.to_ark_pvk().map_err(|e| {
InternalError(format!("Onchain-to-ark convertion err: {e}"))
})?;
let result =
zksig.verify_groth16_proof(public_inputs_hash, &ark_groth16_pvk);
result.map_err(|_| {
// println!("[aptos-vm][groth16] ZKP verification failed");
// println!("[aptos-vm][groth16] PIH: {}", public_inputs_hash);
Expand Down
Loading

0 comments on commit eb4b8f1

Please sign in to comment.