ethereum_derivable_account - [devnet]
Derivable account abstraction that verifies a message signed by SIWE.
- The message format is as follows:
 
Please confirm you explicitly initiated this request from 
URI: 
- The abstract public key is a BCS serialized 
SIWEAbstractPublicKey. - The abstract signature is a BCS serialized 
SIWEAbstractSignature. - This module has been tested for the following wallets:
 
use 0x1::aptos_hash;use 0x1::auth_data;use 0x1::base16;use 0x1::bcs_stream;use 0x1::chain_id;use 0x1::common_account_abstractions_utils;use 0x1::option;use 0x1::secp256k1;use 0x1::string;use 0x1::string_utils;use 0x1::transaction_context;use 0x1::vector;Enum SIWEAbstractSignature
enum SIWEAbstractSignature has dropVariants
MessageV1
Fields
- 
issued_at: string::String - The date and time when the signature was issued
 - 
signature: vector<u8> - The signature of the message
 
MessageV2
Fields
- 
scheme: string::String - The scheme in the URI of the message, e.g. the scheme of the website that requested the signature (http, https, etc.)
 - 
issued_at: string::String - The date and time when the signature was issued
 - 
signature: vector<u8> - The signature of the message
 
Constants
Entry function payload is missing.
const EMISSING_ENTRY_FUNCTION_PAYLOAD: u64 = 2;Address mismatch.
const EADDR_MISMATCH: u64 = 4;Signature failed to verify.
const EINVALID_SIGNATURE: u64 = 1;Invalid signature type.
const EINVALID_SIGNATURE_TYPE: u64 = 3;Unexpected v value.
const EUNEXPECTED_V: u64 = 5;Structs
SIWEAbstractPublicKey
struct SIWEAbstractPublicKey has dropFunctions
deserialize_abstract_public_key
Deserializes the abstract public key which is supposed to be a bcs
serialized SIWEAbstractPublicKey.
fun deserialize_abstract_public_key(abstract_public_key: &vector<u8>): ethereum_derivable_account::SIWEAbstractPublicKeyImplementation
fun deserialize_abstract_public_key(abstract_public_key: &vector<u8>): SIWEAbstractPublicKey {    let stream = bcs_stream::new(*abstract_public_key);    let ethereum_address = bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x));    let domain = bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x));    SIWEAbstractPublicKey { ethereum_address, domain }}deserialize_abstract_signature
Returns a tuple of the signature type and the signature. We include the issued_at in the signature as it is a required field in the SIWE standard.
fun deserialize_abstract_signature(abstract_signature: &vector<u8>): ethereum_derivable_account::SIWEAbstractSignatureImplementation
fun deserialize_abstract_signature(abstract_signature: &vector<u8>): SIWEAbstractSignature {    let stream = bcs_stream::new(*abstract_signature);    let signature_type = bcs_stream::deserialize_u8(&mut stream);    if (signature_type == 0x00) {        let issued_at = bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x));        let signature = bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x));        SIWEAbstractSignature::MessageV1 { issued_at: string::utf8(issued_at), signature }    } else if (signature_type == 0x01) {        let scheme = bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x));        let issued_at = bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x));        let signature = bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x));        SIWEAbstractSignature::MessageV2 { scheme: string::utf8(scheme), issued_at: string::utf8(issued_at), signature }    } else {        abort(EINVALID_SIGNATURE_TYPE)    }}construct_message
fun construct_message(ethereum_address: &vector<u8>, domain: &vector<u8>, entry_function_name: &vector<u8>, digest_utf8: &vector<u8>, issued_at: &vector<u8>, scheme: &vector<u8>): vector<u8>Implementation
fun construct_message(    ethereum_address: &vector<u8>,    domain: &vector<u8>,    entry_function_name: &vector<u8>,    digest_utf8: &vector<u8>,    issued_at: &vector<u8>,    scheme: &vector<u8>,): vector<u8> {    let message = &mut vector[];    message.append(*domain);    message.append(b" wants you to sign in with your Ethereum account:\n");    message.append(*ethereum_address);    message.append(b"\n\nPlease confirm you explicitly initiated this request from ");    message.append(*domain);    message.append(b".");    message.append(b" You are approving to execute transaction ");    message.append(*entry_function_name);    message.append(b" on Aptos blockchain");    let network_name = network_name();    message.append(b" (");    message.append(network_name);    message.append(b")");    message.append(b".");    message.append(b"\n\nURI: ");    message.append(*scheme);    message.append(b"://");    message.append(*domain);    message.append(b"\nVersion: 1");    message.append(b"\nChain ID: ");    message.append(*string_utils::to_string(&chain_id::get()).bytes());    message.append(b"\nNonce: ");    message.append(*digest_utf8);    message.append(b"\nIssued At: ");    message.append(*issued_at);
    let msg_len = message.length();
    let prefix = b"\x19Ethereum Signed Message:\n";    let msg_len_string = string_utils::to_string(&msg_len); // returns string    let msg_len_bytes = msg_len_string.bytes(); // vector<u8>
    let full_message = &mut vector[];    full_message.append(prefix);    full_message.append(*msg_len_bytes);    full_message.append(*message);
    *full_message}recover_public_key
fun recover_public_key(signature_bytes: &vector<u8>, message: &vector<u8>): vector<u8>Implementation
fun recover_public_key(signature_bytes: &vector<u8>, message: &vector<u8>): vector<u8> {    let rs = signature_bytes.slice(0, 64);    let v = signature_bytes[64];    assert!(v == 27 || v == 28, EUNEXPECTED_V);    let signature = secp256k1::ecdsa_signature_from_bytes(rs);
    let maybe_recovered = secp256k1::ecdsa_recover(*message, v - 27, &signature);
    assert!(        maybe_recovered.is_some(),        EINVALID_SIGNATURE    );
    let pubkey = maybe_recovered.borrow();
    let pubkey_bytes = secp256k1::ecdsa_raw_public_key_to_bytes(pubkey);
    // Add 0x04 prefix to the public key, to match the    // full uncompressed format from ethers.js    let full_pubkey = &mut vector[];    full_pubkey.push_back(4u8);    full_pubkey.append(pubkey_bytes);
    *full_pubkey}authenticate_auth_data
fun authenticate_auth_data(aa_auth_data: auth_data::AbstractionAuthData, entry_function_name: &vector<u8>)Implementation
fun authenticate_auth_data(    aa_auth_data: AbstractionAuthData,    entry_function_name: &vector<u8>) {    let derivable_abstract_public_key = aa_auth_data.derivable_abstract_public_key();    let abstract_public_key = deserialize_abstract_public_key(derivable_abstract_public_key);    let digest_utf8 = string_utils::to_string(aa_auth_data.digest()).bytes();    let abstract_signature = deserialize_abstract_signature(aa_auth_data.derivable_abstract_signature());    let issued_at = abstract_signature.issued_at.bytes();    let scheme = abstract_signature.scheme.bytes();    let message = construct_message(&abstract_public_key.ethereum_address, &abstract_public_key.domain, entry_function_name, digest_utf8, issued_at, scheme);    let hashed_message = aptos_hash::keccak256(message);    let public_key_bytes = recover_public_key(&abstract_signature.signature, &hashed_message);
    // 1. Skip the 0x04 prefix (take the bytes after the first byte)    let public_key_without_prefix = public_key_bytes.slice(1, public_key_bytes.length());    // 2. Run Keccak256 on the public key (without the 0x04 prefix)    let kexHash = aptos_hash::keccak256(public_key_without_prefix);    // 3. Slice the last 20 bytes (this is the Ethereum address)    let recovered_addr = kexHash.slice(12, 32);    // 4. Remove the 0x prefix from the utf8 account address    let ethereum_address_without_prefix = abstract_public_key.ethereum_address.slice(2, abstract_public_key.ethereum_address.length());
    let account_address_vec = base16_utf8_to_vec_u8(ethereum_address_without_prefix);    // Verify that the recovered address matches the domain account identity    assert!(recovered_addr == account_address_vec, EADDR_MISMATCH);}authenticate
Authorization function for domain account abstraction.
public fun authenticate(account: signer, aa_auth_data: auth_data::AbstractionAuthData): signerImplementation
public fun authenticate(account: signer, aa_auth_data: AbstractionAuthData): signer {    daa_authenticate(account, aa_auth_data, |auth_data, entry_name| authenticate_auth_data(auth_data, entry_name))}