Skip to content

dimchat/mkm-dart

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

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.

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

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

It's equivalent to meta.seed

ID Address

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

BTC Address

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

ETH Address

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.

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


Copyright © 2023 Albert Moky Followers

About

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

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages