In this document, we describe the implementation details of the Keep network security mechanisms meeting requirements specified in RFC 1.
Messages exchanged in the network must be attributable, maintain integrity, and must optionally be confidential.
It is important to understand the difference between the network topology and communication between peers on the application level. Peers connect to each other based on the rules defined by the underlying P2P network protocol. Two peers exchanging messages on the application level do not necessarily need to have a direct connection with each other on the network level. It is possible that messages exchanged at the application level by two peers are relayed by other peers on the network level.
On the network level, we require all peers to prove their identity and minimum stake when they establish a connection with each other. All peers monitor the on-chain stake of peers they are connected to and are obligated to drop the connection with the given peer if that peer’s on-chain stake drops below the required minimum. This requirement enforces that only peers with a minimum stake are able to be a part of the network and this requirement is enforced by all peers individually.
Even if a message between two peers was relayed by others, this message still needs to fulfil attributability, integrity, and optional confidentiality requirements. Since two peers exchanging messages do not necessarily need to have a connection on the network level with each other, they also perform an identity and stake check before the communication between them happens if such check is not already performed as a part of the protocol those two peers execute.
Each peer is identified by a static public key equal to the delegate key associated with an on-chain stake. No two peers in the network are permitted to have the same static public key.
Keep publishes a list of recommended bootstrap peers but every other network member can enable the bootstrap capability as well. The list contains addresses of bootstrap peers and their static public keys. Bootstrap peers are just like any other peers, but with the added capability of being able to handle network join requests.
The result of joining the network is inherently dependent on the bootstrap peer’s announced view of the network, that is why choosing a right bootstrap peer is so important. Using a malicious bootstrap peer may lead to joining a fake network if the malicious bootstrap peer and other fake network members can prove the ownership of their on-chain stakes. Using a recommended but hacked bootstrap peer may lead to not establishing connections with any other peers in the network if the hacked bootstrap peer does not properly announce new peer’s presence.
A peer wanting to connect to the network sends a network join request to one of the bootstrap peers. Each peer wanting to join a network needs to provide a proof of ownership of an on-chain identity with an associated stake. As part of the network join handshake, the bootstrap peer will also provide proof of its own stake.
The procedure is as follows:
-
Peer joining the network initiates the protocol by sending a network join request message containing randomly generated nonce
n_1
, an 8-byte (64-bit) unsigned integer, to the bootstrap peer. The message is signed with the peer’s static private key. -
The bootstrap peer randomly generates nonce
n_2
, which is also an 8-byte unsigned integer, and computes achallenge
which is the result of calling a cryptographic hash functionhash
on the concatenated bytes ofn1
andn2
(referenced asn1 || n2
). The bootstrap peer sends backchallenge,
n_2
and signs the message with its static private key. -
The peer joining the network recomputes the challenge from
n_1
andn_2
; if it matches the challenge sent by the bootstrap peer, it answers with a message containing the challenge. The message is signed with the peer’s static private key. -
The bootstrap peer validates the challenge.
JOINING PEER BOOTSTRAP PEER
n_1 = random_nonce()
Message { n_1 } ---->
n_2 = random_nonce()
challenge = hash(n_1 || n_2)
<---- Message { challenge, n_2 }
challenge = hash(n_1 || n_2)
Message {challenge} ---->
Message signature is checked for any message that is received. Any message with an invalid signature immediately aborts the protocol. If all signatures have been valid and challenge response was as expected, both parties execute a check for an on-chain stake of each other. If the peer joining the network has a minimum stake, the bootstrap peer connects the peer to the network and announces peer’s presence. If the bootstrap peer has a minimum stake, peer joining the network decides to connect to the bootstrap peer and becomes a part of the network. Nonces and challenge generated during the handshake are discarded. Otherwise, when at least one of the parties does not have a minimum stake, the protocol is aborted. If the bootstrap peer had a deficient stake, the peer joining the network may execute the protocol again with the next bootstrap peer.
Once a peer has completed the network join protocol successfully, it is connected to a bootstrap peer and is a full-fledged network member. It starts discovering other peers in the network and connects to some of those according to the peer discovery strategy implemented in the network. Before the peer decides to connect to any other network member, the same nonce, challenge, and stake check protocol must be executed between the peer and the new network member it is connecting to. Peers decide to connect to each other only if they both complete the protocol successfully and prove their on-chain minimum stakes. This is a trust-no-one strategy which allows for even a corrupted network to heal in the case of a malicious split brain event.
All messages in the network are signed with the sender’s private static key. Each message is uniquely identified by the sender’s address and sequence number incremented for each new message sent by that peer. The message identifier is a part of the signed message content. Each peer in the network maintains a cache of already seen messages for the time no shorter than the longest protocol executed in the network. If a message with the given identifier has been already seen by the given peer, the message is discarded. This behaviour applies both to peers relaying a message and to peer which is the final message receiver.
When a peer receives a message it first checks the signature. If it matches, then peer validates uniqueness of the message ID. If a message with the given ID was not seen before, then the message is accepted.
All peers in the network have a streaming view of the latest chain state that notifies in an event-style when a given address falls below the minimum stake.
If a peer’s stake drops below the required minimum, all connections to that peer are dropped and any unprocessed messages from that peer are immediately discarded. Once the peer’s stake returns to at or above the required minimum, it must initialize its connections with any disconnected peers once again in order to be able to communicate with them. All unprocessed messages will be retried.
Encryption is implemented on the application level and not on the network level. To enable encryption, two parties perform ECDH handshake using standard network messages ensuring attributability and integrity as described in the previous section.
Any of the communicating peers can reveal the symmetric key in order to publicly publish a complaint about the other peer’s message. In such case, the symmetric key is considered as compromised and should not be used for further communications.
Two peers communicating on the application level do not necessarily need to have a direct network-level connection with each other. It is possible messages are relayed by other peers. Each peer relaying the message validates the message signature against sender’s key. If the signature is not valid, peer rejects the message and drops the connection with the peer that relayed that message since that peer is the one that tampered the message.
Peer which tampered the message is blacklisted by the peer who received the relayed, tampered message. Peer is blacklisted for a certain period of time, tracked as an on-chain parameter. This time period is never shorter than the time needed to re-transmit the message. During the time peer is blacklisted, all connection attempts from that peer to the peer who blacklisted it are rejected.
In the Keep network, peers may form groups selected to execute various protocols. The output of the group formation protocol is a list of on-chain addresses. When a peer joins a group, it includes its public static key in every single message sent to the group. The public key is used by other peers in the group to derive the on-chain address of that peer, and check if the given peer is eligible to join and communicate within the group.
-
Discussions on writing this document: https://www.flowdock.com/app/cardforcoin/tech/threads/Zc_bHNDU5eNJY8JHB22NfU2a9Bk
-
libp2p security considerations: https://www.flowdock.com/app/cardforcoin/tech/threads/hKOcyS8EPmZ7PBvpnMQixy0di1e