Add primary key to account
Refactor the accounts to only optionally have an alias.
We still need to retain the current semantics of account structure, but most data should be stored in a separate location: hash-table keyed by the account's ss58 (or pub_key) address, which contains actual data, an account alias is only an interface to all of that.
relevant RFC
My plan
- Make
AccountIdsome string derived from the account signatories- Stay consistent with shared wallets (accounts such that
1 < account.signatories.len()) - Refactor the data access flow as
AccountIdlostDomainId - Absorb any other impacts
- Stay consistent with shared wallets (accounts such that
- Prototype aliases
Out of scope
The following are left to other issues:
- From ADR
- ~~Account alias~~
- Primary and secondary keys
- Account archival
- Key versioning
- Other concerns
- Any functionalities other than as an identifier
- Discussion on appropriate size of ID
Why is this change necessary?
Pros
- Rare collisions between newly requested and the existing IDs
- Offline account creation will be feasible
- One less step to create account
- Users don't have to come up with their own names
- Possibility of utilizing namespace other than those can be derived from public keys
- If non-user accounts are introduced due to some implementation needs
Cons
- Signatories change involves account ID change
- Some blockchains have signatories immutable. What about Iroha?
- Not easy strings for users to remember and induce input errors
- In any case, the string should have a checksum or require some confirmation before transaction execution
- No special use other than as an identifier at the moment
Record of my thoughts
Record of my thoughts
- Motivation?
- Offline account creation?
- Regardless of whether "name@domain" or a key-derived ID is requested, shouldn't its collision be checked when it first comes online?
- Key-derived IDs would be much less likely to collide
- Regardless of whether "name@domain" or a key-derived ID is requested, shouldn't its collision be checked when it first comes online?
- It may be a degeneracy, "name@domain" is easier for users to handle
- In fact, ENS is a move away from hexadecimal addresses. Some other blockchains also offer name services such as SNS or PNS, where users subscribe to paid domains and tie them to their accounts
- But having a default address has the advantage of saving one step in creating an account. No more forcing users to come up with their own names
- Speaking of UX, strings input by users should have a checksum or other confirmation to protect them from errors
- In fact, ENS is a move away from hexadecimal addresses. Some other blockchains also offer name services such as SNS or PNS, where users subscribe to paid domains and tie them to their accounts
- Other (cryptographic) functionalities?
- Probably public keys are used just for convenience to create identifiers and they have no use other than as identifiers
- Do we have a pair
("name@domain", public_keys)or a mapsome_fn(public_keys) -> account_id?- Any example of the use of the strings derived from keys?
- Use of the complementary space of the "human" IDs that can be derived from keys, which could be called "system", "internal", "program", "contract", or "trigger" IDs that are guaranteed to be inaccessible to users with any key
- Such a system account can "sign" transactions without keys
- Program Derived Addresses
- This means triggers could be regarded as primary entities that can execute transactions, which may not applicable for Iroha, where triggers are secondary state as a result of transaction executions
- Such a system account can "sign" transactions without keys
- Ethereum address is path to the account state in Merkle Patricia Trie, but Iroha does not and should not adopt this, I think
- Use of the complementary space of the "human" IDs that can be derived from keys, which could be called "system", "internal", "program", "contract", or "trigger" IDs that are guaranteed to be inaccessible to users with any key
- Any example of the use of the strings derived from keys?
- Do we have a pair
- Probably public keys are used just for convenience to create identifiers and they have no use other than as identifiers
- Offline account creation?
Refactor the data access flow as
AccountIdlostDomainId
Do we necessarily have to exclude DomainId? how do we absorb this impact because we have accounts as part of domain
Account alias
I think we need this, but it must remain optional
Primary and second keys
so every account will have to have a unique primary key, but secondary keys can be used by multiple accounts
Account archival
we can leave this for later
Key versioning
what is meant by this?
Make AccountId some string derived from the account signatories
does it have to be derived? it can be any string set by the user, but when setting it we have to make sure it's unique in the blockchain
Make AccountId some string derived from the account signatories
does it have to be derived? it can be any string set by the user, but when setting it we have to make sure it's unique in the blockchain
obviously in this case offline creation can fail if user sets a non-unique account alias
Added the Record of my thoughts section to the bottom of https://github.com/hyperledger/iroha/issues/2085#issuecomment-1956071335 I'd appreciate if anyone reviews as I don't think I have a solid vision at the moment
So, implicitly an account is going to be identified by it's public key unless an optional alias (name@domain) is introduced, right? Whereas public key formatted aliases should be prohibited?
Introducing the requirement that key pairs (at least ones which are used for a primary key) have to be unique.
Consequently I've got some further questions:
- What if two accounts get created offline using the same key pair, what's the collision resolution here? Actually how can we distinct them?
- Can an account have multiple aliases?
- Can an account drop or change an alias?
- Can an account set an alias used by someone else in the past?
What if two accounts get created offline using the same key pair, what's the collision resolution here? Actually how can we distinct them?
we consider this so improbable that for all intents and purposes is impossible
Can an account have multiple aliases?
I'd prefer not
Can an account drop or change an alias?
I'd say no, let's keep it simple. User can only define an alias when registering account.
Can an account set an alias used by someone else in the past?
yes
Can an account set an alias used by someone else in the past?
yes
Seems all entities referencing an account must rely on the key based identification exclusively though (assets, permission tokens, maybe smth else) to prevent unregistering + registering with same alias kind of 'attack'. Alternatively, aliases might be made exclusive no matter whether the associated account is unregistered or not (which does not seem memory effective though).
Can an account drop or change an alias?
I'd say no, let's keep it simple. User can only define an alias when registering account.
If that's optional I guess we still should allow an account to set that after registering, right?
The other question is how an account is going to be addressed 'by default' (alias alternative), just the primary public key?
Seems all entities referencing an account must rely on the key based identification exclusively though (assets, permission tokens, maybe smth else) to prevent unregistering + registering with same alias kind of 'attack'. Alternatively, aliases might be made exclusive no matter whether the associated account is unregistered or not (which does not seem memory effective though).
if that is a problem, then that is a problem even with the current accounts, no? We've had a similar problem with permission tokens. I've opened #4329 for this
If that's optional I guess we still should allow an account to set that after registering, right?
why? it's optional but you have to decide at the time of registering whether or not to set an alias. If that is really required we can always add that functionality. To keep it simple let's just allow defining aliases at the time of registering
Seems all entities referencing an account must rely on the key based identification exclusively though (assets, permission tokens, maybe smth else) to prevent unregistering + registering with same alias kind of 'attack'. Alternatively, aliases might be made exclusive no matter whether the associated account is unregistered or not (which does not seem memory effective though).
if that is a problem, then that is a problem even with the current accounts, no? We've had a similar problem with permission tokens. I've opened #4329 for this
right, seems so even now, though I was rather referring to a scenario of taking over an asset or entity ownership (apparently) via registering an account with the alias used by the initial account
If that's optional I guess we still should allow an account to set that after registering, right?
why? it's optional but you have to decide at the time of registering whether or not to set an alias. If that is really required we can always add that functionality. To keep it simple let's just allow defining aliases at the time of registering
agree
Prerequisite
-
Before reading my reply to your comments, please take a look at this gist which describes the underlying understanding @mversic @Mingela The reason why gist apart from here is to make the internal links work
-
Aliases was included in the scope of this issue as it seemed to be inseparable from the initial restructural change
Replies to comments
@mversic
1. Need to exclude domain ID?
This question brought insight. See "K" and "KD" comparison in the table
2. Impacts of account ID without domain ID?
The basic idea to absorb those impacts would be to consider the data access flow separately from the organizational chart.
for example:
- All identifiable entities are stored as key-value pairs flatten in the world
- Those entities have referential IDs of other entities
- world.domains: Map<DomainId, Domain>
- domain.accounts: Set<AccountId>
- domain.assets: Set<AssetId>
- world.accounts: Map<AccountId, Account>
- account.domain: DomainId
- account.assets: Set<AssetId>
- account.portfolio(): Map<AssetId, Balance>
- world.assets: Map<AssetId, Asset>
- asset.domain: DomainId
- asset.distribution: Map<AccountId, Balance>
where:
EntityIdandEntityhere are the currentEntitydivided intoidand other fieldsAssethere is a consolidation of the currentAssetandAssetDefinition- Mutable APIs must be carefully implemented to maintain consistent relationships between entities
3. Primary and secondary keys?
I have no idea about secondary keys at the moment. If only for the purpose of mitigating the risk of key theft, another small change might work: introduce "m of n" signature check condition and set it as an authentication policy for personal accounts
4. Is uniqueness enough?
Yeah, that's exactly why I'm cautious about this change. I think this question comes down to a comparison of K and U. If there was a motive to K, it would be about the space that cannot be derived from keys
@Mingela
5. Identified by keys unless has aliases?
In the user interface, that will be true. In the chain, on the other hand, since aliases are supposed to resolve to IDs on the chain surface, accounts will be identified by IDs (essentially keys in K case) regardless of aliases. If backwards compatibility in the user interface is a concern, aliases are optional for new accounts but required for existing accounts
6. Deny aliases of KD format?
Yeah, KD-format ID and KD-format alias will never be distinguished. This means a situation where aliases block new accounts. Maybe KD-format IDs should be rejected?
7. Same key for different IDs?
8. Multiple aliases?
Feasible. Aliases are optional, which means they are mapped to IDs internally
9. Remove or modify aliases?
Feasible. Keys of the alias-to-id map will be checked if they collide
10. Alias transfer, or hijacking?
An alias released by someone else can be retrieved at the next block time. But I don't think something like a takeover would happen. Since alias is supposed to be resolve to ID on the chain surface, alias modification would not affect the relationship between entities linked by IDs. Any query or transaction cannot target the previous holder of the alias because the world state forgets it. On the chain, no one can act as someone else without stealing the private key, regardless of aliases. So if there is a problem, it will happen outside the chain. For example, when an alias acts as an address in the user interface, there may be cases where the alias change is not well known to the parties involved, and the transferred alias could be specified as the destination and the asset could be lost. I can't think of a good solution, but at least this will be not a flaw in the alias specification
@s8sato
- I really would like an account to be referenceable in a static way, if we don't introduce a primary key which would be the only argument of K's
keyhashfunction result (may be even just the public key), I'd rather support UUIDs. To elaborate, we'd just needMap<String, AccountId>to resolve aliases toAccountId(which are UUIDs in this case), preferably aliases still could be in thename@domainformat - Generally a domain inclusion to an account id becomes unnecessary unless we define a domain being the uniqueness scope for primary keys
After reviewing the original requirements, I found the new account IDs required more than just uniqueness. Let me refute my own comment:
UUID as an account ID suggested in ADR would be still feasible as long as it is coupled with the account signatories
The problem is that for such account that does not require registration, there is no clue to the "account signatories" other than its ID (address)
Role of AccountId in authentication
Let’s look back at the current transaction authentication:
// outside chain
- transaction
- payload
- authority: AccountId // 1. authenticator fetches `authority` which the transaction senders claim to have
- signatures // 4. authenticator verifies `signatures` with `signatories`
// inside chain
- account
- id: AccountId // 2. authenticator finds the account matched with `authority`
- signatories: [PublicKey] // 3. authenticator decides who should sign the transaction
- signature_check_condition
And we are trying to realize the following scenario:
- Bob generates keys offline, gets an account ID (address), and asks Alice to send assets to the address
- Alice sends assets to Bob, who is not yet on the chain. This transfer is valid and Bob's account is created from ID (address) only
- Bob sends a transaction with himself as the authority
The situation at this time, assuming no additional fields, is as follows:
// outside chain
- transaction
- payload
- authority: AccountId // 1. authenticator fetches `authority` which the transaction senders claim to have
- signatures // 3. authenticator verifies `signatures` with `account.id`
// inside chain ^^^^^^^^^^
- account
- id: AccountId // 2. authenticator finds the account matched with `authority`
// - signatories: []
- signature_check_condition
If we continue to follow the design where multi-signature transactions directly target multi-signatory accounts, we would fall into one of the following:
-
Have
AccountIdfully containssignatories: [PublicKey] -
Add
transaction.payload.signatoriesfield:signatories in transaction
// outside chain - transaction - payload - authority: KeysHash as AccountId // 1. authenticator fetches `authority` which the transaction senders claim to have - signatories: [PublicKey] // 2. authenticator checks if `authority` is derived from `signatories` - signatures // 4. authenticator verifies `signatures` with `signatories` // inside chain - account - id: KeysHash as AccountId // 3. authenticator finds the account matched with `authority` // - signatories: [] - signature_check_condition
Instead, I think a better way would be to prevent transactions from directly targeting shared accounts.
In this case, transactions would have single signature.
Also, AccountId can simply be the same representation as PublicKey:
// outside chain
- transaction
- payload
- authority: PublicKey as AccountId // 1. authenticator fetches `authority` which the transaction sender claims to have
- signature // 2. authenticator verifies `signature` with `authority`
// inside chain ^^^^^^^^^
- account_personal
- id: PublicKey as AccountId
// - signatories: []
// - signature_check_condition
Another remarkable thing here would be that transaction authentication is self-contained without reference to the chain
Multi-signature alternatives
Thus, since a single transaction can no longer have sufficient authority over shared accounts, another method of registering and modifying shared accounts should be provided. I think stateful triggers would be one of the solutions:
-
A proposal for a shared account is sent by one of the members:
// outside chain - transaction - payload - authority: PublicKey as AccountId - instructions: [RegisterTrigger(shared_account_registerer)] - signature -
The dedicated trigger for the shared account registration is registered and begins collecting signatures:
// inside chain - shared_account_registerer - payload - id: TriggerId - executable // will register the shared account - account_candidate - signatories: [PublicKey] // members of the shared account - signature_check_condition - signatures // collected from signatories -
Each member of the shared account votes on the account registration proposal:
// outside chain - transaction - payload - authority: PublicKey as AccountId - instructions: [ApproveTrigger(shared_account_registerer.payload.id, signature(shared_account_registerer.payload))] - signature -
Once all necessary signatures are gathered, the trigger executes and the shared account is registered:
// inside chain - account_shared - id: PublicKeyLike as AccountId // generated from other fields - signatories: [PublicKey] - signature_check_conditionwhere:
PublicKeyLikehas the same representation asPublicKey, but indicates "system" accounts and is guaranteed to be invalid as aPublicKey
Instructions other than registration for shared accounts would be performed in a similar manner
If shared_account_registerer trigger is introduced why not use such approach for any multisignature transaction instead? I mean instead of registering another type of entity (account_shared), why not execute the target logic right away?
Example would look exactly like step 2, but instead of registering the shared account the executable would perform the 'target' logic.
Basically that means callable triggers might be defined just with custom signature_check_condition to represent 'multisignature' nature of an action.
@Mingela
- A shared account would be registered as an entity to be allowed to hold assets and permissions
- A shared account would be the same
Accounttype as a personal account, which is the intent ofPublicKeyLike
I think I recently here suggested something close to what you are thinking of
I have looked into the comments and thoughts, but don't have any comments/objections.
Register account that does not require registration?
It may seem contradictory that registration is required for shared accounts when no registration is required, so let me explain.
- Alice sends assets to Bob, who is not yet on the chain. This transfer is valid and Bob's account is created from ID (address) only
- Bob sends a transaction with himself as the authority
There is a gap between being the object of a transaction (2) and being the subject that exercises authority (3). The latter condition is that the account is equipped with authentication credentials:
idfor personal accountssignatoriesandsignature_check_conditionfor shared accounts
// inside chain
- account_personal
- id: PublicKey as AccountId // required for authentication
- signatories: []
- signature_check_condition: None
- account_shared
- id: PublicKeyLike as AccountId
- signatories: [PublicKey] // required for authentication
- signature_check_condition // required for authentication
That's why additional registration (or "activation") is required for shared accounts.
Also, this is the reason why PublicKeyLike must be invalid as a PublicKey, in other words, there can be no corresponding private key.
Otherwise, a single transaction signed by such a private key could modify the shared account, since its authority should be interpreted as a personal public key, which passes the authentication.
PoC
- [ ] How to tell
PublicKeyorPublicKeyLike, and what will be the type of them both - [ ] Deterministic generation of
PublicKeyLikewhich is out of the image of thePublicKeygenerating function
Reference
If we go forward with this, I'd like to have a more solid basis for rejecting other candidates.
For example, I suggested implementing multi-signature using triggers, but I'm not sure if that's a good way to go. Another suggestion was collecting signatures outside the chain, perhaps like bitcoin, which would mean this feature relies on another trustful network. One question here is, do we provide a way to handle such a situation where the representative, who has collected signatures and about to submit the multi-signature transaction, disappears despite the signature check condition is met?
Impacts on the existing codebase
AccountIdno longer includingDomainIdentails changes in data structure and access flow- Account signatories become immutable, or mutable involving the account ID
- Anything remarkable else?
Motivation
Currently, new participants cannot take any action without having their accounts registered by others, which is not what a public blockchain does. It is therefore a requirement that pre-registration of accounts becomes optional (or is eliminated and their domain is later requested). Why this leads to the account restructuring can be revealed by the following questions:
- What if the current
name@domainwere to become valid address (transfer destination)? - How can the chain properly handle requests from
name@domainwith no registration? - In other words, how can
name@domain(or just a unique value) alone be authenticated?
The rest is detailed here https://github.com/hyperledger/iroha/issues/2085#issuecomment-1980557101
I consider any valid public key to be a valid reference in a network even if that's not registered/activated yet. In a public blockchain all key pairs just 'exist' (bound to an account).
It becomes more difficult in case of:
- multisignature (shared) accounts, though we could just prohibit creating such offline or provide a way how to calculate the
PublicKeyLikeoffline knowing public keys of the representatives - private blockchain, seems a transaction targeting an account that does not exist yet should be either dropped without reject (retry later) or stored in a specific section of either/both block or/and cache and be processed only when the related account 'registers' (actually reminds me the way how a trigger gets executed on a event)
private blockchain, seems a transaction targeting an account that does not exist yet should be either dropped without reject (retry later) or stored in a specific section of either/both block or/and cache and be processed only when the related account 'registers' (actually reminds me the way how a trigger gets executed on a event)
this can be modeled with a custom executor. iroha_core will not reject such operations, but a custom executor might
@Mingela
- My understanding is both any personal
PublicKeyand any sharedPublicKeyLikeaddresses are valid.- To target the latter address before registration, while I don't know if that is a use case, it needs to be derived at clients when all the members (
signatories: [PublicKey]) are given
- To target the latter address before registration, while I don't know if that is a use case, it needs to be derived at clients when all the members (
- My anticipation is two kinds of "domains" would coexist in one chain if both private and public are default features.
In this case, such an account that does not exist yet could be considered to belong to "the public default domain", where is no authorization specific to any "private domain".
I think this is a kind of authorization (permission validation) issue which we have not yet discussed
- In this way, however, transactions in private domains are still transparent. How to achieve privacy (cf. different ledgers for different channels in HL Fabric) would remain an issue
@sato you said:
Account signatories become immutable, or mutable involving the account ID
I would say that we should not support modifying of the primary key since that is part of account ID. Secondary keys can be modified
AccountId no longer including DomainId entails changes in data structure and access flow
what's the point of having domains if accounts are no longer in domains. I think it's quite important that we keep the ability to register accounts in domains in addition to which we can also have accounts outside any domain
also:
No special use other than as an identifier at the moment
isn't the primary key also the main signatory?
AccountId no longer including DomainId entails changes in data structure and access flow
what's the point of having domains if accounts are no longer in domains. I think it's quite important that we keep the ability to register accounts in domains in addition to which we can also have accounts outside any domain
roles could be attached to any account in a domain (aka default role of a domain) as an example, aggregation of entities by a domain seems a useful feature in general, no domain seems reasonable as well though 'no domain' is like just another domain treated specially
No special use other than as an identifier at the moment
isn't the primary key also the main signatory?
what does 'main' refer to exactly? isn't that determined at the level of a signature_check_condition?
@mversic
- To be honest, as I mentioned before, I don't understand what the concept of primary and secondary keys brings. If you have any ideas please share
- I have no intention to make any changes to the entities hierarchy. Only the data access flow (where the entities and references are stored) would change
- Although I don't quite understand it, I think the primary and secondary keys (vertical inner one person) and the signatories (horizontal across persons) are orthogonal concepts
- The concept of "main signatory" or "representative" seems to suggest a single point of failure. I think that is the very reason why we have maintained
signatories: [PublicKey]instead of separating them into the main and sub signatories - Also, please note that any posts before I noticed the authentication requirement might be outdated
- The concept of "main signatory" or "representative" seems to suggest a single point of failure. I think that is the very reason why we have maintained
A short summary so far
Changes
AccountIdwill be some type that can be parsed toPublicKeyorPublicKeyLike(which is invalid as but has the same representation asPublicKey)name@domainwill be an optional account alias for user interface that is resolved toAccountId
Motivations
- For public blockchain use,
AccountIdshould have an authentication functionality
Implications
DomainIdwill be dropped fromAccountId- Entities hierarchy below the world will have different implementation
- domains, asset definitions, assets
- Any data model relevant to accounts can be involved
- permissions, roles, events
- Entities hierarchy below the world will have different implementation
- Multi-signatory account ID will be linked to the signatories
- never changes, or changes in conjunction with the signatories
- New kind of triggers will be introduced, if elected as a multi-signature implementation
Blockers
- PoC around
PublicKeyandPublicKeyLike
Discussions
- Multi-signature implementation
Results of the discussion:
If we proceed with using public keys as identifiers of accounts we will do this in 3 steps:
- Replace
account@domainwith public key asAccountIdand don't support account aliases - Open a separate issue to discuss whether we want to implement alias as part of Iroha or delegate this to external service (I would prefer to keep alias resolution as a separate service outside Iroha to not complicate the codebase)
- Open a separate issue to discuss implementation of multisig accounts
The main blocker to the decision whether or not to proceed with account restructuring is that this will require significant adjustments in the private blockchain POCs that use Iroha that we have at the moment.
This feature doesn't seem to have benefits for private blockchains, but it also doesn't impair their functionality so I don't have anything against proceeding with it
I think we can close this issue as the title became a bit misleading, or rename the title and keep it as a tracking issue @mversic