Skip to content

solana_derivable_account - [mainnet]

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;

Entry function payload is missing.

const EMISSING_ENTRY_FUNCTION_PAYLOAD: u64 = 3;
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];

Functions

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)
}

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)
}
}

construct_message

fun construct_message(base58_public_key: &vector<u8>, domain: &vector<u8>, entry_function_name: &vector<u8>, digest_utf8: &vector<u8>): vector<u8>
Implementation
fun construct_message(
base58_public_key: &vector<u8>,
domain: &vector<u8>,
entry_function_name: &vector<u8>,
digest_utf8: &vector<u8>,
): vector<u8> {
let message = &mut vector[];
message.append(*domain);
message.append(b" wants you to sign in with your Solana account:\n");
message.append(*base58_public_key);
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\nNonce: ");
message.append(*digest_utf8);
*message
}

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 = j + 1;
};
// Add any remaining carry as new bytes
while (carry > 0) {
bytes.push_back((carry & 0xff) as u8);
carry = carry >> 8;
};
i = 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 = i + 1;
};
vector::reverse(&mut bytes);
assert!(bytes.length() == PUBLIC_KEY_NUM_BYTES, EINVALID_PUBLIC_KEY_LENGTH);
bytes
}

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(&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
);
},
};
}

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)
}
}

Specification

to_public_key_bytes

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

authenticate_auth_data

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

authenticate

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