Jak funguje decentralizovaný messenger na blockchainu

Na začátku roku 2017 jsme začali vyvíjet messenger na blockchainu [název a odkaz jsou v profilu] s diskusí o výhodách oproti klasickým P2P messengerům.

Už uběhlo 2.5 let a podařilo se nám potvrdit naši koncepci: nyní jsou k dispozici aplikace messengera pro iOS, Web PWA, Windows, GNU/Linux, Mac OS a Android.

Dnes vám povíme, jak je messenger na blockchainu nastaven a jak klientské aplikace pracují s jeho API.
Jak funguje decentralizovaný messenger na blockchainu

Chtěli jsme, aby blockchain vyřešil otázky bezpečnosti a soukromí klasických P2P messengerů:

  • Jedno kliknutí pro vytvoření účtu — žádná telefonní čísla a e-maily, bez přístupu k adresářům a geolokacím.
  • Účastníci si nikdy nenavazují přímá spojení, veškerá komunikace probíhá prostřednictvím distribuovaného systému uzlů. IP adresy uživatelů nejsou navzájem dostupné.
  • Všechny zprávy jsou šifrovány End-to-End curve25519xsalsa20poly1305. Tímto snad nikoho nepřekvapíte, ale náš zdrojový kód je otevřený.
  • MITM útoky jsou vyloučeny — každá zpráva je transakcí a je podepsána Ed25519 EdDSA.
  • Zpráva vstupuje do svého bloku. Sekvence a timestamp bloků nelze opravit, a tedy ani pořadí zpráv.
  • „To jsem neřekl“ neobstojí s těmito zprávami v blockchainu.
  • Není centrální struktura, která by prováděla kontroly „autenticity“ zprávy. To provádí distribuovaný systém uzlů na základě konsensu, který patří uživatelům.
  • Nemožnost cenzury — účty nelze blokovat a zprávy mazat.
  • Blockchain 2FA — alternativa pekelné 2FA přes SMS, která zmařila zdraví mnohých.
  • Možnost získat všechny své dialogy z jakéhokoli zařízení kdykoli — to je možnost neuchovávat dialogy zcela lokálně.
  • Potvrzení doručení zpráv. Ne na zařízení uživatele, ale do sítě. V podstatě jde o potvrzení možností příjemce přečíst vaši zprávu. To je užitečná funkce pro odesílání kritických oznámení.

Mezi výhody blockchainu patří také úzká integrace s kryptoměnami Ethereum, Dogecoin, Lisk, Dash, Bitcoin (ten je zatím v procesu) a možnost posílat tokeny v chatech. Dokonce jsme vytvořili integrovanou kryptoburzu.

A teď — jak to všechno funguje.

Zpráva je transakce.

Všichni si již zvykli, že transakce v blockchainu převádějí tokeny (mince) od jednoho uživatele k druhému. Jako u bitcoinu. My jsme vytvořili zvláštní typ transakcí pro přenos zpráv.

Chcete-li poslat zprávu v messengeru na blockchainu, je potřeba projít několika kroky:

  1. Zašifrovat text zprávy
  2. Umístit zašifrovaný text do transakce
  3. Podepsat transakci
  4. Odeslat transakci na libovolný uzel sítě
  5. Distribuovaný systém uzlů určuje „důvěryhodnost“ zprávy
  6. Pokud je vše v pořádku — transakce se zprávou je zahrnuta do dalšího bloku
  7. Příjemce extrahuje transakci se zprávou a dešifruje ji

Kroky 1–3 a 7 probíhají lokálně na klientovi, zatímco kroky 5–6 na uzlech sítě.

Šifrování zprávy

Zpráva je šifrována soukromým klíčem odesílatele a veřejným klíčem příjemce. Veřejný klíč vezmeme ze sítě, ale proto musí být účet příjemce inicializován, tedy mít alespoň jednu transakci. Lze použít REST dotaz GET /api/accounts/getPublicKey?address={ADAMANT address}, a při načítání chatů již budou veřejné klíče partnerů k dispozici.

Jak funguje decentralizovaný messenger na blockchainu

Messenger šifruje zprávy algoritmem curve25519xsalsa20poly1305 (NaCl Box). Vzhledem k tomu, že účet obsahuje klíče Ed25519, je nutné před vytvořením boxu klíče nejprve převést na Curve25519 Diffie-Hellman.

Zde je příklad v JavaScriptu:

/**
 * 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)
  }
}

Vytvoření transakce se zprávou

Transakce má takovou obecnou strukturu:

{
  "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": {}
}

Pro transakci-zprávu má nejdůležitější hodnotu asset — do něj je třeba umístit zprávu v objektu chat se strukturou:

  • zpráva — uchováváme zašifrovanou zprávu
  • own_message — nonce
  • type — typ zprávy

Zprávy se také dělí na typy. V podstatě parametr type oznamuje, jak chápat zpráva. Můžete poslat jen text, nebo objekt s zajímavostmi uvnitř — například takto messenger provádí převody kryptoměn v chatech.

Nakonec vytváříme transakci:

{
  "transaction": {
    "type": 8,
    "amount": 0,
    "senderId": "U12499126640447739963",
    "senderPublicKey": "e9cafb1e7b403c4cf247c94f73ee4cada367fcc130cb3888219a0ba0633230b6",
    "asset": {
      "chat": {
        "message": "cb682accceef92d7cddaaddb787d1184ab5428",
        "own_message": "e7d8f90ddf7d70efe359c3e4ecfb5ed3802297b248eacbd6",
        "type": 1
      }
    },
    "recipientId": "U15677078342684640219",
    "timestamp": 63228087,
    "signature": "zde bude podpis"
  }
}

Podpis transakce

Aby si všichni byli jisti pravostí odesílatele a příjemce, časem odeslání a obsahem zprávy, transakci podepisujeme. Digitální podpis umožňuje ověřit pravost transakce podle veřejného klíče - soukromý klíč pro to není potřeba.

A zde se podpis provádí pomocí soukromého klíče:

Jak funguje decentralizovaný messenger na blockchainu

Z diagramu je vidět, že transakci nejprve hashujeme pomocí SHA-256 a poté podepisujeme. Ed25519 EdDSA a získáváme podpis podpis, a identifikátor transakce je část SHA-256 hash.

Příklad implementace:

1 - Vytvoříme blok dat, včetně zprávy

/**
 * 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 - Spočítáme SHA-256 z bloku dat

/**
 * 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 - Podepisujeme transakci

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

/**
 * Vytváří podpis na základě hashe a klíčového páru.
 * @implements {sodium}
 * @param {hash} hash
 * @param {keypair} keypair
 * @return {signature} signature
 *\/\nadamant.sign = function (hash, keypair) {
  return sodium.crypto_sign_detached(hash, Buffer.from(keypair.privateKey, 'hex'))
}

Odesílání transakce se zprávou na uzel sítě

Protože síť je decentralizovaná, může se použít kterýkoli uzel s otevřeným API. Provádíme POST požadavek na koncový bod api/transactions:

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

V odpovědi obdržíme ID transakce typu

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

Ověření pravosti transakce

Distribuovaný systém uzlů na základě konsensu určuje "pravost" transakce-zprávy. Od koho a komu, kdy, zda nebyla zpráva nahrazena jinou, a zda je správně uveden čas odeslání. To je velmi důležitá výhoda blockchainu - neexistuje centrální struktura, která by odpovídala za ověřování, a posloupnost zpráv a jejich obsah nelze padělat.

Nejprve pravost ověří jeden uzel a poté ji rozesílá dalším - pokud většina říká, že je všechno v pořádku, transakce bude zahrnuta do dalšího bloku řetězu - to je konsensus.

Jak funguje decentralizovaný messenger na blockchainu

Část kódu uzlu, která odpovídá za ověřování, je k dispozici na GitHubu - validator.js a verify.js. Aha, uzel běží na Node.js.

Zařazení transakce se zprávou do bloku

Pokud je dosaženo konsenzu, transakce s naší zprávou se dostane do dalšího bloku spolu s dalšími platnými transakcemi.

Bloky mají přísnou sekvenci a každý následující blok je vytvářen na základě hashů předchozích bloků.

Jak funguje decentralizovaný messenger na blockchainu

Jde o to, že naše zpráva je také součástí této sekvence a nemůže být 'přesunuta'. Pokud se do bloku dostane více zpráv, jejich pořadí bude určeno podle timestamp zpráv.

Čtení zpráv

Aplikační messenger extrahuje transakce z blockchainu, které byly odeslány adresátovi. K tomu jsme vytvořili endpoint api/chatrooms.

Všechny transakce jsou dostupné pro každého — lze získat zašifrované zprávy. Rozšifrovat je však může pouze příjemce se svým soukromým klíčem a veřejným klíčem odesílatele:

**
 * 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) : ''
}

A co ještě?

Vzhledem k tomu, že zprávy jsou tímto způsobem doručovány za přibližně 5 sekund — tedy za čas potřebný k vytvoření nového bloku sítě — vymysleli jsme socketové připojení klient-uzel a uzel-uzel. Když uzel obdrží novou transakci, ověřuje její platnost a předává ji ostatním uzlům. Transakce je dostupná klientům-messengerům ještě před tím, než dojde k dosáhnutí konsenzu a zařazení do bloku. Takto budeme doručovat zprávy okamžitě, stejně jako tradiční messengery.

Abychom uchovali adresář, vytvořili jsme KVS — Key-Value Storage — což je další typ transakcí, ve kterých asset není šifrován NaCl-box, ale NaCl-secretbox. Tak messenger uchovává i další data.

Přenášení souborů/obrázků a skupinové chaty vyžadují ještě hodně práce. Samozřejmě, v rychlém formátu to lze 'přilepit' rychle, ale chceme zachovat stejnou úroveň soukromí.

Ano, je ještě na čem pracovat — ideálně by skutečné soukromí znamenalo, že se uživatelé nebudou připojovat k veřejným uzlům sítě, ale vytvoří si své vlastní. Jak si myslíte, kolik procent uživatelů to dělá? Správně, 0. Částečně se nám tento problém podařilo vyřešit Tor-verzí messengera.

Dokázali jsme, že messenger na blockchainu může existovat. Předtím byla pouze jedna pokus v roce 2012 — bitmessage, která selhala kvůli dlouhé době doručování zpráv, zátěži na procesor a nedostatku mobilních aplikací.

Skepticismus souvisí s tím, že messengery na blockchainu předběhly dobu — lidé nejsou připraveni přijmout odpovědnost za svůj účet, vlastnictví osobních informací zatím není v módě a technologie neumožňují vysoké rychlosti na blockchainu. Následně se budou objevovat technologičtější analogie našeho projektu. Uvidíte.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster