Decentralized Names on the Chia Public Blockchain

Fizpawiz
20 min readAug 25, 2023

--

First, although I work for Chia Network Inc, the ideas and opinions presented on this Medium account are entirely mine and do not necessarily reflect those of my employer.

In this article, I will present a proposal for associating human-readable names with singletons on chain in a fully-decentralized way. I am no longer a developer, and cannot efficiently implement these ideas myself. Still, I understand the Chia technology well, and I hope that this article provides a starting point for someone to implement a fair, efficient and completely decentralized name registry.

So first off, what is the goal? What would such a registry accomplish?

  • Anyone should be able to register a name for something they own
  • No one should be able to register a name for something they don’t own
  • No individual or group should be able to prevent someone from registering a name, nor forcibly take a name that has already been registered
  • There must be some cost to registering a name
  • No single individual or closed group should profit
  • It should not be possible to identify the name being registered before the registration is successfully completed
  • The registry should not depend on ongoing maintenance or support from any particular individual or group
  • There should be some incentive to release a name when it is no longer needed
  • There must be a way to transfer a name
  • There must be an efficient way to read the current state of the name registry, and to detect changes
  • There must be an efficient way to prove ownership of a name, both on chain and off chain
  • Ideally, there should be a way to nest name registries to create namespaces
  • Ideally, there should be a way to create permissioned namespaces
  • Ideally, there should be a way to support a fully decentralized TailDatabase

With these goals in mind, what would a solution look like? First, it will need to be a fully on-chain, permissionless protocol because any centralized solution would give the service provider the ability to choose which requests to honor, and thus be subject to censorship. Second, there will need to be open-source tools available to interact with that protocol so that wallets can integrate the necessary functionality.

Here is the user experience I hope to achieve:

  • A user decides to create a name for a singleton. For example to associate a name with an application stored in a DataLayer table (a “chapp”). Or to associate the name with a DID, such as for a NFT creator.
  • The user writes the request to add the name to the blockchain in a way that includes the hash of the name, a deposit in XCH that will be held to secure the name and a tip in XCH for whomever records the name. To create the name, they will need to demonstrate ownership of the singleton that will be named.
  • At some point thereafter, someone will see that there are several outstanding requests, and will use the on-chain requests to update an on-chain registry, and will accept the offered tips for doing so.
  • Once registered, the requestor reveals the name registered
  • Wallets will be able to detect the update to the registry singleton, look at how the registry was updated, and apply the same updates to their internal copy of the registry.
  • Later, when the user decides they’re done with the name, they’ll write another request to the chain to delete the name and recover the deposit. They will again provide proof of owning the singleton. They will again include a tip, and at some point soon after, someone will execute the request and accept the tip.

I really think it can be that simple for the users.

Overview of Proposed Implementation:

Everything is easier to understand with a diagram. Hopefully that even applies to text-heavy diagrams like these:

What follows in the technical description section is a more detailed discussion of how I envision all of the above to work.

After that, we get into a few more interesting topics, such as:

  • Namespaces, such as “fizpawiz.chia”
  • Permissioned namespaces, where a namespace administrator can assogn names
  • And finally, an approach to a fully-decentralized TailDatabase(!!!)

At the end of this article, I also listed some open questions, most with proposed solutions, but I’d like to get feedback on them.

Technical Description:

What follows is (I hope!) a clear description of the proposed protocol that can be reviewed to understand exactly how it would work. Hopefully, readers will be able to assess the feasibility and security of the proposed solution, and if it looks good, use it as a template for implementation. Clearly describing code in English is challenging to say the least, and I’m sure I’ve made some minor errors, but I think the basic technique is sound.

The technical description does get a little detailed. If you don’t have time now to grok your way through the details, please do skip down to some of the later sections about how to read and use the name registry, how to create nested namespaces, and how to apply the same general approach to Tail Database.

To start, I think it is useful to be very clear about the terminology I’m using:

  • Deposit amount: amount currently required to reserve a name. Matches the 1/8th coin amount currently being rewarded for finding blocks (which declines at halvings)
  • Tip amount: amount paid to whomever actually constructs the spend to update the name table.
  • Name: a name, which must be an atom and may need other restrictions (see “Open Questions” below).
  • Target singleton: the singleton to be referred to with a name. Note these do not need to be DIDs but can be any singleton: DataLayer, NFT, etc.
  • Launcher id: every singleton is identified by the id of the coin used to create the first generation of the singleton. The launcher id never changes through the generations of the singleton.
  • Name record: name hash, target singleton launcher id, deposit amount
  • Name table: table of name records
  • Name table merkle root: when the name table is represented as a merkle tree, this is the root of that tree that is stored on chain
  • Action: Either “insert”, “delete” or “rotate”
  • Request coin: a coin created by the actor submitting a request for an action that includes the name hash, the action to be performed and tip amount, among other things.
  • Transfer sibling: when two request coins are submitted to effect a transfer, one to delete the name and the other to insert it again with a different singleton launcher id, then each request coin considers the other its transfer sibling.
  • Deposit refund receive address: the address to which the deposit refund should be sent when deleting a name.
  • Ephemeral coin: in this context, this is an ephemeral coin that requires an announcement from the target singleton to spend and which is the only way to create a valid request coin.
  • Action record: The action record will include the name record, the action to perform, the transfer sibling coin id and the hash of the remaining parameters curried in to the request coin. If “insert”, then also includes the table records for the prior and subsequent records, sorted by name hash, along with the merkle paths for each. If “delete”, then also includes the merkle path for the table record to be deleted. If “rotate”, then includes the table records and merkle paths for two sequential records, where the lengths of the paths differ by at least two.
  • Name registry singleton: the singleton hosting the name table
  • Executor: the actor that actually constructs and submits the spend of the name singleton to update the name table. Can be anyone.
  • Executor receive address: the receive address of the executor to which tips are paid.

Coins:

  • Ephemeral coin. Making a request for an update to a name registry involves two coins: an ephemeral coin and a request coin. The ephemeral coin has a fixed puzzle, with the name singleton launcher id curried in, which makes it easily found on chain. When spending the ephemeral coin, the solution requires the target singleton launcher id, target singleton mod parameters, the requested name hash, action (only “insert”, “delete” or “rotate”), transfer sibling, deposit refund receive address, tip amount, current block height and its own coin id. If “insert”, the deposit amount will be confirmed to be correct for the given block height, and assert_height_absolute will be used to confirm the given block height is not in the future. It will create a condition to assert a puzzle announcement from the target singleton of the ephemeral coin’s coin id, name singleton launcher id, the requested name hash, the action, the transfer sibling, deposit refund receive address and the tip amount. This ensures the requestor owns the target singleton and that the request parameters were not changed by a malicious farmer. It will create a request coin as the only child. The coin amounts for both the ephemeral coin and the request coin must be the sum of the deposit amount (if “insert” action) and the tip amount. The ephemeral coin will emit an assert_ephemeral condition to ensure it always has a child when recorded on chain.
  • Request coin. The request coin will have the name singleton launcher id, target singleton launcher id, requested name hash, action, transfer sibling, deposit refund receive address, tip amount, current block height and grandparent coin id curried in. It will have two spend modes: abort and execute.
    Abort mode: When the requestor chooses to abort, the request coin will and the target singleton mod parameters in the solution. It will assert a puzzle announcement of this coin’s id and “abort” from the singleton with the curried launcher id and creates a child coin with the deposit and tip amount as a “pay to singleton” coin that the singleton can claim.
    Execute mode: When the executor spends the request coin to execute the request, the coin will accept the name table merkle root in the solution. It will emit a condition asserting that its parent coin had the ephemeral coin puzzle, and that its own amount is the sum of the given tip amount and current deposit amount. It will assert a puzzle announcement from the name singleton of this coin’s coin id. It will create a puzzle announcement with the message “exec”. If the transfer sibling is not nil, the request coin will assert a concurrent spend of that coin id. If the action is “delete”, then a child coin will be created in the amount of the deposit to be refunded, assigned to the given deposit refund receive address. No child coin is created if the action is “insert”.
  • Name singleton. Each name registry is a singleton, and has a curried root hash for the name table, the standard transaction mod hash, and the request coin mod hash. On every spend, it accepts the executor public key, total tip amount and the list of action records in the solution. A minimum of 10 action records is required to spend the coin, and all action records must be sorted by name hash and action, and must be unique. It will emit an agg_sig_me of the entire solution using the given public key. The value of the coin is the total deposit amount for all included records. The value of each generation of the singleton is updated based on the deposit amounts in the actions processed. The total tip amount will be output as a coin to the executor receive address, calculated using the given public key and standard transaction mod hash.
    For each “insert” record: The puzzle will confirm that the given prior and subsequent name hashes sort before and after the given name hash and do not match the given name hash. It will confirm the hashes of the prior and subsequent name records match the start of the corresponding merkle paths, and confirm that the prior and subsequent merkle paths both correctly reach the current merkle root. It will identify the lowest common node in the merkle paths, and insert a node as appropriate and then recalculate the merkle root. It will create a puzzle announcement of the request coin’s coinid. It will assert a puzzle announcement from the request coin of the message “exec”.
    For each “delete” record: The puzzle will confirm that the merkle path given exists in the merkle tree and that the record to delete does not have a zero deposit amount. It will promote the peer to delete the node and recalculate the merkle root. It will emit a create_coin to p2_singleton for the deposit amount so that the owner of that singleton can receive the returned deposit. Finally, it will create a puzzle announcement of the request coin’s coin id and assert a puzzle announcement from the request coin of the message “exec”.
    For each “rotate” record: The puzzle will confirm that the given name records match the leaves in the associated merkle paths. It will also confirm the merkle paths are correct and that the merkle roots match the current merkle root. It will find the lowest common node in the two paths — that will be the pivot node. It will confirm that the first leaf is the rightmost child of the left branch of the pivot node and that the second leaf is the leftmost child of the right branch of the pivot node. It will also confirm that the merkle path lengths differ by at least two. It will determine whether to do a left or right rotation based on which path is longer and then calculate the new merkle root from the appropriate child of the pivot node.
  • Name reveal spend mode: Once a name hash has been inserted into the name registry, a name reveal spend will allow the owner to announce the name associated with the name hash. The solution requires the name record, the merkle path to the root, and the name. First, the name will be verified to be valid, for example that it includes only valid characters. Next, the puzzle will verify that the given name matches the name hash in the name record. Next, it will verify the hash of the name record matches the starting hash in the merkle path. Next it will verify the merkle hash is valid. Finally, it will verify the end of the merkle path matches the curried root. If all of these checks are successful, then the spend will complete successfully with no output conditions. Note the upcoming singleton spend aggregation feature of the Chia blockchain will prevent this from becoming a DOS vector on the name registry singleton.
  • Proof of name alternate spend mode: To support proving that a name is associated with a particular singleton on chain, another alternate spend mode should allow anyone to spend the name singleton with a solution that includes a name record and the current merkle path of that name record. The coin will verify the name record hashes to the starting value in the merkle path, that the merkle path is valid, and that the merkle path ends in the current root. If so, it will create a puzzle announcement of the name and the associated singleton launcher id. Note the upcoming singleton spend aggregation feature of the Chia blockchain will prevent this from becoming a DOS vector on the name registry singleton.
  • Nested namespace alternate spend mode: To support cascading name registries, the name singleton should have an alternate spend mode that accepts in the solution the ephemeral coin’s coin id, name singleton launcher id, the requested name hash, the transfer sibling, deposit refund receive address and the tip amount (but not action) and emits the necessary puzzle announcement to facilitate creating a request coin to add the current name registry as a named singleton in another registry. To avoid DOS attacks, this spend mode should assert_concurrent_spend of the ephemeral coin. Note there should be no way to create the necessary announcement to request removing the singleton from the parent name registry.

Deployment

To deploy a name registry, construct a starting merkle tree with two nodes that are the minimum and maximum possible name values, nil target singleton launcher ids and zero deposits. Launch the name singleton with this merkle root.

To deploy a nested namespace, first deploy the nested namespace as a normal namespace, and then use the alternate spend mode to create the appropriate request coin to add it to the higher-level namespace.

Updating the Name Singleton

Anyone can be an executor and make an update to the names singleton. They will earn the tips from the requested actions for their efforts. They will need to pay the blockchain fee to get the update included on the chain from the tips earned.

The executor will monitor the chain for request coins by puzzle hash. When there are at least 10 outstanding requests, they can create a spend of the names singleton. Requiring a minimum number of requests and requiring they be unique will deter those that would otherwise attempt to spend the name singleton for every action request.

The spend bundle will include all of the associated request coins. These coins do not create output coins but spending them releases their value. That value can then be captured by the name singleton for deposits on insert, or paid to the executor as a part of the tip.

When constructing the merkle paths in the action records, remember that each action updates the merkle root, so the merkle paths in each subsequent action should be constructed from the root output by the prior action.

Executors are expected to include appropriate rotations in every update to keep the tree as nearly balanced as possible. Hopefully, by including that behavior in the open-source executor driver, most executors will contribute to maintaining the tree in a balanced state.

Reading the Name Singleton

By wallets, offchain: The current set of name records can be read by reading the solutions provided to each generation of the singleton. Anyone with access to a full node can “follow” the singleton to get this sequence of solutions.

On chain: The second alternate spend mode of the name registry singleton allows it to create announcements of valid names and their associated singleton launcher ids. It is straightforward to imagine a “pay to name” puzzle that would have a name registry launcher id and name hash curried in, (along with several necessary mod hashes). To spend a “pay to name” coin, a solution would need to be provided with the current name registry root hash and a singleton launcher id. The coin would only successfully spend if it could assert an announcement from the name registry singleton that the name was associated with the given singleton launcher id. If so, it would emit a create_coin with a “pay to singleton” puzzle that could only be spent by an announcement from that named singleton. This would only really be useful in cases where names were expected to be transferred.

Concise Name Proofs

If all you want to do is confirm that a given name is currently valid and references a specific target singleton launcher id, the prover can provide the name record and the current merkle path for that name. The verifier can check that merkle path starts with the hash of the given name record and then confirm the terminal hash in the path matches the current root hash on chain.

The prover can also provide the current name singleton parent coin id and amount and so deliver a concise name proof. The verifier can easily calculate the coin id for the name singleton from the given proof and easily ask any full node if that coin exists and is unspent to verify the proof.

A useful service would be one that provides an up-to-date table of all of these concise name proofs for every name in the table. That way, anyone that knows the current on-chain root hash can use the table to quickly check any name.

The table above can be audited for completeness by overlaying all of the provided merkle paths and confirming there are no gaps, thus preventing censorship by omission.

Another useful service would be a name lookup service that you query with the name and it provides the above proof.

Cascading Names / Namespaces:

Namespaces. Since the name singleton associates names with other singletons, we can easily construct namespaces. For example, the top level name registry singleton can have a named singleton “chia”. The singleton associated with the name “chia” can also be a name registry singleton. In that lower-level name registry, I can have the name “fizpawiz”. In that way, I can use the name “fizpawiz.chia” as a way to indicate a singleton that represents “fizpawiz” and is a part of “chia”.

Namespaces based on intent. Some namespaces may be used to describe the intent of the referenced singleton. For example, the top-level name “did” might indicate that all of the entries in the next lower-level name registry are all DIDs. Same with “.dl” for DataLayer tables, “.nft” for NFTs, etc. Similarly, top-level names can be more semantic than descriptive. So “.site” might be an html-based site stored in a DataLayer table. Also, “.chapp” might be an app similarly stored in a DataLayer table. Or “.python” might be a python library made available through DataLayer.

Can you imagine opening a browser and typing “chia://cadt.chapp” and going directly to a version of the Climate Action Data Trust hosted locally from source files confirmed to exactly match those provided by the CADT Council? Or reference “chia://leftpad.js:5” to exactly use the 5th generation of the “leftpad” library Datalayer singleton in the “js” namespace, which will always be available and immutable thanks to the blockchain and mirrors. Not that you’d want to, but you could even emulate the existing DNS name structure: “chia://www.chia.net” which would involve two namespaces, “net” and “chia”, with a DataLayer singleton at the “www” name.

Concern: cyclical graph. A name singleton can be added a to itself with a name, which introduces a cycle that could cause some readers of the graph to enter infinite loops. Clients of the name registry will need to take care to avoid this situation.

Concern: the “.” character. If a name is allowed to have the “.” character, then names can be ambiguous. “fizpawiz.chia” could be either one name, or a cascaded name. We will probably need to disallow “.” in names.

Permissioned Name Registry

Organizational names. A version of the decentralized name registry could be created that is not decentralized and is instead owned by a particular entity in one way or another. These permissioned name registries would not need ephemeral or request coins, nor minimum numbers of action records nor deposits, nor name hashes and reveals. They would allow the owner of the top-level name to choose who receives names in the lower-level name registries. Thus, for example, “fizpawiz.chia” could only be assigned by the owner of the “chia” top level name. Users of this name would know that “fizpawiz” was a member of “chia”.

The “@” character: The “@” character could be used when transitioning from a higher-level namespace to a lower-level, permissioned namespace. So you’d get “fizpawiz@chia”, where “chia” is a name in the top-level namespace and “fizpawiz” is in the permissioned namespace. This would also introduce the need to disallow “@” in names.

TailDatabase:

The above name registry mechanism doesn’t work for TailDatabase because the thing being named is not a singleton that can be spent by the owner to request the name.

A global TailDatabase in this model will require an update at mint, which can be detected by detecting the “eve” coin. There is no way to directly detect the “eve” coin. So, to detect a mint event, we will use a “pre-eve” ephemeral coin in the mint. The only child of this coin is the eve coin for the CAT. The TAIL hash and inner puzzle are also passed in so the pre-eve coin can confirm the child coin will be a CAT. Since the pre-eve coin is not a CAT, this is a mint event.

The pre-eve coin will also require a non-refundable “deposit”, calculated in the same way as the name deposit, to defend against maliciously polluting the CAT tail database. This can be accomplished by sending that amount to the brick address. I think this will require a larger amount than for names, such as the current amount of the 7/8ths coinbase reward.

The pre-eve coin will create a puzzle announcement of the TAIL being minted, along with the hash of a metadata record containing the CAT name, code, description, image NFT launcher id and tail reveal, as passed in via the solution. Be aware that a malicious farmer could alter the solution while the spend is in the mempool, so the driver code to mint the CAT with a tail database update should confirm that the correct metadata hash got recorded in the TailDatabase update after the transaction.

If the metadata hash was recorded correctly, the tail datatabase singleton will allow a metadata reveal spend, like the above name reveal spend, that allows the owner to announce the name, code, description, etc. The metadata reveal spend will also verify that the metadata is correct, e.g. that the name has only valid characters, that the code is appropriately short, that the NFT launcher id is 32 bytes, etc.

The actual tail database will function similar to the name registry described above, with the CAT pre-eve coin functioning as the request coin. The requirements for tips and a minimum number of updates per spend will not be necessary since the minter is the tail database updater.

Note that I do not propose any mechanism to ensure that the tails themselves are unique. As far as I can tell, that would require that a complete second merkle tree of tails would need to be maintained with every insert, and that feels like excessive overhead for little benefit.

To use this mechanism, we will need to create a new CAT minting tool that will update the taildatabase at mint time.

We will need to “seed” the tail database singleton root hash with the current contents of the existing TailDatabase.

A tail lookup service as described for names would be a valuable service. Also a reverse lookup to fetch all of the names recorded for a particular tail.

Open Questions:

  • Should the name registry require names to consist of characters from a particular character set? If so, would that exclude certain populations?
  • I think we should disallow some characters, such as “.”, “@”, “:”, “/” and “!”. Likely some others also, like quotes and all kinds of braces. Maybe a name registry should only allow alphanumerics? How can we efficiently check for alphanumerics vs symbols on chain? Especially in expanded character sets to be more inclusive of global languages?
  • Case issues could be a problem. Many will expect “fizpawiz” and “Fizpawiz” to reference the same person. Is there a way to efficiently check that a name is all lowercase? Or upper, doesn’t matter. We just need to make sure we don’t have a name accidentally or maliciously created that differs only in case from another name. A concern on this: if I recall correctly, the romanizations of some languages that natively use non-Latin alphabets treat the upper-case and lower-case letters as representing different characters in the native language. Thus, ignoring case strangely limits the ability to correctly express names in those romanizations. I don’t know how to solve for that.
  • Should the name registry attempt to prevent the same target singleton from appearing with multiple names? I don’t think so. I don’t think the problem is worth the solution, as it would require a second merkle tree to assure uniqueness of target singleton launcher id.
  • I don’t think there is any way to prevent typo squatting. It gets worse the larger the allowed character set. This is a real concern, but allowing anyone to censor name registrations is a slippery slope. I would like to propose, as a potential solution, that any user interface that displays a name prefix it with a LifeHash of the singleton launcher id.
  • For the same reason, I don’t think there is any way to police offensive names. Preventing censorship includes putting up with things you don’t like.
  • I don’t think there is any way to prevent name squatting. Once someone owns the name (e.g. “Microsoft”, owned by some dude not affiliated with Microsoft) there is no way to take it away from them. That’s good. And its going to be very upsetting occasionally.
  • Should there be an expiration mechanism? Offhand, I would say no, because we don’t want to take things away from people without notice, and we don’t have a way to notify them. A watchtower could help, but that provides an attack vector where a watchtower operator could maliciously omit notices to hijack a name. But not expiring names leaves the top-level namespace open to getting polluted with a lot of long-forgotten names that can never be recovered. Which may not be bad, actually.
    Thought: What if we create the top-level namespace to have very high deposit requirements, and immediately introduce a set of lower-level namespaces (e.g. “did”, “chapp”, “site”, “nft”, etc) that have lower deposit requirements to encourage people to use those namespaces instead of the top-level namespace?
  • There is an attack where a malicious farmer could steal the tip amount from the executor. I don’t know a way around it, but it will only happen when a malicious farmer actually farms the block with the name singleton update. Maybe it is a non-issue: the tips are not likely to ever be very high, so there is very little incentive for such malware to become prevalent.
  • Last open question: should this have been a CHIP?

--

--

No responses yet