iroha icon indicating copy to clipboard operation
iroha copied to clipboard

Add primary key to account

Open appetrosyan opened this issue 3 years ago • 32 comments

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.

appetrosyan avatar Apr 12 '22 07:04 appetrosyan

relevant RFC

mversic avatar Jan 24 '24 11:01 mversic

My plan

  • Make AccountId some string derived from the account signatories
    • Stay consistent with shared wallets (accounts such that 1 < account.signatories.len())
    • Refactor the data access flow as AccountId lost DomainId
    • Absorb any other impacts
  • 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
    • 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
    • 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 map some_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
            • Ethereum address is path to the account state in Merkle Patricia Trie, but Iroha does not and should not adopt this, I think

s8sato avatar Feb 21 '24 07:02 s8sato

Refactor the data access flow as AccountId lost DomainId

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?

mversic avatar Feb 21 '24 08:02 mversic

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

mversic avatar Feb 21 '24 08:02 mversic

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

mversic avatar Feb 21 '24 08:02 mversic

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

s8sato avatar Feb 21 '24 16:02 s8sato

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?

Mingela avatar Feb 22 '24 10:02 Mingela

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

mversic avatar Feb 27 '24 08:02 mversic

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?

Mingela avatar Feb 27 '24 08:02 Mingela

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

mversic avatar Feb 27 '24 08:02 mversic

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

Mingela avatar Feb 27 '24 08:02 Mingela

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

s8sato avatar Feb 27 '24 18:02 s8sato

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:

  • EntityId and Entity here are the current Entity divided into id and other fields
  • Asset here is a consolidation of the current Asset and AssetDefinition
  • 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?

See KD collides by KD

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 avatar Feb 27 '24 18:02 s8sato

@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 keyhash function result (may be even just the public key), I'd rather support UUIDs. To elaborate, we'd just need Map<String, AccountId> to resolve aliases to AccountId (which are UUIDs in this case), preferably aliases still could be in the name@domain format
  • Generally a domain inclusion to an account id becomes unnecessary unless we define a domain being the uniqueness scope for primary keys

Mingela avatar Feb 28 '24 09:02 Mingela

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:

  1. Bob generates keys offline, gets an account ID (address), and asks Alice to send assets to the address
  2. 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
  3. 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 AccountId fully contains signatories: [PublicKey]

  • Add transaction.payload.signatories field:

    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:

  1. 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
    
  2. 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
    
  3. 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
    
  4. 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_condition
    

    where:

    • PublicKeyLike has the same representation as PublicKey, but indicates "system" accounts and is guaranteed to be invalid as a PublicKey

Instructions other than registration for shared accounts would be performed in a similar manner

s8sato avatar Mar 06 '24 10:03 s8sato

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 avatar Mar 06 '24 11:03 Mingela

@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 Account type as a personal account, which is the intent of PublicKeyLike

I think I recently here suggested something close to what you are thinking of

s8sato avatar Mar 06 '24 11:03 s8sato

I have looked into the comments and thoughts, but don't have any comments/objections.

0x009922 avatar Mar 07 '24 08:03 0x009922

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.

  1. 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
  2. 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:

  • id for personal accounts
  • signatories and signature_check_condition for 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 PublicKey or PublicKeyLike, and what will be the type of them both
  • [ ] Deterministic generation of PublicKeyLike which is out of the image of the PublicKey generating function

Reference

s8sato avatar Mar 08 '24 07:03 s8sato

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

  • AccountId no longer including DomainId entails changes in data structure and access flow
  • Account signatories become immutable, or mutable involving the account ID
  • Anything remarkable else?

s8sato avatar Mar 08 '24 07:03 s8sato

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@domain were to become valid address (transfer destination)?
  • How can the chain properly handle requests from name@domain with 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

s8sato avatar Mar 11 '24 10:03 s8sato

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 PublicKeyLike offline 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)

Mingela avatar Mar 11 '24 12:03 Mingela

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

mversic avatar Mar 11 '24 14:03 mversic

@Mingela

  • My understanding is both any personal PublicKey and any shared PublicKeyLike addresses 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
  • 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

s8sato avatar Mar 11 '24 15:03 s8sato

@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?

mversic avatar Mar 12 '24 06:03 mversic

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?

Mingela avatar Mar 12 '24 07:03 Mingela

@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

s8sato avatar Mar 12 '24 07:03 s8sato

A short summary so far

Changes

  • AccountId will be some type that can be parsed to PublicKey or PublicKeyLike (which is invalid as but has the same representation as PublicKey)
    • name@domain will be an optional account alias for user interface that is resolved to AccountId

Motivations

  • For public blockchain use, AccountId should have an authentication functionality

Implications

  • DomainId will be dropped from AccountId
    • 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
  • 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 PublicKey and PublicKeyLike

Discussions

  • Multi-signature implementation

s8sato avatar Mar 12 '24 07:03 s8sato

Results of the discussion:

If we proceed with using public keys as identifiers of accounts we will do this in 3 steps:

  1. Replace account@domain with public key as AccountId and don't support account aliases
  2. 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)
  3. 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

mversic avatar Mar 12 '24 10:03 mversic

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

s8sato avatar Mar 12 '24 10:03 s8sato