Saltearse al contenido

Abstracción de Cuenta Derivable

La Abstracción de Cuenta Derivable (DAA) es un estándar para abstracción de cuenta que habilita esquemas de autenticación personalizados registrando una derivable_authentication_function.

DAA difiere de la Abstracción de Cuenta (AA) vanilla en que, para una derivable_authentication_function dada, define cómo derivar determinísticamente la dirección de cuenta de una abstract_public_key, lo que puede hacerse fuera de cadena.

En contraste, la AA vanilla se habilita para una cuenta pre-existente específica registrando explícitamente una authentication_function en cadena y enviando una transacción, lo que implica pasos adicionales y cuesta gas para cada cuenta.

Esto permite registrar esquemas de autenticación secundarios con experiencia de usuario idéntica a los nativos. Más específicamente, esto proporciona una forma flexible y segura de gestionar firmas cross-chain. (ver cuentas x-chain)

DAA funciona definiendo un esquema de autenticación personalizado y registrando una función de autenticación válida para realizar autenticación en cadena.

Cada cuenta abstracta debería tener una abstract_public_key asociada y debería poder producir abstract_signatures cuyos formatos dependen del esquema de autenticación.

Simplemente dicho, la derivable_authentication_function necesita verificar que:

  • la abstract_signature es válida para la abstract_public_key dada
  • la abstract_signature depende del digest de la transacción
// La función debería devolver un signer si la autenticación es exitosa, de lo contrario aborta la ejecución
public fun authenticate(account: signer, auth_data: AbstractionAuthData): signer;

El framework DAA automáticamente verifica si la dirección derivada de abstract_public_key coincide con la dirección del signer.

AbstractionAuthData es un enum que representa los datos de autenticación a ser pasados a funciones de autenticación personalizadas. Se usa en todos los sabores de AA, pero la variante DerivableV1 define los siguientes campos:

  • digest: El hash SHA3-256 del mensaje de firma.
  • abstract_signature: Bytes de firma abstracta que necesitan ser verificados contra abstract_public_key.
  • abstract_public_key: Bytes de clave pública abstracta asociados a la cuenta abstracta

Así es como se ve el enum Move:

enum AbstractionAuthData has copy, drop {
V1 { ... }, // Solo aplicable a AA vanilla
DerivableV1 {
digest: vector<u8>, // Hash SHA3-256 del mensaje de firma
abstract_signature: vector<u8>,
abstract_public_key: vector<u8>,
}
}

¿Por qué es importante el digest?

El digest es verificado por la MoveVM para asegurar que el mensaje de firma de la transacción siendo enviada es el mismo que el presentado en el AbstractionAuthData. Esto es importante porque permite a la función de autenticación verificar firmas con respecto a la transacción correcta.

Por ejemplo, si quieres permitir que una clave pública firme transacciones en nombre del usuario, puedes permitir que la clave pública firme una transacción con un payload específico. Sin embargo, si un usuario malicioso envía una firma para la clave pública correcta pero un payload diferente del digest, la firma no será válida.

Con DAA, una derivable_authentication_function dada define un espacio de direcciones de cuenta que pueden derivarse determinísticamente de su abstract_public_key asociada.

La función en cadena se ve como la siguiente:

public fun derive_account_address(derivable_func_info: FunctionInfo, abstract_public_key: &vector<u8>): address {
let bytes = bcs::to_bytes(&derivable_func_info);
bytes.append(bcs::to_bytes(abstract_public_key));
bytes.push_back(DERIVABLE_ABSTRACTION_DERIVED_SCHEME);
from_bcs::to_address(hash::sha3_256(bytes))
}

donde FunctionInfo es un identificador completamente calificado para una función en cadena:

struct FunctionInfo has copy, drop, store {
module_address: address,
module_name: String,
function_name: String
}

La derivación de dirección depende del identificador de la función de autenticación y de un separador de dominio específico de DAA. Debido a esto, cada espacio de direcciones está aislado de los otros y no es posible que la misma cuenta tenga múltiples funciones de autenticación.

Ejemplo (Move)

Este ejemplo demuestra abstracción de cuenta de dominio usando ed25519 hex para firmar.

module aptos_experimental::test_derivable_account_abstraction_ed25519_hex {
use std::error;
use aptos_std::string_utils;
use aptos_std::ed25519::{
Self,
new_signature_from_bytes,
new_unvalidated_public_key_from_bytes,
};
use aptos_framework::auth_data::AbstractionAuthData;
const EINVALID_SIGNATURE: u64 = 1;
/// Función de autorización para abstracción de cuenta derivable.
public fun authenticate(account: signer, aa_auth_data: AbstractionAuthData): signer {
let hex_digest = string_utils::to_string(aa_auth_data.digest());
let public_key = new_unvalidated_public_key_from_bytes(*aa_auth_data.derivable_abstract_public_key());
let signature = new_signature_from_bytes(*aa_auth_data.derivable_abstract_signature());
assert!(
ed25519::signature_verify_strict(
&signature,
&public_key,
*hex_digest.bytes(),
),
error::permission_denied(EINVALID_SIGNATURE)
);
account
}
}

Ejemplo (Typescript)

const derivableAbstractedAccount = new DerivableAbstractedAccount({
/**
* El resultado de la función signer estará disponible como el campo `abstract_signature` en la variante enum `AbstractionAuthData`.
*/
signer: (digest) => {
const hexDigest = new TextEncoder().encode(Hex.fromHexInput(digest).toString());
return solanaAccount.sign(hexDigest).toUint8Array();
},
/**
* La función de autenticación a ser invocada.
*/
authenticationFunction: `0x7::test_derivable_account_abstraction_ed25519_hex::authenticate`,
/**
* La clave pública abstracta (i.e la identidad de cuenta)
*/
abstractPublicKey: account.publicKey.toUint8Array(),
});
    1. Generar un par de claves ED25519
    const ed25519Account = Account.generate();
    1. Crear una DAA
    const daa = new DerivableAbstractedAccount({
    signer: (digest) => {
    const hexDigest = new TextEncoder().encode(Hex.fromHexInput(digest).toString());
    return ed25519Account.sign(hexDigest).toUint8Array();
    },
    authenticationFunction: `0x7::test_derivable_account_abstraction_ed25519_hex::authenticate`,
    abstractPublicKey: ed25519Account.publicKey.toUint8Array(),
    });
    1. Financiar la DAA para crearla en cadena
    await aptos.fundAccount({ accountAddress: daa.accountAddress, amount: 1000000 });
    1. Crear una cuenta destinataria y transferir APT a ella
    const recipient = Account.generate();
    const pendingTxn = await aptos.transaction.signAndSubmitTransaction({
    signer: daa,
    transaction: await aptos.transferCoinTransaction({
    sender: daa.accountAddress,
    recipient: recipient.accountAddress,
    amount: 100,
    }),
    });
    const response = await aptos.waitForTransaction({ transactionHash: pendingTxn.hash });