diff --git a/docs/source/api/pycardano.cip.rst b/docs/source/api/pycardano.cip.rst index 1e2f7bd7..e978b929 100644 --- a/docs/source/api/pycardano.cip.rst +++ b/docs/source/api/pycardano.cip.rst @@ -7,3 +7,8 @@ Implementation of Cardano Improvement Proposals (CIPs) :members: :undoc-members: :show-inheritance: + +.. automodule:: pycardano.cip.cip14 + :members: + :undoc-members: + :show-inheritance: diff --git a/pycardano/cip/__init__.py b/pycardano/cip/__init__.py index a9087731..a3d1a71c 100644 --- a/pycardano/cip/__init__.py +++ b/pycardano/cip/__init__.py @@ -1,3 +1,4 @@ # flake8: noqa from .cip8 import * +from .cip14 import * diff --git a/pycardano/cip/cip14.py b/pycardano/cip/cip14.py new file mode 100644 index 00000000..6126f23a --- /dev/null +++ b/pycardano/cip/cip14.py @@ -0,0 +1,41 @@ +from typing import Union + +from nacl.encoding import RawEncoder +from nacl.hash import blake2b +from pycardano.crypto.bech32 import encode +from pycardano.hash import ScriptHash +from pycardano.transaction import AssetName + + +def encode_asset( + policy_id: Union[ScriptHash, bytes, str], asset_name: Union[AssetName, bytes, str] +) -> str: + """Implementation of CIP14 asset fingerprinting + + This function encodes the asset policy and name into an asset fingerprint, which is + bech32 compliant. + + For more information: + https://developers.cardano.org/docs/governance/cardano-improvement-proposals/cip-0014/ + + Args: + policy_id: The asset policy as `ScriptHash`, `bytes`, or a hex `str` + asset_name: The asset name as `AssetName`, `bytes`, or a hex `str` + """ + if isinstance(policy_id, str): + policy_id = bytes.fromhex(policy_id) + elif isinstance(policy_id, ScriptHash): + policy_id = policy_id.payload + + if isinstance(asset_name, str): + asset_name = bytes.fromhex(asset_name) + elif isinstance(asset_name, AssetName): + asset_name = asset_name.payload + + asset_hash = blake2b( + policy_id + asset_name, + digest_size=20, + encoder=RawEncoder, + ) + + return encode("asset", asset_hash) diff --git a/test/pycardano/test_cip14.py b/test/pycardano/test_cip14.py new file mode 100644 index 00000000..09406550 --- /dev/null +++ b/test/pycardano/test_cip14.py @@ -0,0 +1,72 @@ +import pytest + +from pycardano.cip.cip14 import encode_asset +from pycardano.hash import ScriptHash +from pycardano.transaction import AssetName + + +@pytest.mark.parametrize( + "input_types", [(str, str), (bytes, bytes), (ScriptHash, AssetName)] +) +@pytest.mark.parametrize( + "asset", + [ + { + "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", + }, + ], +) +def test_encode_asset(asset, input_types): + if isinstance(input_types[0], bytes): + policy_id = bytes.fromhex(asset["policy_id"]) + asset_name = bytes.fromhex(asset["asset_name"]) + elif isinstance(input_types[0], str): + policy_id = asset["policy_id"] + asset_name = asset["asset_name"] + + if isinstance(input_types[0], ScriptHash): + policy_id = ScriptHash(policy_id) + asset_name = AssetName(asset_name) + + fingerprint = encode_asset( + policy_id=asset["policy_id"], asset_name=asset["asset_name"] + ) + + assert fingerprint == asset["asset_fingerprint"]