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.

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
timestampbloků 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,
- 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:
- Zašifrovat text zprávy
- Umístit zašifrovaný text do transakce
- Podepsat transakci
- Odeslat transakci na libovolný uzel sítě
- Distribuovaný systém uzlů určuje „důvěryhodnost“ zprávy
- Pokud je vše v pořádku — transakce se zprávou je zahrnuta do dalšího bloku
- 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.

Messenger šifruje zprávy algoritmem curve25519xsalsa20poly1305 (). 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ávuown_message— noncetype— 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:

Z diagramu je vidět, že transakci nejprve hashujeme pomocí SHA-256 a poté podepisujeme. 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.

Část kódu uzlu, která odpovídá za ověřování, je k dispozici na GitHubu - a . 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ů.

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 . 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 — , 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
