We have started a draft specification for the encoding and signing of transactions and off-chain messages in Starknet in our github. Moving it here to centralise the information and invite more people to participate.
Welcome ! And thank you for a great first topic
Since several people have asked us if Argent X is supporting EIP-712 I guess this thread does not have the visibility that it should
I’m thus posting here the content of the Github discussion linked above which is a proposal for a version of EIP-712 adapted to StarkNet.
Note that this is already implemented in
starknet.js and Argent X.
Wallets have to generate signatures for 2 different types of data: transactions that will be sent to an Account Contract (AC) via the sequencer, and off-chain messages that will be returned to the requesting dapp for verification by calling the
is_valid_signature method on the AC.
It is essential that there is no collision between the two sets of signatures, or, in other words, we must ensure that a signature generated on the encoded data of an off-chain message can never be a valid signature on the encoded data of a transaction.
In Ethereum the encoded data of a transaction is clearly defined as
RLP<nonce, gasPrice, startGas, to, value, data> and one can easily satisfy the no-collision requirement by starting the encoded data of an off-chain message by
0x19 since it can never be the first byte of RLP encoded data. This is specified in EIP-191.
In addition, a good encoding scheme should make it easy for the wallet to clearly display to the user what he is signing. In Ethereum, this is specified in EIP-712.
On Starknet we have Account Abstraction, which means that the data to be signed for a transaction is (at least for now) completely open.
We define the 2 short string prefixes:
- PREFIX_TRANSACTION =
- PREFIX_MESSAGE =
and the encoding:
Enc[X=(x0, x1, …, xn-1)] = Enc[Enc[x0], Enc[x1], …, Enc[xn-1]] = h(…h(h(0,Enc[x0]), Enc[x1]), …), n)
when X is an array, and:
Enc[x] = x
when x is a felt, where
is the Pedersen hash on 2 field elements.
We also define
selector(str) = starknet_keccak(str)
As part of the discussions around the Account Contract interface, it was decided that AC will expose a single entry point for the execution of transactions:
@external func execute( to: felt, selector: felt, calldata_len: felt, calldata: felt*, nonce: felt ) -> (response : felt): end
For the moment there exists 2 implementations of Account Contracts:
- Account.sol in https://github.com/OpenZeppelin/cairo-contracts
- ArgentAccount.sol in https://github.com/argentlabs/argent-contracts-starknet
Based on the above, the data to sign for a transaction from an AC calling the function
method(params1, params2, ..., paramsN) on the contract
to can be defined as:
signed_data = Enc[PREFIX_TRANSACTION, account, to, selector(‘method’), Enc[[params1, params2, …, paramsN]], nonce]
Inspired by EIP-712 we can define the encoding of an off-chain message as:
signed_data = Enc[PREFIX_MESSAGE, domain_separator, account, hash_struct(message)]
domain_separatoris defined below.
accountis the Account Contract for which the signing is executed
hash_struct(message)is defined below.
The message to be hashed is represented as a struct:
struct MyStruct: member param1: felt member param2: felt* ... end
and we define its encoding as
hash_struct(message) = Enc[type_hash(MyStruct), Enc[param1], Enc[param2], …, Enc[paramN]]
type_hash is defined as in EIP-712 (but using
selector instead of
type_hash(MyStruct) = selector(‘MyStruct(params1:felt, params2:felt*,…)’)
If MyStruct references other struct types (and these in turn reference even more struct types), then the set of referenced struct types is collected, sorted by name and appended to the encoding. See EIP-712 for more details
domain_separator is defined as the
hash_struct of the
struct StarkNetDomain: member name: felt = 'www.myDapp.com' member version: felt = 1 member chain_id: felt = 'SN_GOERLI' end
chain_id is one of
['SN_GOERLI', 'SN_MAIN'] in short strings.
- In Cairo: https://github.com/argentlabs/argent-contracts-starknet/blob/main/contracts/StructHash.cairo
- in Js: https://github.com/seanjameshan/starknet.js/pull/87
- The 2 prefixes
PREFIX_MESSAGEensure that there will be no collisions between signatures for transactions and off-chain messages.
domain_separatorin the signed data of off-chain messages ensures that the signatures requested by a dapp cannot be used on another dapp, or on the same dapp but on another deployment (e.g. from Goërli to mainnet).
accountin the signed data of off-chain messages ensures that if several Account Contracts use the same key, the signature requested by a dapp for an account cannot be used to authenticate another account.
The proposal has been updated to support multicalls, fees, and Cairo >=0.7.1. The current status of the proposition can still be found in Signing transactions and off-chain messages · Discussion #14 · argentlabs/argent-x · GitHub