x-chain DAA authentication using Sign Personal Message with Sui
AIP-124 - x-chain DAA authentication using Sign Personal Message with Sui
Section titled “AIP-124 - x-chain DAA authentication using Sign Personal Message with Sui”Summary
Section titled “Summary”Implements authentication function giving Sui users native Aptos accounts, and allowing them to sign transactions on Aptos blockchain, directly from their Sui wallet. This is done using derivable account abstraction (AIP-113).
High-level Overview
Section titled “High-level Overview”Allow Sui accounts (Sui Ed25519 key pairs) to create Aptos transactions, and be able to authenticate and execute them directly on the Aptos blockchain, without interaction with the Sui blockchain. We do so by utilizing “Sign Personal Message” specification, and defining a message format that gives enough details to users so they understand what they are signing reasonably well.
Impact
Section titled “Impact”This will allow users with Sui Wallets to be able to interact with the Aptos blockchain.
Alternative Solutions
Section titled “Alternative Solutions”Alternatively, wallets from other chains can implement integration with Aptos natively, providing more seamless experience to the users.
Specification and Implementation Details
Section titled “Specification and Implementation Details”The dApp is required to issue a signPersonalMessage request to the Sui wallet with:
<domain> wants you to sign in with your Sui account:<sui_address>
Please confirm you explicitly initiated this request from <domain>. You are approving to execute transaction <entry_function> on Aptos blockchain (<network_name>).
Nonce: <aptos_txn_digest>And submit the transaction to Aptos with its authenticator set to the following struct:
AccountAuthenticator::Abstraction { function_info: FunctionInfo::from("0x1::sui_derivable_account::authenticate"), auth_data: AbstractionAuthData::DerivableV1 { signing_message_digest: aptos_txn_digest, abstract_signature: bcs::to_bytes(SuiAbstractSignature::SuiDerivedSignature { signature: sign_personal_message_signature, }), abstract_public_key: bcs::to_bytes(SuiAbstractPublicKey { sui_account_address, domain, }), },}with Aptos types:
enum SuiAbstractSignature has drop { SuiDerivedSignature { signature: vector<u8>, },}
struct SuiAbstractPublicKey has drop { sui_account_address: vector<u8>, domain: vector<u8>,}If the transaction is signed correctly, as indicated above, its signature will verify successfully via 0x1::sui_derivable_account::authenticate for the Aptos account whose address is derived via account_abstraction::derive_account_address(function_info, abstract_public_key)
0x1::sui_derivable_account::authenticate will:
- Re-construct the expected text message
- Extract the
public_key, which is part of thesign_personal_message_signature - Derive the
account_addressfrom thepublic_key - Verify that the
account_addressmatches with the one from theabstract_public_key. - In addition, it will verify that
sign_personal_message_signaturefromabstract_signatureis a correct signature of that message.
Looking at what each field in the message the Sui’s wallet displays is and what is it used for:
domainis the website domain (e.g., example.com) that issued the signing request to the Sui wallet. The wallet itself is responsible for ensuring this field is populated correctly.. This sandboxes eachdomaininto separate Aptos account, providing better isolation and security.sui_addressis Sui wallet account address including the0xprefix.entry_functionis a string representation of an Aptos entry function transaction will execute. It contains address, module name and function name, for example0x1::primary_fungible_store::transfer. This restricts usage to only entry functions (i.e. preventing scripts, etc), but is made required as it is the main human-readable information about the transaction that user can read and understand.network_nameis a string representation of the Aptos chain ID on which transaction can be executed on, with: “mainnet” for 1, “testnet” for 2, “local” for 4, and “custom network <chain_id>” for othersaptos_txn_digestis the digest of the Aptos transaction that you want to execute. All details about the transaction itself, and replay protection is inside of the transaction. Note:aptos_txn_digestis guaranteed to be unique on Aptos, so it can be used raw directly as a nonce. Alternatively, it could’ve been designed to haveaptos_txn_digestinside the message, and nonce be a separate user-controlled nonce, but that seems unnecessary.
Verification implementation is:
public fun authenticate(account: signer, aa_auth_data: AbstractionAuthData): signer { let abstract_signature = deserialize_abstract_signature(aa_auth_data.derivable_abstract_signature()); let (signing_scheme, abstract_signature_signature, abstract_signature_public_key) = split_signature_bytes(&abstract_signature.signature);
// Check siging scheme is ED25519 as we currently only support this scheme assert!(get_signing_scheme(signing_scheme) == SuiSigningScheme::ED25519, EINVALID_SIGNING_SCHEME_TYPE);
// Derive the account address from the public key let sui_account_address = derive_account_address_from_public_key(signing_scheme, abstract_signature_public_key);
let derivable_abstract_public_key = aa_auth_data.derivable_abstract_public_key(); let abstract_public_key = deserialize_abstract_public_key(derivable_abstract_public_key);
// Check the account address matches the abstract public key assert!(&sui_account_address == &abstract_public_key.sui_account_address, EACCOUNT_ADDRESS_MISMATCH);
let public_key = new_validated_public_key_from_bytes(abstract_signature_public_key); assert!(public_key.is_some(), EINVALID_PUBLIC_KEY);
let digest_utf8 = string_utils::to_string(aa_auth_data.digest()).bytes();
// Build the raw message let raw_message = construct_message(&sui_account_address, &abstract_public_key.domain, entry_function_name, digest_utf8);
// Prepend Intent to the message let intent = Intent { scope: PersonalMessage, version: V0, app_id: Sui, }; let msg = IntentMessage { intent, value: raw_message, }; // Serialize the whole struct let bcs_bytes = bcs::to_bytes<IntentMessage>(&msg);
// Hash full_message with blake2b256 let hash = aptos_hash::blake2b_256(bcs_bytes);
let signature = new_signature_from_bytes(abstract_signature_signature);
assert!( ed25519::signature_verify_strict( &signature, &public_key_into_unvalidated(public_key.destroy_some()), hash, ), EINVALID_SIGNATURE ); }Reference Implementation
Section titled “Reference Implementation”https://github.com/aptos-labs/aptos-core/pull/16830
Testing
Section titled “Testing”Verified payload signed with signPersonalMessage with Slush is verified correctly and passes authorization
Security Considerations, Risks and Drawbacks
Section titled “Security Considerations, Risks and Drawbacks”domainwill not be verified, and it is on the user to understand risks. For that, we include in the message to “Please confirm you explicitly initiated this request from” - Using this flow doesn’t provide “simulation” information to the user (dapp could run simulation, but cannot be forced to), so user has less information about what is being signed. For that, direct wallet support would be needed.
- Without simulation, entry function is placed inside the message, to help user understand better what it is authorizing. This is a balance between given user too much information (so it doesn’t review it closely), like full json representation of the Aptos transaction, and having users sign without much information. It is a question if adding arguments to the entry function within the message itself is useful, they can give more context, but can also be quite large and hardly understandable.
- Note that actual address of the module where entry function is will be printed, while a lot UIs (explorer, wallet, etc) might maintain list of common ecosystem projects and addresses, and give users more easily understandable name. In order to do so here, we would need to maintain such list onchain, so there are tradeoffs to be considered.
- Currently, we only support
Ed25519signing scheme, tho adding support to more schemes is doable.
Future Potential
Section titled “Future Potential”SuiAbstractSignatureis enum, so that we can support new message formats in the future. For example, we could add list of permissions from AIP-103 to be listed, giving a restricted access to the transaction itself- it also allows us, in case a security issue is found, to deprecate current message format while keeping access to the account
Timeline
Section titled “Timeline”Suggested implementation timeline
Section titled “Suggested implementation timeline”TBD
Suggested developer platform support timeline
Section titled “Suggested developer platform support timeline”TBD
Suggested deployment timeline
Section titled “Suggested deployment timeline”TBD