Anfang 2017 begannen wir mit der Entwicklung eines Messengers auf der Blockchain [Name und Link sind im Profil] und diskutierten die Vorteile im Vergleich zu klassischen P2P-Messengern.
Es ist 2.5 Jahre vergangen, und wir haben unser Konzept bestätigen können: jetzt sind die Messenger-Anwendungen für iOS, Web PWA, Windows, GNU/Linux, Mac OS und Android verfügbar.
Heute erzählen wir, wie der Messenger auf der Blockchain funktioniert und wie die Client-Anwendungen mit seiner API interagieren.

Wir wollten, dass die Blockchain die Sicherheits- und Datenschutzfragen klassischer P2P-Messenger löst:
- Ein Klick zur Erstellung eines Kontos – keine Telefonnummern und E-Mail-Adressen, kein Zugriff auf Adressbücher und Geolokalisierungen.
- Die Gesprächspartner stellen niemals direkte Verbindungen her, sämtliche Kommunikation erfolgt über ein verteiltes System von Knoten. Die IP-Adressen der Nutzer sind einander nicht zugänglich.
- Alle Nachrichten werden End-to-End mit curve25519xsalsa20poly1305 verschlüsselt. Damit überrascht man niemanden, aber unser Quellcode ist offen.
- Ein MITM-Angriff ist ausgeschlossen – jede Nachricht ist eine Transaktion und wird mit Ed25519 EdDSA signiert.
- Die Nachricht gelangt in ihren Block. Die Reihenfolge und
timestampBlöcke sind nicht änderbar, folglich auch die Reihenfolge der Nachrichten. - „Das habe ich nicht gesagt“ zieht nicht bei Nachrichten auf der Blockchain.
- Es gibt keine zentrale Struktur, die die "Echtheit" der Nachrichten überprüft. Das erfolgt durch ein verteiltes System von Knoten auf der Basis von Konsens, das den Benutzern gehört.
- Die Unmöglichkeit von Zensur – Konten können nicht gesperrt und Nachrichten nicht gelöscht werden.
- Blockchain 2FA – eine Alternative zur missratenen 2FA per SMS,
- Die Möglichkeit, alle eigenen Dialoge von jedem Gerät jederzeit abzurufen – das bedeutet, Dialoge überhaupt nicht lokal speichern zu müssen.
- Bestätigung der Nachrichtenlieferung. Nicht an das Nutzergerät, sondern in das Netzwerk. Im Grunde ist dies die Bestätigung, dass der Empfänger Ihre Nachricht lesen kann. Eine nützliche Funktion für das Versenden kritischer Benachrichtigungen.
Zu den Vorteilen der Blockchain gehört auch die enge Integration mit den Kryptowährungen Ethereum, Dogecoin, Lisk, Dash, Bitcoin (dieser ist noch in Arbeit) und die Möglichkeit, Tokens in Chats zu versenden. Wir haben sogar einen eingebauten Krypto-Wechselkursdienst entwickelt.
Und nun – wie funktioniert das alles.
Eine Nachricht ist eine Transaktion.
Alle sind es gewohnt, dass Transaktionen auf der Blockchain Token (Münzen) von einem Nutzer zum anderen übertragen. Wie bei Bitcoin. Wir haben jedoch einen speziellen Transaktionstyp für die Übertragung von Nachrichten geschaffen.
Um eine Nachricht im Messenger auf der Blockchain zu versenden, müssen einige Schritte durchlaufen werden:
- Den Text der Nachricht verschlüsseln.
- Verschlüsselten Text in die Transaktion einfügen
- Transaktion signieren
- Transaktion an einen beliebigen Knoten im Netzwerk senden
- Das verteilte System der Knoten bestimmt die "Echtheit" der Nachricht
- Wenn alles in Ordnung ist – wird die Transaktion mit der Nachricht in den nächsten Block eingefügt
- Der Empfänger extrahiert die Transaktion mit der Nachricht und entschlüsselt sie
Die Schritte 1–3 und 7 werden lokal auf dem Client ausgeführt, während 5–6 auf den Knoten des Netzwerks durchgeführt werden.
Verschlüsselung der Nachricht
Die Nachricht wird mit dem privaten Schlüssel des Absenders und dem öffentlichen Schlüssel des Empfängers verschlüsselt. Den öffentlichen Schlüssel nehmen wir aus dem Netzwerk, dafür muss jedoch das Konto des Empfängers initialisiert sein, d.h. mindestens eine Transaktion haben. Es kann eine REST-Anfrage verwendet werden GET /api/accounts/getPublicKey?address={ADAMANT address}, und beim Laden von Chats sind die öffentlichen Schlüssel der Gesprächspartner bereits verfügbar.

Der Messenger verschlüsselt die Nachrichten mit dem Algorithmus curve25519xsalsa20poly1305 (). Da das Konto Ed25519-Schlüssel enthält, müssen die Schlüssel zur Bildung des Box vorher in Curve25519 Diffie-Hellman umgewandelt werden.
Hier ist ein Beispiel 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)
}
}Bildung der Transaktion mit Nachricht
Die Transaktion hat die folgende allgemeine Struktur:
{
"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": {}
} Für die Nachrichtentransaktion ist der wichtigste Wert asset — darin muss die Nachricht im Objekt platziert werden chat mit der Struktur:
message— das verschlüsselte Nachricht speichernown_message— noncetype— Nachrichtentyp
Nachrichten werden ebenfalls in Typen unterteilt. Im Grunde gibt der Parameter type an, wie man es verstehen soll message. Man kann einfach Text senden, oder ein Objekt mit interessanten Inhalten – beispielsweise macht der Messenger in Chats Kryptowährungsüberweisungen auf diese Weise.
Letztendlich bilden wir die Transaktion:
{
"transaction": {
"type": 8,
"amount": 0,
"senderId": "U12499126640447739963",
"senderPublicKey": "e9cafb1e7b403c4cf247c94f73ee4cada367fcc130cb3888219a0ba0633230b6",
"asset": {
"chat": {
"message": "cb682accceef92d7cddaaddb787d1184ab5428",
"own_message": "e7d8f90ddf7d70efe359c3e4ecfb5ed3802297b248eacbd6",
"type": 1
}
},
"recipientId": "U15677078342684640219",
"timestamp": 63228087,
"signature": "hier wird die Unterschrift sein"
}
}Signatur der Transaktion
Um sicherzustellen, dass alle die Authentizität des Absenders und des Empfängers, den Zeitpunkt des Versands und den Inhalt der Nachricht überprüfen können, wird die Transaktion signiert. Die digitale Signatur ermöglicht es, die Authentizität der Transaktion anhand des öffentlichen Schlüssels zu überprüfen - der private Schlüssel ist dafür nicht erforderlich.
Die Signatur selbst wird jedoch mit dem privaten Schlüssel erstellt:

Aus dem Schema ist zu erkennen, dass die Transaktion zunächst mit SHA-256 gehasht und dann signiert wird. und wir erhalten die Signatur signature, und die Transaktions-ID ist Teil des SHA-256-Hashes.
Beispielimplementierung:
1 - Erstellen eines Datenblocks, einschließlich der Nachricht
/**
* 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 - Berechnung des SHA-256 des Datenblocks
/**
* 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 - Signieren der Transaktion
adamant.transactionSign = function (trs, keypair) {
var hash = this.getHash(trs)
return this.sign(hash, keypair).toString('hex')
}
/**
* Erstellt eine Signatur basierend auf einem Hash und einem Schlüsselpaar.
* @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'))
}Übermittlung der Transaktion mit Nachricht an einen Knoten im Netzwerk
Da das Netzwerk dezentralisiert ist, kann jeder Knoten mit einer offenen API verwendet werden. Wir senden eine POST-Anfrage an den Endpunkt api/transactions:
curl 'api/transactions' -X POST
-d 'TX_DATA'Als Antwort erhalten wir die ID der Transaktion vom Typ
{
"success": true,
"nodeTimestamp": 63228852,
"transactionId": "6146865104403680934"
}Überprüfung der Authentizität der Transaktion
Ein verteiltes System von Knoten, das auf Konsens basiert, bestimmt die „Authentizität“ der Transaktion-Nachricht. Von wem und an wen, wann, ob die Nachricht durch eine andere ersetzt wurde, und ob die angegebene Zeit für den Versand korrekt ist. Das ist ein sehr wichtiger Vorteil der Blockchain - es gibt keine zentrale Struktur, die für die Überprüfungen verantwortlich ist, und die Reihenfolge der Nachrichten sowie deren Inhalt können nicht gefälscht werden.
Zuerst überprüft ein Knoten die Authentizität und sendet sie dann an andere - wenn die Mehrheit sagt, dass alles in Ordnung ist, wird die Transaktion in den nächsten Block der Kette aufgenommen - das ist der Konsens.

Ein Teil des Knotencodes, der für die Überprüfungen verantwortlich ist, kann auf GitHub angesehen werden - und . Aha, der Knoten läuft auf Node.js.
Einschluss der Transaktion mit Nachricht in einen Block
Wenn der Konsens erreicht ist, wird die Transaktion mit unserer Nachricht zusammen mit anderen authentischen Transaktionen in den nächsten Block aufgenommen.
Die Blöcke haben eine strenge Reihenfolge, und jeder nachfolgende Block wird basierend auf den Hashes der vorherigen Blöcke erstellt.

Die Essenz ist, dass unsere Nachricht ebenfalls in diese Sequenz eingefügt wird und nicht „umgestellt“ werden kann. Wenn mehrere Nachrichten in einen Block eingehen, wird ihre Reihenfolge nach timestamp Nachrichten.
Nachrichten lesen
Die Messaging-App extrahiert Transaktionen aus der Blockchain, die an den Empfänger gesendet werden. Zu diesem Zweck haben wir einen Endpunkt geschaffen api/chatrooms.
Alle Transaktionen sind für jeden verfügbar – man kann verschlüsselte Nachrichten erhalten. Entschlüsseln kann sie jedoch nur der Empfänger mit seinem privaten Schlüssel und dem öffentlichen Schlüssel des Absenders:
**
* 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) : ''
}Und was noch?
Da Nachrichten auf diese Weise etwa 5 Sekunden benötigt werden – das ist die Zeit, in der ein neuer Block im Netzwerk erstellt wird – haben wir eine Socket-Verbindung zwischen Client-Knoten und Knoten-Knoten entwickelt. Wenn ein Knoten eine neue Transaktion erhält, prüft er deren Gültigkeit und leitet sie an andere Knoten weiter. Die Transaktion ist für Messengern auch bevor der Konsens erreicht wird und sie in den Block aufgenommen wird zugänglich. So werden wir Nachrichten sofort liefern, genau wie herkömmliche Messenger.
Um unser Adressbuch zu speichern, haben wir KVS – Key-Value Storage – entwickelt, das ist ein weiterer Transaktionstyp, bei dem asset nicht der NaCl-box, sondern . So speichert der Messenger auch andere Daten.
Der Dateitransfer und die Gruppen-Chats erfordern noch viel Arbeit. Natürlich könnte man es im Schnellschuss-Format „anschließen“, aber wir möchten das gleiche Maß an Privatsphäre wahren.
Ja, es gibt noch viel zu tun – idealerweise предполагает echte Privatsphäre, dass die Benutzer sich nicht mit öffentlichen Knoten im Netzwerk verbinden, sondern ihre eigenen betreiben. Wie viele Prozent der Benutzer denken Sie, tun das? Richtig, 0. Teilweise konnten wir dieses Problem mit der Tor-Version des Messengers lösen.
Wir haben bewiesen, dass ein Messenger auf der Blockchain existieren kann. Zuvor gab es nur einen Versuch im Jahr 2012 – , der aufgrund der langen Lieferzeiten von Nachrichten, der CPU-Belastung und des Fehlens mobiler Apps gescheitert ist.
Der Skeptizismus hängt damit zusammen, dass Messenger auf der Blockchain ihrer Zeit weit voraus sind – die Menschen sind nicht bereit, die Verantwortung für ihr Konto selbst zu übernehmen, der Besitz persönlicher Informationen ist zurzeit nicht im Trend, und die Technologien ermöglichen keine hohen Geschwindigkeiten auf der Blockchain. In Zukunft werden technologisch fortschrittlichere Alternativen zu unserem Projekt erscheinen. Sie werden sehen.
Quelle: habr.com
