Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Module 0x1::solana_derivable_account

Derivable account abstraction that verifies a message signed by SIWS.

  1. The message format is as follows:

wants you to sign in with your Solana account: <base58_public_key>

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

Nonce: <aptos_txn_digest>

  1. The abstract public key is a BCS serialized SIWSAbstractPublicKey.
  2. The abstract signature is a BCS serialized SIWSAbstractSignature.
  3. This module has been tested for the following wallets:
use 0x1::auth_data;
use 0x1::bcs_stream;
use 0x1::common_account_abstractions_utils;
use 0x1::ed25519;
use 0x1::option;
use 0x1::string;
use 0x1::string_utils;
use 0x1::transaction_context;
use 0x1::vector;

Enum SIWSAbstractSignature

enum SIWSAbstractSignature has drop
Variants
MessageV1
Fields
signature: vector<u8>

Constants

const PUBLIC_KEY_NUM_BYTES: u64 = 32;

Invalid public key.

const EINVALID_PUBLIC_KEY: u64 = 5;

Signature failed to verify.

const EINVALID_SIGNATURE: u64 = 1;

Invalid signature type.

const EINVALID_SIGNATURE_TYPE: u64 = 4;

const BASE_58_ALPHABET: vector<u8> = [49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122];

Non base58 character found in public key.

const EINVALID_BASE_58_PUBLIC_KEY: u64 = 2;

Invalid public key length.

const EINVALID_PUBLIC_KEY_LENGTH: u64 = 6;

const HEX_ALPHABET: vector<u8> = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102];

Function deserialize_abstract_public_key

Deserializes the abstract public key which is supposed to be a bcs serialized SIWSAbstractPublicKey. The base58_public_key is represented in UTF8. We prefer this format because it’s computationally cheaper to decode a base58 string than to encode from raw bytes. We require both the base58 public key in UTF8 to construct the message and the raw bytes version to do signature verification.

fun deserialize_abstract_public_key(abstract_public_key: &vector<u8>): (vector<u8>, vector<u8>)
Implementation
fun deserialize_abstract_public_key(abstract_public_key: &vector<u8>):
(vector<u8>, vector<u8>) {
    let stream = bcs_stream::new(*abstract_public_key);
    let base58_public_key = bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x));
    let domain = bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x));
    (base58_public_key, domain)
}

Function deserialize_abstract_signature

Returns a tuple of the signature type and the signature.

fun deserialize_abstract_signature(abstract_signature: &vector<u8>): solana_derivable_account::SIWSAbstractSignature
Implementation
fun deserialize_abstract_signature(abstract_signature: &vector<u8>): SIWSAbstractSignature {
    let stream = bcs_stream::new(*abstract_signature);
    let signature_type = bcs_stream::deserialize_u8(&mut stream);
    if (signature_type == 0x00) {
        let signature = bcs_stream::deserialize_vector<u8>(&mut stream, |x| deserialize_u8(x));
        SIWSAbstractSignature::MessageV1 { signature }
    } else {
        abort(EINVALID_SIGNATURE_TYPE)
    }
}

Function to_public_key_bytes

fun to_public_key_bytes(base58_public_key: &vector<u8>): vector<u8>
Implementation
fun to_public_key_bytes(base58_public_key: &vector<u8>): vector<u8> {
    let bytes = vector[0u8];
    let base = 58u16;

    let i = 0;
    while (i < base58_public_key.length()) {
        let char = base58_public_key[i];
        let (found, char_index) = BASE_58_ALPHABET.index_of(&char);
        assert!(found, EINVALID_BASE_58_PUBLIC_KEY);

        let j = 0;
        let carry = (char_index as u16);

        // For each existing byte, multiply by 58 and add carry
        while (j < bytes.length()) {
            let current = (bytes[j] as u16);
            let new_carry = current * base + carry;
            bytes[j] = ((new_carry & 0xff) as u8);
            carry = new_carry >> 8;
            j += 1;
        };

        // Add any remaining carry as new bytes
        while (carry > 0) {
            bytes.push_back((carry & 0xff) as u8);
            carry >>= 8;
        };

        i += 1;
    };

    // Handle leading zeros (1's in Base58)
    let i = 0;
    while (i < base58_public_key.length() && base58_public_key[i] == 49) { // '1' is 49 in ASCII
        bytes.push_back(0);
        i += 1;
    };

    bytes.reverse();
    assert!(bytes.length() == PUBLIC_KEY_NUM_BYTES, EINVALID_PUBLIC_KEY_LENGTH);
    bytes
}

Function 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 abstract_public_key = aa_auth_data.derivable_abstract_public_key();
    let (base58_public_key, domain) = deserialize_abstract_public_key(abstract_public_key);
    let digest_utf8 = string_utils::to_string(aa_auth_data.digest()).bytes();

    let public_key_bytes = to_public_key_bytes(&base58_public_key);
    let public_key = new_validated_public_key_from_bytes(public_key_bytes);
    assert!(public_key.is_some(), EINVALID_PUBLIC_KEY);
    let abstract_signature = deserialize_abstract_signature(aa_auth_data.derivable_abstract_signature());
    match (abstract_signature) {
        SIWSAbstractSignature::MessageV1 { signature: signature_bytes } => {
            let message = construct_message(&b"Solana", &base58_public_key, &domain, entry_function_name, digest_utf8);

            let signature = new_signature_from_bytes(signature_bytes);
            assert!(
                ed25519::signature_verify_strict(
                    &signature,
                    &public_key_into_unvalidated(public_key.destroy_some()),
                    message,
                ),
                EINVALID_SIGNATURE
            );
        },
    };
}

Function 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 {
    daa_authenticate(account, aa_auth_data, |auth_data, entry_name| authenticate_auth_data(auth_data, entry_name))
}

Specification

Function to_public_key_bytes

fun to_public_key_bytes(base58_public_key: &vector<u8>): vector<u8>
ensures result.length() == PUBLIC_KEY_NUM_BYTES;

Function authenticate_auth_data

fun authenticate_auth_data(aa_auth_data: auth_data::AbstractionAuthData, entry_function_name: &vector<u8>)
pragma verify = false;

Function authenticate

public fun authenticate(account: signer, aa_auth_data: auth_data::AbstractionAuthData): signer
pragma verify = false;