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- ...
/* 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
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.
(All data encode with BASE64 algorithm as default, excepts the address)