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)BTCExtended BTCETHExtended 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
mkm.protocol.EntityType = {
/**
* 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 President)
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)
};
var EntityType = mkm.protocol.EntityType;
EntityType.isUser = function (network) {
var user = Enum.getInt(EntityType.USER, 0x00);
var group = Enum.getInt(EntityType.GROUP, 0x01);
return (network & group) === user;
};
EntityType.isGroup = function (network) {
var group = Enum.getInt(EntityType.GROUP, 0x01);
return (network & group) === group;
};
EntityType.isBroadcast = function (network) {
var any = Enum.getInt(EntityType.ANY, 0x80);
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";
The Address field was created with the Meta and a Network ID:
mkm.mkm.BTCAddress = function (string, network) {
ConstantString.call(this, string);
this.__type = network;
};
var BTCAddress = mkm.mkm.BTCAddress;
Class(BTCAddress, ConstantString, [Address]);
Implementation(BTCAddress, {
// Override
getType: function () {
return this.__type;
}
});
/**
* Generate address with fingerprint and network ID
*
* @param {Uint8Array} fingerprint
* @param {uint} network
* @returns {BTCAddress}
*/
BTCAddress.generate = function (fingerprint, network) {
// 1. digest = ripemd160(sha256(fingerprint))
var digest = RIPEMD160.digest(SHA256.digest(fingerprint));
// 2. head = network + digest
var head = [];
head.push(network);
for (var i = 0; i < digest.length; ++i) {
head.push(digest[i]);
}
// 3. cc = sha256(sha256(head)).prefix(4)
var cc = check_code(Uint8Array.from(head));
// 4. data = base58_encode(head + cc)
var data = [];
for (var j = 0; j < head.length; ++j) {
data.push(head[j]);
}
for (var k = 0; k < cc.length; ++k) {
data.push(cc[k]);
}
return new BTCAddress(Base58.encode(Uint8Array.from(data)), network);
};
/**
* Parse a string for BTC address
*
* @param {String} string - address string
* @return {BTCAddress} null on error
*/
BTCAddress.parse = function (string) {
var len = string.length;
if (len < 26 || len > 35) {
return null;
}
// decode
var data = Base58.decode(string);
if (!data || data.length !== 25) {
// throw new RangeError('address length error: ' + string);
return null;
}
// check code
var prefix = data.subarray(0, 21);
var suffix = data.subarray(21, 25);
var cc = check_code(prefix);
if (Arrays.equals(cc, suffix)) {
return new BTCAddress(string, data[0]);
} else {
return null;
}
};
/**
* Get BTC check code
*
* @param {Uint8Array} data
* @return {Uint8Array}
*/
var check_code = function (data) {
var sha256d = SHA256.digest(SHA256.digest(data));
return sha256d.subarray(0, 4);
};mkm.mkm.ETHAddress = function (string) {
ConstantString.call(this, string);
};
var ETHAddress = mkm.mkm.ETHAddress;
Class(ETHAddress, ConstantString, [Address]);
Implementation(ETHAddress, {
// Override
getType: function () {
return EntityType.USER;
}
});
ETHAddress.getValidateAddress = function (address) {
if (!is_eth(address)) {
// not an ETH address
return null;
}
var lower = address.substr(2).toLowerCase();
return '0x' + eip55(lower);
};
ETHAddress.isValidate = function (address) {
return address === this.getValidateAddress(address);
};
/**
* Generate ETH address with key.data
*
* @param fingerprint = key.data
* @return Address object
*/
ETHAddress.generate = function (fingerprint) {
if (fingerprint.length === 65) {
fingerprint = fingerprint.subarray(1);
} else if (fingerprint.length !== 64) {
throw new TypeError('ECC key data error: ' + fingerprint);
}
// 1. digest = keccak256(fingerprint);
var digest = KECCAK256.digest(fingerprint);
// 2. address = hex_encode(digest.suffix(20));
var tail = digest.subarray(digest.length - 20);
var address = Hex.encode(tail);
return new ETHAddress('0x' + eip55(address));
};
/**
* Parse a string for ETH address
*
* @param address - address string
* @return null on error
*/
ETHAddress.parse = function (address) {
if (!is_eth(address)) {
// not an ETH address
return null;
}
return new ETHAddress(address);
};
// https://eips.ethereum.org/EIPS/eip-55
var eip55 = function (hex) {
var sb = new Uint8Array(40);
var hash = KECCAK256.digest(UTF8.encode(hex));
var ch;
var _9 = '9'.charCodeAt(0);
for (var i = 0; i < 40; ++i) {
ch = hex.charCodeAt(i);
if (ch > _9) {
// 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[i] = ch;
}
return UTF8.decode(sb);
};
var is_eth = function (address) {
if (address.length !== 42) {
return false;
} else if (address.charAt(0) !== '0' || address.charAt(1) !== 'x') {
return false;
}
var ch;
for (var i = 2; i < 42; ++i) {
ch = address.charCodeAt(i);
if (ch >= _0 && ch <= _9) {
continue;
}
if (ch >= _A && ch <= _Z) {
continue;
}
if (ch >= _a && ch <= _z) {
continue;
}
// unexpected character
return false;
}
return true;
};
var _0 = '0'.charCodeAt(0);
var _9 = '9'.charCodeAt(0);
var _A = 'A'.charCodeAt(0);
var _Z = 'Z'.charCodeAt(0);
var _a = 'a'.charCodeAt(0);
var _z = 'z'.charCodeAt(0);When you get a meta for the entity ID from the network, you must verify it with the consensus algorithm before accept its public key.
A resource identifier as Login Point.
(All data encode with BASE64 algorithm as default, excepts the address)