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 = seed.getBytes(Charset.forName("UTF-8"));
fingerprint = privateKey.sign(data);
/* Meta(JsON) for hulk@4YeVEN3aUnvC1DNUufCq1bs9zoBSJTzVEj */
{
"version" : 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 and 2 extended properties:
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
public enum 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 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)
// Network ID
public final int value;
EntityType(int network) {
value = network;
}
public boolean equals(int other) {
return value == other;
}
public static boolean isUser(int network) {
return (network & GROUP.value) == USER.value;
}
public static boolean isGroup(int network) {
return (network & GROUP.value) == GROUP.value;
}
public static boolean isBroadcast(int network) {
return (network & ANY.value) == ANY.value;
}
}
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:
/**
* Address like BitCoin
*
* <blockquote><pre>
* 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);
* </pre></blockquote>
*/
public final class BTCAddress extends ConstantString implements Address {
private final byte type;
public BTCAddress(String string, byte network) {
super(string);
type = network;
}
@Override
public int getNetwork() {
return type;
}
/**
* Generate BTC address with fingerprint and network ID
*
* @param fingerprint - meta.fingerprint or key.data
* @param network - address type
* @return Address object
*/
public static BTCAddress generate(byte[] fingerprint, byte network) {
// 1. digest = ripemd160(sha256(fingerprint))
byte[] digest = RIPEMD160.digest(SHA256.digest(fingerprint));
// 2. head = network + digest
byte[] head = new byte[21];
head[0] = network;
System.arraycopy(digest, 0, head, 1, 20);
// 3. cc = sha256(sha256(head)).prefix(4)
byte[] cc = checkCode(head);
// 4. data = base58_encode(head + cc)
byte[] data = new byte[25];
System.arraycopy(head, 0, data, 0, 21);
System.arraycopy(cc,0, data, 21, 4);
return new BTCAddress(Base58.encode(data), network);
}
/**
* Parse a string for BTC address
*
* @param address - address string
* @return null on error
*/
public static BTCAddress parse(String address) {
int len = address.length();
if (len < 26 || len > 35) {
return null;
}
// decode
byte[] data = Base58.decode(address);
if (data == null || data.length != 25) {
return null;
}
// Check Code
byte[] prefix = new byte[21];
byte[] suffix = new byte[4];
System.arraycopy(data, 0, prefix, 0, 21);
System.arraycopy(data, 21, suffix, 0, 4);
byte[] cc = checkCode(prefix);
if (Arrays.equals(cc, suffix)) {
return new BTCAddress(address, data[0]);
} else {
return null;
}
}
private static byte[] checkCode(byte[] data) {
byte[] sha256d = SHA256.digest(SHA256.digest(data));
assert sha256d != null : "sha256 error";
byte[] cc = new byte[4];
System.arraycopy(sha256d, 0, cc, 0, 4);
return cc;
}
}
/**
* Address like Ethereum
*
* <blockquote><pre>
* data format:
* "0x{address}"
*
* algorithm:
* fingerprint = PK.data;
* digest = keccak256(fingerprint);
* address = hex_encode(digest.suffix(20));
* </pre></blockquote>
*/
public final class ETHAddress extends ConstantString implements Address {
public ETHAddress(String string) {
super(string);
}
@Override
public int getNetwork() {
return EntityType.USER.value;
}
// https://eips.ethereum.org/EIPS/eip-55
private static String eip55(String hex) {
StringBuilder sb = new StringBuilder();
byte[] hash = Keccak256.digest(hex.getBytes());
char ch;
for (int i = 0; i < 40; ++i) {
ch = hex.charAt(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.append(ch);
}
return sb.toString();
}
private static boolean isETH(String address) {
if (address.length() != 42) {
return false;
}
if (address.charAt(0) != '0' || address.charAt(1) != 'x') {
return false;
}
char ch;
for (int i = 2; i < 42; ++i) {
ch = address.charAt(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;
}
public static String getValidateAddress(String address) {
if (!isETH(address)) {
// not an ETH address
return null;
}
String lower = address.substring(2).toLowerCase();
return "0x" + eip55(lower);
}
public static boolean isValidate(String address) {
String validate = getValidateAddress(address);
return validate != null && validate.equals(address);
}
/**
* Generate ETH address with key.data
*
* @param fingerprint = key.data
* @return Address object
*/
public static ETHAddress generate(byte[] fingerprint) {
if (fingerprint.length == 65) {
// skip first char
byte[] data = new byte[64];
System.arraycopy(fingerprint, 1, data, 0, 64);
fingerprint = data;
}
assert fingerprint.length == 64 : "key data length error: " + fingerprint.length;
// 1. digest = keccak256(fingerprint);
byte[] digest = Keccak256.digest(fingerprint);
// 2. address = hex_encode(digest.suffix(20));
byte[] tail = new byte[20];
System.arraycopy(digest, digest.length - 20, tail, 0, 20);
String address = "0x" + eip55(Hex.encode(tail));
return new ETHAddress(address);
}
/**
* Parse a string for ETH address
*
* @param address - address string
* @return null on error
*/
public static ETHAddress parse(String address) {
if (!isETH(address)) {
// not an ETH address
return null;
}
return new ETHAddress(address);
}
}
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)