Skill Branches
Blockchain
Traceable
Non-repudiation
Distribution
Publicity
Mentor / Pro
Student
Signature
Verify
Signature
Story
Newbie
1. Create avatar with email with description.
2. Find someone with skills to signature.
That's all.
In network, every user has:
1. Personal card with email and desc (signed).
2. Skill history chain.
Implementation
Specification
Personal Card
Total Length: ushort (0-65535)
Email Address: email with ASCII encoding
Timestamp: uint millisecond before
Public Address: secp256k1 public key
Name: name with utf8mb4 encoding
Description: description with utf8mb4 encoding
Signature: ecdsa signature with keccak256 hash
Specification
Skill chain (512 bytes)
Skills: store bool with pre-defined skills (over 86K types)
Signature Address: skill signer secp256k1 public key
Receiver Address: skill receiver secp256k1 public key
Previous Block Hash: last block payload keccak256 hash
Action: signer action like add(0)
Signature: ecdsa signature with keccak256 hash
Skill Defined
We have more than 86K space to store skill...
0: HTML5
1: CSS Layout (block element, inline element)
2: CSS Flexbox
3: CSS Grid Layout
4: JavaScript Prototype
5: JavaScript Class
6: Go - Goroutine
7: Python - Tuple
.
.
.
0x4F 0x03 0xD2 ....
Transmission Protocol
1. UDP based
2. Kademlia based network
3. Broadcast updates
4. TTL based data query
5. LRU cache for timeout management
6. Local cache with level db
Source Code
export class Block {
static VERSION = Buffer.from('01000000', 'hex');
static ROOT_PUBLIC_KEY = Buffer.from('8c1c9cdbe4768df88a5a40adf6d0caa30adac5b7', 'hex');
static isValid(buffer: Buffer, version?: BlockVersion): boolean {
if (!version) {
return Block.isValid(buffer, buffer.readUInt32LE());
}
switch (version) {
case BlockVersion.ONE:
default: {
const publicKey = buffer.slice(344, 377);
const signature = buffer.slice(448);
const payload = createKeccakHash('keccak256').update(buffer.slice(0, 448)).digest();
return secp256k1.ecdsaVerify(new Uint8Array(signature), new Uint8Array(payload), new Uint8Array(publicKey));
}
}
}
public prevBlock: Buffer;
public timestamp: number;
public skills: Buffer;
public signKey?: Buffer;
public receiver: Buffer;
public action: BlockAction = BlockAction.SIGNATURE;
constructor(options: BlockInitArgs | Buffer) {
if (options instanceof Buffer) {
if (!Block.isValid(options)) throw new Error('Invalid Buffer');
this.receiver = options.slice(377, 410);
this.skills = options.slice(4, 340);
this.timestamp = options.readUInt32LE(340);
this.prevBlock = options.slice(410, 442);
this.action = options.readUInt32LE(442);
} else {
this.signKey = options.signKey;
this.receiver = options.receiver;
this.action = options.action || this.action;
this.prevBlock = options.prevBlock || Buffer.alloc(32);
this.timestamp = Math.round(Date.now() / 1000);
this.skills = options.skills;
}
}
// Total: 512 bytes
// | Version (4) | skills (336) | timestamp (4)
// | signerAddress (33) | receiverAddress (33) | previousBlockHash (32)
// | action (4) | reserved (2) | Signature (64)
toBuffer() {
if (!this.signKey) throw new Error('No Private Key to Sign');
const payloadBuffer = Buffer.alloc(448);
Block.VERSION.copy(payloadBuffer);
this.skills.copy(payloadBuffer, 4);
const timeBuffer = Buffer.alloc(4);
timeBuffer.writeUInt32LE(this.timestamp);
timeBuffer.copy(payloadBuffer, 340);
Buffer.from(secp256k1.publicKeyCreate(this.signKey)).copy(payloadBuffer, 344);
this.receiver.copy(payloadBuffer, 377);
this.prevBlock.copy(payloadBuffer, 410);
const actionBuffer = Buffer.alloc(4);
actionBuffer.writeUInt32LE(this.timestamp);
actionBuffer.copy(payloadBuffer, 442);
const checksumBuffer = createKeccakHash('keccak256').update(payloadBuffer).digest();
const { signature } = secp256k1.ecdsaSign(checksumBuffer, this.signKey);
return Buffer.concat([payloadBuffer, Buffer.from(signature)]);
}
}
export default Block;
UDP Server
class Server {
start() {
this._kBucket = new KBucket({
localNodeId: this._publicKey,
});
this._kBucket.on('added', (newPeer) => {
const peer = newPeer as PeerContact;
const neighbours = this._kBucket?.closest(peer.id, Server.KBUCKET_SIZE);
neighbours?.forEach((neighbour) => {
const neighbourPeer = neighbour as PeerContact;
this._send(neighbourPeer, RequestType.FOUND_NEIGHBOURS, [
Buffer.from([Server.VERSION]),
Server.encodePeer(this.getPeerInfo()),
[Server.encodePeer(peer)],
]);
});
this._debug(this._kBucket?.toArray().length);
});
this._socket = createSocket({ type: 'udp4' });
this._socket.once('listening', () => {
this._debug(`Socket is listening on port ${this._socket?.address().port}`);
this._kBucket?.add(this.getPeerInfo() as PeerContact);
this.emit('listening');
if (options?.initPeers?.length) {
options.initPeers.forEach(async (peer) => {
await this.ping(peer);
await this.findNeighbours(peer);
});
}
});
this._socket.on('message', (message: Buffer, info: RemoteInfo) => {
this._onMessage(message, info);
});
this._socket.bind();
}
}
PING/PONG
class Server {
async ping(peer: PeerInfo): Promise<any> {
const cacheKey = `${peer.address}:${peer.udpPort}`;
const ongoingRequest = this._requestsCache.get(cacheKey);
if (ongoingRequest) return ongoingRequest;
const messageId = this._send(peer, RequestType.PING, [
Buffer.from([Server.VERSION]),
Server.encodePeer(this.getPeerInfo()),
Server.encodePeer(peer),
]);
const messageKey = messageId.toString('hex');
const promise = new Promise((resolve, reject) => {
this._requests.set(messageKey, {
peer,
done: resolve,
timeoutId: setTimeout(() => {
if (this._requests.get(messageKey)) {
this._requests.delete(messageKey);
reject(new Error(`Ping timeout: ${peer.address}:${peer.udpPort}`));
}
}, Server.TIMEOUT_MS),
});
});
this._requestsCache.set(cacheKey, promise);
}
}
private _encode(type: RequestType, data: any) {
const fullData = Buffer.concat([Buffer.from([type]), rlpEncode(data)]);
const fullDataHash = keccak256(fullData);
const { recid, signature } = secp256k1.ecdsaSign(fullDataHash, this._signKey);
const signedData = Buffer.concat([Buffer.from(signature), Buffer.from([recid]), fullData]);
const completeHash = keccak256(signedData);
return Buffer.concat([completeHash, signedData]);
}
private _send(peer: PeerInfo, type: RequestType, data: any) {
const message = this._encode(type, data);
this._socket?.send(message, 0, message.length, peer.udpPort!, peer.address);
return message.slice(0, 32);
}
PING/PONG
class Server {
private _onPing(data: any, messageId: Buffer, info: RemoteInfo) {
const version = data[0][0];
const peer = Server.decodePeer(data[1]);
if (version !== Server.VERSION) throw new Error('Version not matched!');
if (!this._kBucket?.get(peer.id)) {
this._kBucket?.add({
id: peer.id,
address: info.address,
tcpPort: null,
udpPort: info.port,
} as PeerContact);
}
this._send({
id: peer.id,
address: info.address,
tcpPort: null,
udpPort: info.port,
}, RequestType.PONG, [
Buffer.from([Server.VERSION]),
Server.encodePeer(this.getPeerInfo()),
Server.encodePeer({
id: peer.id,
address: info.address,
tcpPort: null,
udpPort: info.port,
}),
messageId,
]);
}
static decodePeer(buffers: Buffer[]): PeerInfo {
const address = ip.toString(buffers[1]);
const tcpPort = buffer2int(buffers[2]);
const udpPort = buffer2int(buffers[3]);
return {
id: buffers[0],
address,
tcpPort: Number.isNaN(tcpPort) ? null : tcpPort,
udpPort: Number.isNaN(udpPort) ? null : udpPort,
};
}
private _decode(data: Buffer) {
const completeHash = keccak256(data.slice(32));
if (!completeHash.equals(data.slice(0, 32))) throw new Error('Incompleted Data');
const fullData = data.slice(97);
const type = fullData[0] as RequestType;
const fullDataHash = keccak256(fullData);
const signature = data.slice(32, 96);
const recid = data[96];
const publicKey = Buffer.from(secp256k1.ecdsaRecover(signature, recid, fullDataHash, false));
return {
type,
data: rlpDecode(fullData.slice(1)),
publicKey,
};
}
}
Neighbors
class Server {
private _onFindNeighbours(data: any, info: RemoteInfo) {
const version = data[0][0];
if (version !== Server.VERSION) throw new Error('Version not matched!');
const remotePeer = {
id: data[1],
address: info.address,
tcpPort: null,
udpPort: info.port,
};
this._send(remotePeer, RequestType.FOUND_NEIGHBOURS, [
Buffer.from([Server.VERSION]),
Server.encodePeer(this.getPeerInfo()),
this._kBucket?.closest(remotePeer.id, Server.KBUCKET_SIZE)
.map(peer => Server.encodePeer(peer as PeerContact)),
]);
}
}
... to be continued
Skill Branches
By Chia Yu Pai
Skill Branches
- 406