Skip to content

dimchat/mkm-objc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

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 bound 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 dataUsingEncoding:NSUTF8StringEncoding];
fingerprint = [privateKey sign:data];

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 Type

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)

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

#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

ETH Address

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

@end

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.

Samples

ID examples

ID1 = @"hulk@4YeVEN3aUnvC1DNUufCq1bs9zoBSJTzVEj";  // Immortal Hulk
ID2 = @"moki@4WDfe3zZ4T7opFSi3iDAKiuTnUHjxmXekk";  // Monkey King

Meta 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)


Copyright © 2018-2025 Albert Moky Followers

About

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

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •