This document introduces a common Account Module for decentralized user identity authentication.
The Meta was generated by your private key, it can be used to build a new ID for entity, or verify the ID/PK pair.
It consists of 4 fields:
Field | Description |
---|---|
type | Algorithm Version |
key | Public Key |
seed | Entity Name (Optional) |
fingerprint | Signature to generate address (Optional) |
If seed
exists, fingerprint = private_key.sign(seed)
MKM
(Default)BTC
Extended BTCETH
Extended ETH- ...
A public key (PK) was binded to an ID by the Meta Algorithm.
A string as same as ID.name for generate the fingerprint.
THe fingerprint field was generated by your private key and seed:
data = utf8_encode(string=seed);
fingerprint = private_key.sign(data=data);
/* Meta(JsON) for hulk@4YeVEN3aUnvC1DNUufCq1bs9zoBSJTzVEj */
{
"type" : 0x01,
"key" : {
"algorithm" : "RSA",
"data" : "-----BEGIN PUBLIC KEY-----\nMIGJAoGBALB+vbUK48UU9rjlgnohQowME+3JtTb2hLPqtatVOW364/EKFq0/PSdnZVE9V2Zq+pbX7dj3nCS4pWnYf40ELH8wuDm0Tc4jQ70v4LgAcdy3JGTnWUGiCsY+0Z8kNzRkm3FJid592FL7ryzfvIzB9bjg8U2JqlyCVAyUYEnKv4lDAgMBAAE=\n-----END PUBLIC KEY-----",
"mode" : "ECB",
"padding" : "PKCS1",
"digest" : "SHA256"
},
"seed" : "hulk",
"fingerprint" : "jIPGWpWSbR/DQH6ol3t9DSFkYroVHQDvtbJErmFztMUP2DgRrRSNWuoKY5Y26qL38wfXJQXjYiWqNWKQmQe/gK8M8NkU7lRwm+2nh9wSBYV6Q4WXsCboKbnM0+HVn9Vdfp21hMMGrxTX1pBPRbi0567ZjNQC8ffdW2WvQSoec2I="
}
The ID is used to identify an entity(user/group). It consists of 3 fields:
Field | Description |
---|---|
type | Entity type |
name | Same with meta.seed (Optional) |
address | Unique Identification |
terminal | Login point (Optional) |
The ID format is name@address[/terminal]
.
# ID examples
ID1 = "hulk@4YeVEN3aUnvC1DNUufCq1bs9zoBSJTzVEj"; // Immortal Hulk
ID2 = "moki@4WDfe3zZ4T7opFSi3iDAKiuTnUHjxmXekk"; // Monkey King
class EntityType(IntEnum):
################################
# Main: 0, 1
################################
USER = 0x00 # 0000 0000
GROUP = 0x01 # 0000 0001 (User Group)
################################
# Network: 2, 3
################################
STATION = 0x02 # 0000 0010 (Server Node)
ISP = 0x03 # 0000 0011 (Service Provider)
# STATION_GROUP = 0x03 # 0000 0011
################################
# Bot: 4, 5
################################
BOT = 0x04 # 0000 0100 (Business Node)
ICP = 0x05 # 0000 0101 (Content Provider)
# BOT_GROUP = 0x05 # 0000 0101
################################
# Management: 6, 7, 8
################################
# SUPERVISOR = 0x06 # 0000 0110 (Company CEO)
# COMPANY = 0x07 # 0000 0111 (Super Group for ISP/ICP)
# CA = 0x08 # 0000 1000 (Certification Authority)
################################
# Customized: 64, 65
################################
# APP_USER = 0x40 # 0100 0000 (Application Customized User)
# APP_GROUP = 0x41 # 0100 0001 (Application Customized Group)
################################
# Broadcast: 128, 129
################################
ANY = 0x80 # 1000 0000 (anyone@anywhere)
EVERY = 0x81 # 1000 0001 (everyone@everywhere)
@classmethod
def is_user(cls, network: int) -> bool:
return (network & cls.GROUP) == cls.USER
@classmethod
def is_group(cls, network: int) -> bool:
return (network & cls.GROUP) == cls.GROUP
@classmethod
def is_broadcast(cls, network: int) -> bool:
return (network & cls.ANY) == cls.ANY
The Name field is a username, or just a random string for group:
- The length of name must more than 1 byte, less than 32 bytes;
- It should be composed by a-z, A-Z, 0-9, or charactors '_', '-', '.';
- It cannot contain key charactors('@', '/').
# Name examples
user_name = "Albert.Moky";
group_name = "Group-9527";
It's equivalent to meta.seed
The Address field was created with the Meta and a Network ID:
from typing import Optional
from mkm.types import ConstantString
from mkm.digest import sha256, ripemd160
from mkm.format import base58_encode, base58_decode
from mkm import Address
class BTCAddress(ConstantString, Address):
"""
Address like BitCoin
~~~~~~~~~~~~~~~~~~~~
data format: "network+digest+code"
network -- 1 byte
digest -- 20 bytes
check code -- 4 bytes
algorithm:
fingerprint = PK.data
digest = ripemd160(sha256(fingerprint));
code = sha256(sha256(network + digest)).prefix(4);
address = base58_encode(network + digest + code);
"""
def __init__(self, address: str, network: int):
super().__init__(string=address)
self.__type = network
@property # Override
def network(self) -> int:
return self.__type
#
# Factory methods
#
@classmethod
def from_data(cls, fingerprint: bytes, network: int) -> Address:
"""
Generate address with fingerprint and network ID
:param fingerprint: meta.fingerprint or key.data
:param network: address type
:return: Address object
"""
# 1. digest = ripemd160(sha256(fingerprint))
digest = ripemd160(sha256(fingerprint))
# 2. head = network + digest
head = chr(network).encode('latin1') + digest
# 3. cc = sha256(sha256(head)).prefix(4)
code = check_code(head)
# 4. data = base58_encode(head + cc)
address = base58_encode(head + code)
return cls(address=address, network=network)
@classmethod
def from_str(cls, address: str) -> Optional[Address]:
"""
Parse a string for BTC address
:param address: address string
:return: Address object
"""
if len(address) < 26 or len(address) > 35:
return None
# decode
data = base58_decode(address)
if data is None or len(data) != 25:
return None
# check code
prefix = data[:21]
suffix = data[21:]
if check_code(prefix) == suffix:
network = ord(data[:1])
return cls(address=address, network=network)
def check_code(data: bytes) -> bytes:
# check code in BTC address
return sha256(sha256(data))[:4]
from typing import Optional
from mkm.types import ConstantString
from mkm.digest import keccak256
from mkm.format import hex_encode
from mkm import Address, EntityType
class ETHAddress(ConstantString, Address):
"""
Address like Ethereum
~~~~~~~~~~~~~~~~~~~~~
data format: "0x{address}"
algorithm:
fingerprint = PK.data
digest = keccak256(fingerprint)
address = hex_encode(digest.suffix(20))
"""
def __init__(self, address: str):
super().__init__(string=address)
@property # Override
def network(self) -> int:
return EntityType.USER.value
@classmethod
def validate_address(cls, address: str) -> Optional[str]:
if is_eth(address=address):
lower = address[2:].lower()
return '0x%s' % eip55(address=lower)
# not an ETH address
@classmethod
def is_validate(cls, address: str) -> bool:
validate = cls.validate_address(address=address)
return validate is not None and validate == address
#
# Factory methods
#
@classmethod
def from_data(cls, fingerprint: bytes) -> Address:
"""
Generate ETH address with key.data
:param fingerprint: key.data
:return: Address object
"""
if len(fingerprint) == 65:
# skip first char
fingerprint = fingerprint[1:]
assert len(fingerprint) == 64, 'key data length error: %d' % len(fingerprint)
# 1. digest = keccak256(fingerprint)
digest = keccak256(data=fingerprint)
# 2. address = hex_encode(digest.suffix(20))
tail = digest[-20:]
address = '0x' + eip55(address=hex_encode(data=tail))
return cls(address=address)
@classmethod
def from_str(cls, address: str) -> Optional[Address]:
"""
Parse a string for ETH address
:param address: address string
:return: Address object
"""
if is_eth(address=address):
return cls(address=address)
# https://eips.ethereum.org/EIPS/eip-55
def eip55(address: str) -> str:
res = ''
table = keccak256(address.encode('utf-8'))
for i in range(40):
ch = address[i]
x = ord(ch)
if x > 0x39:
# check for each 4 bits in the hash table
# if the first bit is '1',
# change the character to uppercase
x -= (table[i >> 1] << (i << 2 & 4) & 0x80) >> 2
ch = chr(x)
res += ch
return res
def is_eth(address: str) -> bool:
if len(address) != 42:
return False
if address[0] != '0' or address[1] != 'x':
return False
for i in range(2, 42):
ch = address[i]
if '0' <= ch <= '9':
continue
if 'A' <= ch <= 'Z':
continue
if 'a' <= ch <= 'z':
continue
# unexpected character
return False
return True
When you get a meta for the entity ID from the network, you must verify it with the consensus algorithm before accepting its public key.
A resource identifier as Login Point.
(All data encode with BASE64 algorithm as default, excepts the address)