# Account keys and addresses derivation standard

**URL:** <https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230>\
**Category:** SNIPs\
**Tags:** conventions\
**Created:** [May 12, 2022, 8:00am UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230 "2022-05-12T08:00:51Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![juniset](https://dub1.discourse-cdn.com/flex005/user_avatar/community.starknet.io/juniset/32/40_2.png) [@juniset](https://community.starknet.io/u/juniset)\
**Post date:** [May 12, 2022, 8:00am UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230/1 "2022-05-12T08:00:51Z")

</div>

## Objective

On Ethereum the address of an EOA account is derived from the associated public key. And multiple keys can be derived from a single secret (seed phrase).

There is thus a deterministic relation between this unique secret and a sequence of accounts, i.e. if you know the secret you can derive a sequence of account addresses.

There is also a deterministic relation between this unique secret and the private keys controlling this sequence of accounts, i.e. if you know the secret you can derive the sequence of private keys controlling these accounts.

This allows for great interoperability between wallets as one can simply import the secret from one wallet to another and immediately recover the account addresses and the controlling private keys.

On StarkNet we have account abstraction which means that the address of an account is computed as a smart contract with no direct relation to the key(s) controlling the account. By default users must thus keep track of their account addresses, and loosing an account address means loosing access to the account.

Furthermore, the key(s) controlling the account can potentially be changed at any point in time depending on the logic implemented by the account.

The objective of this proposition is to allow for some interoperability between StarkNet wallets by standardising the relation between a unique secret and a sequence of accounts such that the addresses of the accounts can be deterministically recovered from the secret.

Depending on the account implementations it might also create a deterministic relation between the secret and the sequence of keys controlling these accounts.

## Proposition

### Step 1: Derive a sequence of `starkPair` from a `secret`

A sequence of keys are derived from a unique `secret` using the Hierarchical Deterministic (HD) Wallet standard:

> **[HD Wallet](https://docs.ethers.org/v5/api/utils/hdnode/)**
>
> Documentation for ethers, a complete, tiny and simple Ethereum library.

We can use standard BIP-44 derivation path:

```jsx
m /44' /9004' /0' /0 /index

```

We apply on the derived private key, a grinding method inspired by

> <https://github.com/authereum/starkware-monorepo/blob/51c5df19e7f98399a2f7e63d564210d761d138d1/packages/starkware-crypto/src/keyDerivation.ts#L85>

In summary, for a given `secret` and `index`, the `starkPair` is given by:

```jsx
const masterNode = HDNode.fromSeed(secret)

const path = m/44'/9004'/0'/0/${index}

const childNode = masterNode.derivePath(path)

const groundKey = grindKey(childNode.privateKey)
const starkPair = ec.getKeyPair(groundKey)

```

### Step 2: Initialise each account with a different `starkPair`

The address of an account on StarkNet is defined in

> **[Page Not Found](https://docs.starknet.io/docs/Contracts/contract-address)**

```jsx
contract_address := pedersen(
		“STARKNET_CONTRACT_ADDRESS”,
		caller_address,
		salt,
		pedersen(contract_code),
		pedersen(constructor_calldata))

```

For each `starkPair` derived in step 1 we can thus create a unique account by passing the public key in the constructor calldata.

We can make the account address recoverable is we require the account to emit a standard event upon creation:

```jsx
@event
func account_created(account: felt, key: felt):
end

```

where

- `account` is the address of the created account (note that this is not technically needed since the account is emitting the event, but could make searching easier depending on how events will be indexed in the future)
- `key` is the public key of the `starkPair`.

Since the signature of StarkNet events only depends on the method name, each implementation can decide to pass additional parameters to the `account_created` event. The only requirement is that the first parameter be the `account` and the second the `key` .

## Recovering account addresses

Given a `secret` , a wallet can easily derive the `starkPair` associated to index 0, 1, 2, 3, … and get all the `event_created` events whose `key` matches one of the derived keys. The wallet automatically has the list of associated account addresses.

## Note

- This proposition is relatively loose in purpose. It does not try to agree on a common bytecode (e.g. using a standard Proxy) or a common method to compute the salt. This could be the work of extending standards targeting more specific use cases. Its main objective is to have a simple method to recover the sequence of account addresses with minimal input from the user (i.e. only the `secret`) for maximum interoperability.
- While the goal of the standard is to recover account addresses from the `secret` , it can also recover the associated private keys for EOA like account implementation (see e.g. [https://github.com/OpenZeppelin/cairo-contracts/blob/main/src/openzeppelin/account/Account.cairo](https://github.com/OpenZeppelin/cairo-contracts/blob/main/src/openzeppelin/account/Account.cairo)) if the derived key is used as the signer.
- There is no guarantee that a matching `account_created` event corresponds to the correct account, i.e. malicious contracts could emit the same event. However, wallets can perform additional checks on the account to identify the legit ones.
- Since StarkNet uses CREATE2, it is possible to have gaps in the sequence of accounts. For example, there may be an account for index 0, 1, 3, 4. In such case account 2 was counterfactually created and it may not be possible to recover its address unless the bytecode, salt, and constructor arguments are fully known.

---

<div class="post-metadata">

**Author:** ![martriay](https://dub1.discourse-cdn.com/flex005/user_avatar/community.starknet.io/martriay/32/25_2.png) [@martriay](https://community.starknet.io/u/martriay)\
**Post date:** [May 13, 2022, 1:05am UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230/2 "2022-05-13T01:05:56Z")

</div>

I like it! Agree on keeping things loose and flexible.

> [@juniset](#):
>
> However, wallets can perform additional checks on the account to identify the legit ones.

Should the `salt` also be made available in the event so it’s feasible to compute the address and compare it with the emitting contract?

I also wonder if it makes sense to split this proposal into different tracks: stark secret derivation and another `account_created` event one so it also applies to [Ethereum-keys kind of accounts](https://community.starknet.io/t/support-web3-wallets-in-starknet/192/6#account-addresses-1). Probably a third one for `contract_address` calculation, if it makes sense to squeeze the “ETH\_CREATE” opcode idea.

---

<div class="post-metadata">

**Author:** ![realcrypt-Braavos](https://dub1.discourse-cdn.com/flex005/user_avatar/community.starknet.io/realcrypt-braavos/32/387_2.png) [@realcrypt-Braavos](https://community.starknet.io/u/realcrypt-Braavos)\
**Post date:** [May 13, 2022, 2:24pm UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230/3 "2022-05-13T14:24:34Z")

</div>

@juniset tnx for this post!  
Interoperability between wallets on StarkNet is important and the proposition looks solid.

However, it is worth addressing the case in which a user replaced the public key stored in the account contract to a new key.  
e.g.: [https://github.com/OpenZeppelin/cairo-contracts/blob/c15e9b7c6ccb0ca719eed7a3a93865c9d7c39581/src/openzeppelin/account/Account.cairo#L65](https://github.com/OpenZeppelin/cairo-contracts/blob/c15e9b7c6ccb0ca719eed7a3a93865c9d7c39581/src/openzeppelin/account/Account.cairo#L65)

To support this we should also add the same event (maybe consider changing its name to something more generic, such as `account_key_binding`) to the function that changes the public key (in the OZ case above, it is `set_public_key`):

```auto
@event
func account_key_binding(account: felt, key: felt):
end

```

This way the account can be recovered, even if the key was changed after the account was created, by simply looking for the `account_key_binding` event, with the new key.

As a side note, it might be worth considering a near-term solution for recovery that does not rely on an indexer, if we see that Alchemy and alike are delayed to deploy a solution.

Another thing worth discussing and penciling-down in this context, is the minimal set of account contract API/functionality that all account contract should support in order to allow basic interoperability.

We suggest to align to the current OZ contracts and support either the basic implementation API:

```auto
func get_public_key
func get_nonce
func supportsInterface
func get_impl_version
func set_public_key
func constructor 
func is_valid_signature
func execute

```

Or the upgradeable proxy pattern:

```auto
func get_public_key
func get_nonce
func supportsInterface
func get_impl_version
func set_public_key
func initializer
func upgrade
func is_valid_signature
func execute

```

On top of that each wallet can add its unique features and extended functionality.

---

<div class="post-metadata">

**Author:** ![juniset](https://dub1.discourse-cdn.com/flex005/user_avatar/community.starknet.io/juniset/32/40_2.png) [@juniset](https://community.starknet.io/u/juniset)\
**Post date:** [May 16, 2022, 3:54pm UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230/4 "2022-05-16T15:54:15Z")

</div>

@martriay @realcrypt-Braavos thanks for the feedback!

In my opinion the proposition should be about one thing (and one thing only): given a secret, how can a wallet create a deterministic sequence of accounts such that any other wallet can “discover” the same sequence of deployed accounts when provided with the secret only.

So it’s really about “discovering” account addresses, and less about “discovering” the keys that control these accounts.

On Ethereum you get both (discovering account address and private key) from a single key derivation standard, but with account abstraction they are two separate problems. The second one cannot be solved for all possible account implementations (think of multisig where there is no notion of “the” signing key of the account) so that’s why I think we should focus on discovering account addresses first then extend the standard for more specific use cases.

For example, and as noted at the end of the proposition, we could/should create a second standard for the subset of account implementations that have only a single signer (e.g. EOA like accounts) in which case the keys associated to these accounts can also be derived from the secret. @realcrypt-Braavos I think your propositions should be discussed as part of this second standard since they focus on accounts with a single signing key.

@martriay Given the goal above, I believe the key derivation and the `account_created` event are needed together in a single standard. I have no clear opinion on passing the `salt` to the event, but not clear yet if it will really help.

---

<div class="post-metadata">

**Author:** ![Yogh333](https://dub1.discourse-cdn.com/flex005/user_avatar/community.starknet.io/yogh333/32/84785_2.png) [@Yogh333](https://community.starknet.io/u/Yogh333)\
**Post date:** [May 16, 2022, 4:28pm UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230/5 "2022-05-16T16:28:55Z")

</div>

Hi,

As far as I have understood, [EIP-2645](https://eips.ethereum.org/EIPS/eip-2645) already defines a way to derive multiple Starknet key pairs from a root seed. Why can’t we use it ?  
Regarding the computation of the address of an account smart contract, even if the public key is updated, its address won’t change. So even with a contract embedding a single public key (easiest use case), we can have several key pairs associated with the same contract address and in that case, the sequence of accounts also refers to a sequence of keys. Actually, the only way to define uniquely an account is the pair (contract address, Starknet pub key).

---

<div class="post-metadata">

**Author:** ![gergold](https://avatars.discourse-cdn.com/v4/letter/g/c37758/32.png) [@gergold](https://community.starknet.io/u/gergold)\
**Post date:** [May 16, 2022, 7:15pm UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230/6 "2022-05-16T19:15:33Z")

</div>

As far as I know, Starkware is discouraging developers from using EIP-2645

---

<div class="post-metadata">

**Author:** ![realcrypt-Braavos](https://dub1.discourse-cdn.com/flex005/user_avatar/community.starknet.io/realcrypt-braavos/32/387_2.png) [@realcrypt-Braavos](https://community.starknet.io/u/realcrypt-Braavos)\
**Post date:** [May 17, 2022, 3:27pm UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230/7 "2022-05-17T15:27:41Z")

</div>

thanks for the detailed response. although now I’m a bit confused: if the idea is only to recover the address, a few questions arise:

1. what will we do with the recovered address, if we cannot control the underlying account?
2. in order to generate the address we need to generate the keys, so we have both address and keys anyhow. why not use them?

my thought was that: given the seed, we can generate keys, then find out the address by searching the events, then allow user to take ownership of this address using any wallet.  
of course, not all set of features of the original wallet may work, but we should make sure that the basic functionality of a wallet may be transferred to another wallet, along with the assets linked to that address.  
and in fact, your very good suggestion allows it - it only lacks the firing of event in case of seed (keys) change. seed/keys change is a big advantage of a smart-contract wallet, as if the user wishes to replace the keys, the user’s address may remain (along with all of its assets), by simply updating the public keys in the existing accounts.

and in case of proxy accounts (which I guess all of the ecosystem will adapt) - we can even allow the user to switch the implementation while retaining the keys.  
it will bring freedom and value to all Starknet users, which is a value we should aspire to.

---

<div class="post-metadata">

**Author:** ![s0lness](https://dub1.discourse-cdn.com/flex005/user_avatar/community.starknet.io/s0lness/32/1391_2.png) [@s0lness](https://community.starknet.io/u/s0lness)\
**Post date:** [May 23, 2022, 9:39am UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230/8 "2022-05-23T09:39:06Z")

</div>

Great writeup Julien! I think we’re on the right track with this. Some thoughts:

> [@juniset](#):
>
> - Since StarkNet uses CREATE2, it is possible to have gaps in the sequence of accounts. For example, there may be an account for index 0, 1, 3, 4. In such case account 2 was counterfactually created and it may not be possible to recover its address unless the bytecode, salt, and constructor arguments are fully known.

This can be solved through a normal “20 address lookup” explained in BIP-44 ([link](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#address-gap-limit)). If the user lost their wallet and tries to recover their funds, the wallet should precisely account for these gaps by checking 20 addresses ahead and stop if there are no funds there.

> [@juniset](#):
>
> - There is no guarantee that a matching `account_created` event corresponds to the correct account, i.e. malicious contracts could emit the same event. However, wallets can perform additional checks on the account to identify the legit ones.

I guess we could solve this through a simple “sign message” at derivation to ascertain the user is indeed deriving the correct account.

---

<div class="post-metadata">

**Author:** ![reallym404](https://dub1.discourse-cdn.com/flex005/user_avatar/community.starknet.io/reallym404/32/16797_2.png) [@reallym404](https://community.starknet.io/u/reallym404)\
**Post date:** [May 23, 2022, 12:19pm UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230/9 "2022-05-23T12:19:46Z")

</div>

At present, the Argent X wallet has two sets of addresses in the main network and the test network. Can one address be used in the main network and the test network at the same time.

---

<div class="post-metadata">

**Author:** ![juniset](https://dub1.discourse-cdn.com/flex005/user_avatar/community.starknet.io/juniset/32/40_2.png) [@juniset](https://community.starknet.io/u/juniset)\
**Post date:** [May 23, 2022, 2:12pm UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230/10 "2022-05-23T14:12:58Z")

</div>

The goal of this proposition is to have 1 standard that can work for all types of accounts. If we start talking about signing keys then that is not possible. For example if we include your suggestions then it will not work for multisigs where there is no concept of a single signing key. For the same reason the `IAccount` interface has no method related to keys (e.g. there is no `get_signing_key` method) to remain general.

But of course in practice you want to recover the address of your account and the control of your account (typically the key). So what you are suggesting is relevant for accounts that have a main signer (the OZ and the Argent account today) and it should be part of another standard that we can write on top of this one.

This is a very broad and generic proposition that we can use as a starting point for other more specific standards.

---

<div class="post-metadata">

**Author:** ![juniset](https://dub1.discourse-cdn.com/flex005/user_avatar/community.starknet.io/juniset/32/40_2.png) [@juniset](https://community.starknet.io/u/juniset)\
**Post date:** [May 25, 2022, 7:38am UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230/11 "2022-05-25T07:38:56Z")

</div>

@realcrypt-Braavos @martriay Irrespective of the discussion about signing keys, do you guys see any issue with the derivation path or `account_created` event?

We will need to change our contracts (proxy and implementation) to support 0.9 and I want to take that opportunity to start aligning with the proposition above, i.e. use the Bip-44 derivation path and emit the `account_created` event when the account is deployed.

---

<div class="post-metadata">

**Author:** ![yoga\_Braavos](https://dub1.discourse-cdn.com/flex005/user_avatar/community.starknet.io/yoga_braavos/32/858_2.png) [@yoga\_Braavos](https://community.starknet.io/u/yoga_Braavos)\
**Post date:** [December 7, 2022, 2:22am UTC](https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230/12 "2022-12-07T02:22:38Z")

</div>

Hi @juniset , if I understand the ArgentX implementation correctly, there is an additional derivation from seed that you do that Braavos wallet does not.  
In ArgentX:  
`ethers.Wallet.fromMnemonic(seedPhrase)` is used to create a private key (which internally uses `HDNode.fromMenmonic` ) and then on top of that your master node is being created using `HDNode.fromSeed`  
In Braavos the master node is derived directly from `HDNode.fromMenmonic` per [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)

For basic interoperability I believe this should be changed to have only a single Master Seed → Master Node derivation (this will also save some CPU cycles)

Regarding `account_changed`, while we used `account_initialized` we can align on some convention - note that OpenZeppelin moved to CamelCase events.

Also post Cairo v1.0, we should define the `public_key` as a key so we will be able to query `starknet_getEvents` on this event properly

One gap that we currently have is data-model incompatibility - the biggest issue is Proxy impl, while Braavos adopted OpenZeppelin implementation, ArgentX have a custom implementation meaning that the `upgrade` entry point looks at irrelevant storage variables. In this aspect we can agree that post re-genesis we can all align to OpenZeppelin’s Proxy impl, WDYT?
