ICRC-1 Account Human Readable Format

@skilesare I think you misunderstood @Zedonboy . We like the legacy 32-byte AccountId. We do not like the public token account address to be “principal” only, or “principal.subaccount”.

None of you have made convincing argument that changing from 32-byte AccountId to “principal.subaccount” was a good idea. Besides, now very unfortunately we are forever stuck with both legacy AccountId and “principal.subaccount” when working with ICP.

I think pretty much we can all agree that changing AccountId to “principal” only was a bad idea, as was done by some previous token standards.

What would that be? You mean a ledger that accepts just plain ETH addresses, no subaccounts?

Why would you do that? Why not leave everything in the legacy accounts and have the ICRC-1 interface on top of it, mapping to the legacy accounts, as is the state of the ICP ledger now? The ICP ledger already accommodates both kinds of address.

You could define an extension to ICRC-1 which is an interface that accepts the legacy addresses. Even an already running ICRC-1 ledger can support this extension by migrating its internal database in a one-time migration event. Offering both interfaces in the same ledger is the best way to see adoption rate.

2 Likes

I’ve only poked around it and drawn some mental pictures in my mind, but the idea would be to maintain a map of ETH addresses to internal accounts and have a standard RPC API endpoint on your canister that would allow access to those funds from standard metamask/evm wallets. It is on the list of things to explore at some point.

Why would you do that? Why not leave everything in the legacy accounts and have the ICRC-1 interface on top of it, mapping to the legacy accounts, as is the state of the ICP ledger now? The ICP ledger already accommodates both kinds of address.

The current ICP ledger has both interfaces, but back-end storage causes problems. There are two kinds of storage 1. The Blockchain and 2. The account tree keyed by the legacy account id. The purpose of migration for the back end would be to eventually remove the false layer of privacy that is there and have things accessible and identifiable by principal. See the current issue we’re having with ICRC2 where on ICP you can’t find your approvals via either the blockchain or via an endpoint so there is a security risk for anyone purchasing a principal that may have ‘ghost approvals’ that you don’t know about. I’m not necessarily suggesting we do this as I think there are plenty of client-side ways to handle this different kind of account structure problem, but it could be done.

The logic of this was discussed extensively during the design of ICRC1. There were plenty of reasons, including your argument that it would be much easier if we only had one number. It is a bummer that the ICP ledger launched with the account id as a one-way hashed version. It is an even bigger bummer that ETH style addresses were not used as they both use ecdsa signatures and the ETH standard was very well established at the time.

The consensus at the time was that a large mistake had been made with legacy account ids and that since almost all tokens that were going to be created were in the future, it made sense at this time to correct the mistake.

One can, and most will be able to use ‘just principal’ and never have to bother with account ids. Wallets should adjust and prioritize the principle at this point. Just for get that account id ever existed and build your applications and wallets that way. Developers that are building canisters that need to look up information in sub-accounts on ICP will still have to jump through some hoops, but libraries exist for that.

You seem to be completely ignoring canister side of things. Using only principal ids as account address is a very bad idea.

Say I am making a canister that automatically takes user’s USDC deposit and turns them into ckUSDC managed by the canister on the user’s behalf. Inevitably I’ll have to let canister call the ckERC20 minter. Alas! This minter only supports minting to principal ids (one ID as you wished for) but not principal id + subaccount! Seems I’m completely stuck and have to abandon this idea of managing the USDC → ckUSDC workflow on user’s behalf. I’m all ears if you have a practical solution.

The consensus at the time was that a large mistake had been made with legacy account ids

Maybe it was a consensus among the committee creating ICRC-1, definitely you don’t have consensus among all developers, including some prominent ones (e.g. AstroX or Deland Labs).

You still haven’t made convincing argument why legacy account ids were bad. I just gave you a reasonable example why principal id only was bad.

1 Like

As for why switching legacy account id to principal.subaccount account id was a bad idea:

  • Maybe devs didn’t have to deal with two forms of account ids for ICP (which dominates all token transfers being made on the IC network, even today)?
  • Maybe devs didn’t have to wait a year and half before getting ICRC-1 support for rosetta client?
  • Maybe the foundation’s engineers wouldn’t make the mistake of not supporting subaccount for ckETH/ckERC20 minter? (and blocking devs who need this feature by another year or more?)
  • Maybe IDs can fit into existing conventions a bit more easily? (E.g. Most zkCircuits use data of 256-bit long, and one would have to modify existing circuits if they need to support a two part ID like principal + subaccount).

I’ll save the privacy argument for another day. But anecdotally, it was obvious that this change was quite costly with no clear benefit.

I tried to find evidence from the old forum thread, working group minutes and other material as to how the switch from account ids to principal.subaccount pairs came to be. Unfortunately, I couldn’t find the transition point. At some point in May 2022 the topic of which one to use is still completely open between the two options. Then the discussion switched to a Discord channel to which I have no access or which may not exist anymore. And at some point in June it had been decided that the principal.subaccount pairs are being used and the discussion from thereon is about other aspects. I can’t tell which argument was responsible for the shift.

Comparing the two options and looking for advantages of each we should should look at only the bare standard which ends at the ledger interface. We should ignore the encoding of the account identifier which is really just an exchange format for sending a payment destination to another user. For example, to illustrate the difference: one could have defined the textual representation that we now have for ICRC-1 accounts, which looks like this h5boe-vqaaa-aaaam-ac36q-cai-ckw43vy.6867bf53ffcaa0cd11f3bb59be8e4d9e71b20d096cc822a5dffecc7561b026ce, as an exchange format, not requiring any new ledger standard, and could have used it for making payments on the original account id based ledger.

So if we focus on the differences between the two standards, ending at the ledger interface, then we see:

  • account ids are shorter, 32 bytes (or without checksum 28 bytes)
  • principal.subaccount pairs are longer but contain more information, most importantly the principal who controls the account

The first difference is important because 32 bytes is often an important barrier, so ICRC-1 gives something up here and account ids have an advantage.

Let’s leave the privacy issue out of the discussion and leave it for another day.

Slight advantage of the principal.subaccount pair at this point is that it requires less tooling/dependencies on the client side because you don’t need to have the code to calculate the hash correctly.

What other advantage can principal.subaccount have, if any? It depends on what the ledger does with that information. For example, does it need the information because it’s internal balance database requires it? Or does it need it to write the information into the generated blockchain?

Let’s discuss the database first. In a single-canister implementation the internal database layout is irrelevant. Whether it is keyed on account ids or keyed on the explicit principal.subaccount pair, or keyed in a hierarchical way on principal first, then subaccount, makes no difference. All ways are possible to get a functional ICRC-1 compliant ledger. In a sharded ledger on the other hand, there could be some advantage to keeping accounts that belong to the same principal on the same shard. But I somehow doubt that sharding was a consideration in the discussion.

Writing the information to the blockchain is the only reason left and that was for sure a goal of ICRC-1. It wanted the explicit principal.subaccount pairs in the blockchain. And it wanted all of them in the blockchain, not only some on an opt-in basis. Because for the opt-in basis the solution would have been to keep the account id interface in place and to extend it with another interface that users who opt-in can use. The reason must have been transparency.

That’s basically the entire trade off here: giving up the <= 32 byte length for transparency and slightly simpler client code.

The weird thing is that ICRC-1 specification does not mention the blockchain and the fact that the full principal.subaccount information is to be written into it. So the real intention of the standard is not mentioned in its specification. I have been critical in the past of the ICRC-1 spec as being underspecified in the sense that it does not talk about semantics, only about the interface. A standard has to include semantics or it’s not a standard. This is another example of this fact. The spec should say that the ledger MUST maintain a blockchain and MUST write the principal.subaccount pair of every transaction into it. For, if it wasn’t like that, then we could have just used the account id based ledger and defined principal.subaccount pairs as an exchange format.

Regarding the trade-off mentioned above, I can understand the trade-off because I can make a lot of arguments for transparency and find that feature very useful. Whether if it is was really worth making a new standard for it after the account ids were already out in the wild is another question.

I meant the ckETH minter could support deposits into 16.7 million subaccounts per principal, those subaccounts that consist of 29 zero bytes and 3 free bytes. For some applications that is enough.

On another note, why is it important that the ETH smart contracts isn’t upgradable? Can’t an upgraded version of the ckETH minter canister not use a second ETH smart contract? Maybe that second ETH smart contract can then emit two events instead of one to get 64 bytes of data instead of 32 bytes, does that work?

Yes, that could be a solution. Though the previous ETH smart contract will still need to be supported for a long time to come (until all existing frontends switch to the new smart contract). My point is not it is difficult to fix this, but how long it will take, given the foundation seems to have a million other priorities.

I’m absolutely not ignoring it. I’ve been one of the largest proponents of derived canister IDs as a resolution to this problem since the discussions started.

I had a call with Psycadelic who was one of the biggest dissenters from the direction that ICRC-1 was going and had been a principal-only advocate since day 1 on July 26th 2022 where I explained the exact problem you are mentioning. Custody of funds should be kept by canisters in distinct accounts. Together we identified that there was an existing derived canister id pathway in the specification that could give a canister multiple principals. They presented the thread here: DIP20 Community Proposal (PsychedelicDAO)

Soon after they left the ecosystem and there was not enough follow up. In an ideal world we would have implemented derived canister ids and ICRC1 would not have needed a subaccount. The principals of a canister would have been derivable via a standard BIPXX like interface and users would have been left with a simple principal-only interface.

I’ve tried bringing these back up multiple times such as here: Derived Canister IDs

The meetings were open and all were invited to participate. Those who didn’t participate forfeited their voice. The discussions took place over months and all the steps along the way were published. Attempting to backtrack two years after the general consensus of those who cared enough to show up is counterproductive.

Here is why account ids are bad: You can’t get rid of the concept of principals due to canisters needing them therefore you end up with two identifiers(accountId for something, principals for others). No one wanted two identifiers. It was confusing to developers, wallet creators, and users. There was an absolute consensus on making sure we only had one kind of identifier and unless we had derived canister IDs it was going to have to be principal + subaccount if we wanted canisters to hold funds in separate custodial accounts(as you have just mentioned). We also couldn’t revert to only account ID because it is a one-way hash and we would have had to keep a dictionary of all one-way hashes on all replicas(at the replica routing level) and that was seen as an exponential and untenable solution.

Sending tokens to a canister that you know as a principal at a different account id is confusing. No one wanted that anymore. It was ubiquitous and complete agreement.

If you can come up with a way to use account ids in place of canister id/principals and solve the problem of needing a translation table for routing at the replica level then please present it.

Other wise you’re just asking to perpetuate a system that everyone seemingly agrees is confusing by having both account ids and principals.

@timo: I’ve tried to copy/paste the discord history here: https://docs.google.com/document/d/1V3DzYIlucX2xXVtmVP5BXDQlu_XNryaHgcS6Ai8CS0c/edit?usp=sharing

It doesn’t do a good job with images, but most of the discussion is there.

This is an artifact of needing to get something out of the door. The basics of ICRC-3 that specify that had been discussed but it was agreed to move it to a different document as to not slow down the release of the initial ICRC-1 standard as DFINITY was waiting on the standard to release the SNS.

1 Like

Thanks for posting this in-depth comment, reading about the consensus to not want two ids makes a lot of sense.

I also agree that backtracking the last years of discussion isn’t viable and also that the risk taken at the time to make the switch seems to be worth it in the long run.

Even though it will take a while for the switch to ICRC-1 to appear outside the IC ecosystem (e.g. centralized exchanges). In practice all major ledgers support the ICRC-1 standard.

The remaining issue is with existing tooling that hasn’t updated yet. Though some of this tooling hasn’t been updated and maintained for a long while in the first place :sweat_smile:

I’m aware from fellow devs that there is a large push from existing tooling and new upcoming tooling to implement and support the ICRC standards, but development takes time, so right now we’re a bit in an inbetween phase of tooling.

I for one, would not go down this path. It will confuse the hell out of developers from being able to tell two canisters apart.

For me and most developers, the distinction between sending tokens to a canister and sending a message (with attached cycles) to a canister is very clear. They do not require the same type of address. Confounding the two would be a dangerous path to take. Besides, the former (i.e. sending token to canister) is only a conceptual thing, and the real message is sent to the token ledger, not the target canister.

I beg to differ. AccountId was never confusing to end users in the first place. End users would not need to deal with or even know about principal ids (had canister ids not appear in the URL that is. With custom domains, this is no longer a problem).

Nor are they confusing to wallet creators. It was actually beneficial not to confuse a token address with an account id in developing or using a wallet.

If I’m not mistaken, the so-called “confusion of two identifiers” only started when some misguided devs put out NFT standards that used principals as addresses. And instead of fixing that mistake, the ICRC standards further solidified it. There is no chance that legacy AccountIds will be phased out, ever (for this reason, I think we should stop calling it “legacy”).

I’d say an additional confusion users often had was that they cannot accept NFTs using their NNS wallet address. Maybe those NFT standards purposely used principal ids to prevent people from mistakenly sending NFTs to AccoutIds? I am not sure. Unfortunately it didn’t help much either.

A wallet identity has a principal and can make calls with that principal the same as a canister can, there’s no difference there.

From the perspective of the ledger the principal of the incoming call could be either from a canister or a wallet (frontend created keypair), there’s really no difference here whatsoever.

The additional hashing layer added on top of the principal does not hide this incoming principal from the call that the ledger receives. It’s anonymity by obscurity, in reality this your wallet principal is publicly broadcasted across the IC nodes and also received by the ledger.

So the old account hash format is only a layer added on top of what’s already there in the core IC implementation for both canisters and wallets.

The only valid argument for wanting the old account format would be concerns regarding the migration from one format to another.

The old format never did really give you any form of anonymity. If you want you want separate anonymous accounts, you must create different/derived keypairs that result in different principals. This is basically the same as any other chain.

I’ll just quote myself from an early reply to Timo

To sum it up, I think ICP made a lot of right assumptions from the start, including challenging the “public by default” norm taken for granted by other chains. Not designing things assuming “public by default” is very different than designing things assuming “private by default”. Your criticism was on the latter. But no, people who prefer AccountId are not delusional in thinking we already have perfect data privacy; we merely are asking please do not design things by assuming “public by default” will always be the case.

1 Like