Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: account abstraction for hash & signature #748

Closed

Conversation

PhilippeR26
Copy link
Collaborator

@PhilippeR26 PhilippeR26 commented Sep 8, 2023

Motivation and Resolution

Starknet.js was not able to interact with accounts including account abstraction about the hash and the signature of a transaction.

Source about account abstraction :

Usage related changes

DAPPS coded with Starknet.js v5.19.5, without signature account abstraction, have nothing to change.

The signature abstraction can be implemented in 5 cases. For each case, a function has to be created explaining how to perform this custom hash +signature :

Starknet.js method Custom hash function Custom signature function Custom hash + signature function Account contract function
myContract.invoke myAccount.execute myAccount.deployContract N/A N/A abstractedTransactionSign() __validate__
__execute__
myAccount.deployAccount N/A N/A abstractedDeployAccountSign() __validate-deploy__
myAccount.declare N/A N/A abstractedDeclareSign() __validate-declare__
myAccount.signMessage myAccount.hashMessage abstractedMessageHash() abstractedMessageSign() N/A isValidSignature()

These functions have to respect the following signatures :

export type AbstractionDeployAccountFunctionSign = (
  standardInputData: DeployAccountSignerDetails,
  privateKey: string,
  ...additionalParams: string[]
) => Signature;

export type AbstractionTransactionFunctionSign = (
  standardInputData: {
    contractAddress: BigNumberish;
    version: BigNumberish;
    calldata: RawCalldata;
    maxFee: BigNumberish;
    chainId: StarknetChainId;
    nonce: BigNumberish;
  },
  privateKey: string,
  ...additionalParams: string[]
) => Signature;

export type AbstractionDeclareFunctionSign = (
  standardInputData: DeclareSignerDetails,
  privateKey: string,
  ...additionalParams: string[]
) => Signature;

export type AbstractionMessageFunctionHash = (
  typedData: TypedData,
  accountAddress: string,
  ...additionalParams: string[]
) => string;

export type AbstractionMessageFunctionSign = (
  msgHash: string,
  privateKey: string,
  ...additionalParams: string[]
) => Signature;

Example of each case is shown in the guides.

export const abstractionFns: AbstractionFunctions = {
    abstractedDeployAccountSign: signDeployAccount,
    abstractedTransactionSign: signTransaction,
    abstractedDeclareSign: signDeclare,
    abstractedMessageHash: hashMessage,
    abstractedMessageSign: signMessage
}
  • Create a specific signer, using this list of functions.
const signerAbstraction = new AbstractedSigner(privateKeyAbstraction, abstractionFns);
  • Create an Account instance, using this signer.
const accountAbstraction = new Account(provider, addressAbstraction, signerAbstraction, "1"); // "1" for a Cairo 1 account contract
  • Interact with Starknet :
const call2 = contractETH.populate("transfer", {
        recipient: accountAddress0,
        amount: 300_000
    });
const { transaction_hash: th2 } = await accountAbstraction.execute(
    call2,
    undefined,
    undefined,
    4, 5, 6); // abstraction is here

A new function has been implemented : account.verifyMessageLocally() for EIP712 like JSON.
Nota : no changes have been made in the ContractFactory class.

Development related changes

Without any modifications in __tests__, tests are passed, meaning that all the new code has not impacted current DAPPS.
A script to test the deployment of a Braavos account, with deploy account abstraction, has been successfully tested.
No tests have been currently implemented. A Cairo v2.1.0 account contract including all cases of signature abstraction will be created and tested.
Probably a new test file will be created for this specific subject.

Checklist:

  • Performed a self-review of the code
  • Rebased to the last commit of the target branch (or merged it into my branch)
  • Documented the changes in code (API docs will be generated automatically)
  • Guides updated
  • Updated the tests
  • All tests are passing (with no change performed in tests)

@netlify
Copy link

netlify bot commented Sep 8, 2023

Deploy Preview for starknetjs ready!

Name Link
🔨 Latest commit c829e3a
🔍 Latest deploy log https://app.netlify.com/sites/starknetjs/deploys/64fb338ba2a3ae0008edff4d
😎 Deploy Preview https://deploy-preview-748--starknetjs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@PhilippeR26
Copy link
Collaborator Author

I just ended to code an abstracted account contract.
I took an OpenZeppelin 0.7.0 account (coded for Cairo v2.2.0). I tortured it until it accepts Cairo v2.1.0 compiler. Then I added abstraction everywhere.
I tested it, and it works...
Starknet.js can interact with an account with specific hashes and signatures.
I will now start Jest tests.
I will probably create a specific test file for this subject.

@PhilippeR26 PhilippeR26 marked this pull request as ready for review September 13, 2023 10:11
@PhilippeR26
Copy link
Collaborator Author

Tests added.
Ready for review.

@janek26
Copy link
Member

janek26 commented Sep 18, 2023

Hey @PhilippeR26 !

I think the class would get too many use cases probably. I would rather enforce a good split into private, protected and public methods in the Account class implementation and stay with the standard class way of extending, like

export NewSignatureSigner extends Signer {
  constructor() {
    super()
  }

  public override transactionSign() {
    // do something before
    super.transactionSign() // eventually use the existing method at some point
    // do something after
  }
}

Why do you need such a customizable Account and Signer object? :)

@PhilippeR26
Copy link
Collaborator Author

Many teams are working on Account Abstraction, and their accounts will not be handled with the current code of Starknet.js.
For example, for Account deploy, Braavos has a specific way to hash and sign, using or not their hardware signer. We can see development for 2FA, SMS/email passwords. And I think that we are just at the beginning of the use of account abstraction.

__tests__/accountAbstraction.test.ts Show resolved Hide resolved
__tests__/accountAbstraction.test.ts Outdated Show resolved Hide resolved
__tests__/accountAbstraction.test.ts Outdated Show resolved Hide resolved
__tests__/accountAbstraction.test.ts Outdated Show resolved Hide resolved
{ classHash: declare.class_hash, salt, unique, constructorCalldata },
details
);
const deploy = addsAbstractionDeploy
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this ternary operator be avoided and handled in the deployContract function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.
If not made this way, the compiler requests an iterator in the type BigNumberish[]|undefined
and I don't know how to handle a such case.

): Promise<DeclareDeployUDCResponse> {
const { constructorCalldata, salt, unique } = payload;
let declare = await this.declareIfNot(payload, details);

let declare = addsAbstractionDeclare
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this ternary operator be avoided and handled in the declareIfNot function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not made this way, the compiler requests an iterator in the type BigNumberish[]|undefined
and I don't know how to handle a such case.

@PhilippeR26
Copy link
Collaborator Author

As required, a new AbstractedSigner has been created, the current Signer remains unchanged.
Guides updated in accordance.

import { AbstractedSignerInterface } from './interface';

export class AbstractedSigner extends Signer implements AbstractedSignerInterface {
// constructor() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are not in the last commit 8dc277b

this.abstractionFunctions = abstractedFns;
}

// public override transactionSign() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are not in the last commit 8dc277b

} from '../types';

export abstract class AbstractedSignerInterface {
public abstract abstractionFunctions: AbstractionFunctions | undefined;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing docs here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In all the project, properties of classes are not documented.
If requested, I can add documentation for the AbstractedSigner class properties.

TypedData,
} from '../types';

export abstract class AbstractedSignerInterface {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will leave to others to give input, but as it is currently, this interface has no effect on the AbstractedSigner (the main effect comes from the Signer itself)

): Promise<Invocation> {
const calldata = getExecuteCalldata(call, this.cairoVersion);
const signature = await this.signer.signTransaction(call, signerDetails);
const signature =
'abstractionFunctions' in this.signer
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this could be avoided if we expand the original SignerInterface with optional parameters for addsAbstraction. cc: @tabaktoni

* @returns response from estimate_fee
*/
public abstract estimateInvokeFee(
calls: AllowArray<Call>,
estimateFeeDetails?: EstimateFeeDetails
estimateFeeDetails?: EstimateFeeDetails,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we push everything related to ...addsAbstraction inside the estimateFeeDetails ? The same applies for all other methods here
cc: @tabaktoni

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number of parameters of abstraction are unknown.
Should be replaced by an array.
My feeling is that it will complicate the user experience.

* @returns Add Transaction Response
*/
public abstract invoke(
method: string,
args?: ArgsOrCalldata,
options?: InvokeOptions
options?: InvokeOptions,
...addsAbstraction: BigNumberish[]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here also, could we just push stuff through options ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment. To balance with UX.

nonce,
blockIdentifier,
});
const adds: string[] = addsAbstraction.map((param) => toHex(param));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line of code is repeated in a lot of places, it would be cool if we could do something different, we can think about it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be with num.bigNumberishArrayToHexadecimalStringArray()?

version,
nonce,
});
const signature =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could also be avoided if we change the SignerInterface

addressSalt,
constructorCalldata: compiledCalldata,
});
const signature =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could also be avoided if we change the SignerInterface

@tabaktoni
Copy link
Collaborator

After releasing v6 final, as c1 gets more stable with c1 acc we can review this one.
Maybe we should close this one, and create new resuing some elements but before implementation, discuss how to introduce it most elegantly.

@PhilippeR26
Copy link
Collaborator Author

Following Toni request, PR closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Transaction signature should support randomness
4 participants