為什麼這個醜陋的公式從塊頭中的“nBits”計算“目標”:目標 = 係數 * 256**(指數 3)
為什麼這個醜陋的公式從塊頭中的“nBits”計算“目標”:目標 = 係數 * 256**(指數 3)。
為什麼不?:目標 = 係數 * 256**(指數)。減去 3 的需要是什麼。我們需要的是能夠生成 256 位長的數字和足夠的精度(我們已經有 3 個字節專用於係數)
更好的是,為什麼不呢?: 目標 = 係數 * 2**(指數)
TL;DR 該公式來自將算法轉換為數學公式。
nBits
將目標編碼為第一個字節作為最終目標的大小,然後是該目標的 3 個最高有效字節。這可以轉化為那個瘋狂的公式。該公式是用於壓縮的實際算法的數學表示。要理解公式為何如此,我們需要先看一下將 256 位目標編碼為 4 字節的原始程式碼
nBits
。這是來自比特幣/比特幣源樹中的 0.1.5,但在 0.1.0 中是相同的:unsigned int GetCompact() const { unsigned int nSize = BN_bn2mpi(this, NULL); std::vector<unsigned char> vch(nSize); nSize -= 4; BN_bn2mpi(this, &vch[0]); unsigned int nCompact = nSize << 24; if (nSize >= 1) nCompact |= (vch[4] << 16); if (nSize >= 2) nCompact |= (vch[5] << 8); if (nSize >= 3) nCompact |= (vch[6] << 0); return nCompact; }
現在首先要看的是這個
BN_bn2pi
函式。早期版本的比特幣使用 OpenSSL 的 Bignum 模組進行這些計算。所以我們需要查看OpenSSL 的文件來了解這個功能。從文件中,我們讀到:BN_bn2mpi() 和 BN_mpi2bn() 將 BIGNUM 從和轉換為一種格式,該格式由以字節為單位的數字長度表示為 4 字節大端數字,數字本身採用大端格式,其中最高有效位表示負數(具有 MSB 集的數字表示以空字節為前綴)。
BN_bn2mpi() 儲存at to 的表示,其中to 必須足夠大以保存結果。大小可以通過呼叫 BN_bn2mpi(a, NULL) 來確定。
這意味著
BN_bn2mpi
將以格式將 Bignum 放入緩衝區<4 byte size> | <variable length number>
呼叫
BN_bn2mpi
withNULL
作為緩衝區將返回該緩衝區需要的字節數。這對於了解為緩衝區分配多少字節很有用。所以讓我們回到
GetCompact
函式。我們看到BN_bn2mpi(this, NULL);
。這意味著nSize
現在是編碼 Bignum 所需的大小。因為這種編碼還包括數字本身的大小,所以我們稍後會看到nSize -= 4;
哪些集合nSize
是實際數字本身的大小。
BN_bn2mpi(this, &vch[0]);
現在將 Bignum 編碼為第一次呼叫vch
指定的大小。BN_bn2mpi
重要的是要記住前 4 個字節是數字的長度,因此實際數字本身從索引 4 (vch[4]
) 開始。最後,構造緊數本身。
nSize << 24
只是將最右邊的字節設置為最nSize
左邊的字節nCompact
。然後該函式設置其餘的緊湊數。每個字節只是被移動到它在 4 字節 int 中的最終位置,並用 OR’dnCompact
來設置它。這些if
語句是針對目標太低以至於它以比緊湊大小本身更少的字節編碼的情況。查看這個函式,我們了解到緊湊編碼實際上只是一個字節表示目標的長度,而 3 個字節表示該目標中的 3 個最高有效字節。如果您查看
SetCompact
接受一個緊湊型nBits
並將其轉換為 Bignum 的函式,您會發現它只是 的倒數GetCompact
,所以我不會解釋它。現在的問題是,我們如何得到看起來很瘋狂的公式?我們是如何從這個嚴格來說只是字節操作的程式碼變成一個數學公式的?
在您的範例中,基於上述算法,我們知道最終數字將是
0x00ffff0000000000000000000000000000000000000000000000000000
。我們希望從 中獲得的前 3 個字節nBits
單獨存在,所以讓我們將這個數字除以:0x00ffff0000000000000000000000000000000000000000000000000000 / 0x00ffff = 0x010000000000000000000000000000000000000000000000000000
現在
0x010000000000000000000000000000000000000000000000000000
是 26 字節長,我們可以看到這是因為我們已經刪除了前 3 個字節。現在我們如何從nBits
? 我們可以取第一個字節,代表整個事物的長度,然後從中減去 3。然後為了得到這個數字,因為一個字節中有 256 個可能的值,我們這樣做256**(0x1d-3)
我們可以進一步擴展這個,因為
256 = 2**8
有些人喜歡用 2 的冪表示的東西。所以這可以變成2**(8*0x1d-3)
。因此0x010000000000000000000000000000000000000000000000000000 = 2**(8*0x1d-3)
所以:
0x00ffff0000000000000000000000000000000000000000000000000000 = 0x010000000000000000000000000000000000000000000000000000 * 0x00ffff = 2**(8*0x1d-3) * 0x00ffff
最後的結果是
2**(8*0x1d-3) * 0x00ffff
。而且,當然,這是一概而論的。那麼一個半相關的問題是為什麼 Bignum 編碼有一個前導
0x00
字節?以更高的精度表示這個最大目標的編碼0x1cffffff
本來可以避免這個無關0x00
的字節。那個前導
0x00
字節完全是因為 Bignum 是有符號的,而目標是一個正整數。最高有效位指示它是否為負。如果目標已經被編碼0xffff....
,那麼解碼這將意味著目標是負的,這是錯誤的。所以編碼放了一個前導0x00
,使數字保持正數。
我之所以回答這個問題,是因為我看到 Murch 在推特上發布了 Andrew Chow 的答案。我在寫我的書《比特幣程式》時對此進行了一些研究,但它沒有寫進書中,所以我認為這是一個放置它的好地方。
您可以將“位”視為有損壓縮的大 base-256 數字(即,每個 8 位字節都是一個數字)。我們在這裡談論的數字是 32 位數字,作為 SHA256(或 HASH256)雜湊的 base-256。
這裡的“指數”實際上只是我們正在壓縮的 base-256 數字的長度(以字節為單位)。係數是前三位有效數字。由於我們希望能夠對負數進行編碼,因此特別保留了最高位以指示負數。因此,如果設置了最高位,我們將 0 字節放在前面(這是完成的,例如在 DER 編碼中)。
真的,它是:
nBits =(前 3 位)* 256^(基數 256 的數字長度)
當你這樣看時,它更合乎邏輯。