diff --git a/rfc/0004_ws2p_v1.md b/rfc/0004_ws2p_v1.md index 474dcb0b4cdd9aa012d982b5928b946e40f5416b..a2bcddee93a1068363d4317cb89a5f416579ca5c 100644 --- a/rfc/0004_ws2p_v1.md +++ b/rfc/0004_ws2p_v1.md @@ -203,8 +203,9 @@ When the timeout is reached, if the request has not been fully processed, it is In cases where the request is fully processed before the timeout, the response is determined according to the following algorithm : - If the public key of the remote node is the same as yourself - If the uuid is not the same, accept the connection. Otherwise, refuse connection. (do not accept oneself connetion). + If the public key of the remote node is the same as yourself { + If the uuid is not the same, accept the connection. Otherwise, refuse connection. (do not accept oneself connetion). + } If the pubkey is banned, refuse connection. If priorityKeysOnly is enable and the pubkey isn't privileged, refuse connection. If there is already an active ws2p connection with this key (whether incoming or outcoming), refuse connection. @@ -479,6 +480,66 @@ JSON error response : err: String(error message) } +BLOCK_IN_JSON_FORMAT Exemple : + + Object({ + "actives": Array( + [] + ), + "certifications": Array( + [] + ), + "currency": String( + "g1" + ), + "dividend": Null, + "excluded": Array( + [] + ), + "fork": Bool( + false + ), + "hash": String( + "000002EFB4B9524708F751497A30811422824D14C3C710D99FBFA93F23E5BC0B" + ), + "identities": Array( + [] + ), + "inner_hash": String( + "00FD7CCE07B97A13F7B98B6506A9D3EE90A407E003285C664274A2B43E2778B9" + ), + "issuer": String( + "FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD" + ), + "issuersCount": Number( + 37 + ), + "issuersFrame": Number( + 183 + ), + "issuersFrameVar": Number( + 3 + ), + "joiners": Array([]), + "leavers": Array([]), + "medianTime": Number(1528981976), + "membersCount": Number(1135), + "monetaryMass": Number(215441409), + "nonce": Number(10400000005428), + "number": Number(128238), + "parameters": String(""), + "powMin": Number(90), + "previousHash": String("000003C8220C8F2AA297AC43CF74943ADEE3EBE9715136CF5AA2CBD952AF04F3"), + "previousIssuer": String("GEWmVzmiKM6RGJsNaE7VcdNdf2T31xSnu6nHXdxuoSmd"), + "revoked": Array([]), + "signature": String("I64oQrF9bDPILLGHjw0tclgHgI75I2yIZSCS5quiXaOUd8ho2QyKrgcDNC6FhhSpyirCfLcyPCXFGDxBy/LKDw=="), + "time": Number(1528986264), + "transactions": Array([]), + "unitbase": Number(0), + "version": Number(10), + "wrong": Bool(false) + }) + ### getBlock JSON Message : diff --git a/rfc/0006_annex/bincode.md b/rfc/0006_annex/bincode.md new file mode 100644 index 0000000000000000000000000000000000000000..588a0ba22ec111492712630402d28eb1d02655f5 --- /dev/null +++ b/rfc/0006_annex/bincode.md @@ -0,0 +1,236 @@ +# Bincode serialization format for ws2pv2 + +Bincode is an alternative serialization format supported by nodes having "RBC" in their api features. The tables given here correspond to the successive fields in the bincode version of the messages. + +## Primitive types + +u8 : Unsigned 8-bit integer. +u16 : Unsigned 16-bit integer. +u32 : Unsigned 32-bit integer. +u64 : Unsigned 64-bit integer. +i8 : Signed 8-bit integer. +i16 : Signed 16-bit integer. +i32 : Signed 32-bit integer. +i64 : Signed 64-bit integer. +bool : boolean stored on 8 bits (0x00 = false, 0x01 = true, any other value must generate an error). +0 : Corresponds to data that must be filled with bits to zero (for example padding). + +## Useful non-primitive types + +### (T, U) + +(T, U) = Tuples of 2 elements. the 1st type T and the second type U. +A tuple is simply a concatenation of types written one after the other. +A tuple can be of any size (no maximum limit), but its size is necessarily fixed. +The different elements may be of different types, but the order cannot change. + +### [T; n] + +Array of n elements of type T. +If the size of the array is variable, its size (in number of cells) is stored on 8 bytes. + +| data | size in bytes | data type | +| :---------: | ------------: | ------------: | +| cells_count | 8 | u64 | +| content* | ?*cells_count | (T1, T2, ...) | + +All cells in the table are written one after the other without a separator, _like_ a tuple of `cells_count` elements of the same type. + +**/*\ CAUTION: If the size of the array is static (=a literal constant) then the array is serialized without a size field. ** +For example, the content of a public key ed25519 is a 32-byte array, (a [u8; 32] therefore), it is serialized directly without a size field. + +### Opt(T) + +Indicates that a field is optional. The optional fields are preceded by a 1-byte boolean that indicates whether or not the field is present. + +| data | size in bytes | data type | +| :-----: | ------------: | --------: | +| is_some | 1 | u8 | +| content | ? | T | + +If `is_some` is equal to `1`, field `content` is present. +If `is_some` is equal to `0`, field `content` is missing. +Any other value of `is_some` is prohibited and will invalidate the entire message. + +### String + +String formatted in utf8 and [NFKC normalized](https://fr.wikipedia.org/wiki/Normalisation_Unicode#NFKC). + +**/!\ WARNING: All strings are systematically prefixed with their size, including for an empty string. The size is stored on 8 bytes as shown in the table below :** + +| data | size in bytes | data type | +| :------: | ------------: | -------------: | +| str_size | 8 | u64 | +| content | str_size | [u8; str_size] | + +content := String in binary utf8 with [NFKC normalization](https://fr.wikipedia.org/wiki/Normalisation_Unicode#NFKC). + +If the string is empty, field `content` is missing and `str_size` is equal to zero. + +### Blockstamp + +| data | size in bytes | data type | +| :--------: | ------------: | --------: | +| block_id | 4 | u32 | +| block_hash | 32 | [u8; 32] | + +### KeysAlgorithm + +| algorithm | u32 | +| :-------: | ---: | +| Ed25519 | 0 | +| Schnorr | 1 | + +### PubkeyBox + +Contains the signatory's public key. + +| data | size in bytes | data type | +| :-------: | ------------: | ------------: | +| algorithm | 4 | KeysAlgorithm | +| content | ? | ?* | + +_*The type of field `content` depends on the algorithm. In the case of Ed25519, `content` is a 32-byte array containing the public key._ + +### SigBox + +Contains the cryptographic signature of a document. + +| data name | size in bytes | data type | +| :-------: | ------------: | ------------: | +| algorithm | 4 | KeysAlgorithm | +| content | ? | ?* | + +_*The type of field `content` depends on the algorithm. In the case of Ed25519, `content` is a 64-byte array containing the signature._ + +## Endianness + +All numbers (integers and floats) are encoded in little endian. + +## Endpoint v2 + +| data name | size in bytes | data type | +| :--------------: | ------------: | ------------: | +| api_name | ? | String | +| api_version | 2 | u16 | +| network_features | 1 | u8 | +| api_features | 1 | u8 | +| endpoint_version | 4 | u32 | +| host | ? | Opt(String) | +| ip_v4 | 4 | Opt([u8; 4]) | +| ip_v6 | 16 | Opt([u16; 8]) | +| port | 2 | u16 | +| path | ? | Opt(String) | + +## Peer card + + +Signed document declaring all the endpoints of a peer. + +| data name | size in bytes | data type | +| :---------------: | ------------- | --------------------------- | +| version | 4 | u32 | +| currency_code | 2 | u16 | +| issuer_public_key | ? | PubkeyBox | +| node_id | 4 | u32 | +| blockstamp | 36 | Blockstamp | +| endpoints_count | 8 | u64 | +| endpoints_datas | ? | [Endpoint; endpoints_count] | +| signature | ? | SigBox | + +## Messages + +All messages are encapsulated in the following format : + +| data name | size in bytes | data type | +| :------------: | ------------: | --------: | +| ws2p_version | 4 | u32 | +| currency_name | ? | String | +| issuer_node_id | 4 | u32 | +| issuer_pubkey | ? | PubkeyBox | +| message_type | 4 | u32 | +| payload | ? | ?* | +| signature | ? | SigBox | + +### message_type + +| `message_type` | code | Hashed | Signed | +|:--------------------:|--------|--------|--------| +| CONNECT | 0 | no | yes | +| ACK | 1 | no | yes | +| SECRET_FLAGS | 2 | no | yes | +| OK | 3 | no | yes | +| KO | 4 | no | yes | +| REQUEST | 5 | yes | no | +| REQUEST_RESPONSE | 6 | yes | no | +| PEERS | 7 | yes | no | +| HEADS_V2 | 8 | yes | no | +| HEADS_V3 | 9 | yes | no | +| BLOCKS | 10 | yes | no | +| PENDING_IDENTITIES | 11 | yes | no | +| PENDING_MEMBERSHIPS | 12 | yes | no | +| PENDING_CERTS | 13 | yes | no | +| PENDING_REVOCATIONS | 14 | yes | no | +| PENDING_TXS | 15 | yes | no | + +### CONNECT message + +| data name | size in bytes | data type | +| :-----------: | ------------- | --------------- | +| challenge | 32 | [u8; 32] | +| af_size | 1 | u8 | +| api_features | af_size | WS2PFeatures | +| flags_size | 1 | u8 | +| flags_queries | flags_size | WS2PFlags | +| peer_card | ? | Peer card | +| chunkstamp | 37 | Opt(Blockstamp) | + +### ACK message + +| data name | size in bytes | data type | +| :-------: | ------------- | --------- | +| challenge | 32 | [u8;32] | + +### SECRET FLAGS message + +| data name | size in bytes | data type | +| :----------: | ------------- | ---------------- | +| flags_size | 1 | u8 | +| flags | flags_size | WS2PSecretFlags | +| member_proof | ? | Opt(MembreProof) | + +MembreProof type : + +| data name | size in bytes | data type | +| :-------: | ------------- | --------- | +| pubkey | ? | PubkeyBox | +| sig | ? | SigBox | + +#### WS2PSecretFlags type definition + +| bit | flag | +| :-------: | --------------- | +| 0000_0001 | LOW_FLOW_DEMAND | + +### OK message + +| data name | size in bytes | data type | +| :-------: | ------------- | --------- | +| prefix/- | 2 | u16/0 | + +#### SYNC_INFO message + +| data name | size in bytes | data type | +| :---------------: | --------------------- | ----------------------------- | +| chunk_size | 32 | u32 | +| target_blockstamp | 36 | Blockstamp | +| milestones_count | 8 | u64 | +| milestones | 32 × milestones_count | [Hash; milestones_count ] | +| peer_cards_count | 8 | u64 | +| peer_cards | ? × peer_cards_count | [PeerCard; peer_cards_count ] | + +### KO message + +| data name | size in bytes | data type | +| :-------: | ------------- | --------- | +| reason | 2 | u16 | \ No newline at end of file diff --git a/rfc/0006_ws2p_v2.md b/rfc/0006_ws2p_v2.md new file mode 100644 index 0000000000000000000000000000000000000000..e00977a32f90b37b7a8dc66d84d9431f634da079 --- /dev/null +++ b/rfc/0006_ws2p_v2.md @@ -0,0 +1,869 @@ +# Duniter WS2P API v2 + +This document details the specifications of WS2P v2. + +## Contents + +* [Contents](#contents) +* [FAQ](#faq) + * [What is WS2P ?](#what-is-ws2p) + * [Why create a synchronization process ?](#why-create-a-synchronization-process) + * [Why remove shared peer records ?](#why-remove-shared-peer-records) + * [Why not a total binarization?](#why-not-a-total-binarization) + * [What consequences for clients software ?](#what-consequences-for-clients-software) +* [Conventions](#conventions) + * [Raw format](#raw-format) + * [Binary format](#binary-format) +* [Message encapsulation](#message-encapsulation) + * [Payload type](#payload.type) +* [Endpoints v2](#endpoints-v2) + * [Endpoint binary format](#endpoint-binary-format) + * [Endpoint utf8 format](#endpoint-utf8-format) +* [Peer card](#peer-card) + * [Peer card Raw format](#peer-card-raw-format) + * [Peer card JSON stringified format](#peer-card-json-format) + * [Getting peer cards from other Duniter nodes](#getting-peer-cards-from-other-duniter-nodes) +* [Priority of WS2P out-coming connections](#priority-of-ws2p-outcoming-connections) +* [Establishing a WS2P connection](#establishing-a-ws2p-connection) + * [List of accepted v1 messages](#list-of-accepted-v1-messages) + * [CONNECT Message](#connect-message) + * [ACK Message](#ack-message) + * [SECRET FLAGS Message](#secret-flags-message) + * [OK Message](#ok-message) +* [Rules for accepting an incoming connection](#rules-for-accepting-an-incoming-connection) +* [WS2Pv2 Messages](#ws2pv2-messages) + * [List of accepted v1 messages](#list-of-accepted-v1-messages) + * [Requests](#requests) + * [Requests responses](#requests-responses) + * [HEADs v2](#heads-v2) + * [HEADs v3](#heads-v3) + * [Documents messages](#documents-messages) + +## FAQ + +### What is WS2P + +WS2P means "**W**eb **S**ocket **To** **P**eer". + +WS2P is the inter-node network layer, it is the only guarantor of synchronization between the different nodes of the network, so its role is critical. + +WS2P is the network part of the Duniter protocol + +WS2P is exclusively based on websocket technology. + +### Why create a synchronization process + +The download phase of the synchronization process is essential, it allows a new peer to download the entire blockchain quickly (then indexed in a second phase called "apply"). + +Historically, this process was managed by the BMA crawler, but since the arrival of WS2P v1 the BMA crawler is no longer in service, and we keep its code only for `sync` command. So for maintainability reasons, it is necessary to migrate this process outside the BMA crawler, then delete the BMA crawler code. + +Without its crawling part, BMA has become a Client API, which is to be removed and replaced by a new Client API named GVA (see [GVA RFC](https://git.duniter.org/nodes/common/doc/blob/graphql_api_rfc/rfc/0003%20RFC%20GraphQL%20API%20for%20Duniter%20Clients.md)). + +Finally, the synchronization process is more about the inter-node network than the interface with clients, so it makes sense to migrate this process into WS2P. + +### Why remove shared peer records + +Historically, shared peer files had been set up to allow the BMA crawler to automatically detect the use of multiple member nodes with the same key and automatically assign a different prefix to different nodes of the same member so that they calculate different nonces spaces. + +Since Duniter-ts 1.6, the user can now manually assign a different prefix to each of his machines. + +In addition, WS2P v1 has provided a new way to uniquely identify nodes (the node id, we will discuss this later). This new way of uniquely identifying nodes opens the way to possible new automatic prefix allocation mechanisms that will not require a shared peer record. + +Finally, shared peer cards have a big drawback: a peer cannot prune obsolete endpoints of other peer sharing the same key because it cannot easily evaluate if these other endpoints are obsolete. In absolute terms, it is possible to bypass problems but it is simpler to directly delete shared peer records, and as we no longer need them: Occam's razor. + +### What is the binary format used for serialization + +To allow easy interfacing between different implementations, WS2P v2 defines network features (detailed later in the RFC) allowing each WS2P Public to declare in its endpoint the list of features it supports. The default format is [CBOR](https://en.wikipedia.org/wiki/CBOR), a widespread binarization format based on JSON. But for byte-efficiency reasons, Dunitrust defined an other serialization format based on the Rust crate *bincode*. + +## Conventions + +### Raw format + +All documents in raw format are explained via a [PEG grammar]. +The raw format is a plain text format used to sign a document or verify its signature. +Only documents whose signature must be verifiable by a client software are signed in this way. This applies in particular to peer cards and HEADs. +WS2pV2 messages are signed in binary format. + +The PEG grammars of raw formats are presented according to the [pest syntax] (a particular implementation of PEG). + +[PEG grammar]: https://en.wikipedia.org/wiki/Parsing_expression_grammar +[pest syntax]: https://pest.rs/book/grammars/syntax.html + +## Common structures + +A certain number of structure like hash appear repeatedly in the messages. Here we describe it once and refer to it later. + +### 32 byte number + +A 32-bytes number is represented in JSON by an array of 32 8-bits numbers. + +```json +[8, 56, 211, ..., 12] // array of 32 8-bits numbers +``` + +### 64 byte number + +A 64-bytes number is represented in JSON by an array of 64 8-bits numbers. + +```json +[8, 56, 211, ..., 12] // array of 64 8-bits numbers +``` + +### Hash, Pubkey, Signature + +Hashes or pubkeys or signatures are just numbers, most often 32B or 64B one. + +## Message encapsulation + +All WS2P v2 message are encapsuled under [PKSTL v1 protocol](./0011_pkstl_v1.md). + +Each WS2Pv2 message is binarized and then placed in the CUSTOM_DATAS field of the PKSTL message that encapsulates it. + +Several binary serialization formats are available: CBOR and Bincode. The message defined below are written in JSON, allowing straightforward serialization to CBOR. For the Bincode serialization, see [Bincode format reference](./0006_annex/bincode.md). + +All WS2Pv2 messages are structured as follows (CBOR format) : + + ```json + { + "ws2p_version": 2, + "currency_name": "g1", + "issuer_node_id": 395, + "issuer_pubkey": { + "algo": "Ed25519", + "content": [52, 147, .., 93], // 32 bytes pubkey + }, + "payload": { + "type": "MESSAGE_TYPE_NAME", + "content": {} // depends on payload.type + }, + } + ``` + +`ws2p_version` := This field is placed first so that future versions of WS2P are not constrained on the other fields, +the only constraint will be to start the message with the version number stored in u32. + +`currency_name` := Empty string is allowed, it allow the user to synchronize his node without having to manually enter the currency on which he synchronizes. The node will then adopt the currency that is specified in the CONNECT message it will receive from the selected reference node. + +_* The type of `payload` is determined by the content of `message_type`._ + +### payload.type + +Exhaustive list of possible `payload.type` values: + +| message type | `payload.type` value | Hashed | Signed | +|:--------------------:|----------------------|--------|--------| +| CONNECT | "Connect" | no | yes | +| ACK | "Ack" | no | yes | +| SECRET_FLAGS | "SecretFlags" | no | yes | +| OK | "Ok" | no | yes | +| KO | "Ko" | no | yes | +| REQUEST | "Request" | yes | no | +| REQUEST_RESPONSE | "ReqRes" | yes | no | +| PEERS | "Peers" | yes | no | +| HEADS_V2 | "HeadsV2" | yes | no | +| HEADS_V3 | "HeadsV3" | yes | no | +| BLOCKS | "Blocks" | yes | no | +| PENDING_IDENTITIES | "PendingIdentities" | yes | no | +| PENDING_MEMBERSHIPS | "PendingMemberships" | yes | no | +| PENDING_CERTS | "PendingCerts" | yes | no | +| PENDING_REVOCATIONS | "PendingRevocations" | yes | no | +| PENDING_TXS | "PendingTxs" | yes | no | + +## Endpoints v2 + + ```json + { + "api": "WS2P", + "api_version": 2, + "network_features": [], // array of bytes, defined below + "api_features": [], // array of bytes, defined below + "host": "subdomain.domaine.tld", + "ip_v4": "5.135.188.170", + "ip_v6": "2001:41d0:8:c5aa::1", + "port": 443, + "path": "ws2p", + } + ``` + +### utf8 format + +The utf8 format is used to display the endpoint in a human-readable format. It is also in this format that the user can manually enter an endpoint. + +General utf8 endpoint format : + + API_NAME VERSION NF1 .. NFn AF PORT HOST IP4 [IP6] PATH + +VERSION := version number prefixed by `V` (example : `V1`) +NF := NETWORK_FEATURE +AF := API_FEATURES in hexadecimal prefixed by `0x` (example : `0x01`) + +Example: + + WS2P V2 TLS 0x7 443 g1.durs.info ws2p + +### network_features + +The 8 bits represent booleans to define the presence or absence of 8 network features. WS2Pv2 defines only 4 features, the remaining 4 are undefined and are in anticipation of future Ğfeatures. + +Network features : + +| bit | feature | +|:---------:|----------| +| 0000_0001 | HTTP | +| 0000_0010 | WS | +| 0000_0100 | S | +| 0000_1000 | TOR | + +HTTP := This feature indicates that the endpoint should be contacted with http protocol. + +WS := This feature indicates that the endpoint should be contacted with websocket protocol. + +S := This feature indicates that the endpoint should be contacted with an SSL/TLS overlay (HTTPS or WSS or any other protocol that support TLS). + +TOR := This feature indicates that the endpoint must be contacted via the tor network (hidden service). + +Note about the network protocol to choose: Each API must define a default network protocol. If no network protocol is specified in the network features, the default API network protocol is chosen. + +For example, the default network protocol for WS2P is `ws://`, it's the only one possible for this API, so there is no need to indicate it in the network features. + +### api_features + +The interpretation of this field depends on the API because it represents API-specific features. Here is the interpretation for the WS2P API : + +WS2PFeatures type definition : + +| bit | feature | +| :-------: | ------- | +| 0000_0100 | RBC | +| 0000_0010 | LOW | + +RBC := Support Rust BinCode format + +LOW := Accept low speed connection requests + +WS2P v2 uses only 2 of 8 features. The 6 free bits can be used for future versions of WS2P. + +## Peer card + +Signed document declaring all the endpoints of a peer. + +Peer card v11 is signed in raw format (without SIGNATURE itself of course). + +A node's peer record can also contain signatures of the node's network key. This allows, for example, to sign mirror nodes with known and trusted keys. +The signed key must be signed with the blockstamp corresponding to the date of signature. More precisely, it is the text `PUBKEY:BLOCKSTAMP` that must be signed. + +### CBOR format + + ```json + { + "version": "v11", + "currency_name": "g1", + "issuer": { + "algo": "Ed25519", + "content": [122, 11, .., 95] // 32 bytes pubkey + }, + "node_id": 10623, + "created_on": 542, + "endpoints": [], // Array of endpoitns v2 + "endpoints_str": [], // Array of Strings (endpoitn v2 in utf8 format) + "sig": { + "algo": "Ed25519", + "content": [187, 7, .., 23] // 64 bytes signature + }, + "certifiers": [ + { + "certifier": { + "algo": "Ed25519", + "content": [122, 11, .., 95] // 32 bytes pubkey + }, + "blockstamp": { + "id": 327, + "hash": [85, 132, .., 27] // 32 bytes hash + }, + "sig": { + "algo": "Ed25519", + "content": [187, 7, .., 23] // 64 bytes signature + }, + } + ] + } + ``` + +### Peer card Raw format + +Approximate description : + + VERSION:CURRENCY:NODE_ID:PUBLIC_KEY:CREATED_ON + ENDPOINT 1 + ... + ENDPOINT n + SIGNATURE + +_/!\ This description is approximate, it allows you to quickly understand the format but is not authoritative for developing a code to parse the format exactly. If you want to develop a code using this format, please refer to its PEG grammar._ + +Example : + + 11:g1:0:7iMV3b6j2hSj5WtrfchfvxivS9swN3opDgxudeHq64fb:50 + WS2P V2 S 7 g1.durs.ifee.fr 443 ws2p + WS2P V2 S 7 84.16.72.210 443 ws2p + EQ2D5almq2RNUi3XZNtTpjo9nWtJF0PzsCW7ROAzCQKiEtpI7/fW8Z23GJ2a/SIxfYSzlq/cZqksE4EoVe1rAw== + +Peer card v11 PEG grammar : + + ```pest + // Single character rules + nl = _{ "\n" } + no_zero_hexa_lower = @{ '1'..'9' | 'a'..'f' } + hexa_lower = @{ ASCII_DIGIT | 'a'..'f' } + hexa_upper = @{ ASCII_DIGIT | 'A'..'F' } + base58 = { !("O" | "I" | "l") ~ ASCII_ALPHANUMERIC } + base64 = { ASCII_ALPHANUMERIC | "+" | "/" } + + // Numbers rules + tens = @{ '1'..'9' ~ ASCII_DIGIT } + u8_hundreds = @{ ("2" ~ ('0'..'4' ~ ASCII_DIGIT | ('0'..'5'){2})) | ("1" ~ ASCII_DIGIT{2}) } + u8 = @{ u8_hundreds | tens | ASCII_DIGIT } + no_zero_u_int = @{ '1'..'9' ~ ASCII_DIGIT* } + u_int = @{ "0" | no_zero_u_int } + + // Usefull types rules + currency = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "-" | "_"){,255} } + block_id = @{ u_int } + hash = @{ hexa_upper{64} } + pubkey = @{ base58{43,44} } + ed25519_sig = @{ base64{88} | (base64{87} ~ "=") | (base64{86} ~ "==") } + + // IP v6 rules + ip6_seg = _{ hexa_lower{1,4} } + ip6_full = _{ (ip6_seg ~ ":"){7} ~ ip6_seg } + ip6_no_full = @{ + ip6_seg? ~ + (":" ~ ip6_seg){0,6} ~ + "::" ~ + (ip6_seg ~ ":"){0,6} ~ + ip6_seg? + } + ip6_inner = @{ ip6_full | ip6_no_full } + ip6 = _{ "[" ~ ip6_inner ~ "] " } + + // Endpoint v2 rules + api_version_inner = @{ no_zero_u_int } + api_version = _{ "V" ~ api_version_inner ~ " " } + http = @{ "HTTP " } + ws = @{ "WS " } + tls = @{ "S " } + tor = @{ "TOR " } + network_features = _{ http? ~ ws? ~ tls? ~ tor? } + api_features_inner = @{ (hexa_lower{2})+ | no_zero_hexa_lower } + api_features = _{ "0x" ~ api_features_inner ~ " " } + domain_name_part = @{ ASCII_ALPHA_LOWER ~ (alphanum_lower | "-" | "_")* } + domain_name_ext = @{ alphanum_lower+ } + domain_name_parts = @{ (domain_name_part ~ ".")+ ~ domain_name_ext } + domain_name_onion = @{ alphanum_lower{16} ~ ".onion" } + domain_name_inner = @{ domain_name_parts | domain_name_onion | domain_name_part } + domain_name = _{ domain_name_inner ~ " " } + ip4_inner = { u8 ~ "." ~ u8 ~ "." ~ u8 ~ "." ~ u8 } + ip4 = _{ ip4_inner ~ " " } + path = _{ " " ~ path_inner } + endpoint_v2 = ${ api_name ~ " " ~ (api_version)? ~ (network_features)? ~ (api_features)? ~ ip4? ~ ip6? ~ domain_name? ~ port ~ path? } + + // Peer v11 rules + peer_v11 = ${ "11:" ~ currency ~ ":" ~ node_id ~ ":" ~ pubkey ~ ":" ~ block_id ~ nl ~ (endpoint_v2 ~ nl)+ ~ ed25519_sig? } + ``` + +### Peer card JSON stringified format + +This human-readable stringified format will be used by all APIs that wish to provide peer cards in a human-readable format. For example, Client APIs. + + ```json + { + "version": 11, + "currency": 1, + "node_id": "15af24db", + "algorithm": "Ed25519", + "pubkey": "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx" + "blockstamp": "128310-000002A569DCEED62227CAC0ABDFE6B2647B21B3193A40398CD12BC2A95C24D9", + "endpoinds": [ + "WS2P 2 1 TLS 3 DEF LOW ABF 443 g1.domaine.tld ws2p" + ], + "signature": "e2BtWtLs3Uqw80bvOx8YGfmDLfA44/apzpZA8YfH+WtnmPY5r4XUlgvctPsq2bHVw3iPWxuxx5oJh0JHITrECw==", + "certifiers": [ + "Ez4huJahi6qfG8eVjyc84CwVtpHagmfUTtRFazXY4G2h:81234-0000047F1D3BB30C24CBD9D60977FC9474D3A985DF88BB9C9CAFE9160D94AC17:oP5iHvXRx1mI+Nic621/tU42kk8Mr+m5wgNZiBPJf4vGSlGSPkTSbzMJgHehnCcEF/UB+xPoLZ2WK1AMODlFDg==" + ] + } + ``` + +### Getting peer cards from other nodes + +Initially, nodes are totally endpoint agnostic and don't know any node. +The ws2p module requires that at least one or more peer cards are already stored in the database when launching the node, +it's up to the `sync` command to handle this (see "WS2P synchronisation" part). + +When node starts up, the WS2P module accesses the peer cards stored in a local database following synchronization. + +## Priority of WS2P outcoming connections + +The WS2P module attempts to establish outcoming connections at the kick-start of the node and then every 10 minutes. +At each attempt, all known endpoints are contacted until the outcoming quota is reached. +If the outcoming quota is already reached at the beginning of an attempt, the least priority connection is removed in order to keep the network dynamic and scalable. + +The WS2P module does not attempt to connect randomly to any node among those of which it knows a WS2P endpoint, +it classifies them according to the following criteria : + +### 1st criterion: network features + +Some network features, imply that the node prioritizes endpoints having such or such network feature. +For example, if the node has the TOR feature, it will prioritize nodes that also have the TOR feature. + +_Note: the first criterion takes precedence over the second one._ + +### 2nd criterion: priority score + +The priority score of a node depends on its public key and is calculated as follows : +by default the priority score is zero +If the key is a member: +1 +If the key is preferred/privileged: +2 +If the key is the same as yourself: +4 (for multi-node) + +In case of equality, the order in which the endpoints will be contacted will depend on the sorting algorithm used according to the implementations, there are no rules, we can consider that it is pseudo-random. + +## Establishing a WS2P connection + +After sorting all WS2P endpoints according to the criteria described in the previous section, +Duniter tries to connect to the other nodes. + +**normal connection** + +1. Opening a websocket pointing to the remote endpoint. If the websocket has been successfully opened before the timeout\*, send a CONNECT message. Otherwise, log the error and close the websocket. +2. Wait to receive a CONNECT message from the remote node, checking the validity. If the CONNECT message is valid, then sending an ACK message. Otherwise, log the error and close the websocket. +3. Wait to receive an ACK message from the remote node, checking the validity. If the ACK message is valid, then sending a SECRET_FLAGS/OK message. Otherwise, log the error and close the websocket. +4. Wait to receive a SECRET_FLAGS/OK message from the remote node, checking the validity. If the message is valid, then sending a SECRET_FLAGS/OK message. Otherwise, log the error and close the websocket. If the receiving message is OK and sending OK message too, skip step 4 and consider the ws2p connection completely established. +5. Wait to receive an OK message from the remote node, checking the validity. If the OK message is valid, then sending an OK message and consider the ws2p connection completely established. Otherwise, log the error and close the websocket. + +\*timeout : 15 seconds for connection to a conventional endpoint. 30 seconds for connection to a hidden tor service. + +For the remote node, only the first step change : + +1. Sudden receipt of a CONNECT message. If the CONNECT message is valid, then sending my own CONNECT message and an ACK message at the same time. Otherwise, log the error and close the websocket. + +**sync connection** + +The connection steps are the same for a sync connection excepts : + +1. The CONNECT message contains a chunkstamp telling from which chunk the asking node wants to synchronize from +2. After having sent it's ACK message, the responding node also sends a SYNC_INFO message described below + +WARNING: independently of all these rules, each implementation must integrate its own anti-spam protections. Any connection can be rejected if the situation requires it (attack by denial of service for example). + +### CONNECT message + + ```json + { + "challenge": [...], // 32 bytes hash + "api_features": [...], // list of 4 integers corresponding to api_features (see in endpoint definition) + "connect_type": {...}, // see below + "peer_card": {...} // look at the peercard definition above + } + ``` + +challenge := random hash generated by the sending node of the CONNECT message, the receiving node will then have to sign this challenge and then send this signature in its ACK message to prove that it has the corresponding private key to the public key it indicates. + +api_features := This is exactly the same type as the field of the same name in the endpoints. But private WS2P nodes do not declare endpoints, so they must be able to indicate in the CONNECT message which features they support. Public WS2P nodes also fill this field, so any changes in the configuration of a public node will be applied on the 1st new connection. (If this was not the case, we would have to wait for the update of the peer record). + +connect_type := See ConnectType below + +peer_card := See PeerCard above + +#### ConnectType type definition + +ConnectType represent the connection type. It can be: + +- Incoming: *connect message sent when receiving an incoming websocket connection* +- Client: *connect message sent by a client* +- Server: *connect message sent between nodes for a normal connection* +- Sync: *connect message sent for a synchronization request* +- SyncAskChunk: *connect message sent for a chunk request* + +Incoming, Client and Server are represented by a string. + +Sync is represented by an object + +``` +{ + "Sync": { + "from_blockstamp": "128310-000002A569DCEED62227CAC0ABDFE6B2647B21B3193A40398CD12BC2A95C24D9" + } +}, +``` + +SyncAskChunk is reprensented by an object + +```json +{ + "SyncAskChunk": "128310-000002A569DCEED62227CAC0ABDFE6B2647B21B3193A40398CD12BC2A95C24D9", +} +``` + + + +### ACK message + + ```json + { + "hash": [...] // 32 bytes hash + } + ``` + +Each node must sign the challenge of the other to prove that it's in possession of the private key corresponding to the public key under which it identifies. +challenge := The challenge given by the other node in their CONNECT message. +The message is already signed at the container level, so there is no need to repeat the signature in the ACK message payload. On the other hand, the challenge to be signed must be in the payload. + +### SECRET FLAGS message + +TODO + +If it is valued, the `member_proof` field must contain a signature of the challenge* send by other node in their CONNECT message. + +_*It's the remote challenge for the signatory, and the local challenge for the verifier._ + +#### WS2PSecretFlags type definition + +| bit | flag | +|:---------:|-----------------| +| 0000_0001 | LOW_FLOW_DEMAND | + +LOW_FLOW_DEMAND := The sender node of this message indicates that it's behind a low speed connection and therefore care must be taken to transmit low volumes of data to it. All the code will do is to be careful to send only essential information and not already sent, which requires caching and pre-processing, that's why we don't do it for normal connections. + +### OK message + +In most cases, the OK message is empty, it simply indicates that the remote node accepts the connection establishment. If the local node has also sent an OK message, then it considers the connection as fully established. +But sometimes the OK message can also transmit additional informations : + + ```json + { + "prefix": ... // null or non-zero integer + } + ``` + +prefix := In ws2p v2, member nodes have the option of not publicly communicating their prefix. It will only reveal their prefix to the node of their choice among those with which they establish a connection. For example, a member node may decide to share its prefix only with other member nodes of the same key. +If this field is zero, it means that the remote node does not want to reveal its prefix (the prefix being necessarily greater than or equal to 1). + +#### SYNC_INFO message + + ```json + { + "chunk_size": 500, + "target_blockstamp": { + "id": 0, + "hash": [...] // 32 bytes hash + }, + "milestones": [...], // list of 32 bytes hashes + "peer_cards": [...] // list of peercards + } + ``` + +chunk_size := defines the size of the block used in milestones. A 1000-block chunk is a good compromise between efficiency of compression and duration of transfer + +target_blockstamp := Indicates the current blockstamp of the message sender node. This blockstamp will be the target to reach for the node being synchronized. + +milestones := Hash table of the last block of each chunk. We do not need the block numbers, we know them. Here the remote node sends the hashs of all these chunk, which correspond to the current hashs of all the blocks having a number in 250 module 249, in ascending order. + +peer_cards := see [definition of binary peer card](#peer-card-binary-format) + +### KO message + + ```json + 0 // number representing reason + ``` + +#### reason + +| value | significance | +|:-----:|------------------| +| 0 | TIMEOUT | +| 1 | FULL | +| 2 | LOW_NO_SYNC | +| 3 | DIFFERENT_BRANCH | + +## WS2Pv2 Messages + +### REQUESTS + +| data name | size in bytes | data type | +|:------------:|---------------|-------------| +| request_id | 4 | u32 | +| request_type | 4 | u32 | +| param_1 | ? | ? | +| param_2 | ? | ? | + +#### request_type interpretation + +| value | significance | param_1 (type) | param_2 (type) | +|:-----:|--------------------|-------------------------|--------------------| +| 0 | EMPTY_REQUEST | - | - | +| 1 | CURRENT_BLOCKSTAMP | - | - | +| 2 | BLOCKS_HASHS | begin_block_id (u32) | blocks_count (u16) | +| 3 | CHUNK | begin_block_id (u32) | blocks_count (u16) | +| 4 | CHUNK_BY_HASH | chunkstamp (Blockstamp) | - | +| 5 | WOT_POOL | folders_count (u16) | min_cert (u8) | + +BLOCKS_HASHS := In case of fork, to quickly find the fork point, the node will request the hashes of the ForkWindowsSize of the local blockchains of the other nodes. +It would be counterproductive to ask directly for the entire blocks, when you will only need them if you actually decide to stack the corresponding branch. + +CHUNK_BY_HASH : During synchronization, chunk is requested by Chunkstamp (= Blockstamp of the last block of the chunk). + +WOT_POOL := For network performance reasons, a Duniter-Rust* node never shares its entire wot pool at once. It randomly selects `folders_count` folders among those having received at least `min_cert` certifications. It's the requesting node that sets the values of `min_cert` and `folders_count` according to its connection rate, its configuration and the rate of new folders it has obtained in these previous requests. + +_*This is part of the specificities of WS2P requests in binary format, which will be introduced in all implementations in the future._ + +### REQUESTS RESPONSES + +| data name | size in bytes | data type | +|:----------------:|---------------|------------| +| request_id | 4 | u32 | +| response_type | 4 | u32 | +| response_content | ? | ? | + +#### response_type interpretation + +| value | significance | +|:-----:|--------------------| +| 0 | EMPTY_RESPONSE | +| 1 | BAD_REQUEST | +| 2 | CURRENT_BLOCKSTAMP | +| 3 | BLOCKS_HASHS | +| 4 | CHUNK | +| 5 | WOT_POOL | + +EMPTY_RESPONSE := `response_content` field is absent. + +BAD_REQUEST := Used only in debug mode. In production, bad requests are simply ignored (The server does not respond). + +#### BAD_REQUEST response + +| data name | size in bytes | data type | +|:------------:|---------------|------------| +| reason | ? | String | + +_*The total payload size of any WS2P message is indicated in the metadata, field `reason` being the only one of unknown size, its size is deduced by calculation._ + +#### CURRENT_BLOCKSTAMP response + +| data name | size in bytes | data type | +|:------------:|---------------|------------| +| blockstamp | 36 | Blockstamp | + +#### BLOCKS_HASHS response + +| data name | size in bytes | data type | +|:------------:|----------------|---------------| +| hashs_count | 8 | u64 | +| hashs | 32*hashs_count | [[u8; 32]; ?] | + +#### CHUNK response + +| data name | size in bytes | data type | +|:------------:|---------------|---------------| +| blocks_count | 8 | u64 | +| blocks | ? | [BlockDocument; blocks_count] | + +#### WOT_POOL response + +| data name | size in bytes | data type | +|:-------------:|---------------|------------------------------| +| certs_count | 8 | u64 | +| certs | ? | CompactCertificationDocument | +| folders_count | 8 | u64 | +| folders | ? | WotPoolFolder | + +CompactCertificationDocument type description : + +| data name | size in bytes | data type | +|:-------------:|---------------|---------------------| +| issuer* | ? | PubKeyBox | +| target* | ? | PubKeyBox | +| block_id | 4 | u32 | +| signature | ? | SigBox | + +WotPoolFolder type description : + +| data name | size in bytes | data type | +|:---------------:|-----------------|------------------------------| +| identity | ? | CompactIdentityDocument | +| membership | ? | CompactPoolMembershipDoc | +| certs_count | 8 | u64 | +| certs | ? | CompactCertificationDocument | + +CompactIdentityDocument type description : + +| data name | size in bytes | data type | +|:-------------:|---------------|---------------------| +| uid | ? | String | +| blockstamp | 36 | Blockstamp | +| pubkey | ? | PubkeyBox | +| signature | ? | SigBox | + +CompactPoolMembershipDoc type description : + +| data name | size in bytes | data type | +|:-------------:|---------------|---------------------| +| blockstamp | 36 | Blockstamp | +| signature | ? | SigBox | + +### HEADS v2 + +| data name | size in bytes | data type | +|:------------:|---------------|-----------| +| head_content | ? | String | + +### HEADs v3 + +| data name | size in bytes | data type | +|:------------------:|-------------------|------------| +| api_outgoing_conf | 1 | u8 | +| api_incoming_conf | 1 | u8 | +| free_mirror_rooms | 1 | u8 | +| low_priority_rooms | 1 | u8 | +| node_id | 4 | u32 | +| pubkey | ? | PubkeyBox | +| blockstamp | 36 | Blockstamp | +| software_name | ? | String | +| soft_version | ? | String | +| signature | ? | SigBox | +| step | 1 | u8 | + +api_outgoing_conf := See "api_outgoing_conf interpretation". + +api_incoming_conf := See "api_incoming_conf interpretation". + +free_member_rooms := Number of rooms available for incoming ws2p connections. + +low_priority_rooms := Number of rooms occupied by nodes with lower priority than a member. The number of rooms available for a member is equal to `free_member_rooms` + `low_priority_rooms`. + +node_id := Field randomly drawn by the node during its 1st configuration. + +pubkey := Public key of the network keychain of the node. + +blockstamp := Current blockstamp of the node. + +signature := Direct signature of the byte vector from `software_size` to `soft_version` included. + +step := Number of head rebounds (unsigned field). + +#### api_outgoing_conf interpretation + +| bit | signification | +|:---------:|---------------------------| +| 0000_0001 | WS2P_PRIVATE_CLEAR | +| 0000_0010 | WS2P_PRIVATE_TLS | +| 0000_0100 | WS2P_PRIVATE_TOR_TO_CLEAR | +| 0000_1000 | WS2P_PRIVATE_TOR_TO_TOR | + +There are still 4 flags for future network layers that could be implemented in the future. + +#### api_incoming_conf interpretation + +| bit | signification | +|:---------:|-------------------| +| 0000_0001 | WS2P_PUBLIC_CLEAR | +| 0000_0010 | WS2P_PUBLIC_TLS | +| 0000_0100 | WS2P_PUBLIC_TOR | + +There are still 5 flags for future network layers that could be implemented in the future. + +#### HEAD v3 Raw format + +Approximate description : + + VERSION:CURRENCY:OUTGOING_CONF:INCOMING_CONF:FREE_MIRROR_ROOMS:FREE_MEMBER_ROOMS:NODE_ID:PUBLIC_KEY:BLOCKSTAMP:SOFTWARE:SOFT_VERSION + SIGNATURE + STEP + +_/!\ This description is approximate, it allows you to quickly understand the format but is not authoritative for developing a code to parse the format exactly. If you want to develop a code using this format, please refer to its PEG grammar._ + +Example : + + 3:g1:0:0:0:0:0:7iMV3b6j2hSj5WtrfchfvxivS9swN3opDgxudeHq64fb:50-000005B1CEB4EC5245EF7E33101A330A1C9A358EC45A25FC13F78BB58C9E7370:durs:0.2.0-a + vwlxpkCbv83qYSiClYA/GD35hs0AsZBnqv7uoE8hqlarT2c6jVRKhjp8JBqmRI7Se4IDwC2owk0mF4CglvyACQ== + 2 + +HEAD v3 PEG grammar : + + ``` + // Single character rules + nl = _{ "\n" } + no_zero_hexa_lower = @{ '1'..'9' | 'a'..'f' } + hexa_lower = @{ ASCII_DIGIT | 'a'..'f' } + hexa_upper = @{ ASCII_DIGIT | 'A'..'F' } + base58 = { !("O" | "I" | "l") ~ ASCII_ALPHANUMERIC } + base64 = { ASCII_ALPHANUMERIC | "+" | "/" } + + // Numbers rules + tens = @{ '1'..'9' ~ ASCII_DIGIT } + u8_hundreds = @{ ("2" ~ ('0'..'4' ~ ASCII_DIGIT | ('0'..'5'){2})) | ("1" ~ ASCII_DIGIT{2}) } + u8 = @{ u8_hundreds | tens | ASCII_DIGIT } + no_zero_u_int = @{ '1'..'9' ~ ASCII_DIGIT* } + u_int = @{ "0" | no_zero_u_int } + + // Usefull types rules + currency = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "-" | "_"){,255} } + block_id = @{ u_int } + hash = @{ hexa_upper{64} } + pubkey = @{ base58{43,44} } + blockstamp = ${ block_id ~ "-" ~ hash } + ed25519_sig = @{ base64{88} | (base64{87} ~ "=") | (base64{86} ~ "==") } + + // HEADv3 rules + api_outgoing_conf = @{ u8 } + api_incoming_conf = @{ u8 } + free_member_rooms = @{ u8 } + free_mirror_rooms = @{ u8 } + software = @{ ASCII_ALPHA ~ ASCII_ALPHANUMERIC* } + soft_version = @{ ASCII_DIGIT ~ "." ~ ASCII_DIGIT ~ "." ~ ASCII_DIGIT ~ ("-" ~ ("a" | "b" | "rc") ~ ASCII_DIGIT ~ ("." ~ ASCII_DIGIT){0,2})? } + step = @{ u_int } + + head_v3 = ${ + "3:" ~ currency ~ ":" ~ + api_outgoing_conf ~ ":" ~ + api_incoming_conf ~ ":" ~ + free_member_rooms ~ ":" ~ + free_mirror_rooms ~ ":" ~ + node_id ~ ":" ~ + pubkey ~ ":" ~ + blockstamp ~ ":" ~ + software ~ ":" ~ + soft_version ~ nl ~ + ed25519_sig? ~ + (nl ~ step)? + } + ``` + +#### HEADv3 JSON format + +This human-readable format will be used by all APIs that wish to provide heads in a human-readable format. For example, Client APIs. + + ```json + { + "content": "VERSION:CURRENCY:OUTGOING_CONF:INCOMING_CONF:FREE_MIRROR_ROOMS:FREE_MEMBER_ROOMS:NODE_ID:PUBLIC_KEY:BLOCKSTAMP:SOFTWARE:SOFT_VERSION", + "signature": "SIGNATURE", // Signature in base64 + "step": 3 + } + ``` + +Example : + + ```json + { + "content": "3:g1:0:0:0:0:0:7iMV3b6j2hSj5WtrfchfvxivS9swN3opDgxudeHq64fb:50-000005B1CEB4EC5245EF7E33101A330A1C9A358EC45A25FC13F78BB58C9E7370:durs:0.2.0-a", + "signature": "vwlxpkCbv83qYSiClYA/GD35hs0AsZBnqv7uoE8hqlarT2c6jVRKhjp8JBqmRI7Se4IDwC2owk0mF4CglvyACQ==", + "step": 2, + } + ``` + +### Documents messages + +All WS2Pv2 documents are necessarily in Binary Format. When document binarization is coded in Duniter-ts, then it will be necessary to define a common binary format for each document. For performance reasons, we will probably opt for the format in which each document is signed. + +We must therefore wait for the binarization of the blockchain protocol. + +In the meantime, Durs nodes exchange documents directly as portable binary objects (this is a Rust feature). diff --git a/rfc/0011_pkstl_v1.md b/rfc/0011_pkstl_v1.md new file mode 100644 index 0000000000000000000000000000000000000000..a5978cd9cadc40c150b400f810d17b363b2a6088 --- /dev/null +++ b/rfc/0011_pkstl_v1.md @@ -0,0 +1,182 @@ +# Public Key Secure Transport Layer v1 + +This document details the specifications of PKSTL v1 (Public Key Secure Transport Layer v1). + +## Contents + +* [Contents](#contents) +* [FAQ](#faq) + * [What is PKSTL ?](#what-is-pkstl) + * [Why ?](#why) + * [How it works ?](#how-it-works) +* [Conventions](#conventions) + * [Endianness](#endianness) + * [Types definition](#types-definition) +* [Negotiation stage](#negotiation-stage) +* [Messages format](#messages-format) + * [CONNECT message](#connect-message) + * [ACK message](#ack-message) + * [USER message](#user-message) + +## FAQ + +### What is PKSTL + +PKSTL (Public Key Secure Transport Layer) is a security layer that ensures the authenticity and confidentiality of communication between 2 programs over any network (Internet or local network or other). +PKSTL is agnostic to the underlying network protocol, as long as it allows the exchange of binary messages. +For example, PKSTL works with the websocket protocol but has no dependence on it. + +### Prerequisites + +Each of the 2 programs must have an Ed25519 key-pair. The program that initiates the connection (the client in the case of client/server communication) must first know the ed25519 public key of the server that the user wishes to contact. + +### Why + +The most common secure communication protocols require the generation of certificates signed by a certification authority. + +It does not really work in a decentralized way, because you have to be accredited by a "certification authority". However, who decides who can be a certification authority? + +In addition, the generation of certificates is either costly or requires complex and regular technical manipulations on the part of the end user of a blockchain server. + +In the Duniter/G1 ecosystem, all blockchain servers have their own Ed25519 key-pair. We can use this as a basis so that we do not need certificates and therefore allow end users to have a secure server automatically (zero conf). + +### How it works + +The communication is symmetrically encrypted via a shared secret generated by Diffie–Hellman exchange. + +The first 2 messages exchanged by the 2 programs are in clear but signed, they constitute the negotiation stage. This negotiation stage is used to generate the shared secret and verify that the other program owns the private key corresponding to its public key. + +Once this negotiation stage is finalized, all exchanged messages are hashed and encrypted (the signature is no longer necessary at this stage because the hash of the message is also encrypted, so it cannot be altered undetectably by a middle man). + +## Conventions + +### Endianness + +All numbers (integers and floats) are encoded in big endian. + +### Types definition + +u8 : Unsigned 8-bit integer. +u16 : Unsigned 16-bit integer. +u32 : Unsigned 32-bit integer. +u64 : Unsigned 64-bit integer. + +## Negotiation stage + +The negotiation stage is perfectly symmetrical, so that an observer cannot distinguish the "client" from the "server". +This allows PKSTL to be used in both peer-to-peer and client/server contexts. + +Each program sends a CONNECT message and an ACK message. The ACK message is a response to the CONNECT message of the other program. +The progress of the negotiation stage can be described by 2 threads each having 3 steps. + +Local thread: + +1. Preparation of the CONNECT message +2. CONNECT message sent +3. A valid ACK message has been received + +Remote thread: + +1. Waiting for the CONNECT message from the other program +2. Receiving a valid CONNECT message +3. A valid ACK message has been sent + +When the two threads reach step 3, then the negotiation stage is considered successfully completed. + +### Shared secret + +The shared secret is generated by Diffie-helman exchange. For security reasons, the key_pair used by each program for the DH exchange is an ephemeral key-pair, randomly generated for one-time use. + +The seed for the encryption algorithm is obtained by derivation HMAC_SHA384, the salt of the HMAC function is the largest of the two ephemeral public keys. + +### Encryption algorithm + +The symmetric encryption algorithm is Chacha20/Poly1305. +The encryption key corresponds to the first 32 bytes of the seed. +The nonce corresponds to the next 12 bytes, and the `aad` to the last 4 bytes. + +## Messages format + +All messages are formatted as follows: + +| Field | Size | Type | Value | +|:------------------:|:-------:|:-------:|:----------:| +| MAGIC_VALUE | 4 | - | 0xE2C2E2D2 | +| VERSION | 4 | u32 | 1 | +| ENCAPSULED_MSG_LEN | 8 | u64 | | +| MSG_TYPE | 2 | u16 | {0,1,2} | +| MSG_CONTENT | *X | [u8;X] | | +| SIGNATURE | 0 or 64 | [u8;64] | | +| HASH | 0 or 32 | [u8;32] | | + +*`X = ENCAPSULED_MSG_LEN - 2` + +MAGIC_VALUE := Special value to recognize that this is a message of the PKSTL protocol. + +VERSION := This field allows the versioning of the PKSTL protocol and therefore future evolution. + +ENCAPSULED_MSG_LEN := encapsuled message length (MSG_TYPE + MSG_CONTENT) + +MSG_TYPE: + +Value | Message type +:-:|:-: + 0 | USER + 1 | CONNECT + 2 | ACK + +If `MSG_TYPE == 2`, them all message is encrypted. Else, all message is in clear. + +MSG_CONTENT := see details by message type + +SIGNATURE := Only provided for CONNECT and ACK messages. Ed25519 signature of all previous bytes. + +HASH := Only provided for USER messages. Sha256 hash of all previous bytes. + +### CONNECT Message + +MSG_CONTENT: + +| Field | Size | Type | Value | +|:------------------:|:----:|:-------:|:----------:| +| EPK | 32 | [u8;32] | | +| SIG_ALGO | 4 | u32 | 1 | +| SIG_PUBKEY | 32 | [u8;32] | | +| CUSTOM_DATAS | *Y | [u8;Y] | | + +*`Y = X - 68` + +APK := Ephemeral public key. + +SIG_ALGO := `1` refers to `Ed25519` algorithm. This field is present to anticipate the use of different algorithms in the future. + +SIG_PUBKEY := Signature public key of remote program. + +CUSTOM_DATAS := optional free user application datas (in clear). + +### ACK Message + +MSG_CONTENT: + +| Field | Size | Type | Value | +|:------------------:|:----:|:-------:|:--------------------:| +| CHALLENGE | 32 | [u8;32] | Sha256 of remote EPK | +| CUSTOM_DATAS | *Z | [u8;Z] | | + +*`Z = X - 32` + +CHALLENGE := Sha256 hash of remote ephemeral public key. + +CUSTOM_DATAS := optional free user application datas (in clear). + +### USER Message + +| Field | Size | Type | Value | +|:------------------:|:----:|:-------:|:--------------------:| +| CUSTOM_DATAS | *X | [u8;X] | | + +CUSTOM_DATAS := user application datas (encrypted). + + + +drafting in progress...