All'inizio del 2017 abbiamo iniziato a creare un messenger basato su blockchain [il nome e il link sono nel profilo] discutendo i vantaggi rispetto ai tradizionali messenger P2P.
È passato 2.5 anno 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 parleremo di come funziona il messenger sulla blockchain e di come le applicazioni client interagiscono 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 e-mail, nessun accesso a rubrica e 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 criptati End-to-End curve25519xsalsa20poly1305. Sembra che questo non sorprenda nessuno, ma noi abbiamo il codice sorgente aperto.
- Un attacco MITM è escluso: ogni messaggio è una transazione e firmato con Ed25519 EdDSA.
- Il messaggio entra nel proprio blocco. La sequenza e
timestampi blocchi non possono essere modificati, quindi nemmeno l'ordine dei messaggi. - «Non l'ho detto» non funzionerà con i messaggi sulla blockchain.
- Non c'è una struttura centrale che verifica l'«autenticità» del messaggio. Questo è fatto da un sistema distribuito di nodi basato sul consenso, che appartiene agli utenti.
- L'impossibilità di censura — gli account non possono essere bloccati e i messaggi non possono essere eliminati.
- La blockchain 2FA è un'alternativa all'infernale 2FA via SMS,
- La possibilità di ricevere tutti i propri dialoghi da qualsiasi dispositivo in qualsiasi momento — è la possibilità di non memorizzare dialoghi localmente.
- Conferma della consegna dei messaggi. Non sul dispositivo dell'utente, ma nella rete. In sostanza, è una conferma della possibilità per il destinatario di leggere il tuo messaggio. È una funzionalità utile per inviare notifiche critiche.
Tra i vantaggi della blockchain c'è anche una stretta integrazione con criptovalute come Ethereum, Dogecoin, Lisk, Dash, Bitcoin (quest'ultimo è ancora in fase di sviluppo) e la possibilità di inviare token nelle chat. Abbiamo persino creato un exchange di criptovalute integrato.
E ora — come funziona tutto questo.
Un messaggio è una transazione.
Tutti si sono ormai abituati al fatto che le transazioni sulla blockchain trasferiscono token (monete) da un utente all'altro. Come nel caso di Bitcoin. Abbiamo creato un tipo speciale di transazioni per la trasmissione di messaggi.
Per inviare un messaggio in un messaggero sulla blockchain, è necessario seguire diversi passaggi:
- Crittografare il testo del messaggio
- Inserire il testo crittografato 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 va bene, la transazione con il messaggio viene inclusa nel blocco successivo
- Il destinatario estrae la transazione con il messaggio e la decrittografa
I passaggi 1–3 e 7 vengono eseguiti localmente sul client, mentre 5–6 sui nodi della rete.
Crittografia del messaggio
Il messaggio è crittografato 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, cioè deve avere almeno una transazione. Può essere utilizzata una richiesta REST GET /api/accounts/getPublicKey?address={ADAMANT address}, e durante il caricamento delle chat le chiavi pubbliche dei messaggeri saranno già disponibili.

Il messaggero crittografa i messaggi utilizzando l'algoritmo curve25519xsalsa20poly1305 (). Poiché l'account contiene le chiavi Ed25519, per formare il box è necessario prima convertire le chiavi in Curve25519 Diffie-Hellman.
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)
}
}Formazione della transazione con il 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 la transazione-messaggio, l'elemento più importante è asset — in esso deve essere inserito il messaggio in un oggetto chat con la seguente struttura:
message— conserviamo il messaggio crittografatoown_message— noncetype— tipo di messaggio
I messaggi sono anche suddivisi in tipi. Fondamentalmente, il parametro type indica come interpretare message. Puoi inviare semplicemente testo, oppure un oggetto con contenuti interessanti — ad esempio, in questo modo il messaggero 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
Affinché tutti siano certi dell'autenticità del mittente e del destinatario, del momento di invio e del contenuto del messaggio, la transazione viene firmata. La firma digitale consente di verificare l'autenticità della transazione tramite la chiave pubblica: per questo non è necessaria la chiave privata.
Ecco come viene eseguita la firma con la chiave privata:

Dallo schema, è evidente che prima hashiamo la transazione con SHA-256, e poi firmiamo e otteniamo la firma signature, e l'identificatore della transazione è una parte dell'hash SHA-256.
Esempio di implementazione:
1 — Formiamo un blocco di dati, inclusi 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 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 un keypair.
* @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 della transazione con messaggio a un nodo della rete
Poiché la rete è decentralizzata, è possibile utilizzare uno qualsiasi dei nodi con API aperta. Effettuiamo una richiesta POST all'endpoint api/transactions:
curl 'api/transactions' -X POST
-d 'TX_DATA'In risposta otterremo 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. Chi, a chi, quando, se il messaggio è stato sostituito da un altro e se l'orario di invio è corretto. Questo è un grande vantaggio della blockchain: non c'è una struttura centrale responsabile dei controlli, e la sequenza dei messaggi e il loro contenuto non possono essere falsificati.
All'inizio, un nodo verifica l'autenticità, e poi la comunica agli altri: se la maggioranza dice che va tutto bene, la transazione sarà inclusa nel prossimo blocco della catena — questo è il consenso.

Una parte del codice del nodo responsabile dei controlli può essere vista su GitHub — e . Sì, il nodo funziona su Node.js.
Includiamo la transazione con messaggio in un blocco
Se il consenso è raggiunto, la transazione con il nostro messaggio sarà inclusa nel blocco successivo insieme ad altre transazioni verificate.
I blocchi seguono una rigorosa sequenza e ogni blocco successivo è formato sulla base degli hash dei blocchi precedenti.

La sostanza è che il nostro messaggio è anche incluso in questa sequenza e non può essere “riordinato”. Se più messaggi sono inclusi in un blocco, il loro ordine sarà determinato da timestamp messaggi.
Lettura dei messaggi
L'applicazione di messaggistica estrae le transazioni dalla blockchain inviate al destinatario. Per questo abbiamo creato l'endpoint api/chatrooms.
Tutte le transazioni sono disponibili per tutti: è possibile ottenere messaggi crittografati. Tuttavia, solo il destinatario può decrittografarli 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 necessario per la generazione di un nuovo blocco nella rete — abbiamo creato una connessione socket cliente-nodo e nodo-nodo. Quando un nodo riceve una nuova transazione, verifica la sua validità e la trasmette ad altri nodi. La transazione è disponibile per i client della messaggistica prima ancora che si raggiunga il consenso e si includa nel blocco. In questo modo possiamo consegnare i messaggi istantaneamente, come i normali servizi di messaggistica.
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.
La trasmissione di file/immagini e le chat di gruppo richiedono ancora molto lavoro. Certo, in un formato veloce è possibile “implementarlo” rapidamente, ma vogliamo mantenere lo stesso livello di privacy.
Sì, c'è ancora molto da lavorare: in un'ideale privacy reale, gli utenti non dovrebbero connettersi a nodi pubblici della rete, ma avrebbero dovuto avviare i propri. Quanti percentuali di utenti credi lo facciano? Esatto, 0. Parzialmente abbiamo risolto questo problema con la versione Tor del messaggero.
Abbiamo dimostrato che un messaggero basato su blockchain può esistere. In precedenza c'è stata solo un tentativo nel 2012 — , fallito a causa dell'elevato tempo di consegna dei messaggi, del carico sul processore e della mancanza di applicazioni mobili.
Lo scetticismo è legato al fatto che i messenger basati su blockchain sono in anticipo sui tempi: le persone non sono pronte a prendere responsabilità per il proprio account, il possesso delle informazioni personali non è ancora una tendenza e la tecnologia non consente di garantire elevate velocità sulla blockchain. Seguiranno analoghi più tecnologici del nostro progetto. Vedrete.
Fonte: habr.com
