Skip to content

Commit

Permalink
Cherry-pick three Aptos Keyless changes (#12367)
Browse files Browse the repository at this point in the history
* Add custom Serialize/Deserialize impl for EphemeralPublicKey, Pepper, Groth16ZkpAndStatement, G1Bytes, and G2Bytes, which output hex when used to serialize json (#12295)

* e2e Move tests for keyless + feature gating (#12296)

* add support for passkey-based EPKs & merge non-malleability signature into ephemeral signature (#12333)

Co-authored-by: Rex Fernando <[email protected]>
  • Loading branch information
alinush and rex1fernando authored Mar 4, 2024
1 parent b24f6cd commit 785188c
Show file tree
Hide file tree
Showing 44 changed files with 1,431 additions and 364 deletions.
4 changes: 3 additions & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ group = "0.13"
guppy = "0.17.0"
handlebars = "4.2.2"
heck = "0.4.1"
hex = "0.4.3"
hex = { version = "0.4.3", features = ["serde"] }
hkdf = "0.10.0"
hostname = "0.3.1"
http = "0.2.9"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ pub enum FeatureFlag {
RefundableBytes,
ObjectCodeDeployment,
MaxObjectNestingCheck,
KeylessAccountsWithPasskeys,
}

fn generate_features_blob(writer: &CodeWriter, data: &[u64]) {
Expand Down Expand Up @@ -266,6 +267,9 @@ impl From<FeatureFlag> for AptosFeatureFlag {
FeatureFlag::RefundableBytes => AptosFeatureFlag::REFUNDABLE_BYTES,
FeatureFlag::ObjectCodeDeployment => AptosFeatureFlag::OBJECT_CODE_DEPLOYMENT,
FeatureFlag::MaxObjectNestingCheck => AptosFeatureFlag::MAX_OBJECT_NESTING_CHECK,
FeatureFlag::KeylessAccountsWithPasskeys => {
AptosFeatureFlag::KEYLESS_ACCOUNTS_WITH_PASSKEYS
},
}
}
}
Expand Down Expand Up @@ -351,6 +355,9 @@ impl From<AptosFeatureFlag> for FeatureFlag {
AptosFeatureFlag::REFUNDABLE_BYTES => FeatureFlag::RefundableBytes,
AptosFeatureFlag::OBJECT_CODE_DEPLOYMENT => FeatureFlag::ObjectCodeDeployment,
AptosFeatureFlag::MAX_OBJECT_NESTING_CHECK => FeatureFlag::MaxObjectNestingCheck,
AptosFeatureFlag::KEYLESS_ACCOUNTS_WITH_PASSKEYS => {
FeatureFlag::KeylessAccountsWithPasskeys
},
}
}
}
Expand Down
11 changes: 10 additions & 1 deletion aptos-move/aptos-vm/src/aptos_vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1325,7 +1325,16 @@ impl AptosVM {

let authenticators = aptos_types::keyless::get_authenticators(transaction)
.map_err(|_| VMStatus::error(StatusCode::INVALID_SIGNATURE, None))?;
keyless_validation::validate_authenticators(&authenticators, &self.features, resolver)?;

// If there are keyless TXN authenticators, validate them all.
if !authenticators.is_empty() {
// Feature-gating keyless TXNs: if they are *not* enabled, return `FEATURE_UNDER_GATING`,
// which will discard the TXN from being put on-chain.
if !self.features.is_keyless_enabled() {
return Err(VMStatus::error(StatusCode::FEATURE_UNDER_GATING, None));
}
keyless_validation::validate_authenticators(&authenticators, &self.features, resolver)?;
}

// The prologue MUST be run AFTER any validation. Otherwise you may run prologue and hit
// SEQUENCE_NUMBER_TOO_NEW if there is more than one transaction from the same sender and
Expand Down
77 changes: 43 additions & 34 deletions aptos-move/aptos-vm/src/keyless_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ use aptos_types::{
invalid_signature,
jwks::{jwk::JWK, PatchedJWKs},
keyless::{
get_public_inputs_hash, Configuration, Groth16VerificationKey, KeylessPublicKey,
KeylessSignature, ZkpOrOpenIdSig,
get_public_inputs_hash, Configuration, EphemeralCertificate, Groth16VerificationKey,
KeylessPublicKey, KeylessSignature, ZKP,
},
on_chain_config::{CurrentTimeMicroseconds, Features, OnChainConfig},
transaction::authenticator::EphemeralPublicKey,
transaction::authenticator::{EphemeralPublicKey, EphemeralSignature},
vm_status::{StatusCode, VMStatus},
};
use move_binary_format::errors::Location;
Expand Down Expand Up @@ -103,22 +103,22 @@ pub(crate) fn validate_authenticators(
features: &Features,
resolver: &impl AptosMoveResolver,
) -> Result<(), VMStatus> {
// Feature gating
for (_, sig) in authenticators {
if !features.is_keyless_enabled() && matches!(sig.sig, ZkpOrOpenIdSig::Groth16Zkp { .. }) {
// Feature-gating for keyless-but-zkless TXNs: If keyless TXNs *are* enabled, and (1) this
// is a ZKless transaction but (2) ZKless TXNs are not yet enabled, discard the TXN from
// being put on-chain.
if matches!(sig.cert, EphemeralCertificate::OpenIdSig { .. })
&& !features.is_keyless_zkless_enabled()
{
return Err(VMStatus::error(StatusCode::FEATURE_UNDER_GATING, None));
}
if (!features.is_keyless_enabled() || !features.is_keyless_zkless_enabled())
&& matches!(sig.sig, ZkpOrOpenIdSig::OpenIdSig { .. })
if matches!(sig.ephemeral_signature, EphemeralSignature::WebAuthn { .. })
&& !features.is_keyless_with_passkeys_enabled()
{
return Err(VMStatus::error(StatusCode::FEATURE_UNDER_GATING, None));
}
}

if authenticators.is_empty() {
return Ok(());
}

let config = &get_configs_onchain(resolver)?;
if authenticators.len() > config.max_signatures_per_txn as usize {
return Err(invalid_signature!("Too many keyless authenticators"));
Expand Down Expand Up @@ -148,41 +148,49 @@ pub(crate) fn validate_authenticators(
for (pk, sig) in authenticators {
let jwk = get_jwk_for_authenticator(&patched_jwks, pk, sig)?;

match &sig.sig {
ZkpOrOpenIdSig::Groth16Zkp(proof) => match jwk {
match &sig.cert {
EphemeralCertificate::ZeroKnowledgeSig(zksig) => match jwk {
JWK::RSA(rsa_jwk) => {
if proof.exp_horizon_secs > config.max_exp_horizon_secs {
if zksig.exp_horizon_secs > config.max_exp_horizon_secs {
return Err(invalid_signature!("The expiration horizon is too long"));
}

// If an `aud` override was set for account recovery purposes, check that it is
// in the allow-list on-chain.
if proof.override_aud_val.is_some() {
config.is_allowed_override_aud(proof.override_aud_val.as_ref().unwrap())?;
if zksig.override_aud_val.is_some() {
config.is_allowed_override_aud(zksig.override_aud_val.as_ref().unwrap())?;
}

let public_inputs_hash = get_public_inputs_hash(sig, pk, &rsa_jwk, config)
.map_err(|_| invalid_signature!("Could not compute public inputs hash"))?;

// The training wheels signature is only checked if a training wheels PK is set on chain
if training_wheels_pk.is_some() {
proof
.verify_training_wheels_sig(
training_wheels_pk.as_ref().unwrap(),
&public_inputs_hash,
)
.map_err(|_| {
invalid_signature!("Could not verify training wheels signature")
})?;
match zksig.proof {
ZKP::Groth16(_) => {
let public_inputs_hash =
get_public_inputs_hash(sig, pk, &rsa_jwk, config).map_err(
|_| invalid_signature!("Could not compute public inputs hash"),
)?;

// The training wheels signature is only checked if a training wheels PK is set on chain
if training_wheels_pk.is_some() {
zksig
.verify_training_wheels_sig(
training_wheels_pk.as_ref().unwrap(),
&public_inputs_hash,
)
.map_err(|_| {
invalid_signature!(
"Could not verify training wheels signature"
)
})?;
}

zksig
.verify_groth16_proof(public_inputs_hash, pvk)
.map_err(|_| invalid_signature!("Proof verification failed"))?;
},
}

proof
.verify_proof(public_inputs_hash, pvk)
.map_err(|_| invalid_signature!("Proof verification failed"))?;
},
JWK::Unsupported(_) => return Err(invalid_signature!("JWK is not supported")),
},
ZkpOrOpenIdSig::OpenIdSig(openid_sig) => {
EphemeralCertificate::OpenIdSig(openid_sig) => {
match jwk {
JWK::RSA(rsa_jwk) => {
openid_sig
Expand Down Expand Up @@ -213,5 +221,6 @@ pub(crate) fn validate_authenticators(
},
}
}

Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 0d06e418a86d5a07e5ac65d22e86d78a819989e1e0815db4750efcc12dc9d905 # shrinks to block_split = Whole
cc c5ad38cf3be8dea3f90d1504b070a030228a03829c63964d2855e8946d17ffe6 # shrinks to block_split = Whole
2 changes: 2 additions & 0 deletions aptos-move/e2e-move-tests/src/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,7 @@ impl MoveHarness {
}

/// Reads the resource data `T`.
/// WARNING: Does not work with resource groups (because set_resource does not work?).
pub fn read_resource<T: DeserializeOwned>(
&self,
addr: &AccountAddress,
Expand Down Expand Up @@ -714,6 +715,7 @@ impl MoveHarness {
}

/// Write the resource data `T`.
/// WARNING: Does not work with resource groups.
pub fn set_resource<T: Serialize>(
&mut self,
addr: AccountAddress,
Expand Down
Loading

0 comments on commit 785188c

Please sign in to comment.