Skip to content

Commit

Permalink
Merge pull request #107 from ChainSafe/cayman/discv5.1
Browse files Browse the repository at this point in the history
Initial discv5.1 update
  • Loading branch information
wemeetagain committed Nov 19, 2020
2 parents a4c9cb4 + 77c54ff commit 05ba828
Show file tree
Hide file tree
Showing 28 changed files with 1,397 additions and 1,312 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@
"typescript": "^3.8.3"
},
"dependencies": {
"@types/err-code": "^2.0.0",
"base64url": "^3.0.1",
"bcrypto": "^4.2.8",
"bigint-buffer": "^1.1.5",
"debug": "^4.1.1",
"dgram": "^1.0.1",
"err-code": "^2.0.3",
"ip6addr": "^0.2.3",
"is-ip": "^3.1.0",
"libp2p-crypto": "^0.18.0",
Expand Down
4 changes: 2 additions & 2 deletions src/message/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ export function createPongMessage(
};
}

export function createFindNodeMessage(distance: number): IFindNodeMessage {
export function createFindNodeMessage(distances: number[]): IFindNodeMessage {
return {
type: MessageType.FINDNODE,
id: createRequestId(),
distance,
distances,
};
}

Expand Down
37 changes: 36 additions & 1 deletion src/message/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
ITopicQueryMessage,
Message,
MessageType,
ITalkReqMessage,
ITalkRespMessage,
} from "./types";
import { ENR } from "../enr";

Expand All @@ -29,6 +31,10 @@ export function decode(data: Buffer): Message {
return decodeFindNode(data);
case MessageType.NODES:
return decodeNodes(data);
case MessageType.TALKREQ:
return decodeTalkReq(data);
case MessageType.TALKRESP:
return decodeTalkResp(data);
case MessageType.REGTOPIC:
return decodeRegTopic(data);
case MessageType.TICKET:
Expand Down Expand Up @@ -78,10 +84,14 @@ function decodeFindNode(data: Buffer): IFindNodeMessage {
if (!Array.isArray(rlpRaw) || rlpRaw.length !== 2) {
throw new Error(ERR_INVALID_MESSAGE);
}
if (!Array.isArray(rlpRaw[1])) {
throw new Error(ERR_INVALID_MESSAGE);
}
const distances = ((rlpRaw[1] as unknown) as Buffer[]).map((x) => (x.length ? x.readUInt8(0) : 0));
return {
type: MessageType.FINDNODE,
id: toBigIntBE(rlpRaw[0]),
distance: rlpRaw[1].length ? rlpRaw[1].readUInt8(0) : 0,
distances,
};
}

Expand All @@ -98,6 +108,31 @@ function decodeNodes(data: Buffer): INodesMessage {
};
}

function decodeTalkReq(data: Buffer): ITalkReqMessage {
const rlpRaw = (RLP.decode(data.slice(1)) as unknown) as RLP.Decoded;
if (!Array.isArray(rlpRaw) || rlpRaw.length !== 3) {
throw new Error(ERR_INVALID_MESSAGE);
}
return {
type: MessageType.TALKREQ,
id: toBigIntBE(rlpRaw[0]),
protocol: rlpRaw[1],
request: rlpRaw[2],
};
}

function decodeTalkResp(data: Buffer): ITalkRespMessage {
const rlpRaw = (RLP.decode(data.slice(1)) as unknown) as RLP.Decoded;
if (!Array.isArray(rlpRaw) || rlpRaw.length !== 2) {
throw new Error(ERR_INVALID_MESSAGE);
}
return {
type: MessageType.TALKRESP,
id: toBigIntBE(rlpRaw[0]),
response: rlpRaw[1],
};
}

function decodeRegTopic(data: Buffer): IRegTopicMessage {
const rlpRaw = (RLP.decode(data.slice(1)) as unknown) as Buffer[];
if (!Array.isArray(rlpRaw) || rlpRaw.length !== 4 || !Array.isArray(rlpRaw[2])) {
Expand Down
16 changes: 15 additions & 1 deletion src/message/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
ITopicQueryMessage,
Message,
MessageType,
ITalkReqMessage,
ITalkRespMessage,
} from "./types";

export function encode(message: Message): Buffer {
Expand All @@ -25,6 +27,10 @@ export function encode(message: Message): Buffer {
return encodeFindNodeMessage(message as IFindNodeMessage);
case MessageType.NODES:
return encodeNodesMessage(message as INodesMessage);
case MessageType.TALKREQ:
return encodeTalkReqMessage(message as ITalkReqMessage);
case MessageType.TALKRESP:
return encodeTalkRespMessage(message as ITalkRespMessage);
case MessageType.REGTOPIC:
return encodeRegTopicMessage(message as IRegTopicMessage);
case MessageType.TICKET:
Expand Down Expand Up @@ -58,7 +64,7 @@ export function encodePongMessage(m: IPongMessage): Buffer {
}

export function encodeFindNodeMessage(m: IFindNodeMessage): Buffer {
return Buffer.concat([Buffer.from([MessageType.FINDNODE]), RLP.encode([toBuffer(m.id), m.distance])]);
return Buffer.concat([Buffer.from([MessageType.FINDNODE]), RLP.encode([toBuffer(m.id), m.distances])]);
}

export function encodeNodesMessage(m: INodesMessage): Buffer {
Expand All @@ -68,6 +74,14 @@ export function encodeNodesMessage(m: INodesMessage): Buffer {
]);
}

export function encodeTalkReqMessage(m: ITalkReqMessage): Buffer {
return Buffer.concat([Buffer.from([MessageType.TALKREQ]), RLP.encode([toBuffer(m.id), m.protocol, m.request])]);
}

export function encodeTalkRespMessage(m: ITalkRespMessage): Buffer {
return Buffer.concat([Buffer.from([MessageType.TALKRESP]), RLP.encode([toBuffer(m.id), m.response])]);
}

export function encodeRegTopicMessage(m: IRegTopicMessage): Buffer {
return Buffer.concat([
Buffer.from([MessageType.REGTOPIC]),
Expand Down
34 changes: 27 additions & 7 deletions src/message/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,24 @@ export enum MessageType {
PONG = 2,
FINDNODE = 3,
NODES = 4,
REGTOPIC = 5,
TICKET = 6,
REGCONFIRMATION = 7,
TOPICQUERY = 8,
TALKREQ = 5,
TALKRESP = 6,
REGTOPIC = 7,
TICKET = 8,
REGCONFIRMATION = 9,
TOPICQUERY = 10,
}

export type Message = RequestMessage | ResponseMessage;

export type RequestMessage = IPingMessage | IFindNodeMessage | IRegTopicMessage | ITopicQueryMessage;
export type RequestMessage = IPingMessage | IFindNodeMessage | ITalkReqMessage | IRegTopicMessage | ITopicQueryMessage;

export type ResponseMessage = IPongMessage | INodesMessage | ITicketMessage | IRegConfirmationMessage;
export type ResponseMessage =
| IPongMessage
| INodesMessage
| ITalkRespMessage
| ITicketMessage
| IRegConfirmationMessage;

export interface IPingMessage {
type: MessageType.PING;
Expand All @@ -36,7 +43,7 @@ export interface IPongMessage {
export interface IFindNodeMessage {
type: MessageType.FINDNODE;
id: RequestId;
distance: number;
distances: number[];
}

export interface INodesMessage {
Expand All @@ -46,6 +53,19 @@ export interface INodesMessage {
enrs: ENR[];
}

export interface ITalkReqMessage {
type: MessageType.TALKREQ;
id: RequestId;
protocol: Buffer;
request: Buffer;
}

export interface ITalkRespMessage {
type: MessageType.TALKRESP;
id: RequestId;
response: Buffer;
}

export interface IRegTopicMessage {
type: MessageType.REGTOPIC;
id: RequestId;
Expand Down
39 changes: 24 additions & 15 deletions src/packet/constants.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
// length of a tag in a packet
export const TAG_LENGTH = 32;
export const MAX_PACKET_SIZE = 1280;
export const MIN_PACKET_SIZE = 63;

// length of an authentication tag
export const AUTH_TAG_LENGTH = 12;
export const MASKING_KEY_SIZE = 16;

// length of magic token
export const MAGIC_LENGTH = 32;
export const PROTOCOL_SIZE = 6;
export const VERSION_SIZE = 2;
export const FLAG_SIZE = 1;
export const NONCE_SIZE = 12;
export const AUTHDATA_SIZE_SIZE = 2;
export const STATIC_HEADER_SIZE = 23;

// length of nonce
export const ID_NONCE_LENGTH = 32;
export const MESSAGE_AUTHDATA_SIZE = 32;
export const WHOAREYOU_AUTHDATA_SIZE = 24;
export const MIN_HANDSHAKE_AUTHDATA_SIZE = 34 + 64 + 33;

export const RANDOM_DATA_LENGTH = 44;
export const SIG_SIZE_SIZE = 1;
export const EPH_KEY_SIZE_SIZE = 1;

export const MAX_PACKET_SIZE = 1280;
export const MASKING_IV_SIZE = 16;

export const ID_NONCE_SIZE = 16;

export const ERR_TOO_SMALL = "ERR_PACKET_TOO_SMALL";
export const ERR_TOO_LARGE = "ERR_PACKET_TOO_LARGE";

export const WHOAREYOU_STRING = "WHOAREYOU";
export const ERR_INVALID_PROTOCOL_ID = "ERR_INVALID_PROTOCOL_ID";
export const ERR_INVALID_VERSION = "ERR_INVALID_VERSION";
export const ERR_INVALID_FLAG = "ERR_INVALID_FLAG";

export const ERR_TOO_SMALL = "packet too small";
export const ERR_TOO_LARGE = "packet too large";
export const ERR_UNKNOWN_FORMAT = "unknown format";
export const ERR_INVALID_BYTE_SIZE = "invalid byte size";
export const ERR_INVALID_AUTHDATA_SIZE = "ERR_INVALID_AUTHDATA_SIZE";
93 changes: 28 additions & 65 deletions src/packet/create.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,41 @@
import { randomBytes } from "bcrypto/lib/random";
import sha256 = require("bcrypto/lib/sha256");
import { NodeId, SequenceNumber } from "../enr";
import { ID_NONCE_SIZE, MASKING_IV_SIZE, NONCE_SIZE } from "./constants";
import { encodeMessageAuthdata, encodeWhoAreYouAuthdata } from "./encode";
import { IHeader, IPacket, PacketType } from "./types";

import { AUTH_TAG_LENGTH, ID_NONCE_LENGTH, RANDOM_DATA_LENGTH, WHOAREYOU_STRING } from "./constants";
import { Tag, AuthTag, IWhoAreYouPacket, IAuthResponse, Nonce, IAuthHeader, PacketType, IRandomPacket } from "./types";
import { NodeId, SequenceNumber, ENR } from "../enr";
import { fromHex, toHex } from "../util";

export function createRandomPacket(tag: Tag): IRandomPacket {
return {
type: PacketType.Random,
tag,
authTag: randomBytes(AUTH_TAG_LENGTH),
message: randomBytes(RANDOM_DATA_LENGTH),
};
}

export function createMagic(nodeId: NodeId): Buffer {
return sha256.digest(Buffer.concat([fromHex(nodeId), Buffer.from(WHOAREYOU_STRING, "utf-8")]));
}

export function createWhoAreYouPacket(nodeId: NodeId, authTag: AuthTag, enrSeq: SequenceNumber): IWhoAreYouPacket {
export function createHeader(flag: PacketType, authdata: Buffer, nonce = randomBytes(NONCE_SIZE)): IHeader {
return {
type: PacketType.WhoAreYou,
magic: createMagic(nodeId),
token: authTag,
idNonce: randomBytes(ID_NONCE_LENGTH),
enrSeq: Number(enrSeq),
protocolId: "discv5",
version: 1,
flag,
nonce,
authdataSize: authdata.length,
authdata,
};
}

export function createAuthTag(): AuthTag {
return randomBytes(AUTH_TAG_LENGTH);
}

export function createAuthHeader(
idNonce: Nonce,
ephemeralPubkey: Buffer,
authResponse: Buffer,
authTag?: AuthTag
): IAuthHeader {
export function createRandomPacket(srcId: NodeId): IPacket {
const authdata = encodeMessageAuthdata({ srcId });
const header = createHeader(PacketType.Message, authdata);
const maskingIv = randomBytes(MASKING_IV_SIZE);
const message = randomBytes(44);
return {
authTag: authTag || createAuthTag(),
idNonce,
authSchemeName: "gcm",
ephemeralPubkey,
authResponse,
maskingIv,
header,
message,
};
}

export function createAuthResponse(signature: Buffer, enr?: ENR): IAuthResponse {
export function createWhoAreYouPacket(nonce: Buffer, enrSeq: SequenceNumber): IPacket {
const idNonce = randomBytes(ID_NONCE_SIZE);
const authdata = encodeWhoAreYouAuthdata({ idNonce, enrSeq });
const header = createHeader(PacketType.WhoAreYou, authdata, nonce);
const maskingIv = randomBytes(MASKING_IV_SIZE);
const message = Buffer.alloc(0);
return {
version: 5,
signature,
nodeRecord: enr,
maskingIv,
header,
message,
};
}

// calculate node id / tag

export function createSrcId(dstId: NodeId, tag: Tag): NodeId {
const hash = sha256.digest(fromHex(dstId));
// reuse `hash` buffer for output
for (let i = 0; i < 32; i++) {
hash[i] = hash[i] ^ tag[i];
}
return toHex(hash);
}

export function createTag(srcId: NodeId, dstId: NodeId): Tag {
const nodeId = fromHex(srcId);
const hash = sha256.digest(fromHex(dstId));
// reuse `hash` buffer for output
for (let i = 0; i < 32; i++) {
hash[i] = hash[i] ^ nodeId[i];
}
return hash;
}
Loading

0 comments on commit 05ba828

Please sign in to comment.