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 = privateKey.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(seed);
fingerprint = privateKey.sign(data);
/* Meta(JsON) for hulk@4YeVEN3aUnvC1DNUufCq1bs9zoBSJTzVEj */
{
"type" : '1',
"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 {
/// Main: 0, 1
static const USER = (0x00); // 0000 0000
static const GROUP = (0x01); // 0000 0001 (User Group)
/// Network: 2, 3
static const STATION = (0x02); // 0000 0010 (Server Node)
static const ISP = (0x03); // 0000 0011 (Service Provider)
// static const STATION_GROUP = (0x03); // 0000 0011
/// Bot: 4, 5
static const BOT = (0x04); // 0000 0100 (Business Node)
static const ICP = (0x05); // 0000 0101 (Content Provider)
// static const BOT_GROUP = (0x05); // 0000 0101
/// Management: 6, 7, 8
// static const SUPERVISOR = (0x06); // 0000 0110 (Company CEO)
// static const COMPANY = (0x07); // 0000 0111 (Super Group for ISP/ICP)
// static const CA = (0x08); // 0000 1000 (Certification Authority)
// /// Customized: 64, 65
// static const APP_USER = (0x40); // 0100 0000 (Application Customized User)
// static const APP_GROUP = (0x41); // 0100 0001 (Application Customized Group)
/// Broadcast: 128, 129
static const ANY = (0x80); // 1000 0000 (anyone@anywhere)
static const EVERY = (0x81); // 1000 0001 (everyone@everywhere)
static bool isUser(int network) {
return network & GROUP == USER;
}
static bool isGroup(int network) {
return network & GROUP == GROUP;
}
static bool isBroadcast(int network) {
return network & ANY == 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:
import 'dart:typed_data';
import 'package:mkm/type.dart';
import 'package:mkm/digest.dart';
import 'package:mkm/format.dart';
import 'package:mkm/protocol.dart';
/// 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);
///
class BTCAddress extends ConstantString implements Address {
BTCAddress(super.string, int network) : _type = network;
final int _type;
@override
int get network => _type;
/// Generate BTC address with fingerprint and network ID
///
/// @param fingerprint - meta.fingerprint or key.data
/// @param network - address type
/// @return Address object
static BTCAddress generate(Uint8List fingerprint, int network) {
// 1. digest = ripemd160(sha256(fingerprint))
Uint8List digest = RIPEMD160.digest(SHA256.digest(fingerprint));
// 2. head = network + digest
BytesBuilder bb = BytesBuilder(copy: false);
bb.addByte(network);
bb.add(digest);
Uint8List head = bb.toBytes();
// 3. cc = sha256(sha256(head)).prefix(4)
Uint8List cc = _checkCode(head);
// 4. data = base58_encode(head + cc)
bb = BytesBuilder(copy: false);
bb.add(head);
bb.add(cc);
return BTCAddress(Base58.encode(bb.toBytes()), network);
}
/// Parse a string for BTC address
///
/// @param address - address string
/// @return null on error
static BTCAddress? parse(String address) {
if (address.length < 26 || address.length > 35) {
return null;
}
// decode
Uint8List? data = Base58.decode(address);
if (data == null || data.length != 25) {
return null;
}
// check code
Uint8List prefix = data.sublist(0, 21);
Uint8List suffix = data.sublist(21, 25);
Uint8List cc = _checkCode(prefix);
if (Arrays.equals(cc, suffix)) {
return BTCAddress(address, data[0]);
} else {
return null;
}
}
}
Uint8List _checkCode(Uint8List data) {
return SHA256.digest(SHA256.digest(data)).sublist(0, 4);
}
import 'dart:typed_data';
import 'package:mkm/type.dart';
import 'package:mkm/digest.dart';
import 'package:mkm/format.dart';
import 'package:mkm/protocol.dart';
/// Address like Ethereum
/// ~~~~~~~~~~~~~~~~~~~~~
///
/// data format: "0x{address}"
///
/// algorithm:
/// fingerprint = PK.data;
/// digest = keccak256(fingerprint);
/// address = hex_encode(digest.suffix(20));
///
class ETHAddress extends ConstantString implements Address {
ETHAddress(super.string);
@override
int get network => EntityType.USER;
static String? getValidateAddress(String address) {
if (!_ETH.isETH(address)) {
// not an ETH address
return null;
}
String lower = address.substring(2).toLowerCase();
String eip55 = _ETH.eip55(lower);
return '0x$eip55';
}
static bool isValidate(String address) {
String? validate = getValidateAddress(address);
return validate != null && validate == address;
}
/// Generate ETH address with key.data
///
/// @param fingerprint = key.data
/// @return Address object
static ETHAddress generate(Uint8List fingerprint) {
if (fingerprint.length == 65) {
// skip first char
fingerprint = fingerprint.sublist(1);
}
assert(fingerprint.length == 64, 'key data error: ${fingerprint.length}');
// 1. digest = keccak256(fingerprint);
Uint8List digest = KECCAK256.digest(fingerprint);
// 2. address = hex_encode(digest.suffix(20));
Uint8List tail = digest.sublist(digest.length - 20);
String address = _ETH.eip55(Hex.encode(tail));
return ETHAddress('0x$address');
}
/// Parse a string for ETH address
///
/// @param address - address string
/// @return null on error
static ETHAddress? parse(String address) {
if (!_ETH.isETH(address)) {
// not an ETH address
return null;
}
return ETHAddress(address);
}
}
class _ETH {
// https://eips.ethereum.org/EIPS/eip-55
static String eip55(String hex) {
StringBuffer sb = StringBuffer();
Uint8List hash = KECCAK256.digest(UTF8.encode(hex));
int ch;
for (int i = 0; i < 40; ++i) {
ch = hex.codeUnitAt(i);
if (ch > _c9) {
// check for each 4 bits in the hash table
// if the first bit is '1',
// change the character to uppercase
ch -= (hash[i >> 1] << (i << 2 & 4) & 0x80) >> 2;
}
sb.writeCharCode(ch);
}
return sb.toString();
}
static bool isETH(String address) {
if (address.length != 42) {
return false;
}
if (address.codeUnitAt(0) != _c0 || address.codeUnitAt(1) != _cx) {
return false;
}
int ch;
for (int i = 2; i < 42; ++i) {
ch = address.codeUnitAt(i);
if (ch >= _c0 && ch <= _c9) {
continue;
}
if (ch >= _cA && ch <= _cZ) {
continue;
}
if (ch >= _ca && ch <= _cz) {
continue;
}
// unexpected character
return false;
}
return true;
}
static final int _c0 = '0'.codeUnitAt(0);
static final int _c9 = '9'.codeUnitAt(0);
static final int _cA = 'A'.codeUnitAt(0);
static final int _cZ = 'Z'.codeUnitAt(0);
static final int _ca = 'a'.codeUnitAt(0);
static final int _cx = 'x'.codeUnitAt(0);
static final int _cz = 'z'.codeUnitAt(0);
}
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)