STORY
NIP:04 and 3-Way Handshake Algorithm: Enabling Multiplayer Gameplay in Nostrawars
AUTHOR
Joined 2022.06.19
PROJECT
DATE
VOTES
sats
COMMENTS

NIP:04 and 3-Way Handshake Algorithm: Enabling Multiplayer Gameplay in Nostrawars

In my yesterday's post, I shared some updates on my project Nostrawars for Nostrhack. In this post, I'll dive deeper into the technical details of what I've been working on and how I'm implementing features like messaging and the 3-way handshake algorithm.

One of the key aspects of Nostrawars is enabling players to connect with each other in real-time. To achieve this, I've been using the NIP04 messaging protocol to facilitate communication between players. When a player creates or joins a room, new keys are generated for both players and they connect to a relay. Once the connection is established, they subscribe to the relay with the event filter as follows:

subscription = relay.sub(
    [{
        kinds: [4],
        since: Math.floor(Date.now() / 1000),
        '#p': [pk1]
    }]
);

The 'kinds' property is set to 4, which represents "encrypted direct message". 'since' specifies that events should only be received from the current time onwards, and '#p' ensures that only messages sent to the player's public key are received_._

When an event is received from the opponent, it must be decrypted using the player's private key. This is accomplished using the following code:

// Subscribe to relay and listen for events
subscription.on('event', (event) => {
    console.log(`sub got event: ${JSON.stringify(event)}`);
    // Decrypt the event content and pass it to the eventReceivedCallback function
    _decryptText(event, sk1, eventReceivedCallback);
});

// Asynchronously decrypt the event content and call the eventReceivedCallback function
async function _decryptText(event, sk1, eventReceivedCallback) {
    plaintext = await nip04.decrypt(sk1, event.pubkey, event.content);
    eventReceivedCallback(plaintext, event.pubkey);
}

The 'eventReceivedCallback' function is called with the decrypted message and the opponent's public key as arguments.

On the sending side, events are automatically published to the opponent during the 3-way handshake algorithm and when the game stats are shared. The event object includes the kind, sender and receiver public keys, encrypted content, and a timestamp. Here's an example:

event = {
    kind: 4,
    pubkey: senderPk,
    tags: [['p', receiverPk]],
    content: ciphertext,
    created_at: Math.floor(Date.now() / 1000)
}

The 'tags' property specifies the receiver's public key, while 'content' contains the encrypted message. Encryption and decryption occur client-side_._

Now, let's talk about the 3-way handshake algorithm. When a player creates a room, the player waits for the opponent to join and send a "SYN:${uuid.v4()}" message. Any other message received is ignored. The player client then parses the message, splitting the terms by the ':' character. 'SYN' is the message type and '${uuid.v4()}' is a random sequence of bytes.

The player client then sends a "SYN-ACK:${sha256(uuid.v4())}" message to the opponent player. The opponent receives this message and attempts to match the SHA256 hash of the received message with the SHA256 hash of the original "${uuid.v4()}" sequence generated by the opponent during the "SYN" message. If both hashes match, the opponent sends an ACK message in the form "ACK:sha256hash", with 'sha256hash' being the received hash.

The player client then verifies that the received hash matches the one sent in the "SYN-ACK" message. If the hash matches, both players are directed to the GameScreen.

Here's some pseudocode that summarizes the 3-way handshake algorithm:

That's how NIP04 and the 3-way handshake algorithm works in Nostrawars. If you have any questions or comments, feel free to share them below_._

Thanks for reading!