Skip to content

ethereum_derivable_account - [mainnet]

Derivable account abstraction that verifies a message signed by SIWE.

  1. The message format is as follows:

wants you to sign in with your Ethereum account: <ethereum_address>

Please confirm you explicitly initiated this request from . You are approving to execute transaction <entry_function_name> on Aptos blockchain (<network_name>).

URI: :// Version: 1 Chain ID: <chain_id> Nonce: Issued At: <issued_at>

  1. The abstract public key is a BCS serialized SIWEAbstractPublicKey.
  2. The abstract signature is a BCS serialized SIWEAbstractSignature.
  3. 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 drop
Variants
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

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;

Entry function payload is missing.

const EMISSING_ENTRY_FUNCTION_PAYLOAD: u64 = 2;

Unexpected v value.

const EUNEXPECTED_V: u64 = 5;

Structs

SIWEAbstractPublicKey

struct SIWEAbstractPublicKey has drop
Fields
ethereum_address: vector<u8>
domain: vector<u8>

Functions

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::SIWEAbstractPublicKey
Implementation
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::SIWEAbstractSignature
Implementation
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 = vector::length(message);
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 = vector::slice(signature_bytes, 0, 64);
let v = *vector::borrow(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!(
option::is_some(&maybe_recovered),
EINVALID_SIGNATURE
);
let pubkey = option::borrow(&maybe_recovered);
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[];
vector::push_back(full_pubkey, 4u8);
vector::append(full_pubkey, 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 = vector::slice(&public_key_bytes, 1, vector::length(&public_key_bytes));
// 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 = vector::slice(&kexHash, 12, 32);
// 4. Remove the 0x prefix from the utf8 account address
let ethereum_address_without_prefix = vector::slice(&abstract_public_key.ethereum_address, 2, vector::length(&abstract_public_key.ethereum_address));
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): signer
Implementation
public fun authenticate(account: signer, aa_auth_data: AbstractionAuthData): signer {
let maybe_entry_function_payload = transaction_context::entry_function_payload();
if (maybe_entry_function_payload.is_some()) {
let entry_function_payload = maybe_entry_function_payload.destroy_some();
let entry_function_name = entry_function_name(&entry_function_payload);
authenticate_auth_data(aa_auth_data, &entry_function_name);
account
} else {
abort(EMISSING_ENTRY_FUNCTION_PAYLOAD)
}
}