Skip to content

Latest commit

 

History

History
321 lines (257 loc) · 8.02 KB

0003.md

File metadata and controls

321 lines (257 loc) · 8.02 KB

PLIP-3

Publishing and challenges

draft optional

Publications like comments, votes, etc. can be published peer to peer over libp2p Gossipsub. The Gossipsub topic name is defined in subplebbit.pubsubTopic and must be a libp2p peer ID (hash of an Ed25519 public key). Gossipsub messages are CBOR encoded.

Challenge exchange

An author first publishes a CHALLENGEREQUEST which contains his publication, like a comment or vote, encrypted using the scheme defined in subplebbit.encryption. The subplebbit replies with a CHALLENGE, such as a base64 image captcha, a plain text question, etc. The author replies with a CHALLENGEANSWER. The subplebbit finally replies with a CHALLENGEVERIFICATION with challengeVerification.challengeSuccess true or false.

NOTE: The CHALLENGEREQUEST can include challenge answers before receiving the CHALLENGE if some challenges are defined in subplebbit.challenges.

NOTE 2: The subplebbit can omit CHALLENGE and directly reply with a successful or unsuccessful CHALLENGEVERIFICATION, for example if the author is whitelisted, or banned.

Pubsub message types

CHALLENGEREQUEST

The first message in a challenge exchange.

For example (CBOR encoded):

{
  "type": "CHALLENGEREQUEST",
  "challengeRequestId": <CBOR byte string>,
  "timestamp": 1504321021,
  "encrypted": {
    "ciphertext": <CBOR byte string>,
    "iv": <CBOR byte string>,
    "tag": <CBOR byte string>,
    "type": "ed25519-aes-gcm"
  },
  "protocolVersion": "1.0.0",
  "userAgent": "/plebbit-js:0.0.1/",
  "signature": {
    "signature": <cbor byte string>,
    "publicKey": <cbor byte string>,
    "type": "ed25519",
    "signedPropertyNames": ["type", "challengeRequestId", "timestamp", "encrypted", "protocolVersion", "userAgent"]
  }
}

Required fields are:

  • type
  • challengeRequestId
  • timestamp
  • encrypted
  • protocolVersion
  • userAgent
  • signature

challengeRequest.encrypted.ciphertext decrypts to JSON:

{
  "comment": {
    "title": "Why did the banana go to the doctor?",
    "content": "It wasn't peeling well.",
    "subplebbitAddress": "jokes.eth",
    "author": {"address": "john.eth"},
    "timestamp": 1728174027,
    "signature": {
      "signature": <base64>,
      "publicKey": <base64>,
      "type": "ed25519",
      "signedPropertyNames": ["title", "content", "subplebbitAddress", "author", "timestamp"]
    }
  }
}

Optional encrypted fields are:

  • comment
  • vote

CHALLENGE

The second message in a challenge exchange. A reply by the subplebbit to a CHALLENGEREQUEST.

For example (CBOR encoded):

{
  "type": "CHALLENGE",
  "challengeRequestId": <CBOR byte string>,
  "timestamp": 1504321021,
  "encrypted": {
    "ciphertext": <CBOR byte string>,
    "iv": <CBOR byte string>,
    "tag": <CBOR byte string>,
    "type": "ed25519-aes-gcm"
  },
  "protocolVersion": "1.0.0",
  "userAgent": "/plebbit-js:0.0.1/",
  "signature": {
    "signature": <cbor byte string>,
    "publicKey": <cbor byte string>,
    "type": "ed25519",
    "signedPropertyNames": ["type", "challengeRequestId", "timestamp", "encrypted", "protocolVersion", "userAgent"]
  }
}

Required fields are:

  • type
  • challengeRequestId
  • timestamp
  • encrypted
  • protocolVersion
  • userAgent
  • signature

challenge.encrypted.ciphertext decrypts to JSON:

{
  "challenges": [
    {
      "type": "image/png",
      "challenge": <base64 image>,
      "caseInsensitive": true
    },
    {
      "type": "text/plain",
      "challenge": "What is the password?"
    }
  ]
}

Required encrypted fields are:

  • challenges

CHALLENGEANSWER

The third message in a challenge exchange. A reply by the author to a CHALLENGE.

For example (CBOR encoded):

{
  "type": "CHALLENGEANSWER",
  "challengeRequestId": <CBOR byte string>,
  "timestamp": 1504321021,
  "encrypted": {
    "ciphertext": <CBOR byte string>,
    "iv": <CBOR byte string>,
    "tag": <CBOR byte string>,
    "type": "ed25519-aes-gcm"
  },
  "protocolVersion": "1.0.0",
  "userAgent": "/plebbit-js:0.0.1/",
  "signature": {
    "signature": <cbor byte string>,
    "publicKey": <cbor byte string>,
    "type": "ed25519",
    "signedPropertyNames": ["type", "challengeRequestId", "timestamp", "encrypted", "protocolVersion", "userAgent"]
  }
}

Required fields are:

  • type
  • challengeRequestId
  • timestamp
  • encrypted
  • protocolVersion
  • userAgent
  • signature

challengeAnswer.encrypted.ciphertext decrypts to JSON:

{
  "challengeAnswers": [
    "DK4K3A",
    "password"
  ]
}

Required encrypted fields are:

  • challengeAnswers

CHALLENGEVERIFICATION

The fourth and final message in a challenge exchange. A reply by the subplebbit to a CHALLENGEANSWER.

For example (CBOR encoded):

{
  "type": "CHALLENGEVERIFICATION",
  "challengeRequestId": <CBOR byte string>,
  "timestamp": 1504321021,
  "challengeSuccess": true,
  "encrypted": {
    "ciphertext": <CBOR byte string>,
    "iv": <CBOR byte string>,
    "tag": <CBOR byte string>,
    "type": "ed25519-aes-gcm"
  },
  "protocolVersion": "1.0.0",
  "userAgent": "/plebbit-js:0.0.1/",
  "signature": {
    "signature": <cbor byte string>,
    "publicKey": <cbor byte string>,
    "type": "ed25519",
    "signedPropertyNames": ["type", "challengeRequestId", "challengeSuccess", "timestamp", "encrypted", "protocolVersion", "userAgent"]
  }
}

Required fields are:

  • type
  • challengeRequestId
  • challengeSuccess
  • timestamp
  • encrypted
  • protocolVersion
  • userAgent
  • signature

challengeVerification.encrypted.ciphertext decrypts to JSON:

{
  "comment": {
    "title": "Why did the banana go to the doctor?",
    "content": "It wasn't peeling well.",
    "subplebbitAddress": "jokes.eth",
    "author": {"address": "john.eth"},
    "timestamp": 1728174027,
    "signature": {
      "signature": <base64>,
      "publicKey": <base64>,
      "type": "ed25519",
      "signedPropertyNames": ["title", "content", "subplebbitAddress", "author", "timestamp"]
    },
    "depth": 0,
    "previousCid": "QmeQSmkNfDzfuqkKVFrRjuA9VBxF1ubpB24xxtxs1Go67E"
  },
  "commentUpdate": {
    "cid": "QmXnEICVkZBHKgjtj7Vt63HWq3ZfPjcGTSPs79oXtfEZxc",
    "signature": {
      "signature": <base64>,
      "publicKey": <base64>,
      "type": "ed25519",
      "signedPropertyNames": ["cid"]
    }
  }
}

Required encrypted fields are:

  • comment
  • commentUpdate

Signature

Same as defined in PLIP-1.

Encryption

The gossipsubMessage.encrypted.type "ed25519-aes-gcm" encrypt algorithm is:

  const plaintext = JSON.stringify({comment})
  const plaintextWithRandomPadding = plaintext + '             ' // add random amount of empty space to plaintext JSON

  const iv = randomBytes(12)

  const aesGcmKey = ed25519.getSharedSecret(
    base64.decode(gossipsubMessageSignaturePrivateKey), // private key the author used to sign the gossipsub message
    base64.decode(subplebbit.encryption.publicKey) // subplebbit public key
  )
  const aesGcmKey16Bytes = aesGcmKey.slice(0, 16)

  const {ciphertext, tag} = encryptAesGcm(plaintextWithRandomPadding, iv, aesGcmKey16Bytes)

  gossipsubMessage.encrypted = {ciphertext, iv, tag, type: 'ed25519-aes-gcm'}

The gossipsubMessage.encrypted.type "ed25519-aes-gcm" decrypt algorithm is:

  const aesGcmKey = ed25519.getSharedSecret(
    base64.decode(subplebbitPrivateKey), // subplebbit private key
    base64.decode(gossipsubMessage.signature.publicKey) // public key the author used to sign the gossipsub message
  )
  const aesGcmKey16Bytes = aesGcmKey.slice(0, 16)

  const plaintext = decryptAesGcm(
    gossipsubMessage.encrypted.ciphertext, 
    aesGcmKey16Bytes, 
    gossipsubMessage.encrypted.iv, 
    gossipsubMessage.encrypted.tag
  )

  const {comment} = JSON.parse(plaintext)

Uses

Allow users to post, reply and vote in your subplebbit, fully peer to peer

Publishing peer to peer via Gossipsub is more censorship resistant as it's more difficult to DDOS, doesn't require an SSL certificate from a provider, a publicly accessible HTTP endpoint, etc.