Aspect Case: Enable Session Keys for Your dApp

Mar 2, 2024

Technical

We've implemented an Aspect case — the Session Key Aspect! With it, Artela users can bypass the manual signing process when interacting with specific dApps, using the Session Key to interact automatically and securely!

We've applied it to Artela's DEX and Fully on-chain game applications, achieving one-click trading functionality and enhancing the user experience in high-frequency DeFi transactions!

In this blog, we'll detail how to utilize Aspect's features to implement native extensions for EoA accounts step by step.

What is Session Key?

Session key allows users to generate temporary sub-keys for their EoA wallets, enabling interactions with specific applications during a set timeframe. Normally, interacting with crypto applications involves manual transaction signing via the wallet. However, with a session key, transactions are automatically signed, removing the need for manual wallet signature confirmations.

In high-frequency trading context, this significantly improves the user experience. For instance, in perp markets where high-frequency trading are crucial, the typical wallet signature process can take upwards of twenty seconds, during which prices may significantly change. If the session key allows for transaction signatures within milliseconds of a trade request, it greatly enhances the trading experience.

Similarly, in fully on-chain gaming, every character movement requires a smart contract transaction. Manual signing for each move severely hampers playability. With session keys, a click to move can instantly generate and send a transaction to the blockchain, vastly improving game interaction.

This concept parallels the session idea in Web2, where users log in once and don't repeatedly enter passwords for subsequent actions within the session. Session keys bring this streamlined experience to Web3 applications, enhancing operational efficiency and user satisfaction in environments requiring frequent interactions.

Implemented by Aspect

In typical blockchain systems, the transaction processing flow is fixed and can't be altered by applications. For instance, in Ethereum, transactions are signed by an EoA using a specific signature method bound to the EoA's private key. The signature is then verified during the consensus process before execution.

In Artela, however, applications can modify the transaction execution flow through Aspects. During the transaction verification stage, Artela triggers the execution of Aspects, which can contain custom transaction validation logic, effectively replacing the default verification process.

Thus, on Artela, applications can tailor their transaction execution flows! In scenarios requiring high-frequency trading, an application could implement a session key Aspect. Here, an EoA would register its session key, and the application's Aspect, when verifying transactions, would recognize transactions signed by the session key as valid interactions from the EoA.

Procedure overview

The Session Key Aspect encompasses three main processes: EoA creation of the session key, using it to sign transactions, and the blockchain's verification of the transaction signatures.

Procedure 1. EoA registers session key to Aspect

Users generate a random, temporary key pair and use their EoA to sign a transaction that calls the Aspect. This transaction includes the session key's fundamental information: the session key's address, the address of the contract it can call, the methods of the contract it can invoke, and the key's expiration time. The Aspect will then store this session key information in its state.

Procedure 2. Use session key to sign transaction

The session key can sign transactions in place of the EoA. For transactions signed with a session key, the 'from' value is the address of the EoA, but the signature itself is from the session key.

Procedure 3. Artela verify the transaction

When Artela reaches the tx verify join point, it invokes the Session Key Aspect to perform transaction verification. The Session Key Aspect verifies whether the EoA has registered this session key. If it does, the Aspect continues to verify the legitimacy of the signature and whether the contract and method the session key is attempting to call are permitted.

Aspect Development

Once the process design is complete, development is carried out using the Aspect SDK.

We need to write an Aspect that implements two interfaces: ITransactionVerifier and IAspectOperation.

  • ITransactionVerifier is an interface for a join point. Implementing this interface means that instead of using the default behavior, Artela will call this interface to complete the transaction verification.

  • IAspectOperation is a management interface for Aspects. Implementing this interface implies that an EoA can interact with the Aspect similarly to how it would with a smart contract. This interface is used to facilitate users in registering their Session keys.

export class Aspect implements  IAspectOperation, ITransactionVerifier {

     /**
     * Join point: transaction verify
     *
     * When baselayer process tx's verification, it will call this join point method
     * to verify transaction and retrieve the sender address of transaction.
     */
    verifyTx(input: TxVerifyInput): Uint8Array { }

     /**
     * operation can be called by EoA.
     */
    operation(input: OperationInput): Uint8Array { }
}

IAspectOperation Interface

Operation acts as a unified management entry point, taking a byte array as its parameter. We'll design this parameter to have multiple sub-interfaces. The first two bytes of the byte array identify the sub-interface, while the subsequent bytes are the parameters for that sub-interface.

We'll designate 0x0001 as the registration interface and 0x1001 as the query interface.

operation(input: OperationInput): Uint8Array {
    // calldata encode rule
    // * 2 bytes: op code
    //      op codes lists:
    //           0x0001 | registerSessionKey
    //           0x1001 | getSessionKey
    //           0x100...
    //
    // * variable-length bytes: params
    //      encode rule of params is defined by each method

    const calldata = uint8ArrayToHex(input.callData);
    const op = this.parseOP(calldata);
    const params = this.parsePrams(calldata);

    if (op == "0001") {
        this.registerSessionKey(this.rmPrefix(uint8ArrayToHex(input.tx!.from)), params);
        return new Uint8Array(0);
    }
    ...
}

The implementation of the registerSessionKey registration interface is as follows:

  1. Decode the byte array to obtain the required session key.

    This involves a simple encoding where the first 20 bytes represent the address of the session key, the second set of 20 bytes denote the contract that the session key can call, followed by a variable-length byte array for the list of contract methods the session key can invoke, and finally, the last 8 bytes for the expiration block height of the session key.

  2. Write the session key into the state of the Aspect.

    Once written into the state, the session key can be retrieved from the state during the tx verify phase.

registerSessionKey(eoa: string, params: string): void {
        /**
         * params encode rules:
         *      20 bytes: session key
         *           eg. 1f9090aaE28b8a3dCeaDf281B0F12828e676c326
         *      20 bytes: contract address
         *           eg. 388C818CA8B9251b393131C08a736A67ccB19297
         *      2 bytes: length of methods set
         *           eg. 0002
         *      variable-length: 4 bytes * length of methods set; methods set
         *           eg. 0a0a0a0a0b0b0b0b
         *           means there are two methods: ['0a0a0a0a', '0b0b0b0b']
         *      8 bytes: expire block height
         */
        const encodeKey = params + eoa;

        const sKeyObj = new SessionKey(encodeKey);
        sKeyObj.verify();

        // save key
        const stateKey = sKeyObj.getStateKey();
        sys.aspect.mutableState.get<string>(stateKey).set(sKeyObj.getEncodeKey());

        ...
    }

ITransactionVerifier Interface

The main steps of implementing this interface are as follows:

  1. Decode the validationData of the transaction: This field contains the actual signature information. We need to verify the legitimacy of the transaction based on the information in this field.

    • from is the address of the EoA.

    • r, s, v are the signature of the session key.

verifyTx(input: TxVerifyInput): Uint8Array {
      // validation data encode rules:
      //     20 bytes: from
      //         eg. e2f8857467b61f2e4b1a614a0d560cd75c0c076f
      //     32 bytes: r
      //     32 bytes: s
      //     1 bytes: v

      // 0. decode validation data
      const params = uint8ArrayToHex(input.validationData);

      sys.require(params.length == 170, "illegal validation data, actual: " + params.length.toString());
      const from = params.slice(0, 40);
      const r = params.slice(40, 104);
      const s = params.slice(104, 168);
      const v = params.slice(168, 170);

      ...
  1. Verify the transaction signature through ecRecover: This step involves verifying the transaction's signature to obtain the actual signer.

    • Access the original transaction data in the Aspect runtime context by the sys API .

    • Use the sys's ecRecover API to verify the signature and obtain the actual signer's address, which is the address of the session key.

 		// 1. verify sig
     const unsignedHash = Protobuf.decode<BytesData>(
         sys.hostApi.runtimeContext.get("tx.unsigned.hash"),
         BytesData.decode);

     const msgHash = this.rmPrefix(uint8ArrayToHex(unsignedHash.data));

     const recoverResult = sys.hostApi.crypto.ecRecover(msgHash, BigInt.fromString(v, 16), BigInt.fromString(r, 16), BigInt.fromString(s, 16));
     const ret = hexToUint8Array(recoverResult);
     const sKey = uint8ArrayToHex(ret.subarray(12, 32));
     sys.require(sKey != "", "illegal signature, verify fail");
  1. Verify the registered session key:

  • First, obtain the contract address that the current transaction intends to call from the join point's input. Then, match the registered session key with the EoA address, contract address, and session key address.

  • If a match is found, it means the EoA has registered a session key authorized to call this contract.

  • Next, verify whether the method being called by the current transaction is within the session key's authorized access. Finally, access the current consensus block height in the Aspect runtime context and determine if the session key has expired at this height.

      // 2. match session key from Aspect's state
      //        session keys in state are registered
      const encodeSKey = sys.aspect.readonlyState.get<string>(
          SessionKey.getStateKey(this.rmPrefix(uint8ArrayToHex(input.tx!.to)), from, sKey)
      ).unwrap();

      sys.require(encodeSKey != "", "illegal session key");

      const sKeyObj = new SessionKey(encodeSKey);

      // 3. verify session key scope
      const method = parseCallMethod(input.callData);
      sys.require(sKeyObj.getMethodArray().includes(this.rmPrefix(method)),
          "illegal session key scope, method isn't allowed. actual is " + method + ". detail: " + uint8ArrayToHex(input.callData));

      // 4. verify expire block height
      const response = sys.hostApi.runtimeContext.get("block.header.number");
      var blockNumber = Protobuf.decode<UintData>(response, UintData.decode).data;

      // const currentBlockHeight = ctx.tx.content.unwrap().blockNumber;
      const expireBlockHeight = sKeyObj.getExpireBlockHeight();
      const currentBlockHeight = blockNumber + 1;
      sys.require(currentBlockHeight <= expireBlockHeight,
          "session key has expired; " + expireBlockHeight.toString() + " < " + currentBlockHeight.toString());

4. Return and Notify: After passing the above verifications, return the from value to the base layer, indicating that the transaction has been validated and the from value resolved. At this point, the session key has used its signature to represent the EoA in completing the transaction signing.

  verifyTx(input: TxVerifyInput): Uint8Array {
      ...

      // 5. return main key
      return hexToUint8Array(from);
  }

Enabling the Aspect for dApps

That concludes the general implementation process for the Session Key!

After deployment, the Aspect doesn't automatically activate. It requires a smart contract to actively bind it, only then will it affect transactions calling that contract. Thus, the Session Key Aspect is a universally applicable module that any smart contract can opt to activate, enabling session key functionality for its users!

Integrate the Session Key Aspect into your contract with these two steps:

  • Bind your contract to this Aspect. For a binding guide, click here: guide.

  • Integrate the Aspect's JavaScript client into your dApp's frontend. For a development guide, click here: guide.

For a reference to an integrated Demo project, click here: Session key integration demo.

Experience the Session Key Aspect

We've also deployed a demo project that has integrated the Session Key Aspect, offering a quick experience of interactive-free web3 applications! We plan to promote and deploy this Aspect on Artela Testnet as a public good, so stay tuned!

If YOU’RE interested in building the next generation of DeFi with Artela (which, if you’re still reading, you totally are), join our Developer Portal, Discord, X, and send us an email at info@artela.network. We can’t wait to work with you!