{"id":38054822,"url":"https://github.com/dimchat/mkm-py","last_synced_at":"2026-01-16T20:22:43.106Z","repository":{"id":49765784,"uuid":"165263636","full_name":"dimchat/mkm-py","owner":"dimchat","description":"Ming Ke Ming (名可名) -- Account Module","archived":false,"fork":false,"pushed_at":"2025-09-20T20:13:32.000Z","size":807,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-20T22:10:02.815Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dimchat.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-01-11T15:15:19.000Z","updated_at":"2025-09-20T20:13:35.000Z","dependencies_parsed_at":"2023-01-31T06:01:07.299Z","dependency_job_id":"881776e1-95a9-4d6b-8ccf-5e9f132c631c","html_url":"https://github.com/dimchat/mkm-py","commit_stats":{"total_commits":137,"total_committers":1,"mean_commits":137.0,"dds":0.0,"last_synced_commit":"cc4acbf4267f8352c7d78218e98198589ae31cf3"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/dimchat/mkm-py","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimchat%2Fmkm-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimchat%2Fmkm-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimchat%2Fmkm-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimchat%2Fmkm-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dimchat","download_url":"https://codeload.github.com/dimchat/mkm-py/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimchat%2Fmkm-py/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28482267,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T11:59:17.896Z","status":"ssl_error","status_checked_at":"2026-01-16T11:55:55.838Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-16T20:22:42.874Z","updated_at":"2026-01-16T20:22:43.074Z","avatar_url":"https://github.com/dimchat.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ming Ke Ming (名可名) -- Account Module (Python)\n\n[![License](https://img.shields.io/github/license/dimchat/mkm-py)](https://github.com/dimchat/mkm-py/blob/master/LICENSE)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/dimchat/mkm-py/pulls)\n[![Platform](https://img.shields.io/badge/Platform-Python%203-brightgreen.svg)](https://github.com/dimchat/mkm-py/wiki)\n[![Issues](https://img.shields.io/github/issues/dimchat/mkm-py)](https://github.com/dimchat/mkm-py/issues)\n[![Repo Size](https://img.shields.io/github/repo-size/dimchat/mkm-py)](https://github.com/dimchat/mkm-py/archive/refs/heads/master.zip)\n[![Tags](https://img.shields.io/github/tag/dimchat/mkm-py)](https://github.com/dimchat/mkm-py/tags)\n[![Version](https://img.shields.io/pypi/v/mkm)](https://pypi.org/project/mkm)\n\n[![Watchers](https://img.shields.io/github/watchers/dimchat/mkm-py)](https://github.com/dimchat/mkm-py/watchers)\n[![Forks](https://img.shields.io/github/forks/dimchat/mkm-py)](https://github.com/dimchat/mkm-py/forks)\n[![Stars](https://img.shields.io/github/stars/dimchat/mkm-py)](https://github.com/dimchat/mkm-py/stargazers)\n[![Followers](https://img.shields.io/github/followers/dimchat)](https://github.com/orgs/dimchat/followers)\n\nThis [document](https://github.com/moky/DIMP/blob/master/MingKeMing-Identity.md) introduces a common **Account Module** for decentralized user identity authentication.\n\n## Features\n\n- [Meta](#meta)\n    - [Type](#meta-type)\n    - [Key](#public-key)\n    - [Seed](#seed)\n    - [Fingerprint](#fingerprint)\n- [ID](#id)\n    - [Type](#id-type)\n    - [Name](#id-name)\n    - [Address](#id-address)\n    - [Terminal](#terminal)\n\n## Meta\n\nThe **Meta** was generated by your **private key**, it can be used to build a new ID for entity, or verify the ID/PK pair.\n\nIt consists of 4 fields:\n\n| Field       | Description                              |\n| ----------- | ---------------------------------------- |\n| type        | Algorithm Version                        |\n| key         | Public Key                               |\n| seed        | Entity Name (Optional)                   |\n| fingerprint | Signature to generate address (Optional) |\n\nIf ```seed``` exists, ```fingerprint = private_key.sign(seed)```\n\n### Meta Type\n\n1. ```MKM``` _(Default)_\n2. ```BTC```\n3. ~~Extended BTC~~\n4. ```ETH```\n5. ~~Extended ETH~~\n6. ...\n\n### Public Key\n\nA **public key** (PK) was binded to an ID by the **Meta Algorithm**.\n\n### Seed\n\nA string as same as **ID.name** for generate the fingerprint.\n\n### Fingerprint\n\nTHe **fingerprint** field was generated by your **private key** and **seed**:\n\n````python\ndata = utf8_encode(string=seed);\nfingerprint = private_key.sign(data=data);\n````\n\n### Meta Example\n```javascript\n/* Meta(JsON) for hulk@4YeVEN3aUnvC1DNUufCq1bs9zoBSJTzVEj */\n{\n    \"type\"        : 0x01,\n    \"key\"         : {\n        \"algorithm\" : \"RSA\",\n        \"data\"      : \"-----BEGIN PUBLIC KEY-----\\nMIGJAoGBALB+vbUK48UU9rjlgnohQowME+3JtTb2hLPqtatVOW364/EKFq0/PSdnZVE9V2Zq+pbX7dj3nCS4pWnYf40ELH8wuDm0Tc4jQ70v4LgAcdy3JGTnWUGiCsY+0Z8kNzRkm3FJid592FL7ryzfvIzB9bjg8U2JqlyCVAyUYEnKv4lDAgMBAAE=\\n-----END PUBLIC KEY-----\",\n        \"mode\"      : \"ECB\",\n        \"padding\"   : \"PKCS1\",\n        \"digest\"    : \"SHA256\"\n    },\n    \"seed\"        : \"hulk\",\n    \"fingerprint\" : \"jIPGWpWSbR/DQH6ol3t9DSFkYroVHQDvtbJErmFztMUP2DgRrRSNWuoKY5Y26qL38wfXJQXjYiWqNWKQmQe/gK8M8NkU7lRwm+2nh9wSBYV6Q4WXsCboKbnM0+HVn9Vdfp21hMMGrxTX1pBPRbi0567ZjNQC8ffdW2WvQSoec2I=\"\n}\n```\n\n## ID\nThe **ID** is used to identify an **entity**(user/group). It consists of 3 fields:\n\n| Field       | Description                    |\n| ----------- | ------------------------------ |\n| type        | Entity type                    |\n| name        | Same with meta.seed (Optional) |\n| address     | Unique Identification          |\n| terminal    | Login point (Optional)         |\n\nThe ID format is ```name@address[/terminal]```.\n\n```\n# ID examples\nID1 = \"hulk@4YeVEN3aUnvC1DNUufCq1bs9zoBSJTzVEj\";  // Immortal Hulk\nID2 = \"moki@4WDfe3zZ4T7opFSi3iDAKiuTnUHjxmXekk\";  // Monkey King\n```\n\n### ID Type\n\n```python\nclass EntityType(IntEnum):\n    \n    ################################\n    #  Main: 0, 1\n    ################################\n    USER = 0x00             # 0000 0000\n    GROUP = 0x01            # 0000 0001 (User Group)\n\n    ################################\n    #  Network: 2, 3\n    ################################\n    STATION = 0x02          # 0000 0010 (Server Node)\n    ISP = 0x03              # 0000 0011 (Service Provider)\n    # STATION_GROUP = 0x03  # 0000 0011\n\n    ################################\n    #  Bot: 4, 5\n    ################################\n    BOT = 0x04              # 0000 0100 (Business Node)\n    ICP = 0x05              # 0000 0101 (Content Provider)\n    # BOT_GROUP = 0x05      # 0000 0101\n\n    ################################\n    #  Management: 6, 7, 8\n    ################################\n    # SUPERVISOR = 0x06     # 0000 0110 (Company CEO)\n    # COMPANY = 0x07        # 0000 0111 (Super Group for ISP/ICP)\n    # CA = 0x08             # 0000 1000 (Certification Authority)\n\n    ################################\n    #  Customized: 64, 65\n    ################################\n    # APP_USER = 0x40       # 0100 0000 (Application Customized User)\n    # APP_GROUP = 0x41      # 0100 0001 (Application Customized Group)\n\n    ################################\n    #  Broadcast: 128, 129\n    ################################\n    ANY = 0x80              # 1000 0000 (anyone@anywhere)\n    EVERY = 0x81            # 1000 0001 (everyone@everywhere)\n\n    @classmethod\n    def is_user(cls, network: int) -\u003e bool:\n        return (network \u0026 cls.GROUP) == cls.USER\n\n    @classmethod\n    def is_group(cls, network: int) -\u003e bool:\n        return (network \u0026 cls.GROUP) == cls.GROUP\n\n    @classmethod\n    def is_broadcast(cls, network: int) -\u003e bool:\n        return (network \u0026 cls.ANY) == cls.ANY\n```\n\n### ID Name\nThe **Name** field is a username, or just a random string for group:\n\n1. The length of name must more than 1 byte, less than 32 bytes;\n2. It should be composed by a-z, A-Z, 0-9, or charactors '_', '-', '.';\n3. It cannot contain key charactors('@', '/').\n\n```\n# Name examples\nuser_name  = \"Albert.Moky\";\ngroup_name = \"Group-9527\";\n```\n\nIt's equivalent to ```meta.seed```\n\n### ID Address\n\nThe **Address** field was created with the Meta and a **Network ID**:\n\n#### BTC Address\n```python\nfrom typing import Optional\n\nfrom mkm.types import ConstantString\nfrom mkm.digest import sha256, ripemd160\nfrom mkm.format import base58_encode, base58_decode\nfrom mkm import Address\n\n\nclass BTCAddress(ConstantString, Address):\n    \"\"\"\n        Address like BitCoin\n        ~~~~~~~~~~~~~~~~~~~~\n\n        data format: \"network+digest+code\"\n            network    --  1 byte\n            digest     -- 20 bytes\n            check code --  4 bytes\n\n        algorithm:\n            fingerprint = PK.data\n            digest      = ripemd160(sha256(fingerprint));\n            code        = sha256(sha256(network + digest)).prefix(4);\n            address     = base58_encode(network + digest + code);\n    \"\"\"\n\n    def __init__(self, address: str, network: int):\n        super().__init__(string=address)\n        self.__type = network\n\n    @property  # Override\n    def network(self) -\u003e int:\n        return self.__type\n\n    #\n    #   Factory methods\n    #\n    @classmethod\n    def from_data(cls, fingerprint: bytes, network: int) -\u003e Address:\n        \"\"\"\n        Generate address with fingerprint and network ID\n\n        :param fingerprint: meta.fingerprint or key.data\n        :param network:     address type\n        :return: Address object\n        \"\"\"\n        # 1. digest = ripemd160(sha256(fingerprint))\n        digest = ripemd160(sha256(fingerprint))\n        # 2. head = network + digest\n        head = chr(network).encode('latin1') + digest\n        # 3. cc = sha256(sha256(head)).prefix(4)\n        code = check_code(head)\n        # 4. data = base58_encode(head + cc)\n        address = base58_encode(head + code)\n        return cls(address=address, network=network)\n\n    @classmethod\n    def from_str(cls, address: str) -\u003e Optional[Address]:\n        \"\"\"\n        Parse a string for BTC address\n\n        :param address: address string\n        :return: Address object\n        \"\"\"\n        if len(address) \u003c 26 or len(address) \u003e 35:\n            return None\n        # decode\n        data = base58_decode(address)\n        if data is None or len(data) != 25:\n            return None\n        # check code\n        prefix = data[:21]\n        suffix = data[21:]\n        if check_code(prefix) == suffix:\n            network = ord(data[:1])\n            return cls(address=address, network=network)\n\n\ndef check_code(data: bytes) -\u003e bytes:\n    # check code in BTC address\n    return sha256(sha256(data))[:4]\n```\n\n#### ETH Address\n```python\nfrom typing import Optional\n\nfrom mkm.types import ConstantString\nfrom mkm.digest import keccak256\nfrom mkm.format import hex_encode\nfrom mkm import Address, EntityType\n\nclass ETHAddress(ConstantString, Address):\n    \"\"\"\n        Address like Ethereum\n        ~~~~~~~~~~~~~~~~~~~~~\n\n        data format: \"0x{address}\"\n\n        algorithm:\n            fingerprint = PK.data\n            digest      = keccak256(fingerprint)\n            address     = hex_encode(digest.suffix(20))\n    \"\"\"\n\n    def __init__(self, address: str):\n        super().__init__(string=address)\n\n    @property  # Override\n    def network(self) -\u003e int:\n        return EntityType.USER.value\n\n    @classmethod\n    def validate_address(cls, address: str) -\u003e Optional[str]:\n        if is_eth(address=address):\n            lower = address[2:].lower()\n            return '0x%s' % eip55(address=lower)\n        # not an ETH address\n\n    @classmethod\n    def is_validate(cls, address: str) -\u003e bool:\n        validate = cls.validate_address(address=address)\n        return validate is not None and validate == address\n\n    #\n    #   Factory methods\n    #\n    @classmethod\n    def from_data(cls, fingerprint: bytes) -\u003e Address:\n        \"\"\"\n        Generate ETH address with key.data\n\n        :param fingerprint: key.data\n        :return: Address object\n        \"\"\"\n        if len(fingerprint) == 65:\n            # skip first char\n            fingerprint = fingerprint[1:]\n        assert len(fingerprint) == 64, 'key data length error: %d' % len(fingerprint)\n        # 1. digest = keccak256(fingerprint)\n        digest = keccak256(data=fingerprint)\n        # 2. address = hex_encode(digest.suffix(20))\n        tail = digest[-20:]\n        address = '0x' + eip55(address=hex_encode(data=tail))\n        return cls(address=address)\n\n    @classmethod\n    def from_str(cls, address: str) -\u003e Optional[Address]:\n        \"\"\"\n        Parse a string for ETH address\n\n        :param address: address string\n        :return: Address object\n        \"\"\"\n        if is_eth(address=address):\n            return cls(address=address)\n\n\n# https://eips.ethereum.org/EIPS/eip-55\ndef eip55(address: str) -\u003e str:\n    res = ''\n    table = keccak256(address.encode('utf-8'))\n    for i in range(40):\n        ch = address[i]\n        x = ord(ch)\n        if x \u003e 0x39:\n            # check for each 4 bits in the hash table\n            # if the first bit is '1',\n            #     change the character to uppercase\n            x -= (table[i \u003e\u003e 1] \u003c\u003c (i \u003c\u003c 2 \u0026 4) \u0026 0x80) \u003e\u003e 2\n            ch = chr(x)\n        res += ch\n    return res\n\n\ndef is_eth(address: str) -\u003e bool:\n    if len(address) != 42:\n        return False\n    if address[0] != '0' or address[1] != 'x':\n        return False\n    for i in range(2, 42):\n        ch = address[i]\n        if '0' \u003c= ch \u003c= '9':\n            continue\n        if 'A' \u003c= ch \u003c= 'Z':\n            continue\n        if 'a' \u003c= ch \u003c= 'z':\n            continue\n        # unexpected character\n        return False\n    return True\n```\n\nWhen you get a meta for the entity ID from the network,\nyou must verify it with the consensus algorithm before accepting its **public key**.\n\n### Terminal\n\nA resource identifier as **Login Point**.\n\n(All data encode with **BASE64** algorithm as default, excepts the **address**)\n\n----\n\nCopyright \u0026copy; 2018-2025 Albert Moky\n[![Followers](https://img.shields.io/github/followers/moky)](https://github.com/moky?tab=followers)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdimchat%2Fmkm-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdimchat%2Fmkm-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdimchat%2Fmkm-py/lists"}