This RFC defines the migration process of funds residing on WOTS addresses in the current legacy network to the post-Chrysalis Phase 2 network.
The IOTA protocol wants to move away from WOTS as it created a number of security, protocol and UX issues:
- WOTS signatures are big and make up a disproportional amount of data of a transaction.
- It is only safe to spend from an address once. Spending multiple times from the same address reveals random parts of the private key, making any subsequent transfers (other than the first) susceptible to thefts.
- As a prevention mechanism to stop users from spending multiple times from the same address, nodes have to keep an ever growing list of those addresses.
In the beginning of the new Chrysalis Phase 2 network, only Ed25519 addresses are supported. The protocol will no longer support WOTS addresses. Therefore, there needs to be a migration process from WOTS addresses to Ed25519 address in the new network.
To make the migration as smooth as possible, the specified mechanism allows for users to migrate their funds at any time with only a small delay until they're available on the new network.
This RFC outlines the detailed architecture of how users will be able to migrate their funds and specifies the underlying components and their purposes.
On a high-level the migration process works as follows:
- Users create migration bundles in the legacy network which target their Ed25519 address in the new network.
- The Coordinator then mints those migrated funds in receipts which are placed within milestones on the new network.
- Nodes in the new network evaluate receipts and book the corresponding funds by creating new UTXOs in the ledger.
- Users issue migration bundles which effectively burn their funds. During this period, normal value bundles and zero-value transactions are allowed to become part of a milestone cone.
- The Coordinator is stopped, and a new global snapshot is created which as its only solid entry point contains the last issued milestone (and put on dbfiles.iota.org). This global snapshot is used to create the genesis snapshot containing the already migrated funds for the new network. The remainder of the total supply which has not been migrated is allocated on the
TreasuryOutput. Users are instructed to check the validity of these two snapshots.
- A new Hornet version is released which only allows migration bundles to be broadcasted or be part of milestone cones. Users must update their node software as otherwise they will no longer peer.
- The legacy network is restarted with the global snapshot, and the new network bootstraps with the genesis snapshot.
- Further funds migrated in the legacy network are transferred to the new network using the receipt mechanism.
Changes to the legacy network
In order to facilitate the migration process, the node software making up the legacy network needs to be updated. This update will be deployed by stopping the Coordinator and forcing all nodes to upgrade to this new version.
The node software will no longer book ledger mutations to non-migration addresses. This means that users are incentivized to migrate their funds as they want to use their tokens. See this document on what migration addresses are.
A migration bundle is defined as follows:
- It contains exactly one output transaction of which the destination address is a valid migration address and is positioned as the tail transaction within the bundle. The output transaction value is at least 1'000'000 tokens.
- It does not contain any zero-value transactions which do not hold signature fragments. This means that transactions other than the tail transaction must always be part of an input.
- Input transactions must not use migration addresses.
The node will only use tail transactions of migration or milestone bundles for the tip-pool. This means that past cones referenced by a milestone will only include such bundles.
The legacy node software is updated with an additional HTTP API command called
getWhiteFlagConfirmation which given request data in the following form:
returns data for the given milestone white-flag confirmation:
milestoneBundle contains the milestone bundle trytes and
includedBundles is an array of tryte arrays of included bundles in the same DFS order as the white-flag confirmation. Trytes within a bundle "array" are sorted from
currentIndex = 0 ascending to the
This HTTP API command allows interested parties to verify which migration bundles were confirmed by a given milestone.
Milestone inclusion Merkle proof
The Coordinator will only include migration bundles (respectively the tails of those bundles) in its inclusion Merkle proof. Nodes which do not run with the updated code will crash once the updated confirmation is in place.
Preventing non-migration bundles
As an additional measure to prevent users from submitting never confirming non-migration bundles (which would lead to key-reuse), nodes will no longer accept non-migration bundles in the HTTP API.
HTTP API level checks:
- The user must submit an entire migration bundle. No more single zero-value transactions, value-spam bundles etc. are allowed.
- Input transactions are spending the entirety of the funds residing on the corresponding address. There must be more than 0 tokens on the given address.
Wallet software must be updated to no longer support non-migration bundles.
There are no restrictions put in place on the gossip level, as it is too complex to prevent non-migration transactions to be filtered out, however, these transactions will never become part of a milestone cone.
TreasuryTransaction is a payload which contains a reference to the current
TreasuryOutput (in form of a
TreasuryInput object) and an output
TreasuryOutput which deposits the remainder.
|Type||uint32||Set to value 4 to denote a TreasuryTransaction.|
TreasuryInput is equivalent to a normal
UTXOInput but instead of referencing a transaction, it references a milestone. This input can only be used within
|Input Type||byte||Set to value 1 to denote an TreasuryInput.|
|Milestone Hash||Array<byte>>||The hash of the milestone which created the referenced TreasuryOutput.|
TreasuryOutput is a special output type which represents the treasury of the network, respectively the non yet migrated funds. At any given moment in time, there is only one
|Output Type||byte||Set to value 2 to denote an TreasuryInput.|
|Amount||uint64||The amount of funds residing in the treasury.|
TreasuryOutput can not be referenced or spent by transactions, it can only be referenced by receipts.
TreasuryOutput can be queried from the HTTP API and needs to be included within snapshots in order to keep the total supply intact.
Receipts allow for fast migration of funds from the legacy into the new network by representing entries of funds which were migrated in the old network.
Receipts are listings of funds for which nodes must generate UTXOs in the form of
SigLockedSingleOutputs targeting the given address. Receipts are embedded within milestone payloads and therefore signed by the Coordinator. A milestone may contain up to one receipt as a payload. The Coordinator chooses whether to embed a receipt payload or not.
|Payload Type||uint32||Set to value 3 to denote a Receipt.|
|Migrated At||uint32||The index of the legacy milestone in which the listed funds were migrated at.|
|Final||bool||Flags whether this receipt is the last receipt for the given Migrated At index.|
|Funds Count||uint16||Denotes how many migrated fund entries are within the receipt.|
Migrated Funds Entry
|Payload Length||uint32||The length in bytes of the payload.|
funds_array_countcan be max 127 and must be > 0.
fundsarray must be in lexical sort order (by their serialized form).
tail_transaction_hashmust be unique over the entire receipt.
depositmust be ≥ 1'000'000 IOTA tokens.
payload_lengthcan not be zero.
treasury_transactionmust be a
migrated_atcan not decrease between subsequent receipts.
- There must not be any subsequent receipts with the same
migrated_atindex after the one with the
finalflag set to true.
- The amount field of the previous
TreasuryOutputminus the sum of all the newly migrated funds must equal the amount of the new
Legitimacy of migrated funds
While the syntactical and semantic validation ensures that the receipt's integrity is correct, it doesn't actually tell whether the given funds were really migrated in the legacy network.
In order validate this criteria, the node software performs following operation:
- The HTTP API of a legacy node is queried for all the
tail_transaction_hashes, the addresses and their corresponding migrated funds.
- The node checks whether the funds within the receipt matches the response from the legacy node.
- Additionally, if the receipt's
finalflag was set to true, it is validated whether all funds for the given legacy milestone were migrated by looking at all the receipts with the same
If the operation fails, the node software must gracefully terminate with an appropriate error message.
In an optimal setting, node operators choose to only ask their own deployed nodes in the legacy network.
After successful receipt validation, the node software generates UTXOs in the following form: A
SigLockedSingleOutput is allocated with the given
ed25519_address and the funds as the deposit. As there is no actual transaction which generates the UTXO and normally a UTXO ID consists of
transaction hash | the output index, the milestone hash of the milestone which included the receipt with the given funds is used as the
transaction hash. The
output index equals the index of the funds within the receipt (this is also why the receipt is limited to 127 entries). This allows to easily look up in which milestone these funds were generated in.
If one wants to audit the UTXO path of an input, it means that milestones need to be kept forever as they're needed to recognize that a certain output was generated by it. However, this can be offloaded to a 2nd level service.
All the generated
SigLockedSingleOutputs from the migrated funds are then booked into the ledger and the new
TreasuryOutput is persisted as a UTXO using the milestone hash of the receipt which included the
Treasury Transaction payload.
For transparency reasons, the IF offers software which renders a dashboard showing details throughout the entire migration process:
- A list of outstanding funds residing on migration addresses to be migrated with the milestone index at which they were created.
- Migrated funds.
- Generated receipts.
At current legacy network ledger size of 261446 entries (addresses with ≥ 1'000'000 tokens), it would take min. ~2058 receipts to migrate all funds. While theoretically the max message size allows for more entries to be within one receipt, it is limited by the fact that the index of the migrated address within the receipt is used to generate the
output_index of the generated
SigLockedSingleDeposit (further explained below).
Assuming the best case scenario in which all 261446 entries were sent to migration addresses in the legacy network, these funds could therefore be migrated into the new network within ~5.7h (at a 10 second milestone interval). Of course, in practice users will migrate over time and the receipt mechanism will need to be in place as long as the new network runs.
If looked at the receipt validation from a higher-level, it becomes quite apparent that this is analogous to previous global snapshots where users would post comments on a PR on GitHub saying that they computed the same ledger state, just that it is more granular and automatic while still leveraging the same source of truth: the ledger state/database of nodes.
- The local snapshot file format needs to also include the to be applied receipts and supply information.
Copyright and related rights waived via CC0.