作者:Elle Mouton
在本文中,我會簡要介紹比特幣輕客戶端的需要,以及為什么 “致密區塊過濾器(compact block filters)” 比 “布隆過濾器(Bloom filters)” 更好地滿足了這種需要。然后,我會深入解釋致密區塊過濾器是怎么工作的,并會附上在測試網上構造這樣的過濾器的逐步講解。
比特幣輕客戶端(bitcoin light software)是一種軟件,其目標是不存儲比特幣區塊鏈但支持比特幣錢包。這意味著,它需要能夠向網絡廣播交易,但最重要的是,它必須能夠從鏈上找出跟本錢包相關的新交易。這樣的相關交易有兩種:給本錢包發送資金(為本錢包的一個地址創建了一個新的輸出)、花費本錢包所控制的其中一個 UTXO。
在 BIP 158 出現以前,輕客戶端最常用的方法是由 BIP 37 描述的布隆過濾器 1。布隆過濾器的模式是,你找出自己感興趣的所有數據對象(比如,被花費的以及被創建的 UTXO 的腳本公鑰)、將它們逐個多次哈希,然后將每個結果添加到一個叫做 “布隆過濾器” 的位圖(bit map)中。這個過濾器代表了你感興趣的東西。然后,你將這個過濾器發送給一個你信任的比特幣節點,并要求他們給你發送所有跟這個過濾器相匹配的東西。
問題在于,這個過程并不是非常私密,因為,你還是向這個接收過濾器的比特幣節點泄露了一些信息。他們可以知曉你感興趣的交易以及你完全沒興趣的交易;他們還可以不給你發送跟過濾器相匹配的東西。所以,這對輕客戶端來說并不是理想的。但同時,它對向輕客戶端提供服務的比特幣節點來說也不理想。每次你發送一個過濾器,他們就必須從硬盤加載相關的區塊,然后確定是否有交易跟你的過濾器相匹配。只需要不停發送假的過濾器,你就可以轟炸他們 —— 這本質上是一種 DOS 攻擊。只需要非常少的能量就可以創建一個過濾器,但需要耗費很多能量,才能響應這樣的請求。
OK,那么我們需要的屬性有:
更強的隱私性
客戶端-服務端 的負載更少不對稱性。即,服務端需要做的工作應該更少。
更少信任。輕客戶端不需要擔心服務端不返回相關的交易。
使用致密區塊過濾器,服務端(全節點)將為每個區塊構造一個確定性的過濾器,包含這個區塊中的所有數據對象。這個過濾器只需計算一次,就可以永久保存下來。如果輕客戶端請求某一個區塊的過濾器,這里是沒有不對稱性的 —— 因為服務端不需要做比發起請求的客戶端更多的工作。輕客戶端也可以選擇多個信息源來下載完整的區塊,以確保它們是一致的;而且,輕客戶端總是可以下載完整的區塊,從而自己檢查服務端所提供的過濾器是不是正確的(與相關的區塊內容相匹配)。另一個好處是,這種方式更加隱私。輕客戶端不再需要給服務端發送自己想要的數據的指紋。這樣分析一個輕客戶端的活動也會變得更加困難。輕客戶端從服務端獲取這樣的過濾器之后,自己檢查過濾器中是否有自己想要的數據,如果有,輕客戶端就請求完整的區塊。還要指出的一點是,為了以這種方式服務輕客戶端,全節點需要持久保存這些過濾器,而輕客戶端可能也想要持久保存少數幾個過濾器,所以,保證過濾器盡可能輕量,是非常重要的(這也是為什么它叫做 “致密區塊過濾器”)。
很好!現在我們要進入難得東西了。這樣的過濾器是怎么創建的?它看起來是什么樣子的?
我們的需求是?
我們想把特定對象的指紋放在過濾器中,這樣一來,當客戶端想要檢查一個區塊是否可能包含了跟自己相關的內容時,他們可以列舉所有的對象,并檢查一個過濾器是否與這些對象匹配。
我們希望這個過濾器盡可能小。
實質上,我們想要的是某種東西,它可以用一個遠遠小于區塊的體積,來總結一個區塊的某一些信息。
包含在過濾器中的基本信息有:每一筆交易的輸入的腳本公鑰(也就是每一個被花費的 UTXO 的腳本公鑰),以及每一筆交易的每一個輸出的腳本公鑰。基本上就是這樣:
objects = {spk1, spk2, spk3, spk4, ..., spkN} // 一個由 N 個腳本公鑰構成的列表從技術上來說,我們到這里就可以止步了 —— 就這樣的腳本公鑰列表,也可以充當我們的過濾器。這是一個區塊的濃縮版本,包含了輕客戶端所需的信息。有了這樣的列表,輕客戶端可以 100% 確認一個區塊中是否有自己感興趣的交易。但這樣的列表體積非常大。所以,我們接下來的步驟,都是為了讓這個列表盡可能緊湊。這就是讓事情有趣起來的地方。
首先,我們可以將每一個對象都轉化成某個范圍內的一個數字、并使得每個對象數字在這個范圍內是均勻分布的。假設我們有 10 個對象(N = 10),然后我們有某一種函數,可以將每個對象都轉化成一個數字。假設我們選擇的范圍是 [0, 10](因為我們有 10 個對象)。現在,我們的 “哈希加轉換成數字” 函數將取每一個對象為輸入,并分別產生一個大小在 [0, 10] 之間的數字。這些數字在這個范圍內是均勻分布的。這就意味著,在排序之后,我們將得到一個這樣的圖(在非常非常理想的情況下):
狗狗幣開發工具集libdogecoin發布更新,支持二維碼生成、BIP39等標準:3月25日消息,根據開發者Michi Lumin 3月23日的公告,狗狗幣基金會(Dogecoin Foundation)發布了libdogecoin工具集的重大更新。Lumin稱這是一次“大更新”,包括幾個新功能以及各種修復和改進。這次更新包括對BIP39的支持,這是一種被比特幣錢包和其他錢包廣泛用于創建和恢復地址的標準。這次更新還增加了對相關地址標準的全面支持,如BIP32、BIP44和SLIP44。
這意味著用戶有更多與種子短語相關的選項,種子短語是一個常用單詞列表,可以作為記憶工具。Lumin表示,此次更新將支持多種語言。此外,該工具集能夠生成二維碼,這通常用于移動錢包交易。(Crypto Slate)[2023/3/25 13:26:16]
首先,這非常棒,因為我們已經大大縮減了每一個對象的指紋的大小。現在每個對象都只是一個數字了。那么,我們的新過濾器看起來就像這樣:
numbers := {1,2,3,4,5,6,7,8,9,10}輕客戶端下載這樣的過濾器,希望知道自己正在尋找的某個對象是否跟這個過濾器相匹配。他們只需取出自己感興趣的對象,然后運行相同的 “哈希加轉換成數字” 函數,看看結果是否在這個過濾器中,就可以了。問題在哪里呢?問題是過濾器中的數字已經占據了這個空間內的一切可能性!這意味著,不管輕客戶端關心什么樣的對象,產生的數字結果都一定會跟這個過濾器相匹配。換句話來說,這個過濾器的 “假陽性率(false-positive rate)” 為 1 (譯者注:這里的 “假陽性” 指區塊中可能不包含客戶端所關心的交易,但通過過濾器得到的結果卻是有)。這就不太好了。這也說明,在我們壓縮數據以生成過濾器的過程中,我們弄丟了太多信息。我們需要更高的假陽性率。那么,假設我們希望假陽性率為 5.那么,我們可以讓區塊內的對象均勻匹配到 [0, 50] 范圍內的數字:
這樣看起來就好一些了。如果我是一個客戶端,下載到了這樣的過濾器,要通過過濾器檢查一個我關心的對象是否在對應的區塊中,過濾器說有但區塊中并沒有(假陽性)的概率就下降到 1/5 。很好,現在我們是將 10 個對象映射成了 0 到 50 之間的數字。這個新的數字列表就是我們的過濾器。再說一遍,我們隨時可以停下來,但是,我們也可以進一步壓縮它們!
現在,我們有了一個有序的數字列表,這些數字均勻地分布在 [0, 50] 區間內。我們知道這個列表中有 10 個數字。這意味著,我們可以推斷出,在這個有序數字列表內,相鄰兩個數字的 差值 最有可能是 5 。總的來說,如果我們有 N 個對象,并希望假陽性率為 M,那么空間的大小應該是 N * M 。也即這些數字的大小應該在 0 到 N * M 之間,但(排序之后)相鄰兩個數字檢查的插值大概率是 M 。而 M 存儲起來肯定比 N * M 空間內的數字要小。所以,我們不必存儲每一個數字,我們可以存儲相鄰兩個數字的差值。在上面這個例子中,這意味著,我們不是存儲 [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50] 作為過濾器,而是存儲 [0, 5, 5, 5, 5, 5, 5, 5, 5, 5],憑此重構初始列表是很簡單的。如你縮減,存儲數字 50,顯然比存儲數字 5 要占用更多空間。但是,為何要止步呢?我們再壓縮多一點!
接下來就要用到 “Golomb-Rice 編碼” 了。這種編碼方式可以很好地編碼一個表內各數字都非常接近與某一個數字的列表。而我們的列表恰好就是這樣的!我們的列表中的數字,每一個都很可能接近 5(或者說,接近于我們的假陽性率,即 M),所以,取每一個數字與 M 的商(將每一個數字除以 5 并忽略掉余數),將很有可能是 0 (如果數字稍小于 5)或者 1(數字稍大于 5)。商也有可能是 2、3,等等,但概率會小很多。很棒!所以我們可以利用這一點:我們將用盡可能少的比特來編碼更小的商,同時使用更多的比特幣來編碼更大、但更不可能出現的商。然后,我們還需要編碼余數(因為我們希望能夠準確地重建整個列表),而這些余數總是在 [0, M - 1] 范圍內(在這個案例中就是 [0, 4])。為編碼商,我們使用下列映射:
上述映射很容易理解:數字 1 的數量表示了我們要編碼的商,而 0 表示商編碼的結尾。所以,對我們列表中的每一個數字,我們都可以使用上述表格來編碼商,然后使用可以表示最大值為 M-1 的比特川,將余數轉化為 2 進制。在這里余數為 4,只需要 3 個比特就可以了。這里是一個展示我們的例子中可能的余數及其編碼的表格:
所以,在理想的情況下,我們的列表 [0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5] 可以編碼成:
0000 10000 10000 10000 10000 10000 10000 10000 10000 10000在我們轉移到一個更現實的案例之前,我們來看看是否可以靠這個過濾器恢復我們的最初的列表。
比特幣社區新提案提議改進BIP審議流程:金色財經報道,比特幣核心開發者Jeremy Rubin提出比特幣改進提案BIP119,以提高比特幣去中心化決策的質量。目前該提案正在討論中。該BIP提議對BIP審議流程進行改變,即為BIP文檔提出一套高標準,并采用這些開放標準作為事實上的最低質量標準,以便BIP被考慮進行更廣泛的討論。此外,該提案建議設立一個提案跟蹤器以提高BIP流程的明確性,每個提案跟蹤器都應包含以下部分:歷史、描述、資源、成本和風險、收益、替代方案、激活和事后分析。(Bitcoin Magazine)[2022/5/5 2:51:49]
現在我們擁有的是 “0000100001000010000100001000010000100001000010000”。我們知道 Golomb-Rice 編碼,也知道 M 為 5(因為這將是公開知識,每一個使用這種過濾器構造的人都會知曉)。因為我們知道 M 為 5,所以我們知道會有 3 個比特來編碼余數。所以,我們可以將這個過濾器轉化成下列的 “商-余數” 數組列表:
[(0, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0)]知道商是通過除以 M(5)得到的,所以我們可以恢復出:
[0, 5, 5, 5, 5, 5, 5, 5, 5, 5]而且,我們知道這個列表所表示的是相鄰兩數之間的差值,所以我們可以恢復最初的列表:
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]更加現實的例子我們現在要嘗試為一個真實的比特幣 testnet 區塊構造一個過濾器。我將使用區塊 2101914 。我們看看它的過濾器長什么樣:
$ bitcoin-cli getblockhash 2101914000000000000002c06f9afaf2b2b066d4f814ff60cfbc4df55840975a00e035c$ bitcoin-cli getblockfilter 000000000000002c06f9afaf2b2b066d4f814ff60cfbc4df55840975a00e035c{ "filter": "5571d126b85aa79c9de56d55995aa292de0484b830680a735793a8c2260113148421279906f800c3b8c94ff37681fb1fd230482518c52df57437864023833f2f801639692646ddcd7976ae4f2e2a1ef58c79b3aed6a705415255e362581692831374a5e5e70d5501cdc0a52095206a15cd2eb98ac980c22466e6945a65a5b0b0c5b32aa1e0cda2545da2c4345e049b614fcad80b9dc9c903788163822f4361bbb8755b79c276b1cf7952148de1e5ee0a92f6d70c4f522aa6877558f62b34b56ade12fa2e61023abf3e570937bf379722bc1b0dc06ffa1c5835bb651b9346a270", "header": "8d0cd8353342930209ac7027be150e679bbc7c65cc62bb8392968e43a6ea3bfe"}好了親愛的,看看我們如何從區塊構造出這樣的過濾器吧。
這里所用的完整代碼可以在這個 github 庫中找到。我在這里只會展示一些偽代碼片段。這段代碼的核心是一個名為 constructFilter 的函數,比特幣客戶端可以用它來調用 bitcoind 以及對應的區塊。這個函數看起來是這樣的:
func constructFilter(bc *`bitcoind`.Bitcoind, block `bitcoind`.Block) ([]byte, error) { // 1. 從區塊中收集我們想要添加到一個過濾器中的所有對象 // 2. 將這些對象轉化為數字,并排序 // 3. 取得排序后的數字列表中相鄰兩數的差值 // 4. 使用 Golomb-Rice 編碼來編碼這些差值}所以,第一步是從區塊中收集我們想要添加到過濾器中的所有對象。從 BIP 出發,我們知道,這些對象包括所有被花費的腳本公鑰,以及每一個輸出的腳本公鑰。BIP 還設定了一些額外的規則:我們會跳過 coinbase 交易的輸入(因為它沒有輸入,這是沒有意義的),而且我們會通過所有 OP_RETURN 輸出。我們也會刪除重復數據。如果有兩個相同的腳本公鑰,我們只會向過濾器添加一次。
Crypto.com成為新加坡區塊鏈創新計劃(SBIP)黃金會員:12月29日消息,Crypto.com宣布成為新加坡區塊鏈創新計劃(SBIP)的黃金會員。 Crypto.com將與新加坡國立大學(NUS)合作,為全球更廣泛的區塊鏈應用提供新的可擴展性和互操作性解決方案。[2021/12/29 8:11:58]
// 我們希望向過濾器添加的對象的列表// 包括所有被花費的腳本公鑰,以及每一個輸出的腳本公鑰// 我們使用了 “圖(map)”,這樣就去除了重復的腳本公鑰objects := make(map[string] struct{})// 便利區塊中的每一筆交易for i, tx := range block.Tx { // 添加每一個輸出的腳本公鑰 // 到我們的對象列表中 for _, txOut := range tx.Vout { scriptPubKey := txOut.ScriptPubKey if len(scriptPubKey) == 0 { continue } // 如遇 OP_RETURN (0x6a) 輸出,則跳過 if spk == 0x6a { continue } objects[skpStr] = struct{}{} } // 不添加 coinbase 交易的輸入 if i == 0 { continue } // 對每一個輸入,獲取其腳本公鑰 for _, txIn := range tx.Vin { prevTx, err := bc.GetRawTransaction(txIn.Txid) if err != nil { return nil, err } scriptPubKey := prevTx.Vout[txIn.Vout].ScriptPubKey if len(scriptPubKey) == 0 { continue } objects[spkStr] = struct{}{} }}OK,現在已經收集好了所有的對象。現在,我們定義變量 N 為對象圖的長度。在這里,N 為 85 .
動態 | Cointext支持支付協議BIP70:據bitcoinexchangeguide報道,Cointext已經宣布支持支付協議BIP70。據悉,Bitpay平臺一直用該協議快速處理BTC和BCH的發票。這意味著,最初,Cointext用戶將能夠復制BIP70支付網址并將其用于發票的支付,所有這些都將通過短信服務完成。[2019/3/13]
下一步則是將我們的對象轉換成在一個區間內均勻分布的數字。再說一次,這個范圍取決于你想要多高的假陽性率。BIP158 定義常量 M 為 784931 。這意味著,出現假陽性的概率為 1/784931 。就像我們前面做的一樣,我們取假陽性率 M 乘以 N,得到所有數字的分布范圍。這個范圍我們定義為 F,即 F = M * N 。在這里,F = 66719135 。我不準備細說將對象映射為數字的函數(你可以在上面鏈接的 github 庫中找到相關的細節)。你需要知道的僅僅是,它會取對象、常量 F(定義了需要將對象映射到的范圍)以及一個鍵(區塊哈希值)作為輸入。一旦我們得出所有的數字,我們會用升序排列這個列表,然后創建一個叫做 differences(差值) 的新列表,保存排好序的數字列表內相鄰兩個數字之間的差值。
numbers := make([]uint64, 0, N)// 遍歷所有對象,將它們轉化為在 [0, F] 范圍內均勻分布的數字// 并將這些數字存儲為 `numbers` 列表for o := range objects { // Using the given key, max number (F) and object bytes (o), // convert the object to a number between 0 and F. v := convertToNumber(b, F, key) numbers = append(numbers, v。// 給 numbers 列表排序sort.Slice(numbers, func(i, j int) bool { return numbers[i] < numbers[j] })// 將數字列表轉化為差值列表differences := make([]uint64, N)for i, num := range numbers { if i == 0 { differences[i] = num continue } differences[i] = num - numbers[i-1]}很棒!這里有張圖,展示了 numbers 和 differences 列表。
如你縮減,85 個數字均勻地分布在整個空間中!所以 differences 列表中的數值也非常小。
最后一步是用 Golomb-Rice 編碼來編碼這個 differences 列表。回憶一下前面的解釋,我們需要用最有可能的數值來除每個差值,然后編碼商和余數。在我們前面的例子中,我說最有可能的數值就是 M,而余數會在 [0, M] 之間。但是,BIP158 并不是這么做的,因為我們發現 2,這并不是事實上能讓最終的過濾器體積最小化的參數。所以我們不使用 M,而是定義一個新的常量 P,并使用 2 ^ P 作為 Golomb-Rice 參數。P 定義成 19 。這意味著我們要將每一個差值除以 2 ^ 19,以獲得商和余數,并且余數會用 19 比特編碼成二進制。
Bitpay為其錢包添加BIP70功能保護比特幣用戶:為保護比特幣用戶,Bitpay為其錢包添加BIP70功能。批評者擔心依賴既有公共密鑰體系的BIP70將增加AML/KYC監視和監控交易的風險。因為Bitpay采用了BIP70而不是segwit,此舉也受到比特幣核心開發者的批評。Samourai團隊發表意見稱,“我們絕對不支持Bitpay,因為他們利用市場份額的主導地位,迫使錢包供應商支持他們的商業計劃,他們是為了自己的利益而劫持網絡”。[2018/1/9]
filter := bstream.NewBStreamWriter(0)// 對于 `differences` 列表中的每個數值,通過除以 2 ^ P 來獲得商和余數。for _, d := range differences { q := math.Floor(float64(d)/math.Exp2(float64(P))) r := d - uint64(math.Exp2(float64(P))*q) // 編碼商 for i := 0; i < int(q); i++ { filter.WriteBit(true) } filter.WriteBit(false) filter.WriteBits(r, P。很棒!現在我們可以輸出過濾器了,得到:
71d126b85aa79c9de56d55995aa292de0484b830680a735793a8c2260113148421279906f800c3b8c94ff37681fb1fd230482518c52df57437864023833f2f801639692646ddcd7976ae4f2e2a1ef58c79b3aed6a705415255e362581692831374a5e5e70d5501cdc0a52095206a15cd2eb98ac980c22466e6945a65a5b0b0c5b32aa1e0cda2545da2c4345e049b614fcad80b9dc9c903788163822f4361bbb8755b79c276b1cf7952148de1e5ee0a92f6d70c4f522aa6877558f62b34b56ade12fa2e61023abf3e570937bf379722bc1b0dc06ffa1c5835bb651b9346a270除了開頭的兩個字節,其余都跟我們在 bitcoind 中得到的過濾器完全一樣!為什么前面的 2 字節會有區別呢?因為 BIP 說 N 的值需要在 CompactSize 格式中編碼,并出現在過濾器的前面,這樣才能被接收者解碼。這是用下列辦法完成的:
fd := filter.Bytes()var buffer bytes.Bufferbuffer.Grow(wire.VarIntSerializeSize(uint64(N)) + len(fd))err = wire.WriteVarInt(&buffer, 0, uint64(N))if err != nil { return nil, err}_, err = buffer.Write(fd)if err != nil { return nil, err}如果我們現在再打印出過濾器,就會發現它跟 bitcoind 的結果完全一樣:
5571d126b85aa79c9de56d55995aa292de0484b830680a735793a8c2260113148421279906f800c3b8c94ff37681fb1fd230482518c52df57437864023833f2f801639692646ddcd7976ae4f2e2a1ef58c79b3aed6a705415255e362581692831374a5e5e70d5501cdc0a52095206a15cd2eb98ac980c22466e6945a65a5b0b0c5b32aa1e0cda2545da2c4345e049b614fcad80b9dc9c903788163822f4361bbb8755b79c276b1cf7952148de1e5ee0a92f6d70c4f522aa6877558f62b34b56ade12fa2e61023abf3e570937bf379722bc1b0dc06ffa1c5835bb651b9346a270成功!
不過,我個人的理解是,不需要把 N 加入過濾器中。如果你知道 P 的值,那你就能找出 N 的值。來看看我們是否能用第一個過濾器輸出恢復初始的數字列表:
b := bstream.NewBStreamReader(filter)var ( numbers []uint64 prevNum uint64)for { // 從字節串中讀取商。遇到的第一個 0 表示商的結尾 // 再遇到 0 之前,1 的數量就代表了商 var q uint64 c, err := b.ReadBit() if err != nil { return err } for c { q++ c, err = b.ReadBit() if errors.Is(err, io.EOF) { break } else if err != nil { return err } } // 隨后的 P 個比特編碼了余數 r, err := b.ReadBits(P) if errors.Is(err, io.EOF) { break } else if err != nil { return err } n := q*uint64(math.Exp2(float64(P))) + r num := n + prevNum numbers = append(numbers, num) prevNum = num}fmt.Println(numbers)上述操作會產生相同的數字列表,所以我們可以重構它而無需知曉 N 。所以我不確認將 N 加入過濾器的理由是什么。如果你知道為什么一定要加入 N ,請告訴我!
感謝閱讀!
登鏈社區
個人專欄
閱讀更多
金色早8點
Odaily星球日報
金色財經
Block unicorn
DAOrayaki
曼昆區塊鏈法律
作者:蔣海波 Berachain、Tenet、Mangate等項目希望對PoS機制進行改進,以讓流動性更加可持續、增加網絡的安全性、提高資金的利用率.
1900/1/1 0:00:00作者:Kyle Liu,Bing Ventures投資經理;翻譯:金色財經xiaozouStarkNet是一個采用zkSTARKs擴展以太坊的第二層解決方案.
1900/1/1 0:00:00作者:sed 來源:medium 編譯:金色財經,善歐巴去中心化金融 (DeFi) 已成為金融領域的一股變革力量,為用戶提供了無需中介機構即可進行借貸和投資的新機會.
1900/1/1 0:00:00摘要 數字加密生態與傳統金融互為需要。雖然在基礎設施不完備的情況下,結合面并不完全契合。兩者結合,目前還是能夠解決實際問題。單純區塊鏈技術的引入無法解決司法權的割裂.
1900/1/1 0:00:00近日,受SEC起訴幣安影響BNB價格大幅下挫。當BNB幣價降至255美元附近時,網上爆出去年10月BNB Bridge黑客事件的攻擊者在 Venus 上抵押的BNB頭寸即將面臨大規模清算風險.
1900/1/1 0:00:00上次[LSDFi Map]發布后發布后,我們的預測的產品大部分都已經出現,比如LST支持的穩定幣(R,TAI,USDL等).
1900/1/1 0:00:00