ethereum_derivable_account - [mainnet]
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 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
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) }}