Skip to content

Commit

Permalink
add support for passkey-based EPKs & merge non-malleability signature…
Browse files Browse the repository at this point in the history
… into ephemeral signature (#12333)

* added support for passkey-based ephemeral signatures

* merge ephemeral signature and non-malleability signature into one
  • Loading branch information
alinush authored Mar 4, 2024
1 parent 3b5f407 commit a7a474f
Show file tree
Hide file tree
Showing 28 changed files with 563 additions and 306 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

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

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
67 changes: 40 additions & 27 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 @@ -107,11 +107,16 @@ pub(crate) fn validate_authenticators(
// 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.sig, ZkpOrOpenIdSig::OpenIdSig { .. })
if matches!(sig.cert, EphemeralCertificate::OpenIdSig { .. })
&& !features.is_keyless_zkless_enabled()
{
return Err(VMStatus::error(StatusCode::FEATURE_UNDER_GATING, None));
}
if matches!(sig.ephemeral_signature, EphemeralSignature::WebAuthn { .. })
&& !features.is_keyless_with_passkeys_enabled()
{
return Err(VMStatus::error(StatusCode::FEATURE_UNDER_GATING, None));
}
}

let config = &get_configs_onchain(resolver)?;
Expand Down Expand Up @@ -143,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
18 changes: 12 additions & 6 deletions aptos-move/e2e-move-tests/src/tests/keyless_feature_gating.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use aptos_types::{
get_sample_esk, get_sample_groth16_sig_and_pk, get_sample_iss, get_sample_jwk,
get_sample_openid_sig_and_pk,
},
Configuration, KeylessPublicKey, KeylessSignature, ZkpOrOpenIdSig,
Configuration, EphemeralCertificate, KeylessPublicKey, KeylessSignature,
TransactionAndProof,
},
on_chain_config::FeatureFlag,
transaction::{
Expand Down Expand Up @@ -129,17 +130,22 @@ fn get_keyless_txn(

println!("RawTxn sender: {:?}", raw_txn.sender());

let mut txn_and_zkp = TransactionAndProof {
message: raw_txn.clone(),
proof: None,
};
let esk = get_sample_esk();
sig.ephemeral_signature = EphemeralSignature::ed25519(esk.sign(&raw_txn).unwrap());

// Compute the training wheels signature if not present
match &mut sig.sig {
ZkpOrOpenIdSig::Groth16Zkp(proof) => {
match &mut sig.cert {
EphemeralCertificate::ZeroKnowledgeSig(proof) => {
// Training wheels should be disabled.
proof.training_wheels_signature = None
proof.training_wheels_signature = None;
txn_and_zkp.proof = Some(proof.proof);
},
ZkpOrOpenIdSig::OpenIdSig(_) => {},
EphemeralCertificate::OpenIdSig(_) => {},
}
sig.ephemeral_signature = EphemeralSignature::ed25519(esk.sign(&txn_and_zkp).unwrap());

let transaction = SignedTransaction::new_keyless(raw_txn, pk, sig);
println!(
Expand Down
60 changes: 60 additions & 0 deletions aptos-move/framework/move-stdlib/doc/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ return true.
- [Function `is_object_code_deployment_enabled`](#0x1_features_is_object_code_deployment_enabled)
- [Function `get_max_object_nesting_check_feature`](#0x1_features_get_max_object_nesting_check_feature)
- [Function `max_object_nesting_check_enabled`](#0x1_features_max_object_nesting_check_enabled)
- [Function `get_keyless_accounts_with_passkeys_feature`](#0x1_features_get_keyless_accounts_with_passkeys_feature)
- [Function `keyless_accounts_with_passkeys_feature_enabled`](#0x1_features_keyless_accounts_with_passkeys_feature_enabled)
- [Function `change_feature_flags`](#0x1_features_change_feature_flags)
- [Function `change_feature_flags_for_next_epoch`](#0x1_features_change_feature_flags_for_next_epoch)
- [Function `on_new_epoch`](#0x1_features_on_new_epoch)
Expand Down Expand Up @@ -464,6 +466,18 @@ Lifetime: transient



<a id="0x1_features_KEYLESS_ACCOUNTS_WITH_PASSKEYS"></a>

Whether keyless accounts support passkey-based ephemeral signatures.

Lifetime: transient


<pre><code><b>const</b> <a href="features.md#0x1_features_KEYLESS_ACCOUNTS_WITH_PASSKEYS">KEYLESS_ACCOUNTS_WITH_PASSKEYS</a>: u64 = 54;
</code></pre>



<a id="0x1_features_KEYLESS_BUT_ZKLESS_ACCOUNTS"></a>

Whether the ZK-less mode of the keyless accounts feature is enabled.
Expand Down Expand Up @@ -2296,6 +2310,52 @@ Lifetime: transient



</details>

<a id="0x1_features_get_keyless_accounts_with_passkeys_feature"></a>

## Function `get_keyless_accounts_with_passkeys_feature`



<pre><code><b>public</b> <b>fun</b> <a href="features.md#0x1_features_get_keyless_accounts_with_passkeys_feature">get_keyless_accounts_with_passkeys_feature</a>(): u64
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b> <b>fun</b> <a href="features.md#0x1_features_get_keyless_accounts_with_passkeys_feature">get_keyless_accounts_with_passkeys_feature</a>(): u64 { <a href="features.md#0x1_features_KEYLESS_ACCOUNTS_WITH_PASSKEYS">KEYLESS_ACCOUNTS_WITH_PASSKEYS</a> }
</code></pre>



</details>

<a id="0x1_features_keyless_accounts_with_passkeys_feature_enabled"></a>

## Function `keyless_accounts_with_passkeys_feature_enabled`



<pre><code><b>public</b> <b>fun</b> <a href="features.md#0x1_features_keyless_accounts_with_passkeys_feature_enabled">keyless_accounts_with_passkeys_feature_enabled</a>(): bool
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b> <b>fun</b> <a href="features.md#0x1_features_keyless_accounts_with_passkeys_feature_enabled">keyless_accounts_with_passkeys_feature_enabled</a>(): bool <b>acquires</b> <a href="features.md#0x1_features_Features">Features</a> {
<a href="features.md#0x1_features_is_enabled">is_enabled</a>(<a href="features.md#0x1_features_KEYLESS_ACCOUNTS_WITH_PASSKEYS">KEYLESS_ACCOUNTS_WITH_PASSKEYS</a>)
}
</code></pre>



</details>

<a id="0x1_features_change_feature_flags"></a>
Expand Down
11 changes: 11 additions & 0 deletions aptos-move/framework/move-stdlib/sources/configs/features.move
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,17 @@ module std::features {
is_enabled(MAX_OBJECT_NESTING_CHECK)
}

/// Whether keyless accounts support passkey-based ephemeral signatures.
///
/// Lifetime: transient
const KEYLESS_ACCOUNTS_WITH_PASSKEYS: u64 = 54;

public fun get_keyless_accounts_with_passkeys_feature(): u64 { KEYLESS_ACCOUNTS_WITH_PASSKEYS }

public fun keyless_accounts_with_passkeys_feature_enabled(): bool acquires Features {
is_enabled(KEYLESS_ACCOUNTS_WITH_PASSKEYS)
}

// ============================================================================================
// Feature Flag Implementation

Expand Down
10 changes: 9 additions & 1 deletion crates/aptos-crypto/src/poseidon_bn254.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Implements the Poseidon hash function for BN-254, which hashes $\le$ 16 field elements and
//! produces a single field element as output.
use anyhow::bail;
use ark_ff::PrimeField;
use ark_ff::{BigInteger, PrimeField};
use once_cell::sync::Lazy;
// TODO(keyless): Figure out the right library for Poseidon.
use poseidon_ark::Poseidon;
Expand Down Expand Up @@ -206,6 +206,14 @@ pub fn pack_bytes_to_one_scalar(chunk: &[u8]) -> anyhow::Result<ark_bn254::Fr> {
Ok(fr)
}

/// Utility method to convert an Fr to a 32-byte slice.
pub fn fr_to_bytes_le(fr: &ark_bn254::Fr) -> [u8; 32] {
fr.into_bigint()
.to_bytes_le()
.try_into()
.expect("expected 32-byte public inputs hash")
}

#[cfg(test)]
mod test {
use crate::{
Expand Down
4 changes: 2 additions & 2 deletions protos/typescript/src/aptos/remote_executor/v1/network_msg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export const NetworkMessageServiceClient = makeGenericClientConstructor(
};

function bytesFromBase64(b64: string): Uint8Array {
if (globalThis.Buffer) {
if ((globalThis as any).Buffer) {
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
} else {
const bin = globalThis.atob(b64);
Expand All @@ -257,7 +257,7 @@ function bytesFromBase64(b64: string): Uint8Array {
}

function base64FromBytes(arr: Uint8Array): string {
if (globalThis.Buffer) {
if ((globalThis as any).Buffer) {
return globalThis.Buffer.from(arr).toString("base64");
} else {
const bin: string[] = [];
Expand Down
4 changes: 2 additions & 2 deletions protos/typescript/src/aptos/transaction/v1/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9237,7 +9237,7 @@ export const WriteOpSizeInfo = {
};

function bytesFromBase64(b64: string): Uint8Array {
if (globalThis.Buffer) {
if ((globalThis as any).Buffer) {
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
} else {
const bin = globalThis.atob(b64);
Expand All @@ -9250,7 +9250,7 @@ function bytesFromBase64(b64: string): Uint8Array {
}

function base64FromBytes(arr: Uint8Array): string {
if (globalThis.Buffer) {
if ((globalThis as any).Buffer) {
return globalThis.Buffer.from(arr).toString("base64");
} else {
const bin: string[] = [];
Expand Down
2 changes: 1 addition & 1 deletion testsuite/generate-format/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub fn get_registry() -> Result<Registry> {
tracer.trace_type::<transaction::authenticator::AnyPublicKey>(&samples)?;
tracer.trace_type::<transaction::authenticator::AnySignature>(&samples)?;
tracer.trace_type::<transaction::webauthn::AssertionSignature>(&samples)?;
tracer.trace_type::<aptos_types::keyless::ZkpOrOpenIdSig>(&samples)?;
tracer.trace_type::<aptos_types::keyless::EphemeralCertificate>(&samples)?;

// events
tracer.trace_type::<WithdrawEvent>(&samples)?;
Expand Down
2 changes: 1 addition & 1 deletion testsuite/generate-format/src/aptos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ pub fn get_registry() -> Result<Registry> {
tracer.trace_type::<transaction::authenticator::AnyPublicKey>(&samples)?;
tracer.trace_type::<transaction::authenticator::AnySignature>(&samples)?;
tracer.trace_type::<transaction::webauthn::AssertionSignature>(&samples)?;
tracer.trace_type::<aptos_types::keyless::ZkpOrOpenIdSig>(&samples)?;
tracer.trace_type::<aptos_types::keyless::EphemeralCertificate>(&samples)?;
tracer.trace_type::<write_set::WriteOp>(&samples)?;

// aliases within StructTag
Expand Down
2 changes: 1 addition & 1 deletion testsuite/generate-format/src/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub fn get_registry() -> Result<Registry> {
tracer.trace_type::<transaction::authenticator::AnyPublicKey>(&samples)?;
tracer.trace_type::<transaction::authenticator::AnySignature>(&samples)?;
tracer.trace_type::<transaction::webauthn::AssertionSignature>(&samples)?;
tracer.trace_type::<aptos_types::keyless::ZkpOrOpenIdSig>(&samples)?;
tracer.trace_type::<aptos_types::keyless::EphemeralCertificate>(&samples)?;
tracer.trace_type::<write_set::WriteOp>(&samples)?;
tracer.trace_type::<PersistedStateValueMetadata>(&samples)?;

Expand Down
9 changes: 3 additions & 6 deletions testsuite/generate-format/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use aptos_crypto::ed25519::{Ed25519PublicKey, Ed25519Signature};
use aptos_types::{
keyless,
keyless::{Groth16Zkp, IdCommitment, Pepper, SignedGroth16Zkp, ZkpOrOpenIdSig},
keyless::{EphemeralCertificate, Groth16Proof, IdCommitment, Pepper, ZeroKnowledgeSig},
transaction::authenticator::{EphemeralPublicKey, EphemeralSignature},
};
use clap::{Parser, ValueEnum};
Expand Down Expand Up @@ -93,11 +93,8 @@ pub(crate) fn trace_keyless_structs(
idc: IdCommitment::new_from_preimage(&Pepper::from_number(2), "", "", "").unwrap(),
};
let keyless_signature = keyless::KeylessSignature {
sig: ZkpOrOpenIdSig::Groth16Zkp(SignedGroth16Zkp {
proof: Groth16Zkp::dummy_proof(),
non_malleability_signature: EphemeralSignature::Ed25519 {
signature: signature.clone(),
},
cert: EphemeralCertificate::ZeroKnowledgeSig(ZeroKnowledgeSig {
proof: Groth16Proof::dummy_proof().into(),
exp_horizon_secs: 0,
extra_field: None,
override_aud_val: None,
Expand Down
Loading

0 comments on commit a7a474f

Please sign in to comment.