Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CIP-0014: User-facing Asset Fingerprint #64

Merged
merged 5 commits into from
Mar 26, 2021

Conversation

KtorZ
Copy link
Member

@KtorZ KtorZ commented Feb 2, 2021

Abstract

This specification defines a user-facing asset fingerprint as a bech32-encoded blake2b-160 digest of the concatenation of the policy id and the asset name.

Motivation

The Mary era of Cardano introduces the support for native assets. On the blockchain, native assets are uniquely identified by both their so-called policy id and asset name. Neither the policy id nor the asset name are intended to be human-readable data.

On the one hand, the policy id is a hash digest of either a monetary script or a Plutus script. On the other hand, the asset name is an arbitrary bytestring of up to 32 bytes (which does not necessarily decode to a valid UTF-8 sequence). In addition, it is possible for an asset to have an empty asset name, or, for assets to have identical asset names under different policies.

Because assets are manipulated in several user-facing features on desktop and via hardware applications, it is useful to come up with a short(er) and human-readable identifier for assets that user can recognize and refer to when talking about assets. We call such an identifier an asset fingerprint.

Specification

We define the asset fingerprint in pseudo-code as:

assetFingerprint := encodeBech32
  ( datapart = hash
    ( algorithm = 'blake2b'
    , digest-length = 20
    , message = policyId | assetName
    )
  , humanReadablePart = 'asset'
  )

where | designates the concatenation of two byte strings. The digest-length is given in bytes (so, 160 bits).

Rationale

Design choices

  • The asset fingerprint needs to be somewhat unique (although collisions are plausible, see next section) and refer to a particular asset. It must therefore include both the policy id and the asset name.

  • Using a hash gives us asset id of a same deterministic length which is short enough to display reasonably well on small screens.

  • We use bech32 as a user-facing encoding since it is both user-friendly and quite common within the Cardano eco-system (e.g. addresses, pool ids, keys).

Security Considerations

  • With a 160-bit digest, an attacker needs at least 2^80 operations to find a collision. Although 2^80 operations is relatively low (it remains expansive but doable for an attacker), it
    is considered safe within the context of an asset fingerprint as a mean of user verification within a particular wallet. An attacker may obtain advantage if users can be persuaded
    that a certain asset is in reality another (which implies to find a collision, and make both assets at the reach of the user).

  • We recommend however that in addition to the asset fingerprint, applications also show whenever possible a visual checksum calculated from the policy id and the asset name as specified in CIP-YET-TO-COME. Such generated images, which are designed to be unique and easy to distinguish, in combination with a readable asset fingerprint gives strong verification means to end users.

Backwards Compatibility

N/A

Reference Implementations

Haskell (GHC >= 8.6.5)

Language Extensions
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeApplications #-}
Imports
-- package: base >= 4.0.0
import Prelude
import Data.Function
    ( (&) )

-- package: bech32 >= 1.0.2
import qualified Codec.Binary.Bech32 as Bech32

-- package: bech32-th >= 1.0.2
import Codec.Binary.Bech32.TH
    ( humanReadablePart )

-- package: bytestring >= 0.10.0.0
import Data.ByteString
    ( ByteString )

-- package: cryptonite >= 0.22
import Crypto.Hash
    ( hash )
import Crypto.Hash.Algorithms
    ( Blake2b_160 )

-- package: memory >= 0.14
import Data.ByteArray
    ( convert )

-- package: text >= 1.0.0.0
import Data.Text
    ( Text )
newtype PolicyId = PolicyId ByteString
newtype AssetName = AssetName ByteString
newtype AssetFingerprint = AssetFingerprint Text

mkAssetFingerprint :: PolicyId -> AssetName -> AssetFingerprint
mkAssetFingerprint (PolicyId policyId) (AssetName assetName)
    = (policyId <> assetName)
    & convert . hash @_ @Blake2b_160
    & Bech32.encodeLenient hrp . Bech32.dataPartFromBytes
    & AssetFingerprint
  where
    hrp = [humanReadablePart|asset|]

TypeScript

Imports
// package 'blake2b@2.1.3'
import blake2b from 'blake2b';
// package 'bech32@2.0.0'
import { bech32 } from 'bech32';
export function assetFingerprint(
  policyId: Uint8Array,
  assetName: Uint8Array,
): string {
  const datapart = blake2b(20).update(new Uint8Array([...policyId, ...assetName])).digest('binary');

  const words = bech32.toWords(datapart);
  return bech32.encode('asset', words);
}

See also: @emurgo/cip14-js.

Test Vectors

ℹ️ policy_id and asset_name are hereby base16-encoded; their raw, decoded, versions should be used when computing the fingerprint.

- policy_id: 7eae28af2208be856f7a119668ae52a49b73725e326dc16579dcc373
  asset_name: ""
  asset_fingerprint: asset1rjklcrnsdzqp65wjgrg55sy9723kw09mlgvlc3

- policy_id: 7eae28af2208be856f7a119668ae52a49b73725e326dc16579dcc37e
  asset_name: ""
  asset_fingerprint: asset1nl0puwxmhas8fawxp8nx4e2q3wekg969n2auw3

- policy_id: 1e349c9bdea19fd6c147626a5260bc44b71635f398b67c59881df209
  asset_name: ""
  asset_fingerprint: asset1uyuxku60yqe57nusqzjx38aan3f2wq6s93f6ea

- policy_id: 7eae28af2208be856f7a119668ae52a49b73725e326dc16579dcc373
  asset_name: 504154415445
  asset_fingerprint: asset13n25uv0yaf5kus35fm2k86cqy60z58d9xmde92

- policy_id: 1e349c9bdea19fd6c147626a5260bc44b71635f398b67c59881df209
  asset_name: 504154415445
  asset_fingerprint: asset1hv4p5tv2a837mzqrst04d0dcptdjmluqvdx9k3

- policy_id: 1e349c9bdea19fd6c147626a5260bc44b71635f398b67c59881df209
  asset_name: 7eae28af2208be856f7a119668ae52a49b73725e326dc16579dcc373
  asset_fingerprint: asset1aqrdypg669jgazruv5ah07nuyqe0wxjhe2el6f

- policy_id: 7eae28af2208be856f7a119668ae52a49b73725e326dc16579dcc373
  asset_name: 1e349c9bdea19fd6c147626a5260bc44b71635f398b67c59881df209
  asset_fingerprint: asset17jd78wukhtrnmjh3fngzasxm8rck0l2r4hhyyt

- policy_id: 7eae28af2208be856f7a119668ae52a49b73725e326dc16579dcc373
  asset_name: 0000000000000000000000000000000000000000000000000000000000000000
  asset_fingerprint: asset1pkpwyknlvul7az0xx8czhl60pyel45rpje4z8w

Copyright

CC-BY-4.0

@KtorZ KtorZ self-assigned this Feb 2, 2021
@dcoutts
Copy link
Contributor

dcoutts commented Feb 2, 2021

Why hash the concatenation of the policy id and sub-identifier? The concatenation of the two parts of the identifier is already unique. There is no need to hash it.

CIP-0014/CIP-0014.md Outdated Show resolved Hide resolved
@KtorZ
Copy link
Member Author

KtorZ commented Feb 3, 2021

@dcoutts a hash gives us two nice properties:

  • It can lead to shorter identifier, provided that using a shorter digest (e.g. 20 bytes) is still reasonable in this context.

  • it normalizes the length of the identifier to a fixed length. Since this identifier is meant to be used in user facing application, it seems easier to consider an element of fixed sized rather than something of variable length in designs.

@KtorZ
Copy link
Member Author

KtorZ commented Feb 3, 2021

We could arguably get to a fixed length by padding it, though it'd just be more noisy. In the end, the length may be variable but capped anyway, so it isn't necessarily a property to hold on.

@rvl
Copy link
Contributor

rvl commented Feb 5, 2021

  1. How short does the user-facing asset id need to be so that it displays well on small screens?
  2. How long does the user-facing asset id need to be so that it's adequately collision resistant?

@gitmachtl
Copy link
Contributor

Please don't forget us (the SPOs and tool providers) in all of this! We have now written tools and tested this for 2 months to be ready for the mainnet fork and now you wanna change it? We need the policyID.name scheme to work completely offline, and for the policies we need them in "cleartext" via the cli. So this needs to be working in both ways. If you wanna hash/encode it, fine, but we need a method to get back and forth between the two representatives easily on the cli. Cardano-CLI the "human interacting interface" should provide the information like it is doing right now.

@gitmachtl
Copy link
Contributor

Also we are working with the utxo json output format like this one to clearly get out the policyID and names. We need that information in offline mode without maintaining databases to know what policy.script is/was used to mint/burn tokens:

{
  "f6a160b7f6609ca99370469222e1dfea8d029c92fec74ac61a17c15baef4be94#0": {
    "amount": [
      95812877,
      [
        [
          "34250edd1e9836f5378702fbf9416b709bc140e04f668cc355208518",
          [
            [
              "",
              3
            ],
            [
              "Testcoin1",
              39000
            ],
            [
              "Testcoin2",
              75
            ],
            [
              "SomeOtherCoin",
              100
            ]
          ]
        ],
        [
          "8c4662efcb7fd069c9e4003192b430e9e153e5c3e11099e3dab29772",
          [
            [
              "CoinFromMars",
              334
            ]
          ]
        ],
        [
          "ecd07b4ef62f37a68d145de8efd60c53d288dd5ffc641215120cc3db",
          [
            [
              "",
              12
            ]
          ]
        ]
      ]
    ],
    "address": "00897789a996ec52f93668d18dd4dbe5f00036a0406f6da37185c641caf695c35024868c4dd6f694e16b88bdf6986e31a074f790d75241c44b"
  }
}

We need this information to group them by policyIDs and as i say to interact with the assets with the right policy.scripts.

@KtorZ
Copy link
Member Author

KtorZ commented Feb 5, 2021

@gitmachtl Please don't forget us (the SPOs and tool providers) in all of this! We have now written tools and tested this for 2 months to be ready for the mainnet fork and now you wanna change it?

Thanks for commenting, this is actually why I am making this a public CIP! This little thing has quite some impact over many interfaces. So we better agree on a standard way to approach this, that is safe for end-users and remain usable.

@gitmachtl We need the policyID.name scheme to work completely offline

This one does too.

@gitmachtl So this needs to be working in both ways. If you wanna hash/encode it, fine, but we need a method to get back and forth between the two representatives easily on the cli.

Going further with that proposal, applications consuming data from the ledger such as cardano-db-sync or cardano-wallet may store the assetFingerprint next to the policyId and assetName to facilitate querying. A hash is by definition not reversible (or it's a poor hash) so, in order to reverse it, one needs an index that can map hashes to their original inputs. Although this thinking is a bit backward too because, the whole point of this proposal is to be:

(a) A user-facing piece of text
(b) Entirely computable from the policyId and assetName.

This means that applications would typically keep storing the policyId and assetName, and only compute the assetFingerprint on-the-fly for display. Yet, reverse-search would require to have constructed an index to map the fingerprint back to the policy + assetName.

@KtorZ KtorZ changed the title CIP-0014: User-facing Asset Id CIP-0014: User-facing Asset Fingerprint Feb 5, 2021
@KtorZ
Copy link
Member Author

KtorZ commented Feb 5, 2021

@rvl How short does the user-facing asset id need to be so that it displays well on small screens?

Worse-case is the Ledger Nano S which can show up to 20 characters. Although, users can scroll in theory, we know that in practice many won't.

@rvl How long does the user-facing asset id need to be so that it's adequately collision resistant?

At least 224 bits make it safe. 160 bits is enough within this particular context although having another more visual identifier would be recommended because humans are simply bad at memorizing byte strings.

@ashisherc
Copy link

ashisherc commented Feb 6, 2021

In continuation to what @gitmachtl and @refi93 have pointed above. Having a hash solves some problems but also introduces more complexities for wallets, explorers, and other tools.

The hash by nature is not reversible and hence we essentially end up making bech32(hash(policyId.assetName)) a unique ONE-way identifier for assets (for end-user a token would be the hash after all). This is naturally against the current ledger spec, which defines unique tokens by policyId.assetName

This looks to be introducing a bigger change, a wallet keeping track of 3 different data points essential to only show end-users a human-friendly identifier and map the actual policyId and assetName to send transactions, which should not be the case, bech32(policyId.assetName) is friendlier as it can be computed on the fly to show the identifier (the requirement) and also is reversible hence easing out the load on toolings.

@KtorZ
Copy link
Member Author

KtorZ commented Feb 7, 2021

@ashisherc thanks for your feedback, nevertheless, this point has already been raised and discussed in a thread earlier on this PR, see: #64 (comment)

Because the asset name is an open string, it is a good vector for phishing attack. Even when bech32 encoded, it is very easy to create two strings that ressemble each others. As for wallets and explorers, it suffices to store the asset fingerprint next to the asset name and policy id.

  There's a bit of a debate about the 'token' vs 'asset' terminology.
  Though, 'token' tends to refer more to the actual _thing_ being
  exchanged, whereas 'asset' tends to refer to its nature. In this
  context, 'asset' seems therefore a better fit.
KtorZ added a commit to cardano-foundation/cardano-wallet that referenced this pull request Feb 11, 2021
iohk-bors bot added a commit to cardano-foundation/cardano-wallet that referenced this pull request Feb 11, 2021
2512: Add fingerprint to assets (cf: CIP-0014) r=KtorZ a=KtorZ

# Issue Number

<!-- Put here a reference to the issue that this PR relates to and which requirements it tackles. Jira issues of the form ADP- will be auto-linked. -->



# Overview

<!-- Detail in a few bullet points the work accomplished in this PR -->

Motivation, design and rationale explained in: [CIP-0014](cardano-foundation/CIPs#64)


# Comments

<!-- Additional comments or screenshots to attach if any -->

<!--
Don't forget to:

 ✓ Self-review your changes to make sure nothing unexpected slipped through
 ✓ Assign yourself to the PR
 ✓ Assign one or several reviewer(s)
 ✓ Jira will detect and link to this PR once created, but you can also link this PR in the description of the corresponding ticket
 ✓ Acknowledge any changes required to the Wiki
 ✓ Finally, in the PR description delete any empty sections and all text commented in <!--, so that this text does not appear in merge commit messages.
-->


Co-authored-by: KtorZ <matthias.benkort@gmail.com>
Co-authored-by: Johannes Lund <johannes.lund@iohk.io>
iohk-bors bot added a commit to cardano-foundation/cardano-wallet that referenced this pull request Feb 11, 2021
2512: Add fingerprint to assets (cf: CIP-0014) r=KtorZ a=KtorZ

# Issue Number

<!-- Put here a reference to the issue that this PR relates to and which requirements it tackles. Jira issues of the form ADP- will be auto-linked. -->



# Overview

<!-- Detail in a few bullet points the work accomplished in this PR -->

Motivation, design and rationale explained in: [CIP-0014](cardano-foundation/CIPs#64)


# Comments

<!-- Additional comments or screenshots to attach if any -->

<!--
Don't forget to:

 ✓ Self-review your changes to make sure nothing unexpected slipped through
 ✓ Assign yourself to the PR
 ✓ Assign one or several reviewer(s)
 ✓ Jira will detect and link to this PR once created, but you can also link this PR in the description of the corresponding ticket
 ✓ Acknowledge any changes required to the Wiki
 ✓ Finally, in the PR description delete any empty sections and all text commented in <!--, so that this text does not appear in merge commit messages.
-->


Co-authored-by: KtorZ <matthias.benkort@gmail.com>
Co-authored-by: Johannes Lund <johannes.lund@iohk.io>
assetFingerprint := encodeBech32
( datapart = hash
( algorithm = 'blake2b'
, digest-length = 20
Copy link
Contributor

@refi93 refi93 Feb 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worse-case is the Ledger Nano S which can show up to 20 characters. Although, users can scroll in theory, we know that in practice many won't.

@KtorZ To make it clear - even though the hash digest was set to 20 bytes, the current bech32 assetId length exceeds Ledger's screen capacity of 20 characters (it has the "asset1" prefix, checksum at the end and one bech32 character is not the same as 1 byte, obviously) - so users will indeed have to scroll on Ledger to see it fully.

However, given that we chose to go with a hash of the policyId and assetName concatenation, even the first screenful of the id should be a reasonable assurance that given assetId is correct, though it's definitely recommendable to scroll through the whole assetId to be completely sure

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

he current bech32 assetId length exceeds Ledger's screen capacity of 20 characters

I know :( .. bech32 isn't the "smallest" encoding possible, base64 with no padding would produce shorter strings but they'd also be too long. My hope is that this is short-enough to still allow users to check them, if not fully, at least as much as possible from what they get on the first screen 😬

, digest-length = 20
, message = policyId | assetName
)
, humanReadablePart = 'asset'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about more descriptive such as
humanReadablePart = 'fngrprnt'

Copy link
Member Author

@KtorZ KtorZ Feb 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Referring to it as fingerprint would actually be less descriptive 😶 There are many things that can be a fingerprint of something. Yet here, it is used to referred to an asset, hence the prefix. In the same way we don't use hash as a prefix for key hashes for instance, but we use: addr_vkh or stake_vkh 👍

Copy link
Contributor

@dcoutts dcoutts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy with the idea now.

Good things: it's a shorter hash than before, which clarifies things. We've all come to the consensus that showing the potentially-human-readable part is unwise since it's a prime opportunity for confusion by confusing the sub-identifier with the overall human-readable name.

@crptmppt crptmppt merged commit 592bbb6 into cardano-foundation:master Mar 26, 2021
@KtorZ KtorZ deleted the user-facing-asset-id branch March 26, 2021 16:57
Scitz0 added a commit to cardano-community/guild-operators that referenced this pull request Apr 17, 2021
# Added
- Ability to create & update a Cardano Token Registry submission JSON file
  - Requires 'token-metadata-creator' tool, instructions to download/build this tool added to Guild Operators documentation:
  - https://cardano-community.github.io/guild-operators/#/Build/offchainMetadataTools
- Token Registry lookup in Wallet >> Show
- Token asset fingerprint generation according to cardano-foundation/CIPs#64

# Changed
- Redesigned input handling to be more flexible and improve output

Many line changes due to some code refactoring for println and the new ask function to handle input
Scitz0 added a commit to cardano-community/guild-operators that referenced this pull request Apr 21, 2021
# Added
- Ability to create & update a Cardano Token Registry submission JSON file
  - Requires 'token-metadata-creator' tool, instructions to download/build this tool added to Guild Operators documentation:
  - https://cardano-community.github.io/guild-operators/#/Build/offchainMetadataTools
- Token Registry lookup in Wallet >> Show
- Token asset fingerprint generation according to cardano-foundation/CIPs#64

# Changed
- Redesigned input handling to be more flexible and improve output

Many line changes due to some code refactoring for println and the new ask function to handle input
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.