All'inizio del 2017 abbiamo iniziato a sviluppare un messenger basato su blockchain [il nome e il link sono nel profilo] discutendo dei vantaggi rispetto ai tradizionali messenger P2P.
Sono passati 2.5 anni e siamo riusciti a confermare il nostro concetto: ora sono disponibili applicazioni del messenger per iOS, Web PWA, Windows, GNU/Linux, Mac OS e Android.
Oggi spiegheremo come funziona il messenger sulla blockchain e come le applicazioni client possono interagire con la sua API.

Volevamo che la blockchain risolvesse i problemi di sicurezza e privacy dei tradizionali messenger P2P:
- Un clic per creare un'account — niente telefoni o email, senza accesso ai rubinetti e alla geolocalizzazione.
- I conversatori non stabiliscono mai connessioni dirette, tutta la comunicazione avviene attraverso un sistema distribuito di nodi. Gli indirizzi IP degli utenti non sono accessibili l'uno all'altro.
- Tutti i messaggi sono crittografati End-to-End curve25519xsalsa20poly1305. Sembrerebbe che non sorprenda nessuno, ma il nostro codice sorgente è aperto.
- Attacchi MITM sono esclusi — ogni messaggio è una transazione e viene firmato da Ed25519 EdDSA.
- Il messaggio entra nel suo blocco. La sequenza e
timestampi blocchi non possono essere modificati, quindi nemmeno l'ordine dei messaggi. - La frase "Non l'ho detto" non funziona con i messaggi nella blockchain.
- Non esiste una struttura centrale che verifica la "veridicità" del messaggio. Questo è compito di un sistema distribuito di nodi basato sul consenso, di proprietà degli utenti.
- L'impossibilità di censura: non è possibile bloccare gli account e rimuovere i messaggi.
- Blockchain 2FA: un'alternativa all'infernale 2FA via SMS,
- La possibilità di accedere ai propri dialoghi da qualsiasi dispositivo in qualsiasi momento significa non doverli conservare localmente.
- Conferma di consegna dei messaggi. Non sul dispositivo utente, ma nella rete. Fondamentalmente, è una conferma che il destinatario può leggere il tuo messaggio. Questa è una funzione utile per l'invio di notifiche critiche.
Tra i vantaggi della blockchain ci sono anche una stretta integrazione con criptovalute come Ethereum, Dogecoin, Lisk, Dash, Bitcoin (quest'ultimo è in fase di sviluppo) e la possibilità di inviare token nelle chat. Abbiamo persino realizzato un exchange di criptovalute integrato.
E adesso — come funziona tutto ciò.
Un messaggio è una transazione.
È ormai risaputo che le transazioni nella blockchain trasferiscono token (monete) da un utente all'altro, come nel caso del Bitcoin. Noi abbiamo creato un tipo speciale di transazione per la trasmissione di messaggi.
Per inviare un messaggio nel messenger basato su blockchain, è necessario seguire alcuni passaggi:
- Cifrare il testo del messaggio
- Inserire il testo cifrato nella transazione
- Firmare la transazione
- Inviare la transazione a un nodo della rete
- Il sistema distribuito di nodi determina la “veridicità” del messaggio
- Se tutto è a posto, la transazione con il messaggio viene inclusa nel blocco successivo
- Il destinatario estrae la transazione con il messaggio e la decifra
I passaggi 1–3 e 7 vengono eseguiti localmente sul client, mentre i passaggi 5–6 avvengono sui nodi della rete.
Cifratura del messaggio
Il messaggio viene cifrato con la chiave privata del mittente e la chiave pubblica del destinatario. La chiave pubblica la prenderemo dalla rete, ma per questo l'account del destinatario deve essere stato inizializzato, ossia deve avere almeno una transazione. Si può utilizzare una richiesta REST GET /api/accounts/getPublicKey?address={ADAMANT address}, e durante il caricamento delle chat le chiavi pubbliche dei contatti saranno già disponibili.

Il messenger cripta i messaggi utilizzando l'algoritmo curve25519xsalsa20poly1305 (). Poiché l'account contiene chiavi Ed25519, è necessario convertire le chiavi in Curve25519 Diffie-Hellman per formare il box.
Ecco un esempio 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)
}
}Creazione di una transazione con messaggio
La transazione ha la seguente struttura generale:
{
"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": {}
} Per una transazione-messaggio, il valore più importante è asset — in esso deve essere inserito il messaggio nell'oggetto chat con la struttura:
message— salviamo il messaggio crittografatoown_message— noncetype— tipo di messaggio
I messaggi sono anche suddivisi in tipi. In sostanza, il parametro type indica come interpretare message. È possibile inviare semplicemente del testo, oppure un oggetto con interessanti contenuti all'interno — ad esempio, così il messenger esegue trasferimenti di criptovalute nelle chat.
Alla fine, formiamo la transazione:
{
"transaction": {
"type": 8,
"amount": 0,
"senderId": "U12499126640447739963",
"senderPublicKey": "e9cafb1e7b403c4cf247c94f73ee4cada367fcc130cb3888219a0ba0633230b6",
"asset": {
"chat": {
"message": "cb682accceef92d7cddaaddb787d1184ab5428",
"own_message": "e7d8f90ddf7d70efe359c3e4ecfb5ed3802297b248eacbd6",
"type": 1
}
},
"recipientId": "U15677078342684640219",
"timestamp": 63228087,
"signature": "qui ci sarà la firma"
}
}Firma della transazione
Per garantire l'affidabilità del mittente e del destinatario, il momento dell'invio e il contenuto del messaggio, la transazione viene firmata. La firma digitale consente di verificare l'autenticità della transazione tramite la chiave pubblica — non è necessaria la chiave privata.
Ecco come viene eseguita la firma utilizzando la chiave privata:

Dallo schema si vede che prima hashiamo la transazione con SHA-256, e poi la firmiamo e otteniamo la firma signature, e l'identificatore della transazione è parte dell'hash SHA-256.
Esempio di implementazione:
1 — Formiamo il blocco di dati, incluso il messaggio
/**
* 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 — Calcoliamo lo SHA-256 dal blocco di dati
/**
* 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 — Firmiamo la transazione
adamant.transactionSign = function (trs, keypair) {
var hash = this.getHash(trs)
return this.sign(hash, keypair).toString('hex')
}
/**
* Crea una firma basata su un hash e una coppia di chiavi.
* @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'))
}Invio di una transazione con un messaggio a un nodo della rete
Poiché la rete è decentralizzata, qualsiasi nodo con API aperta va bene. Fare una richiesta POST all'endpoint api/transactions:
curl 'api/transactions' -X POST
-d 'TX_DATA'In risposta riceveremo l'ID della transazione di tipo
{
"success": true,
"nodeTimestamp": 63228852,
"transactionId": "6146865104403680934"
}Verifica dell'autenticità della transazione
Un sistema distribuito di nodi basato sul consenso determina l'"autenticità" della transazione-messaggio. Da chi e a chi, quando, non è stato sostituito il messaggio con un altro, e se è corretto l'orario di invio. Questo è un grande vantaggio della blockchain: non esiste una struttura centrale responsabile delle verifiche, e la sequenza dei messaggi e il loro contenuto non possono essere falsificati.
Inizialmente l'autenticità viene verificata da un nodo, poi distribuita ad altri: se la maggioranza dice che va tutto bene, la transazione sarà inclusa nel blocco successivo della catena — questo è il consenso.

Puoi vedere la parte del codice del nodo responsabile dei controlli su GitHub — e . Sì, il nodo funziona su Node.js.
Attiviamo la transazione con il messaggio nel blocco
Se il consenso viene raggiunto, la transazione con il nostro messaggio verrà inclusa nel prossimo blocco insieme ad altre transazioni verificate.
I blocchi hanno una rigorosa sequenza e ogni blocco successivo viene creato sulla base degli hash dei blocchi precedenti.

Il punto è che il nostro messaggio è anche incluso in questa sequenza e non può essere “riordinato”. Se più messaggi entrano in un blocco, il loro ordine sarà definito da timestamp messaggi.
Lettura dei messaggi
L'app di messaggistica estrae le transazioni dalla blockchain che sono state inviate al destinatario. A tal fine, abbiamo creato un endpoint api/chatrooms.
Tutte le transazioni sono disponibili per chiunque — è possibile ottenere messaggi crittografati. Tuttavia, solo il destinatario potrà decrittarli con la propria chiave privata e la chiave pubblica del mittente:
**
* 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) : ''
}E cos'altro?
Poiché i messaggi vengono consegnati in circa 5 secondi — il tempo di apparizione di un nuovo blocco di rete — abbiamo ideato una connessione socket client-nodo e nodo-nodo. Quando un nodo riceve una nuova transazione, verifica la sua validità e la trasmette ad altri nodi. La transazione è disponibile ai client-messenger anche prima che si raggiunga il consenso e venga inclusa nel blocco. Così, consegneremo i messaggi in modo istantaneo, come i consueti messaggeri.
Per memorizzare la rubrica, abbiamo creato KVS — Key-Value Storage — un altro tipo di transazione in cui asset non viene crittografato il NaCl-box, ma . In questo modo, il messaggero memorizza anche altri dati.
Il trasferimento di file/immagini e le chat di gruppo richiedono ancora molto lavoro. Certo, in un formato approssimativo si può 'implementare' rapidamente, ma vogliamo mantenere lo stesso livello di privacy.
Sì, c'è ancora molto da fare — idealmente, una vera privacy implica che gli utenti non si connettano a nodi pubblici della rete, ma creino i propri. Cosa ne pensate, quanti percento degli utenti fa così? Giusto, 0. Parzialmente, abbiamo risolto questo problema con la versione Tor del messaggero.
Abbiamo dimostrato che un messenger basato su blockchain può esistere. In precedenza, c'era stata solo un'unica tentativo nel 2012 — , fallito a causa dell'alto tempo di consegna dei messaggi, del carico sulla CPU e dell'assenza di applicazioni mobile.
E lo scetticismo è dovuto al fatto che i messengers basati su blockchain sono in anticipo rispetto ai tempi — le persone non sono pronte a prendersi la responsabilità del proprio account, il possesso delle informazioni personali non è ancora una tendenza, e le tecnologie non consentono di garantire elevate velocità sulla blockchain. In seguito, appariranno analoghi più tecnologici del nostro progetto. Vedrete.
Fonte: habr.com
