Hoe een gedecentraliseerde messenger op de blockchain werkt

Begin 2017 zijn we begonnen met het ontwikkelen van een messengersysteem op blockchain [de naam en link staan in het profiel] en hebben we de voordelen ten opzichte van klassieke P2P-messengers besproken.

Er is 2.5 jaar verstreken, en we hebben ons concept kunnen bevestigen: op dit moment zijn de messenger-applicaties beschikbaar voor iOS, Web PWA, Windows, GNU/Linux, Mac OS en Android.

Vandaag vertellen we hoe de blockchain-messenger werkt en hoe de clientapplicaties gebruik maken van de API.
Hoe een gedecentraliseerde messenger op de blockchain werkt

We wilden dat blockchain de problemen van veiligheid en privacy van klassieke P2P-messengers oplost:

  • Één klik om een account aan te maken — geen telefoonnummers of e-mailadressen, geen toegang tot contactlijsten en geolocaties.
  • Gesprekken hebben nooit directe verbindingen; alle communicatie verloopt via een gedistribueerd systeem van knooppunten. IP-adressen van gebruikers zijn niet beschikbaar voor elkaar.
  • Alle berichten zijn End-to-End versleuteld met curve25519xsalsa20poly1305. Dit zal niemand echt verrassen, maar onze broncode is openbaar.
  • MITM-aanvallen zijn uitgesloten — elk bericht is een transactie en wordt ondertekend met Ed25519 EdDSA.
  • Een bericht komt in zijn blok. De volgorde van de timestamp blokken kan niet worden aangepast, en daarmee ook de volgorde van de berichten.
  • "Ik heb dat niet gezegd" werkt niet met berichten in de blockchain.
  • Er is geen centrale structuur die de 'betrouwbaarheid' van een bericht controleert. Dit gebeurt via het gedistribueerde knooppuntensysteem op basis van consensus, en dat behoort toe aan de gebruikers.
  • Censuurechts is onmogelijk — accounts kunnen niet worden geblokkeerd en berichten kunnen niet worden verwijderd.
  • Blockchain 2FA — een alternatief voor de vervelende 2FA via SMS, die veel problemen heeft veroorzaakt.
  • De mogelijkheid om al je gesprekken vanaf elk apparaat op elk moment te bekijken — dat is de mogelijkheid om gesprekken helemaal niet lokaal op te slaan.
  • Bevestiging van de levering van berichten. Niet aan het apparaat van de gebruiker, maar aan het netwerk. In wezen is dit de bevestiging dat de ontvanger in staat is om jouw bericht te lezen. Dit is een handige functie voor het verzenden van kritieke meldingen.

Onder de voordelen van blockchain vallen ook de nauwe integratie met cryptocurrencies zoals Ethereum, Dogecoin, Lisk, Dash, Bitcoin (deze is nog in ontwikkeling) en de mogelijkheid om tokens in chats te verzenden. We hebben zelfs een ingebouwde crypto-exchange gemaakt.

En dan, hoe werkt dit allemaal?

Een bericht is een transactie

Iedereen is inmiddels gewend dat transacties in de blockchain tokens (munten) van de ene gebruiker naar de andere overdragen. Zoals bij Bitcoin. Wij hebben een speciaal type transacties gecreëerd voor het verzenden van berichten.

Om een bericht in de messenger op de blockchain te verzenden, moet je verschillende stappen doorlopen:

  1. Versleutel de tekst van het bericht
  2. Plaats de versleutelde tekst in de transactie
  3. Onderteken de transactie
  4. Stuur de transactie naar een willekeurige node in het netwerk
  5. Het gedistribueerde systeem van nodes bepaalt de 'betrouwbaarheid' van het bericht
  6. Als alles in orde is, wordt de transactie met het bericht opgenomen in het volgende blok
  7. De ontvanger haalt de transactie met het bericht op en decodeert deze

De stappen 1–3 en 7 worden lokaal op de client uitgevoerd, terwijl 5–6 op de nodes in het netwerk worden uitgevoerd.

Versleuteling van het bericht

Het bericht wordt versleuteld met de privésleutel van de verzender en de openbare sleutel van de ontvanger. We nemen de openbare sleutel uit het netwerk, maar hiervoor moet het account van de ontvanger zijn geïnitialiseerd, dat wil zeggen dat het ten minste één transactie moet hebben. We kunnen een REST-aanroep gebruiken GET /api/accounts/getPublicKey?address={ADAMANT address}, en bij het laden van chats zijn de openbare sleutels van de gesprekspartners al beschikbaar.

Hoe een gedecentraliseerde messenger op de blockchain werkt

De messenger versleutelt berichten met het algoritme curve25519xsalsa20poly1305 (NaCl Box). Aangezien het account Ed25519-sleutels bevat, moeten de sleutels vooraf worden omgevormd naar Curve25519 Diffie-Hellman voor de vorming van de box.

Hier is een voorbeeld in JavaScript:

/**
 * Encodes a text message for sending to ADM
 * @param {string} msg message to encode
 * @param {*} recipientPublicKey recipient's public key
 * @param {*} privateKey our private key
 * @returns {{message: string, nonce: string}}
 */
adamant.encodeMessage = function (msg, recipientPublicKey, privateKey) {
  const nonce = Buffer.allocUnsafe(24)
  sodium.randombytes(nonce)

  if (typeof recipientPublicKey === 'string') {
    recipientPublicKey = hexToBytes(recipientPublicKey)
  }

  const plainText = Buffer.from(msg)
  const DHPublicKey = ed2curve.convertPublicKey(recipientPublicKey)
  const DHSecretKey = ed2curve.convertSecretKey(privateKey)

  const encrypted = nacl.box(plainText, nonce, DHPublicKey, DHSecretKey)

  return {
    message: bytesToHex(encrypted),
    nonce: bytesToHex(nonce)
  }
}

Vorming van de transactie met het bericht

De transactie heeft de volgende algemene structuur:

{
  "id": "15161295239237781653",
  "height": 7585271,
  "blockId": "16391508373936326027",
  "type": 8,
  "block_timestamp": 45182260,
  "timestamp": 45182254,
  "senderPublicKey": "bd39cc708499ae91b937083463fce5e0668c2b37e78df28f69d132fce51d49ed",
  "senderId": "U16023712506749300952",
  "recipientId": "U17653312780572073341",
  "recipientPublicKey": "23d27f616e304ef2046a60b762683b8dabebe0d8fc26e5ecdb1d5f3d291dbe21",
  "amount": 204921300000000,
  "fee": 50000000,
  "signature": "3c8e551f60fedb81e52835c69e8b158eb1b8b3c89a04d3df5adc0d99017ffbcb06a7b16ad76d519f80df019c930960317a67e8d18ab1e85e575c9470000cf607",
  "signatures": [],
  "confirmations": 3660548,
  "asset": {}
}

Voor de transactie-bericht is de belangrijkste waarde asset — hier moet het bericht worden geplaatst in het object chat met de structuur:

  • bericht — we slaan het versleutelde bericht op
  • own_message — nonce
  • type — type van het bericht

Berichten zijn ook onderverdeeld in types. In wezen geeft de parameter type aan hoe het moet worden begrepen bericht. Je kunt eenvoudigweg tekst verzenden, maar ook een object met interessante dingen erin — bijvoorbeeld zo maakt de messenger cryptocurrency-overmakingen in chats.

Uiteindelijk vormen we de transactie:

{
  "transaction": {
    "type": 8,
    "amount": 0,
    "senderId": "U12499126640447739963",
    "senderPublicKey": "e9cafb1e7b403c4cf247c94f73ee4cada367fcc130cb3888219a0ba0633230b6",
    "asset": {
      "chat": {
        "message": "cb682accceef92d7cddaaddb787d1184ab5428",
        "own_message": "e7d8f90ddf7d70efe359c3e4ecfb5ed3802297b248eacbd6",
        "type": 1
      }
    },
    "recipientId": "U15677078342684640219",
    "timestamp": 63228087,
    "signature": "handtekening zal hier komen"
  }
}

Handtekening van de transactie

Om ervoor te zorgen dat iedereen zeker is van de betrouwbaarheid van zowel de verzender als de ontvanger, het tijdstip van verzending en de inhoud van het bericht, wordt de transactie ondertekend. Een digitale handtekening maakt het mogelijk om de geldigheid van de transactie te verifiëren aan de hand van de openbare sleutel — de privésleutel is hiervoor niet nodig.

De handtekening zelf wordt met de privésleutel uitgevoerd:

Hoe een gedecentraliseerde messenger op de blockchain werkt

Uit het schema blijkt dat de transactie eerst met SHA-256 wordt gehashed en vervolgens wordt ondertekend. Ed25519 EdDSA en we krijgen de handtekening handtekening, en de transactie-ID is een deel van de SHA-256-hash.

Voorbeeld van implementatie:

1 — We vormen een gegevensblok, inclusief het bericht

/**
 * Calls `getBytes` based on transaction type
 * @see privateTypes
 * @implements {ByteBuffer}
 * @param {transaction} trs
 * @param {boolean} skipSignature
 * @param {boolean} skipSecondSignature
 * @return {!Array} Contents as an ArrayBuffer.
 * @throws {error} If buffer fails.
 */

adamant.getBytes = function (transaction) {

  ...

  switch (transaction.type) {
    case constants.Transactions.SEND:
      break
    case constants.Transactions.CHAT_MESSAGE:
      assetBytes = this.chatGetBytes(transaction)
      assetSize = assetBytes.length
      break

…

    default:
      alert('Not supported yet')
  }

  var bb = new ByteBuffer(1 + 4 + 32 + 8 + 8 + 64 + 64 + assetSize, true)

  bb.writeByte(transaction.type)
  bb.writeInt(transaction.timestamp)

  ...

  bb.flip()
  var arrayBuffer = new Uint8Array(bb.toArrayBuffer())
  var buffer = []

  for (var i = 0; i < arrayBuffer.length; i++) {
    buffer[i] = arrayBuffer[i]
  }

  return Buffer.from(buffer)
}

2 — We berekenen de SHA-256 van het gegevensblok

/**
 * Creates hash based on transaction bytes.
 * @implements {getBytes}
 * @implements {crypto.createHash}
 * @param {transaction} trs
 * @return {hash} sha256 crypto hash
 */
adamant.getHash = function (trs) {
  return crypto.createHash('sha256').update(this.getBytes(trs)).digest()
}

3 — We ondertekenen de transactie

adamant.transactionSign = function (trs, keypair) {
  var hash = this.getHash(trs)
  return this.sign(hash, keypair).toString('hex')
}

/**
 * Maakt een handtekening op basis van een hash en een sleutelpair.
 * @implements {sodium}
 * @param {hash} hash
 * @param {keypair} keypair
 * @return {signature} signature
 *\/ 
adamant.sign = function (hash, keypair) {
  return sodium.crypto_sign_detached(hash, Buffer.from(keypair.privateKey, 'hex'))
}

Transactie verzenden met een bericht naar een netwerknode

Aangezien het netwerk gedecentraliseerd is, kan elke node met een open API worden gebruikt. We maken een POST-verzoek naar de eindpunt api/transactions:

curl 'api/transactions' -X POST 
  -d 'TX_DATA'

Als antwoord krijgen we een ID van de transactie van het type

{
    "success": true,
    "nodeTimestamp": 63228852,
    "transactionId": "6146865104403680934"
}

Verificatie van de transactie

Een gedistribueerd systeem van nodes op basis van consensus bepaalt de “betrouwbaarheid” van de transactie-bericht. Van wie en aan wie, wanneer, of het bericht niet door een ander is vervangen en of de tijd van verzending correct is. Dit is een belangrijk voordeel van blockchain — er is geen centrale structuur die verantwoordelijk is voor controles, en de volgorde van berichten en hun inhoud kan niet worden vervalst.

Eerst controleert één node de betrouwbaarheid, en vervolgens verspreidt deze het naar anderen — als de meeste zeggen dat alles in orde is, wordt de transactie opgenomen in het volgende blok van de keten — dat is consensus.

Hoe een gedecentraliseerde messenger op de blockchain werkt

Een deel van de code van de node dat verantwoordelijk is voor de controles is te zien op GitHub — validator.js en verify.js. Oké, de node draait op Node.js.

We voegen de transactie met het bericht toe aan het blok

Als er consensus is bereikt, wordt de transactie met ons bericht opgenomen in het volgende blok samen met andere geldige transacties.

Blokken hebben een strikte volgorde, en elk volgend blok wordt gevormd op basis van de hashes van de voorgaande blokken.

Hoe een gedecentraliseerde messenger op de blockchain werkt

Het punt is dat ons bericht ook in deze volgorde is opgenomen en niet kan worden 'verplaatst'. Als meerdere berichten in een blok terechtkomen, wordt hun volgorde bepaald door timestamp de berichten.

Berichten lezen

De berichten-app haalt transacties uit de blockchain die naar de ontvanger zijn gestuurd. Hiervoor hebben we de endpoint gemaakt api/chatrooms.

Alle transacties zijn voor iedereen toegankelijk — je kunt versleutelde berichten ontvangen. Maar alleen de ontvanger kan deze ontcijferen met zijn privé-sleutel en de publieke sleutel van de afzender:

**
 * Decodes the incoming message
 * @param {any} msg encoded message
 * @param {string} senderPublicKey sender public key
 * @param {string} privateKey our private key
 * @param {any} nonce nonce
 * @returns {string}
 */
adamant.decodeMessage = function (msg, senderPublicKey, privateKey, nonce) {
  if (typeof msg === 'string') {
    msg = hexToBytes(msg)
  }

  if (typeof nonce === 'string') {
    nonce = hexToBytes(nonce)
  }

  if (typeof senderPublicKey === 'string') {
    senderPublicKey = hexToBytes(senderPublicKey)
  }

  if (typeof privateKey === 'string') {
    privateKey = hexToBytes(privateKey)
  }

  const DHPublicKey = ed2curve.convertPublicKey(senderPublicKey)
  const DHSecretKey = ed2curve.convertSecretKey(privateKey)
  const decrypted = nacl.box.open(msg, nonce, DHPublicKey, DHSecretKey)

  return decrypted ? decode(decrypted) : ''
}

Wat nog meer?

Aangezien berichten op deze manier in ongeveer 5 seconden worden geleverd — dat is de tijd waarin een nieuw blok in het netwerk verschijnt — hebben we een socketverbinding tussen client-knoop en knoop-knoop bedacht. Wanneer een knoop een nieuwe transactie ontvangt, controleert deze de geldigheid en geeft deze door aan andere knopen. De transactie is al beschikbaar voor berichtenklanten nog voordat de consensus is bereikt en deze in een blok is opgenomen. Zo zullen we berichten onmiddellijk afleveren, net als gebruikelijke berichtenapps.

Om het adresboek te beheren, hebben we KVS gemaakt — Key-Value Storage — dit is weer een ander type transactie, waarbij asset niet de NaCl-box wordt versleuteld, maar NaCl-secretbox.Op deze manier slaat de messenger ook andere gegevens op.

Bestanden/afbeeldingen verzenden en groepschats vereisen nog veel werk. Natuurlijk, in een halfslachtige aanpak kan dit snel 'worden toegevoegd', maar we willen hetzelfde niveau van privacy behouden.

Ja, er is nog veel werk aan de winkel — idealiter veronderstelt echte privacy dat gebruikers zich niet verbinden met publieke knopen van het netwerk, maar hun eigen knopen opzetten. Hoeveel procent van de gebruikers denkt u doet dat? Juist, 0. Dit probleem hebben we gedeeltelijk opgelost met de Tor-versie van de messenger.

We hebben bewezen dat een messenger op de blockchain kan bestaan. Eerder was er slechts één poging in 2012 — bitmessage,die faalde door de lange levertijd van berichten, de belasting van de processor en het ontbreken van mobiele applicaties.

Scepticisme heeft te maken met het feit dat blockchain-messagingdiensten de tijd vooruit zijn — mensen zijn nog niet bereid om de verantwoordelijkheid voor hun account op zich te nemen, het bezitten van persoonlijke informatie is momenteel niet in de mode, en de technologieën stellen niet in staat om hoge snelheden op de blockchain te garanderen. Er zullen meer geavanceerde alternatieven van ons project op de markt komen. U zult het zien.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster