🏠

Predictable actor address generation

Last edited
Jun 21, 2022 1:07 AM
Project
FVM Capabilities & Standards

At present, the only way to confidently know what address an actor will have once created is to actually create it. The “stable” actor address computed by the NewActorAddress VM syscall remains fixed over chain re-orgs but depends on the history of the account actor originating the transaction where it’s called (it varies with call sequence number) and so can only be trusted by that party (until finality).

I propose adding a method to the Init actor which creates an actor at an address that is predictable ahead of time, controlled by the actor deployer, and remains independent of chain events (state and call sequence). This is the equivalent of Ethereum’s CREATE2 opcode.

Motivation

Predictable actor addresses allow generation of an address to which an actor can be deployed in the future, controlled by a specific creator. Funds can be sent to such an address prior to an actor being deployed there, and only the nominated creator can later deploy a contract to control them. The resulting address is independent of future events–it does not depend on any blockchain state.

One big use case for this facility is smart-contract wallets. With predictable addresses, users can receive funds into a wallet address prior to deploying any wallet actor there, but confident they will be able to do so. A “user” here could include smart-contract wallet platforms, which can generate addresses for unsophisticated end users prior to those users ever generating a private key, and then deploy a wallet actor only if and when that address receives funds. They could then hand control of the wallet actor over to one or more account actors.

Another use case is state-channel applications that wish to perform off-chain interactions, with the reliable possibility of reverting to on-chain transactions to settle disputes. Predictable contract addresses allow parties to exchange signed messages to such an actor prior to it being deployed. The parties can trust that only a specific piece of code could ever be deployed to that address.

Background

Current state

The existing process for actor address generation and creation is:

  • An actor sends an Exec message to the Init actor specifying code CID and constructor params
  • The Init actor uses the NewActorAddress syscall to obtain a stable address. This is calculated as hash(originator address, originator callseqnum, count of NewActorAddress during this txn). The originator address is the SECKP/BLS public-key-derived address.
  • The Init actor computes the next actor ID, and stores mapping of address to ID
  • The Init actor calls the CreateActor syscall with computed actor ID and Exec code CID
  • The Init actor invokes constructor of the newly created actor with Exec parameters
  • The Init actor returns the computed actor ID and stable address

The computed actor address is stable in the sense that the originator can rely on it being constant even if a reorg causes the allocated actor ID (computed next) to change. It does not depend on state. However, it is not predictable far in advance or trustable by other parties because it depends on the originator callseqnum.

Prior art - CREATE2

The Ethereum CREATE2 opcode (EIP-104) deploys a contract to an address that is computed as hash(0xFF, sender, salt, bytecode). This guarantees that if sender ever deploys bytecode using CREATE2 and the provided salt, it will be stored in the resulting address. Note that the bytecode here is “initcode”, which is evaluated to generate the code to be deployed, and introduces some hard-to-think-about security challenges. CREATE2 can only be used by contracts (factories) because EOA transaction don’t have salt–this is by design.

More background on the EVM contract deployment process, but Filecoin is different (simpler) due to coupling of the VM with the Init actor.

Proposal

Add a new method Exec2 (name TBC) to the Init actor.

type Exec2Params {
	CodeCid: Cid, // Same as Exec
	AddressSalt: Bytes // New
  ConstructorParams: Bytes // Same as Exec
}

Exec2 behaves similarly to Exec, but rather than using NewActorAddress to compute the address, it computes it as hash(senderID, params.AddressSalt, params.CodeCid). This address should have type tag Actor (=2), the same as those generated by NewActorAddress. These two methods of generating an address will never collide (up to hash security) because the first hashed items SenderID and OriginatorAddress have no overlap.

The hash function shall be blake2b, following the existing actor address scheme.

With this scheme, someone can compute off-chain what address would be generated when a specified sender deploys a specific code CID with a known salt at any point in the future.

Implementation notes

Note that thanks to VM/Init actor coupling, no new syscall is needed here. The only change is to the Init actor.

VM implementations would need to change the behaviour when a message is sent to an actor-type address that is not yet initialised. At present, such sends are rejected (SysErrInvalidReceiver). Instead, the destination actor must be created with a placeholder code CID representing code yet to be deployed. The deployment capability must be restricted to the Init actor, as is currently the case.

Open questions

Dependency on constructor parameters

This scheme leaves the constructor parameters out of the address inputs. This means that the sender does not commit to them. Future FVM code delegation mechanisms could mean that different constructor parameters result in very different actor behaviour, which is a risk to any third party relying on the code to be deployed to some address.

We could consider including the code CID in the hash inputs. This would provide complete clarity over the code and state to be deployed, but also remove flexibility. E.g, a smart contract wallet platform would be unable to specify different end-users’ private keys, or any other parameter, to different actors at construction (but could invoke a subsequent method to authorize them).

Unification with NewActorAddress

This scheme raises the question of whether we could unify the address generation approach with that used for the existing actor addresses. The use case for NewActorAddress is generating a address without an externally provided salt value, stable, but not necessarily predictable. If you squint, the inputs to NewActorAddress could form the salt to an address created with the proposed scheme–they take the place of an externally-provided unique value.

If we added syscalls to expose the originator address and originator nonce, the Init actor could read these value to construct most of the salt, and then compute the address. The remaining salt item is to distinguish multiple new actor creations in a single top-level transaction. Ethereum does this by incrementing the nonce on the caller contract (which for us is always Init), but this makes the address unstable across re-orgs.

  • Would adding a simple sequence number parameter to Exec be good enough for all practical needs?
  • Is there anything else that’s not chain state we could use to distinguish multiple calls? Like gas used so far? It would be really nice to remove the internal counter that the VM has to keep to support this call.

If we do this, we could remove the NewActorAddress syscall, because the Init actor would compute the address.

Compatibility with future address schemes

The FVM team have proposed a new universal address scheme in https://github.com/filecoin-project/fvm-specs/blob/505fa1ed0daabfcf2033e8fe79d9b64019a36ca0/04-evm-mapping.md. Since predictable addresses created by this proposal are in the same address space as existing type 2 actor addresses, they will be similarly compatible with this scheme. Alternatively, a 256-bit hash function could be used to natively create a class-4 address from the beginning.