aip | title | authors | discussions-to (*optional) | Status | type | created |
---|---|---|---|---|---|---|
36 |
Universally Unique Identifiers |
Draft |
Standard |
06/01/2023 |
This AIP proposes to add two new native functions generate_unique_address_internal
and get_txn_hash
in Aptos framework. The generate_unique_address_internal
generates and outputs a unique 256-bit identifier (of type address) for each function call. The get_txn_hash
function outputs the hash of the current transaction. The generate_unique_address_internal
function calls can run efficiently and in parallel. In other words, when two transactions run generate_unique_address_internal
method, they can be executed in parallel without any conflicts. Initially, we will use these unique identifiers internally as addresses for newly created Move Objects, specifically in Token V2 minting.
We also propose to add a wrapper function called generate_auid
which wraps the unique address generated by the generate_unique_address_internal
in the non-copy-able AUID struct type, guaranteeing that every two instances of AUID value are different.
There is a general need to be able to create unique identifiers or addresses. There is no such utility in Move today, and so various alternatives have been used, which bring performance implications. We want to provide such a utility for all usecases that needed it.
Concretely, when a new object is created, we need to associate it with a unique address. For named objects, we deterministically derive it from the name. But for all other objects, we currently derive it from a GUID (Globally Unique Identifier) that we create on the fly. A GUID consists of a tuple (address, creation_num)
. We create GUID by having address
be the account address of the object or resource’s creator, and the creation_num
is the guid sequence number of the object or resource created by that account. As the sequence number creation_num
has to be incremented for each object/resource creation, GUID generation is inherently sequential, within the same address. As an example, in Token V2 whenever a new token is minted using token::create_from_account
, object is created to back it, which uses GUID generated based on the collection address, and so all such mints from the same collection are inherently sequential.
This AIP thereby creates a new type of identifier called UUID (Universally Unique Identifier). This is a 256-bit identifier that is universally unique amongst all the identifiers generated by all the accounts for all purposes (and are different from other methods generating identifiers that use domain separation from Scheme
in authenticator.rs
). We propose adding two native functions create_unique_address
and get_txn_hash
in Aptos framework. Every time a MOVE code calls create_unique_address
, the function outputs a universally unique 256-bit value (of type address
). The function creates a unique address by using the hash of the current transaction.
As exposing the transaction hash might have more applications in the future, we also propose to add the get_txn_hash
function which returns the hash of the current transaction.
We additionally create object::create_object
and token::create_token
functions, utilizing new utility functions, to provide more efficient untracked token minting. Those deprecate object::create_object_from_account
, object::create_object_from_object
and token::create_from_account
functions.
We additionally propose to add create_uuid
Move function, which calls the create_unique_address
function and outputs a struct UUID containing the unique address. The address field of the UUID struct cannot be modified by other Move modules, nor the struct can be copied. As the only way of creating a UUID struct is by calling the create_uuid
method, any code that receives a UUID struct as input can be confident that the underlying address is uniquely generated, and no two instances of UUID will match.
The Aptos Framework contains a transaction_context
module, which can be used by MOVE modules to retrieve information related to the current transaction being executed. When a transaction is executed, the session first creates a context. The context contains, amongst other things, NativeTransactionContext
. The transaction_context
module provides an API interface to answer questions on NativeTransactionContext
.
For this AIP, when a session is created, we will store the transaction hash in the NativeTransactionContext
. We will additionally store a uuid_counter
in the context and initiate the counter to 0. We will add a native function called create_unique_address
in transaction_context
module. When called, the create_unique_address
function will first increment the uuid_counter
and outputs a value in the below format
output identifier = SHA3-256(txn_hash || uuid_counter || 0xFB)
For domain separation, new entry (0xFB
) is added into Scheme
in authenticator.rs
, which is appended to the end of the SHA3-256 input for domain separation, making sure to generate different identifiers from mechanisms and purposes defined there. Formula is equivalent to how output identifier is created from Guid(txn_hash, uuid_counter) in the 0xFB namespace.
Hash of SessionId
is used as txn_hash. Each user transaction contains the sender’s account information and sequence number that is incremented for each transaction. For BlockMetadata
transaction, hash of BlockData
is used (which has epoch and round number inside). And there should be only one genesis transaction.
This means each transaction accepted by Aptos consensus has unique set of bytes, and so produces unique transaction hash (assuming no SHA3-256 collisions).
Alternative place to do domain separation would be to have each callsite do their own domain separation. object.move
does domain separation for itself for existing uses, and it might be cleaner from it's perspective to keep UUID domain separation there. But that would require all other callers to deal with domain separation, and so we've added a commend into object.move
to make it clear the same domain separation is happening in native code instead.
If someone needs domain separation from a method that is not aligned with authenticator.rs
, they can do additional domain separation on top of the uuid returned.
Another alternative to the above create_unique_address
function, is to expose the get_txn_hash
and get_txn_unique_seq_num
native functions, and do hashing in move.
This feature is currently implemented in the PR aptos-labs/aptos-core#8401.
Above function produces unique identifiers that have no collisions within them (given no SHA3-256 collisions), or across other ways to generate unique addresses captured in authenticator.rs
- like all current GUID flows. But it isn’t “attacker-proof” (neither are the current GUID flows), i.e. if someone knows transaction hash, it can front-run and create a transaction that knows that object address. So in order to guarantee no collisions, scoping of who can create resources and not allowing input addresses can provide such guarantee. (i.e. object.move
doesn’t provide a function to create an object at a given input address, it always internally computes it).
Generated identifiers are random-looking, and only way to retrieve them later is to store their address from where you need to access them (or use events during indexing for off chain analysis). In contrast, creating uuids from names is a repeatable process, and can be looked up later without indexing of the handle. For this reason, at this time, we are replacing all functions within object.move
except for create_named_object
to use above utility.