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 bound 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 dataUsingEncoding:NSUTF8StringEncoding];
fingerprint = [privateKey sign:data];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].
typedef NS_ENUM(UInt8, MKMNetworkID) {
/**
* Main: 0, 1
*/
MKMEntityType_User = 0x00, // 0000 0000
MKMEntityType_Group = 0x01, // 0000 0001 (User Group)
/**
* Network: 2, 3
*/
MKMEntityType_Station = 0x02, // 0000 0010 (Server Node)
MKMEntityType_ISP = 0x03, // 0000 0011 (Service Provider)
//MKMEntityType_StationGroup = 0x03, // 0000 0011
/**
* Bot: 4, 5
*/
MKMEntityType_Bot = 0x04, // 0000 0100 (Business Node)
MKMEntityType_ICP = 0x05, // 0000 0101 (Content Provider)
//MKMEntityType_BotGroup = 0x05, // 0000 0101
/**
* Management: 6, 7, 8
*/
//MKMEntityType_Supervisor = 0x06, // 0000 0110 (Company President)
//MKMEntityType_Company = 0x07, // 0000 0111 (Super Group for ISP/ICP)
//MKMEntityType_CA = 0x08, // 0000 1000 (Certification Authority)
/*
* Customized: 64, 65
*/
//MKMEntityType_AppUser = 0x40, // 0100 0000 (Application Customized User)
//MKMEntityType_AppGroup = 0x41, // 0100 0001 (Application Customized Group)
/**
* Broadcast: 128, 129
*/
MKMEntityType_Any = 0x80, // 1000 0000 (anyone@anywhere)
MKMEntityType_Every = 0x81, // 1000 0001 (everyone@everywhere)
};
typedef UInt8 MKMEntityType;
#define MKMEntityTypeIsUser(network) (((network) & MKMEntityType_Group) == MKMEntityType_User)
#define MKMEntityTypeIsGroup(network) (((network) & MKMEntityType_Group) == MKMEntityType_Group)
#define MKMEntityTypeIsBroadcast(network) (((network) & MKMEntityType_Any) == MKMEntityType_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:
#import <MingKeMing/Type.h>
#import <MingKeMing/MingKeMing.h>
NS_ASSUME_NONNULL_BEGIN
/*
* Address like BitCoin
*
* data format: "network+digest+code"
* network -- 1 byte
* digest -- 20 bytes
* code -- 4 bytes
*
* algorithm:
* fingerprint = sign(seed, SK); // public key data
* digest = ripemd160(sha256(fingerprint));
* code = sha256(sha256(network + digest)).prefix(4);
* address = base58_encode(network + digest + code);
*/
@interface DIMBTCAddress : MKString <MKMAddress>
- (instancetype)initWithString:(NSString *)address type:(MKMEntityType)network
NS_DESIGNATED_INITIALIZER;
/**
* Generate address with fingerprint and network ID
*
* @param fingerprint = meta.fingerprint or key.data
* @param network - address type
* @return Address object
*/
+ (instancetype)generate:(NSData *)fingerprint type:(MKMEntityType)network;
/**
* Parse a string for BTC address
*
* @param string - address string
* @return null on error
*/
+ (instancetype)parse:(NSString *)string;
@end
NS_ASSUME_NONNULL_END#import "DIMBTCAddress.h"
@interface DIMBTCAddress ()
@property (nonatomic) MKMEntityType network; // Network ID
@end
/**
* BTC address algorithm:
* digest = ripemd160(sha256(fingerprint));
* check_code = sha256(sha256(network + digest)).prefix(4);
* addr = base58_encode(network + digest + check_code);
*/
@implementation DIMBTCAddress
- (instancetype)init {
NSAssert(false, @"DON'T call me!");
NSString *string = nil;
return [self initWithString:string];
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
NSAssert(false, @"DON'T call me!");
NSString *string = nil;
return [self initWithString:string type:0];
}
- (instancetype)initWithString:(NSString *)address {
//NSAssert(false, @"DON'T call me!");
return [self initWithString:address type:0];
}
/* designated initializer */
- (instancetype)initWithString:(NSString *)address type:(MKMEntityType)network {
if (self = [super initWithString:address]) {
_network = network;
}
return self;
}
- (id)copyWithZone:(nullable NSZone *)zone {
DIMBTCAddress *address = [super copyWithZone:zone];
if (address) {
address.network = _network;
}
return address;
}
#pragma mark Coding
static inline NSData *check_code(NSData *data) {
assert([data length] == 21);
NSData *sha256d = MKSHA256Digest(MKSHA256Digest(data));
return [sha256d subdataWithRange:NSMakeRange(0, 4)];
}
+ (instancetype)generate:(NSData *)fingerprint type:(MKMEntityType)network {
// 1. digest = ripemd160(sha256(fingerprint))
NSData *digest = MKRipeMD160Digest(MKSHA256Digest(fingerprint));
// 2. head = network + digest
NSMutableData *data = [[NSMutableData alloc] initWithBytes:&network length:1];
[data appendData:digest];
// 3. cc = sha256(sha256(head)).prefix(4)
NSData *cc = check_code(data);
// 4. addr = base58_encode(_h + cc)
[data appendData:cc];
NSString *string = MKBase58Encode(data);
return [[self alloc] initWithString:string type:network];
}
+ (instancetype)parse:(NSString *)string {
if (string.length < 26 || string.length > 35) {
return nil;
}
// decode
NSData *data = MKBase58Decode(string);
if (data.length != 25) {
return nil;
}
// Check Code
NSData *prefix = [data subdataWithRange:NSMakeRange(0, 21)];
NSData *suffix = [data subdataWithRange:NSMakeRange(21, 4)];
NSData *cc = check_code(prefix);
if ([cc isEqualToData:suffix]) {
UInt8 *bytes = (UInt8 *)data.bytes;
return [[self alloc] initWithString:string type:bytes[0]];
} else {
return nil;
}
}
@end#import <MingKeMing/Type.h>
#import <MingKeMing/MingKeMing.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Address like Ethereum
*
* data format: "0x{address}"
*
* algorithm:
* fingerprint = PK.data;
* digest = keccak256(fingerprint);
* address = hex_encode(digest.suffix(20));
*/
@interface DIMETHAddress : MKString <MKMAddress>
+ (NSString *)validateAddress:(NSString *)address;
+ (BOOL)isValidate:(NSString *)address;
/**
* Generate ETH address with key.data
*
* @param fingerprint = key.data
* @return Address object
*/
+ (instancetype)generate:(NSData *)fingerprint;
/**
* Parse a string for ETH address
*
* @param string - address string
* @return null on error
*/
+ (instancetype)parse:(NSString *)string;
@end#import "DIMETHAddress.h"
// https://eips.ethereum.org/EIPS/eip-55
static inline NSString *eip55(NSString *hex) {
NSData *utf8 = MKUTF8Encode(hex);
NSData *digest = MKKeccak256Digest(utf8);
UInt8 *origin = (UInt8 *)utf8.bytes;
UInt8 *hash = (UInt8 *)digest.bytes;
UInt8 buffer[40];
UInt8 ch;
for (int i = 0; i < 40; ++i) {
ch = origin[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;
}
buffer[i] = ch;
}
return [[NSString alloc] initWithBytes:buffer length:40 encoding:NSUTF8StringEncoding];
}
static inline BOOL is_eth(NSString *address) {
if (address.length != 42) {
return NO;
}
NSData *data = MKUTF8Encode(address);
UInt8 *buffer = (UInt8 *)data.bytes;
if (buffer[0] != '0' || buffer[1]!= 'x') {
return NO;
}
char ch;
for (int i = 2; i < 42; ++i) {
ch = buffer[i];
if (ch >= '0' && ch <= '9') {
continue;
}
if (ch >= 'A' && ch <= 'Z') {
continue;
}
if (ch >= 'a' && ch <= 'z') {
continue;
}
// unexpected character
return NO;
}
return YES;
}
@implementation DIMETHAddress
// Override
- (MKMEntityType)network {
return MKMEntityType_User;
}
#pragma mark Coding
+ (NSString *)validateAddress:(NSString *)address {
if (is_eth(address)) {
address = [address substringFromIndex:2];
address = [address lowercaseString];
return [NSString stringWithFormat:@"0x%@", eip55(address)];
}
return nil;
}
+ (BOOL)isValidate:(NSString *)address {
NSString *validate = [self validateAddress:address];
return [validate isEqualToString:address];
}
+ (instancetype)generate:(NSData *)fingerprint {
if (fingerprint.length == 65) {
fingerprint = [fingerprint subdataWithRange:NSMakeRange(1, 64)];
}
NSAssert(fingerprint.length == 64, @"key data length error: %lu", fingerprint.length);
// 1. digest = keccak256(fingerprint);
NSData *digest = MKKeccak256Digest(fingerprint);
// 2. address = hex_encode(digest.suffix(20));
NSData *tail = [digest subdataWithRange:NSMakeRange(digest.length - 20, 20)];
NSString *hex = MKHexEncode(tail);
NSString *address = [NSString stringWithFormat:@"0x%@", eip55(hex)];
return [[self alloc] initWithString:address];
}
+ (instancetype)parse:(NSString *)string {
if (is_eth(string)) {
return [[self alloc] initWithString:string];
}
return nil;
}
@endWhen 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.
ID examples
ID1 = @"hulk@4YeVEN3aUnvC1DNUufCq1bs9zoBSJTzVEj"; // Immortal Hulk
ID2 = @"moki@4WDfe3zZ4T7opFSi3iDAKiuTnUHjxmXekk"; // Monkey KingMeta Example (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="
}(All data encoded with BASE64 algorithm as default, excepts the address)