localethereum簡(jiǎn)介

localethereum的官網(wǎng)上是這樣介紹自己的:點(diǎn)對(duì)點(diǎn)的以太門戶,即使用以太坊智能合約和點(diǎn)對(duì)點(diǎn)的加密技術(shù)促成ether和法幣在人與人之間的直接兌換,不需要中間人擔(dān)保。這和0x和kyber等直接促成虛擬幣兌換的去中心化交易所不一樣,它主要解決的是去中心的法幣與虛擬幣兌換這個(gè)核心問(wèn)題,并確保這個(gè)過(guò)程是保密和安全的。為此它做了很多努力,包括:
- 點(diǎn)對(duì)點(diǎn)的消息加密
- 消息前向保密
- 財(cái)務(wù)前向保密
- 簽名系統(tǒng)
自去年9月上線以來(lái),運(yùn)營(yíng)良好,沒(méi)有出現(xiàn)黑客事件,且體驗(yàn)還可以,流程不算復(fù)雜。首先注冊(cè)一個(gè)賬號(hào),和普通網(wǎng)站一樣,需要用戶名和密碼以及開(kāi)啟兩步驗(yàn)證;掛單者(Maker)掛一個(gè)訂單,這個(gè)單會(huì)出現(xiàn)在網(wǎng)站上;其他受價(jià)者(Taker)可以在網(wǎng)站上接單并發(fā)起交易,localethereum為這筆交易生成一份以太坊智能合約;賣方將ether轉(zhuǎn)到localethereum錢包,然后再?gòu)膌ocalethereum錢包把想要交易的ether轉(zhuǎn)到智能合約,至此,交易的鏈上部分完成,接下來(lái)等待買家鏈下法幣支付;在這個(gè)過(guò)程中雙方可以通過(guò)localethereum的點(diǎn)對(duì)點(diǎn)的加密消息進(jìn)行溝通,比如把銀行賬號(hào)告知對(duì)方以便法幣轉(zhuǎn)賬等等。
總之整個(gè)流程下來(lái),交互習(xí)慣就跟其他中心化交易所一樣,但它卻是去中心化的,點(diǎn)對(duì)點(diǎn)的。你不必?fù)?dān)心黑客侵入盜幣或交易所破產(chǎn),這絕對(duì)是一種技術(shù)進(jìn)步,接下來(lái)我會(huì)為大家一一剖析它的安全機(jī)制。因?yàn)樗皇情_(kāi)源的,所以只能結(jié)合一些開(kāi)放的說(shuō)明資料以及頁(yè)面行為來(lái)加以分析。
賬號(hào)密碼
localethereum用戶需要賬號(hào)和密碼,但是它的密碼和我們通常認(rèn)知的密碼不一樣,它的密碼純粹是本地的。也就是說(shuō)服務(wù)器端不存儲(chǔ)密碼的相關(guān)信息。在我們創(chuàng)建賬號(hào)的時(shí)候,就有一段提示:
Your web browser is going to generate a private key offline, and then encrypt it using AES256-CBC to a PBKDF2-stretched version of your password. 你的瀏覽器將會(huì)離線生成一個(gè)私鑰,這個(gè)私鑰使用AES256-CBC加密,加密秘鑰用你的密碼經(jīng)PBKDF2運(yùn)算后得到。
PBKDF2的作用是“延展”人類的密碼,使之更長(zhǎng),更隨機(jī),不可逆,不容易被猜測(cè)。即使像通常的網(wǎng)站也不直接存儲(chǔ)人類的明文密碼,否則萬(wàn)一數(shù)據(jù)庫(kù)被盜,或者網(wǎng)站監(jiān)守自盜,那么用戶的損失將是巨大的,因?yàn)橛脩裟苡浀拿艽a是有限的,他在很多網(wǎng)站使用的可能是同一個(gè)密碼,一旦一個(gè)地方發(fā)生泄露,所有的這些信息將面臨極大的暴露風(fēng)險(xiǎn)。Like this, no fuck to say!

AES256-CBC是對(duì)稱加密算法,即它可以做加密解密,是一種安全的可逆運(yùn)算。
創(chuàng)建賬號(hào)的時(shí)候,在Chrome里面也可以看到如下的服務(wù)端請(qǐng)求:
POST https://api.localethereum.com/v1/accounts/new
{
"username": "jon",
"email": "jon@f...",
"account_key_identity_public": "01e355ef0f6b45e7ff86...",
"account_key_encrypted_root": {
"ciphertext": "47f57c64067abaa...",
"iv": "cb331d0e0...",
"passphrase_salt": "e88be51f58...",
"pbkdf2_iterations": 50906
}
}
確實(shí)沒(méi)有上傳密碼,但是account_key_identity_public和account_key_encrypted_root是干么用的,光看字面也看不明白。為了更好理解先來(lái)看看localethereum本地的一段js代碼:

這下大致清楚了,反推一下:
var password = "abc"; //明文密碼,記在人腦
var passphrase_salt = "e88be51f58..."; //鹽,創(chuàng)建賬號(hào)的時(shí)候隨機(jī)生成
var pbkdf2_iterations = "50906"; //加鹽迭代次數(shù),創(chuàng)建賬號(hào)的時(shí)候生成
//對(duì)明文密碼進(jìn)行延展
var key = PBKDF2(password, passphrase_salt, {
keySize: 256 / 32,
iterations: pbkdf2_iterations
})
var accountKeyRoot = "0f45e6dae7cc40c..." //秘鑰根,創(chuàng)建賬號(hào)的時(shí)候隨機(jī)生成
var iv = "cb331d0e0..."; //初始向量,創(chuàng)建賬號(hào)的時(shí)候隨機(jī)生成
//加密的秘鑰根
var ciphertext = AES.encrypt(accountKeyRoot, key, {
iv: iv,
mode: CBC,
padding: NoPadding
})
var n = ecsign(sha3(accountKeyRoot, 256), "enc");
//用于對(duì)其他私密信息進(jìn)行加密用的秘鑰
var accountKeyEnc = toRpcSig(n.v, n.r, n.s);
//秘鑰對(duì)
var accountKeyIdentityPair = E(accountKeyRoot);
//公鑰
var account_key_identity_public = accountKeyIdentityPair.publicKey;
所以服務(wù)端實(shí)際上存儲(chǔ)的是用戶秘鑰的根(有時(shí)候也稱之為種子),不過(guò),這個(gè)秘鑰的根是經(jīng)過(guò)用戶本地密碼加密保護(hù)過(guò)再給服務(wù)端的。用戶登錄的時(shí)候,雖然是先輸入賬號(hào)和密碼,再輸入二次驗(yàn)證碼,但實(shí)際上,服務(wù)端是先校驗(yàn)二次驗(yàn)證碼,成功之后再?gòu)姆?wù)端獲取用戶的account_key_identity_public和account_key_encrypted_root到本地,在本地用密碼解開(kāi)account_key_encrypted_root獲得秘鑰的根accountKeyRoot,存儲(chǔ)到Local Storage:
{
"username": "jon",
"accountKeyRoot": "e1dde6d4f...",
"sessionToken": "MTUwNTQzMn0.kwHcAkDjelD..."
}
accountKeyRoot就是你的數(shù)字身份的DNA,自創(chuàng)建開(kāi)始就不可變更,你可以修改你的密碼,但這僅僅只是改變了存儲(chǔ)在服務(wù)器端的密文,用新密碼解密后還是原來(lái)的內(nèi)容。如果有人直接盜走了accountKeyRoot,他就可以在數(shù)字世界代表你。
掛單者秘鑰(Maker keys)
每個(gè)localethereum用戶都會(huì)先行創(chuàng)建很多簽名的秘鑰對(duì),存在服務(wù)端,結(jié)構(gòu)如:
{
"public_key": "371854cb5efc...",
"signature": "0x7ae22085f5047e0c3d5...",
"encrypted_private": {
"ciphertext": "54b8c3c2c168778...",
"iv": "417a9c6e..."
}
}
這些秘鑰允許人們創(chuàng)建交易、發(fā)送消息、用離線的錢包轉(zhuǎn)賬和部署智能合約,而且能夠保持前向保密(當(dāng)前秘鑰泄露不影響到以前的數(shù)據(jù))。

翻譯過(guò)來(lái)就是:
- 生成隨機(jī)的256位secp256k1秘鑰對(duì)(
MakerKey-private和MakerKey-public) - 用賬號(hào)的私鑰
accountKeyIdentityPrivate對(duì)SHA3(MakerKey-public)的結(jié)果進(jìn)行ECDSA簽名(MakerKey-signature) -
MakerKey-private用一個(gè)隨機(jī)的IV和accountKeyEnc進(jìn)行AES-256加密之后,連同MakerKey-public和MakerKey-signature發(fā)到服務(wù)端保存。
錢包地址
和Maker keys一樣,localethereum也會(huì)預(yù)先生成很多的以太坊地址:
{
"n": 1,
"address": "0xfe742544...",
"signature": "0xe03a28b6ec8a1...",
"encrypted_private": {
"ciphertext": "77d8cc2e176c...",
"iv": "df4193c2..."
}
}

不一樣的地方在于每個(gè)地址有一個(gè)鏈私鑰,它由前一個(gè)鏈私鑰經(jīng)過(guò)以下算法得來(lái):
var chainPrivateKey=HmacSHA256(previousChainPrivateKey, [2]);
var privateKey=HmacSHA256(chainPrivateKey, [0,1]);
var address = publicToAddress(privateToPublic(privateKey));
最后將chainPrivateKey加密連同其他相信息存到服務(wù)器端。
交易
假設(shè)Bob在網(wǎng)站上看到Alice掛的單,想要受價(jià)交易,他就向localethereum請(qǐng)求獲取Alice的Maker key和簽名的錢包地址,localethereum會(huì)給他一個(gè)Alice全新未使用的MakerKey-public和MakerAddress-address。
當(dāng)Bob用Alice的公鑰驗(yàn)證這兩個(gè)簽名之后,確定key和錢包地址確實(shí)是屬于Alice的,他就可以向她轉(zhuǎn)賬和發(fā)送消息。但事情并非就此簡(jiǎn)單。Bob還需要做三件事:
- 他也要生成自己的全新的一次性secp256k1秘鑰對(duì)(
TakerKey-private和TakerKey-public) - 他從服務(wù)端拿一個(gè)自己未使用的錢包地址(
TakerAddress) - Bob將這些信息捆綁在一起,并用他自己的私鑰
accountKeyIdentityPrivate對(duì)SHA3(MakerKey-public+MakerAddress-address+TakerKey-public+TakerAddress)的結(jié)果簽名(TradeSignature)
他將TradeSignature, TakerKey-public和TakerAddress發(fā)給服務(wù)端,當(dāng)Alice上線后她就可以驗(yàn)證簽名。雙方開(kāi)始安全交易。
創(chuàng)建交易的時(shí)候可以看到Chrome發(fā)起的請(qǐng)求(貌似可以不需要MakerAddress-address):
POST https://api.localethereum.com/v1/trades/new
{
"offer_id": "d2d8bcc5-9b1...",
"signature": "0x69a1b74997...",
"maker_key_public_key": "28b75263cf15bb9d432...",
"taker_address": "0xfe725e38b24925a2a7895...",
"taker_key_public_key": "30e43861d4c616...",
"taker_key_encrypted_private": {
"iv": "52bc72e0d...",
"ciphertext": "48764d5..."
}
}
一個(gè)成功了的交易結(jié)構(gòu)是這樣的:
GET https://api.localethereum.com/v1/trades/ef3771d7-51c...
{
"id": "ef3771d7-51c...",
"state": "released",
"maker_account_id": "63abacee-0cc...",
"taker_account_id": "d9058c2a-23e...",
"maker_username": "Din",
"taker_username": "Vager",
"maker_fee": 0.25,
"taker_fee": 0.75,
"maker_account_key_identity_public": "f19bb6af3eed...",
"taker_account_key_identity_public": "bbac5c7e884eecae5dd...",
"offer_id": "0d52e362-887...",
"offer_headline": "",
"offer_terms_of_trade": "",
"created_at": "2017-12-14T06:06:57.067Z",
"wei_amount_for_buyer": "1000000000000000000",
"wei_amount_for_seller": "1010110000000000000",
"local_currency_amount": 8898.1,
"local_currency_code": "CNY",
"direction": "maker_seller_taker_buyer",
"maker_address": "0x0a2ffbabdc7f5f0...",
"maker_address_signature": "0x1d5dc75a401e1209f6fad...",
"taker_address": "0x3fa8a7bf52ec2a7eb09...",
"taker_signature": "0xbf90926a5681bc7508e6e6375f...",
"maker_key": {
"public_key": "57cf2817a7bcf0880aef18fd1fb...",
"encrypted_private": null
},
"taker_key": {
"signature": null,
"public_key": "6eb485558f07c8e0ac3d9934a06e..."
},
"reported_by_you": false,
"contract_escrow": {
"trade_hash": "fe6fc04f7b06932c4...",
"contract_address": "0x09678741bd...",
"seller_can_cancel_after": 0,
"total_gas_fees_spent_by_relayer": "0",
"currently_exists": false,
"events": [{
"observed_at": "2017-12-14T06:13:36.036Z",
"block_number": 4729641,
"transaction_hash": "0x0abdd751a...",
"event_name": "Created"
}, {
"observed_at": "2017-12-14T06:18:14.786Z",
"block_number": 4729661,
"transaction_hash": "0xd3a800117e83...",
"event_name": "Released"
}],
"relays": [{
"created_at": "2017-12-14T06:17:22.889Z",
"sent_at": "2017-12-14T06:17:39.103Z",
"confirmed_at": "2017-12-14T06:18:12.796Z",
"confirmed_at_block": 4729661,
"action_byte": "05",
"transaction_hash": "0xd3a800117..."
}]
},
"sent_transactions": [{
"trade_hash": "fe6fc04f7b06932...",
"name": "createEscrow",
"transaction_hash": "0x0abdd751...",
"sent_at": "2017-12-14T06:12:58.309Z"
}],
"maker_address_encrypted_private": {
"ciphertext": "db93a6462e0a...",
"iv": "0a52107d14afd..."
},
"marked_as_paid_at": null,
"marked_for_released_at": "2017-12-14T06:17:22.889Z",
"marked_as_cancelled_at": null,
"cancelled_at": null,
"cancelled_by_account_id": null,
"disputed_at": null,
"disputed_by_account_id": null,
"left_feedback": "good",
"payment_method": {
"id": 7,
"name": "Alipay",
"description_for_buyer": "You’ll send the seller money online via Alipay.",
"description_for_seller": "The buyer will send you money online via Alipay.",
"payment_window_in_minutes": 120
},
"delete_party_key_at": "2017-12-21T06:18:14.786Z",
"hash": 2554751258,
"last_message_id": "bd2c3e2a-bdc..."
}
消息
點(diǎn)對(duì)點(diǎn)加密消息用的是Elliptic curve Diffie–Hellman (ECDH)異步秘鑰交換協(xié)議,這個(gè)協(xié)議允許雙方用一方的私鑰和另一方的公鑰派生一個(gè)共享秘鑰。ECDH的美妙之處在于:
SharedSecret-root = ECDH(MakerKey-public, TakerKey-private) = ECDH(TakerKey-public, MakerKey-private)
接著用SharedSecret-root通過(guò)HKDF算法生成更加安全的秘鑰(SharedSecret-enc和SharedSecret-mac)。發(fā)送消息的時(shí)候,Alice和Bob用SharedSecret-enc和隨機(jī)值IV通過(guò)AES256-CBC加密消息,且每條消息都用各自的accountKeyIdentityPrivate簽名。為了做進(jìn)一步認(rèn)證和完整性檢查,還會(huì)用SharedSecret-mac對(duì)每個(gè)加密消息進(jìn)行HMAC-SHA256哈希校驗(yàn)。
一旦發(fā)生爭(zhēng)議,一方只要提交SharedSecret-root讓網(wǎng)站做仲裁即可。