作者: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] 之間的數字。這些數字在這個范圍內是均勻分布的。這就意味著,在排序之后,我們將得到一個這樣的圖(在非常非常理想的情況下):
NFT抵押借貸協議BendDAO提案BIP#9投票開啟:金色財經報道,據NFT 抵押借貸協議 BendDAO 聯合創始人 @CodeInCoffee 在社交媒體透露,提案 BIP#9 投票已經正式開啟,本次投票時間只有 24 小時,旨在修改部分協議參數來解決流動性危機。包括將清算閾值調整為 70%、將拍賣周期調整為 4 小時、將利息基礎利率調整為 20%、以及當發生壞賬時 BendDAO 社區可投票決定如何處理。截至目前,有 1500 萬枚 veBEND Token 被用于投票且全部為贊成票,反對票比例0%,棄權票比例為 0%。[2022/8/22 12:41:43]
首先,這非常棒,因為我們已經大大縮減了每一個對象的指紋的大小。現在每個對象都只是一個數字了。那么,我們的新過濾器看起來就像這樣:
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在我們轉移到一個更現實的案例之前,我們來看看是否可以靠這個過濾器恢復我們的最初的列表。
現在我們擁有的是 “0000100001000010000100001000010000100001000010000”。我們知道 Golomb-Rice 編碼,也知道 M 為 5(因為這將是公開知識,每一個使用這種過濾器構造的人都會知曉)。因為我們知道 M 為 5,所以我們知道會有 3 個比特來編碼余數。所以,我們可以將這個過濾器轉化成下列的 “商-余數” 數組列表:
比特幣社區新提案提議改進BIP審議流程:金色財經報道,比特幣核心開發者Jeremy Rubin提出比特幣改進提案BIP119,以提高比特幣去中心化決策的質量。目前該提案正在討論中。該BIP提議對BIP審議流程進行改變,即為BIP文檔提出一套高標準,并采用這些開放標準作為事實上的最低質量標準,以便BIP被考慮進行更廣泛的討論。此外,該提案建議設立一個提案跟蹤器以提高BIP流程的明確性,每個提案跟蹤器都應包含以下部分:歷史、描述、資源、成本和風險、收益、替代方案、激活和事后分析。(Bitcoin Magazine)[2022/5/5 2:51:49]
[(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 輸出。我們也會刪除重復數據。如果有兩個相同的腳本公鑰,我們只會向過濾器添加一次。
用友網絡擬募資建設融合區塊鏈等技術的商業創新平臺YonBIP:用友網絡科技股份有限公司(600588.SH)發布2020年度非公開發行A股股票預案。本次募投項目擬用于建設用友商業創新平臺YonBIP。據悉。本項目建設期,將融合人工智能、大數據、云計算、區塊鏈、物聯網等新技術。在其供應鏈云總體應用架構中,利用區塊鏈技術實現產業鏈交易全程可信可追溯,利用大數據及人工智能技術,實現智能預測、智能預警。(證券日報)[2020/7/1]
// 我們希望向過濾器添加的對象的列表// 包括所有被花費的腳本公鑰,以及每一個輸出的腳本公鑰// 我們使用了 “圖(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 比特編碼成二進制。
ZIL漲幅13.08% 漲幅排名huobipro第一名:根據huobipro數據顯示,BTC交易區目前漲幅排名第一的是ZIL,漲幅達到13.08%,ZIL現價為1.05元人民幣。Zilliqa(ZIL)作為一個新的公有區塊鏈,著重為高吞吐量的應用程序提供運行平臺。它將分片技術從理論變為實踐,運用創新的密碼技術和共識協議,提供隨著網絡增容而不斷提高的交易處理能力。[2018/5/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
曼昆區塊鏈法律
2023年6月27日,Chibi Finance團隊實施了一起退出騙局,導致投資者資金損失超過100萬美元.
1900/1/1 0:00:00編譯:Nick 非同質化代幣NFT在 2021 年經歷了一波熱潮,許多人選擇將這些彩色圖片作為他們Web3身份的象征.
1900/1/1 0:00:00作者:zkSync;編譯:Kate, Marsbit 今天,我們很高興地分享我們實現這一使命的擴展愿景:ZK堆棧(ZK Stack),一個用于構建ZK驅動的主權鏈的模塊化框架.
1900/1/1 0:00:00作者:Vitalik,以太坊創始人;翻譯:金色財經cryptonaitive在《以太坊生態系統需要三個技術轉型》一文中.
1900/1/1 0:00:00作者:Artist、Hins、Freya、Chance、Chad;導師指導:Jademont、Elaine、Bill@Waterdrip Capital本份研報旨在讓讀者深入了解Web3分布式存.
1900/1/1 0:00:00來源:上海市科學技術委員會“元宇宙”是未來虛擬世界與現實社會相交互的重要平臺。為貫徹落實《上海市建設具有全球影響力的科技創新中心“十四五”規劃》和《上海市培育“元宇宙”新賽道行動方案(2022-.
1900/1/1 0:00:00