Skip to content

dimchat/mkm-java

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ming Ke Ming (名可名) -- Account Module (Java)

License PRs Welcome Platform Issues Repo Size Tags Version

Watchers Forks Stars Followers

This document introduces a common Account Module for decentralized user identity authentication.

Features

Meta

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)

Meta Type

  1. MKM (Default)
  2. BTC
  3. Extended BTC
  4. ETH
  5. Extended ETH
  6. ...

Public Key

A public key (PK) was binded to an ID by the Meta Algorithm.

Seed

A string as same as ID.name for generate the fingerprint.

Fingerprint

THe fingerprint field was generated by your private key and seed:

data = seed.getBytes(Charset.forName("UTF-8"));
fingerprint = privateKey.sign(data);

Meta Example

/* 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="
}

ID

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

ID Type

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;
    }
}

ID Name

The Name field is a username, or just a random string for group:

  1. The length of name must more than 1 byte, less than 32 bytes;
  2. It should be composed by a-z, A-Z, 0-9, or charactors '_', '-', '.';
  3. It cannot contain key charactors('@', '/').
# Name examples
user_name  = "Albert.Moky";
group_name = "Group-9527";

ID Address

The Address field was created with the Meta and a Network ID:

BTC Address

/**
 *  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;
    }
}

ETH Address

/**
 *  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.

Terminal

A resource identifier as Login Point.

(All data encode with BASE64 algorithm as default, excepts the address)


Copyright © 2018-2025 Albert Moky Followers

About

Ming Ke Ming (名可名) -- Account Module

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages