Skip to content

dimchat/mkm-js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

License PRs Welcome Platform Issues Repo Size Tags

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 = UTF8.encode(seed);
fingerprint = privateKey.sign(data);

Meta Example

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

ID

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

ID Type

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

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

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

ETH Address

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.

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