時(shí)間:2023-05-27|瀏覽:230
原文作者:LORENZO? 編譯:btcstudy
一個(gè)網(wǎng)絡(luò)中的計(jì)算機(jī)依據(jù)協(xié)議跟彼此交流。在這里,“協(xié)議” 指的是一套規(guī)則系統(tǒng),指定了消息應(yīng)該如何傳輸和解讀。閃電網(wǎng)絡(luò)協(xié)議中的支付消息傳輸部分由 BOLT#4 描述,也叫 “洋蔥路由協(xié)議(Onion Rounting Protocol)”。
洋蔥路由是一種先于閃電網(wǎng)絡(luò) 25 年誕生的技術(shù)。它也被用在 Tor 中,正是 “Tor” 這個(gè)名字(“The Onion Router”)的由來。閃電網(wǎng)絡(luò)使用的是一個(gè)稍微修改之后的版本,叫做 “基于來源的洋蔥路由”,縮寫是 “SPHINX”。在這篇文章中,我們就要講講洋蔥路由是怎么工作的。
世界上存在許多不同的通信協(xié)議,但因?yàn)殚W電網(wǎng)絡(luò)是一個(gè)支付網(wǎng)絡(luò),選擇一個(gè)盡可能少揭示正被轉(zhuǎn)發(fā)的支付的信息的協(xié)議,就是合理的。
如果閃電網(wǎng)絡(luò)使用跟互聯(lián)網(wǎng)一樣的協(xié)議,每一個(gè)中間人都會(huì)知道誰是支付的發(fā)送者、誰是接收者、整條路徑上的其他中間人是誰。洋蔥路由是一個(gè)好的選擇,因?yàn)槠涮匦员WC了中間節(jié)點(diǎn):
只知道自己的上一個(gè)節(jié)點(diǎn)(誰給自己發(fā)來了消息)和下一個(gè)節(jié)點(diǎn)(要把消息轉(zhuǎn)發(fā)到哪里去)。
不知道整條路徑的長度;
不知道自己在路徑中的位置。
我們用包裹作為類比,解釋一下洋蔥路由是怎么工作的。
假設(shè) Alice 要給 Dina 支付。首先,Alice 要為自己的支付找出一條可行的路徑:
Alice?→?Bob?→?Chan?→?Dina
然后,她構(gòu)造出一個(gè) “洋蔥”。她要從 Dina 開始(從路徑的末端開始)。她把一個(gè)秘密消息(支付內(nèi)容)放在一個(gè)發(fā)送給 Dina 的包裹中,并且使用一個(gè)只有她和 Dina 知道的密鑰來上鎖?,F(xiàn)在,她把這個(gè)包裹放到另一個(gè)準(zhǔn)備發(fā)送給 Chan 的包裹中,并且使用只有她和 Chan 知道的密鑰,給這個(gè)發(fā)送給 Chan 的包裹上鎖。對以此類推。
Alice 把最終的洋蔥(包裹)發(fā)給路徑上的第一個(gè)中間人,Bob。Bob 使用自己的密鑰解鎖自己的包裹,然后看到下一個(gè)包裹是發(fā)送給 Chan 的。于是他把包裹轉(zhuǎn)發(fā)給 Chan。Chan 也一樣,解開包裹之后,把里面那個(gè)包裹轉(zhuǎn)發(fā)給 Dina。最后,Dina 打開屬于自己的包裹,發(fā)現(xiàn)其中的支付消息。
在洋蔥路由中,像 Bob 和 Chan 這樣的中間人,并不知道給 Dina 的信息的內(nèi)容,也不知道整條支付路徑的長度。他們唯一知道的,就是給他們轉(zhuǎn)發(fā)這個(gè)包裹的人,以及下一個(gè)接收包裹的人。這保證了消息的隱私性和路徑的機(jī)密性。每一個(gè)中間人都只能觸及專門為 TA 制作的那一層消息。
在閃電網(wǎng)絡(luò)的基于來源的洋蔥路由中,發(fā)送者選擇支付路徑,并為這條路徑構(gòu)造出完整的洋蔥,這可以被視為隱私漏洞(譯者注:接收者的網(wǎng)絡(luò)位置必須向發(fā)送者曝光)。別的路由方案比如 “盲化路由”(中文譯本),通過向發(fā)送者混淆部分支付路徑來解決這個(gè)問題。不過,在這篇文章中,我們專講 SPHINX。
現(xiàn)在,我們來了解一下洋蔥路由的規(guī)范。在一開始,我們需要定義這些東西:
發(fā)送者是 “最初節(jié)點(diǎn)”(Alice);
接收者是 “最終節(jié)點(diǎn)”(Dina);
支付路徑上的每一個(gè)中間節(jié)點(diǎn)都是一 “跳”(Bob 和 Chan);
每一跳之間的通信信息,叫做 “跳的負(fù)載”。
一旦 Alice 選出了一條支付路徑,她就從 gossip 協(xié)議中獲得每一條支付通道的信息,以創(chuàng)建每一跳的負(fù)載,本質(zhì)上這就是在告訴每一跳,如何為正在轉(zhuǎn)發(fā)的支付創(chuàng)建 HTLC(哈希時(shí)間鎖合約)。
為了建立一個(gè)合適的 HTLC,每一跳都需要:
需要轉(zhuǎn)發(fā)的數(shù)額;
支付的秘密值;
繼續(xù)發(fā)送洋蔥的支付通道的 ID;
時(shí)間鎖的長度。
這些數(shù)據(jù)中的大部分,都來自 “通道更新” 消息,這樣的消息包含了關(guān)于路由手續(xù)費(fèi)、事件所要求、支付通道 ID 的信息。需要轉(zhuǎn)發(fā)的總數(shù)額,是支付的數(shù)額加上后續(xù)每一跳所收取的手續(xù)費(fèi)總和;而支付的秘密值則是由 Dina 計(jì)算出來并嵌進(jìn)支付發(fā)票中的(由洋蔥消息告知路徑上的每一跳)。
Alice 從最終節(jié)點(diǎn) Dina 開始。她在包裹中包含轉(zhuǎn)發(fā)數(shù)額、時(shí)間鎖時(shí)長數(shù)值、支付秘密值以及支付數(shù)額。注意,她不需要再加入通道 ID,因?yàn)?Dina 就是最終節(jié)點(diǎn),不需要再將支付轉(zhuǎn)發(fā)給其他人。
乍看起來,提供轉(zhuǎn)發(fā)數(shù)額是多余的,因?yàn)檫@個(gè)數(shù)額跟支付數(shù)額是一樣的,但是,多路徑(multipath)支付會(huì)將支付總額通過多條路徑送達(dá),那時(shí)候兩個(gè)數(shù)值就會(huì)不一致。
在 Chan 的負(fù)載中,Alice 加入 Chan 跟 Dina 的通道 ID。她還添加了轉(zhuǎn)發(fā)數(shù)額以及時(shí)間鎖數(shù)值。最后,Alice 創(chuàng)建給 Bob 的負(fù)載。Chan 為通過自己跟 Dina 的通道的支付收取 100 聰,因此,Alice 需要告訴 Bob 的轉(zhuǎn)發(fā)數(shù)額是支付額加上手續(xù)費(fèi)。根據(jù) Chan 的通道更新消息,時(shí)間鎖的數(shù)值也提高了 20(以區(qū)塊為單位)。最后,Alice 也要考慮 Bob 的手續(xù)費(fèi)和時(shí)間鎖要求,給他一個(gè)時(shí)間鎖長度為 700040、價(jià)值為 100200 聰?shù)?HTLC。
下一筆,Alice 通過為每一跳(包括最終節(jié)點(diǎn))生成一個(gè)共享秘密值(shared secret),準(zhǔn)備好洋蔥。這個(gè)共享秘密值可以由 Alice 和目標(biāo)那一跳各自生成出來,辦法就是用自己的私鑰與對方的公鑰相乘。
共享秘密值對洋蔥路由來說是必要的,這讓 Alice 和每一跳可以推導(dǎo)出相同的密鑰。然后,Alice 使用這些密鑰來混淆洋蔥的每一層,而那一跳則使用密鑰來解開混淆。
為了保護(hù) Alice 的隱私,她會(huì)為一個(gè)洋蔥創(chuàng)建一個(gè)一次性的會(huì)話密鑰,而不是使用自己的節(jié)點(diǎn)公鑰,以推導(dǎo)共享秘密值。她給第一跳使用這個(gè)會(huì)話密鑰,然后,對后續(xù)的每一跳,Alice 都將最新的密鑰乘以一個(gè)盲化因子,從而確定性地隨機(jī)化密鑰。這些用來創(chuàng)建共享秘密值密鑰,我們叫做 “臨時(shí)密鑰” 。
Bob、Chan 和 Dina,都需要跟 Alice 得到相同的秘密值,因此,他們需要知曉用在自己的會(huì)話中的臨時(shí)密鑰。Alice 只將第一個(gè)密鑰放到洋蔥中,以節(jié)約消息的體積。每一跳都計(jì)算下一個(gè)臨時(shí)密鑰,并將它嵌在給下一個(gè)節(jié)點(diǎn)的洋蔥中。各跳可以使用自己的公鑰和共享秘密值計(jì)算出 Alice 所用的盲化因子,從而確定下一個(gè)臨時(shí)密鑰。
如前所述,共享秘密值會(huì)被用來生成一些密鑰,Alice 和對應(yīng)跳可以用這些密鑰對洋蔥做一些操作。我們來看看每一個(gè)密鑰的用途。
Rho key
Rho key 被 Alice 用來加密一層洋蔥;這樣會(huì)混淆負(fù)載的內(nèi)容,使外人無法解讀。只有 rho key 的主人可以解密負(fù)載。這就是收到洋蔥的節(jié)點(diǎn)要做的事:使用跟 Alice 的共享秘密值推導(dǎo)出 rho key,然后解密洋蔥、閱讀內(nèi)容。
Mu key
Alice 使用 mu key 來為每一個(gè)負(fù)載創(chuàng)建一個(gè)校驗(yàn)和。她也會(huì)把校驗(yàn)和交給接收洋蔥的那一跳。反過來,這一跳會(huì)使用 mu key 生成所收到的負(fù)載的校驗(yàn)和,檢查是否與 Alice 給出的相匹配。這是為了檢查負(fù)載的完整性,驗(yàn)證它沒有被篡改過。
Pad key
這個(gè)密鑰僅為 Alice 所用,用來生成隨機(jī)的 “垃圾” 數(shù)據(jù)。這些數(shù)據(jù)也是洋蔥的一部分,而且它跟支付路徑的長度、洋蔥已經(jīng)通過多少跳無關(guān),它讓洋蔥總是保持相同的體積,即使其某些內(nèi)容需是無關(guān)緊要的。這就是洋蔥路由如何隱藏路徑長度的,實(shí)際上就是在保護(hù)發(fā)送者和接收者的隱私。
Um key
這個(gè)密鑰也用來檢查洋蔥內(nèi)包含的數(shù)據(jù)的完整性,但僅在回傳錯(cuò)誤時(shí)使用。沒錯(cuò),它叫做 “um” 是因?yàn)檫@是 “mu” 的倒寫。在支付出錯(cuò)的情形中,發(fā)現(xiàn)錯(cuò)誤的那一跳將使用 um key 創(chuàng)建一個(gè)校驗(yàn)和,當(dāng)前一個(gè)節(jié)點(diǎn)收到這個(gè)報(bào)錯(cuò)時(shí),也使用 um key 來驗(yàn)證消息的完整性。
最終的洋蔥包裹看起來是這樣的:
現(xiàn)在,Alice 擁有了給每一跳的負(fù)載,以及給每一跳的共享秘密值。我們來看看 Alice 如何將這些信息轉(zhuǎn)化為最終的洋蔥。她先從最終節(jié)點(diǎn)開始,然后一步一步往回推。
她先創(chuàng)建一個(gè)空的、長為 1300 字節(jié)的域,這也是所有洋蔥負(fù)載的總長。然后,她使用 pad key創(chuàng)建一段長為 1300 字節(jié)的隨機(jī)串,這就是對任何一跳都沒用的垃圾。做這一步,是為了確保每一層洋蔥看起來都是一樣的,所以既無法看出路徑的總長(有多少跳),也看不出誰是發(fā)送者、誰是接收者。
然后,她給需要使用的負(fù)載創(chuàng)建一個(gè)校驗(yàn)和,并放在負(fù)載的末尾。在給最終節(jié)點(diǎn)的消息中,校驗(yàn)和全部為 0,以告知 Dina,她就是這個(gè)洋蔥的最終接收者。把校驗(yàn)和添加到負(fù)載的末尾之后,Alice 就把負(fù)載(以及校驗(yàn)和)放到垃圾的開頭,并刪去整條消息超過 1300 字節(jié)的部分,以保證整個(gè)消息的長度就是 1300 字節(jié)。
然后,Alice 使用 rho key 創(chuàng)建一個(gè)隨機(jī)字節(jié)串,并對上一步得到的洋蔥負(fù)載使用異或(XOR)運(yùn)算,得到混淆后的負(fù)載。負(fù)載的原文可以通過對混淆文使用這個(gè)隨機(jī)字節(jié)串的 XOR 運(yùn)算得到(譯者注:換言之,這里的 XOR 就是對稱加密的算法,而隨機(jī)字節(jié)串就是密鑰)。XOR 操作會(huì)逐比特對比洋蔥負(fù)載和(由 rho key 生成的)隨機(jī)字節(jié)串,僅當(dāng)其中一個(gè)數(shù)據(jù)的比特是 1 時(shí),才會(huì)輸出 1;這就得出了一個(gè)混淆后的負(fù)載。XOR 操作巧妙的地方在于,只要你得到了對的那個(gè)隨機(jī)字節(jié)串以及混淆后的負(fù)載,只需用兩者再次運(yùn)行 XOR 操作,就可以得到混淆之前的負(fù)載。
因?yàn)槭盏窖笫[的節(jié)點(diǎn)可以推導(dǎo)出相同的 rho key,可以他們可以生成跟 Alice 一樣的隨機(jī)字節(jié)串。這就是沿路的各個(gè)節(jié)點(diǎn)可以解開混淆、讀到內(nèi)容的辦法。
準(zhǔn)備好一跳的混淆洋蔥后,Alice 就給下一個(gè)節(jié)點(diǎn)重復(fù)相同的步驟。關(guān)鍵區(qū)別在于,完成 Dina 的洋蔥之后,她就不再需要生成垃圾了。她只需在有用的負(fù)載和校驗(yàn)和之后接上上一步所生成的混淆洋蔥,再剪去超過 1300 字節(jié)的部分。下面這個(gè) GIF 演示了整個(gè)過程:https://youtu.be/FzedRXqZDyY
最后,Alice 拿到最終的混淆洋蔥并添加一個(gè)校驗(yàn)和,這樣 Bob 就可以驗(yàn)證這個(gè)洋蔥的完整性。然后,Alice 加入會(huì)話公鑰,這樣 Bob 就可以使用這個(gè)公鑰來計(jì)算共享秘密值。最后,她還要加上一個(gè)表示版本的字節(jié),告知其它節(jié)點(diǎn)如何解讀其中的數(shù)據(jù)。對 BOLT#4 所描述的版本來說,版本字節(jié)應(yīng)為 0。
為了發(fā)送這個(gè)洋蔥包裹,發(fā)送者創(chuàng)建一條 update_add_htlc
消息,包含下列字段:
通道 ID:這個(gè)消息所關(guān)乎的具體通道。
ID:這個(gè) HTLC 的標(biāo)識(shí)符。
數(shù)額:這個(gè) HTLC 的價(jià)值。
支付哈希值:由支付的接收方創(chuàng)建。
過期時(shí)間:這個(gè) HTLC 將在一定區(qū)塊之后過期。
洋蔥包裹:為這筆支付創(chuàng)建的洋蔥,也就是上面講到的東西。
額外的數(shù)據(jù):用來指定額外的數(shù)據(jù)。
準(zhǔn)備好消息后,Alice 就把消息發(fā)送個(gè) Bob。收到消息后,Bob 就可以開始解碼屬于自己的洋蔥了。他先從洋蔥包裹中獲得會(huì)話密鑰,然后使用它推導(dǎo)出跟 Alice 的共享秘密值。
有了共享秘密值,Bob 生成 mu key,以驗(yàn)證嵌在洋蔥包裹中、負(fù)載的校驗(yàn)和。如果負(fù)載沒有被篡改過,校驗(yàn)和應(yīng)該能匹配上。
為了防止路徑中的其他節(jié)點(diǎn)知道路徑有多長,Bob 會(huì)在洋蔥包裹內(nèi)增加一個(gè) 1300 字節(jié)長、充滿了 0 的字段。然后,Bob 從 rho key 中生成一個(gè) 2600 字節(jié)長的隨機(jī)字節(jié)串。Bob 使用這個(gè)隨機(jī)字節(jié)串,對填充了 0 的洋蔥負(fù)載作 “異或” 運(yùn)算。
還記得我怎么跟你說混淆洋蔥負(fù)載的嗎?使用混淆后的洋蔥負(fù)載作為輸入,跟相同的字節(jié)串運(yùn)行 “異或” 操作,就能得到混淆前的洋蔥負(fù)載。因?yàn)?Alice 和 Bob 使用相同的共享秘密值,生成了相同的 rho key,Bob 可以解開混淆。這樣做的額外好處是,它又將 1300 字節(jié)長的填充字符變成了隨機(jī)字節(jié)。
Bob 解開混淆的負(fù)載中包括了他這一跳的負(fù)載數(shù)據(jù)以及一個(gè)指紋。Bob 保存這個(gè)指紋,以便將它添加到發(fā)送給 Chan 的洋蔥包裹中。在 Bob 將屬于自己的負(fù)載從洋蔥消息中分離出來后,他將洋蔥包裹轉(zhuǎn)回 1300 字節(jié)的原始大小,并跟 Alice 一樣隨機(jī)化自己的會(huì)話密鑰。最后,Bob 加上版本字節(jié)、會(huì)話密鑰以及他準(zhǔn)備放在洋蔥負(fù)載中的指紋,就通過 update_add_htlc
消息將洋蔥包裹轉(zhuǎn)發(fā)給 Chan。
這個(gè)過程會(huì)一直持續(xù),直至消息送到最終節(jié)點(diǎn),Dina。當(dāng) Dina 收到 update_add_htlc
消息時(shí),她可以揣進(jìn)到自己所生成的秘密值的哈希值,這說明這個(gè) HTLC 就是要發(fā)給她的。因此,Dina 只需檢查指紋、解開洋蔥消息、揭曉屬于自己的負(fù)載。這個(gè)動(dòng)圖演示了整個(gè)過程:https://youtu.be/NhHAE6m9L6A
我們介紹的是一個(gè)成功案例,也就是一切都按部就班的案例,但如果這個(gè)過程發(fā)生了一些錯(cuò)誤,那就必須一路回傳一條消息,以通知所有節(jié)點(diǎn)出了問題。這個(gè)過程跟常規(guī)的洋蔥路由類似。發(fā)現(xiàn)一個(gè)錯(cuò)誤的故節(jié)點(diǎn)需要從共享秘密值中推導(dǎo)出 um key,并使用它生成一個(gè)隨機(jī)字節(jié)串,然后使用異或運(yùn)算來混淆返回的洋蔥包裹。
發(fā)現(xiàn)錯(cuò)誤的節(jié)點(diǎn)將給支付路徑的上一個(gè)節(jié)點(diǎn)回傳一條消息。每一跳都使用 um key 和 ammag key 作相同的操作,直到發(fā)送者收到這個(gè)包裹。最后,發(fā)送者分別使用 ammag key 和 um key 解開包裹的混淆并驗(yàn)證。
錯(cuò)誤可能由洋蔥包裹、節(jié)點(diǎn)或通道引起。如果你經(jīng)常使用閃電網(wǎng)絡(luò),你可能遇到過這樣的錯(cuò)誤,比如 “通道不可用” 或 “手續(xù)費(fèi)不足”。
參考文獻(xiàn)
Mastering the Lightning Network
BOLT #4: Onion Routing Protocol
熱點(diǎn):BTC