验证Kaspa链的完整性
作者:Shai Wyborski,Michael Sutton
本笔记本的目的是引导您完成验证卡斯帕链真实性的过程。也就是说,向您展示如何以加密方式验证Kaspa的当前UTXO集是从一个空的UTXO集合自然演变而来的,从而验证没有前提的断言。
正如你可能知道的,在主网推出大约两周后,一个重大错误导致网络分裂。该漏洞的影响在2021年11月22日开始出现,到2021年11日23日,网络完全支离破碎。为了在修复错误后重新启动网络,许多用户被要求在崩溃前提供他们的节点数据。开发人员检查了数据,以隔离大多数网络已知的最新可能的块,所选的块是带有哈希的块
0fca37ca667c2d550a6c4416数据9717e0927128c424fa4dbec436ab13aeef
其时间戳为
2021年11月22日,星期一,7:34:31。
我们称这个区块为检查点区块。
新节点与一个新的起源块硬连接,其UTXO集与上述块的UTXO集合相同,我们称之为硬连接起源。节点数据只能用于验证向下到硬连线起源的链。(你可以在这里、这里和这里阅读更多关于故障的信息,以及它是如何合并的。此外,仅从节点数据无法证明硬连线起源的UTXO集确实与检查点块的UT集匹配。)。
为了填补这一空白,我们要求社区在崩溃时存储数据目录的副本(例如此处可用)。有了这些可用的数据,上述所有内容都可以得到验证。我们强调,这些额外的数据并不被认为是真实的,它在这个过程中以一种不可伪造的方式进行了密码验证。
此外,我们使用比特币的完整性作为时间戳服务,以证明genesis块(原始和硬连线)是在建议的时间创建的,而不是更早。这相当于用今天的报纸拍照。这消除了使用弱硬件长时间挖掘网络,同时假装使用强硬件短时间挖掘网络的可能性。这部分过程不是证明链的真实性所必需的,而是用来证明时间戳操作背后没有隐藏的前提。
身份验证过程如下:
验证修剪块链的哈希值。也就是说,验证每个标头的哈希是否与存储在指向它的标头中的哈希匹配(这种检查看起来是同义重复的,例如“很明显”“哈希为x的块具有哈希x”。然而,依赖于用于索引标头的哈希与一致使用的哈希相同的假设是不安全的,实际上是高度可利用的)。
用硬连接到代码中的有效负载重构coinbase事务,并验证其哈希是否正确。
验证检查点块的哈希是否与Discord中公布的哈希匹配。
验证硬连线genesis coinbase中的硬连线比特币头是否与网络恢复运行的时间匹配。
从存储的数据目录中恢复检查点块的UTXO集,计算其哈希,并验证它是否与存储在硬连线genesis头中的UTXO-承诺相匹配。
重建原始genesis区块的coinbase交易,有效载荷提交给比特币区块哈希,并验证其哈希是否正确。
验证原始genesis coinbase中的硬连线比特币头是否与主网启动时间匹配。
从快照中恢复修剪头,并重复从硬连接的genesis块到原始genesis块的第一步。
验证原始genesis块中的UTXO承诺是否对应于一个空的UTXOset。
粗略地说,步骤1。和8。验证当前状态是从硬连线起源进化而来的,检查点起源是从原始起源进化而成的。步骤5。验证UTXO集合在检查点块和硬连线起源之间没有改变,证明当前状态是从硬连线起源演变而来的。步骤9。验证原始起源有一个空的UTXO集合。剩下的步骤证明,这两个起源区块并没有比我们声称的更早创建。总之,这提供了Kaspa状态完整性的密码证明(依赖于blake2b对链完整性的抗碰撞性,依赖于MuHash对空UTXO的抗冲突性,以及检查点和硬连线起源UTXO集的一致性,以及比特币的完整性以排除时间戳操作)。
我们现在将浏览完成所有这些操作的代码(代码是在linux机器上编写和运行的,在其他操作系统上运行可能需要调整)。
如果您打算在本地运行代码,可以使用以下命令确保您拥有所有必需的库:
pip install numpy pandas plywel protobuf==3.20.0 tqdm笔记本markupsafe==2.0.1
在Google Colab上运行
您也可以选择在Google Colab中运行代码。为此,您需要:
将完全同步的主网节点的datadir2压缩到datadir2.Zip
将datadir2.zip上传到Google Colab笔记本的“文件”部分
取消注释下面单元格中的行并运行命令
代码的第一部分并不特别有趣,只需注意,除了存储模块之外,所有导入的目录都是标准的Python目录,存储模块的代码在这里可用。该模块用于恢复和序列化来自Kaspa节点datadirs的数据,主要是一个protobuff包装器。
In [ ]:
# If running on Google Colab, uncomment then run these commands# Otherwise, skip this cell# # Install dependencies# !pip install numpy pandas plyvel protobuf==3.20.0 tqdm notebook markupsafe==2.0.1# !git clone https://github.com/kaspagang/kaspad-py-explorer# # Copy the py files into the local# !cp kaspad-py-explorer/src/*.py ./# # Download the pre_checkpoint_store and extract it# # This is used to check the genesis proof from the checkpoint hash# !apt install megatools# !megadl https://mega.nz/file/rOJmhLIR#5j7wko32Mh0MlsQnC9yVG6jCvPql7Isqcyvgh3kmxKk# !mkdir pre-checkpoint-data# !unrar e kaspa-data-22-11-21-correct-utxo-commit.rar pre-checkpoint-data# # 1. Zip your datadir2 from your fully synced node# # 2. Then upload your datadir2.zip into the Files section of this Colab notebook# # 3. Uncomment the line below and run it# !unzip datadir2.zip# !ls -al
In [1]:
import osimport numpy as npimport pandas as pdfrom store import *
The next code block implements the logic of hashing a header/transaction. It does nothing but serialize the contents of the structure and passing it to the standard implementation of blake. The dilligent reader might want to verify that the fields are serialized the same way in both implementations. However, this is not necessary, since if they are serialized differently and still produce the same hash this essentially means we found a collission in blake2b, which is believed to be collision resistant.
In [2]:
import hashlibimport struct# Relevant hashing functions as implemented by the golang # and rust kaspa codebases# Note: uses only standart hash libsdef transaction_hash(t):
hasher = hashlib.blake2b(digest_size=32, key=b"TransactionHash")
hasher.update(struct.pack(f"<HQ", t.version, len(t.inputs)))
for ti in t.inputs:
hasher.update(ti.previousOutpoint.transactionID.transactionId)
hasher.update(struct.pack(f"<IQ", ti.previousOutpoint.index,
len(ti.signatureScript)))
hasher.update(ti.signatureScript)
# Note: a subsequent HF added sig_op_count hashing here
hasher.update(struct.pack(f"<Q", ti.sequence))
hasher.update(struct.pack(f"<Q", len(t.outputs)))
for to in t.outputs:
hasher.update(struct.pack(f"<QHQ", to.value,
to.scriptPublicKey.version,
len(to.scriptPublicKey.script)))
hasher.update(to.scriptPublicKey.script)
hasher.update(struct.pack(f"<Q", t.lockTime))
hasher.update(t.subnetworkID.subnetworkId)
hasher.update(struct.pack(f"<QQ", t.gas, len(t.payload)))
hasher.update(t.payload)
return hasher.digest()def header_hash(h):
hasher = hashlib.blake2b(digest_size=32, key=b"BlockHash")
hasher.update(struct.pack(f"<HQ", h.version, len(h.parents)))
for level_parents in h.parents:
hasher.update(struct.pack(f"<Q", len(level_parents.parentHashes)))
for parent in level_parents.parentHashes:
hasher.update(parent.hash)
hasher.update(h.hashMerkleRoot.hash)
hasher.update(h.acceptedIDMerkleRoot.hash)
hasher.update(h.utxoCommitment.hash)
hasher.update(struct.pack(f"<QIQQQQ",
h.timeInMilliseconds,
h.bits,
h.nonce,
h.daaScore,
h.blueScore,
len(h.blueWork)))
hasher.update(h.blueWork)
hasher.update(h.pruningPoint.hash)
return hasher.digest()
The next code block implements the logic to perform checks 1. and 7. above. Each pruning block stores the hash of the next pruning block, this code verifies that all pruning blocks indeed hash correctly to the hash stored in the next pruning block. Note that this check is not actually performed yet.
In [3]:
# Asserts a verified chain of hashes from the given block to the given genesisdef assert_cryptographic_hash_chain_to_genesis(
store,
block_hash,
genesis_hash):
i = 0
while True:
if block_hash == genesis_hash:
print('Reached the queried genesis block: \n',
genesis_hash.hex(), 'via', i, 'pruning points')
return
header = store.get_raw_header(block_hash)
# Assert the block hash is correct
assert(header_hash(header) == block_hash)
block_hash = header.pruningPoint.hash
i += 1
The next two blocks copy the data required from the golang implementation. By following the links in the comments one can verify that they match the data used by nodes after the hard-fork. At the very bottom of the second block we see that the checkpoint block hash indeed matches the hash posted to Discord. We can also see that the payload contains the hash of a Bitcoin block with the following timestamp:
2021-11-25 19:53:36 GMT +2
which matches the time the network resumed operation. This completes checks 3. and 4. above, though note that this does not prove anything before check 2. is also complete.
In [4]:
# Take genesis hash from current go code# Golang ref: # https://github.com/kaspanet/kaspad/blob/master/domain/dagconfig/genesis.go#L56genesis_hash = bytes([
0x58, 0xc2, 0xd4, 0x19, 0x9e, 0x21, 0xf9, 0x10,
0xd1, 0x57, 0x1d, 0x11, 0x49, 0x69, 0xce, 0xce,
0xf4, 0x8f, 0x9, 0xf9, 0x34, 0xd4, 0x2c, 0xcb,
0x6a, 0x28, 0x1a, 0x15, 0x86, 0x8f, 0x29, 0x99])
In [5]:
# Build genesis's coinbase tx payload, which references the pre-halt checkpoint # Golang ref: # https://github.com/kaspanet/kaspad/blob/master/domain/dagconfig/genesis.go#L18genesis_tx_payload = bytes([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # Blue score
0x00, 0xE1, 0xF5, 0x05, 0x00, 0x00, 0x00, 0x00, # Subsidy
0x00, 0x00, # Script version
0x01, # Varint
0x00, # OP-FALSE
# ומה די עליך ועל אחיך ייטב בשאר כספא ודהבה למעבד כרעות אלהכם תעבדון
0xd7, 0x95, 0xd7, 0x9e, 0xd7, 0x94, 0x20, 0xd7,
0x93, 0xd7, 0x99, 0x20, 0xd7, 0xa2, 0xd7, 0x9c,
0xd7, 0x99, 0xd7, 0x9a, 0x20, 0xd7, 0x95, 0xd7,
0xa2, 0xd7, 0x9c, 0x20, 0xd7, 0x90, 0xd7, 0x97,
0xd7, 0x99, 0xd7, 0x9a, 0x20, 0xd7, 0x99, 0xd7,
0x99, 0xd7, 0x98, 0xd7, 0x91, 0x20, 0xd7, 0x91,
0xd7, 0xa9, 0xd7, 0x90, 0xd7, 0xa8, 0x20, 0xd7,
0x9b, 0xd7, 0xa1, 0xd7, 0xa4, 0xd7, 0x90, 0x20,
0xd7, 0x95, 0xd7, 0x93, 0xd7, 0x94, 0xd7, 0x91,
0xd7, 0x94, 0x20, 0xd7, 0x9c, 0xd7, 0x9e, 0xd7,
0xa2, 0xd7, 0x91, 0xd7, 0x93, 0x20, 0xd7, 0x9b,
0xd7, 0xa8, 0xd7, 0xa2, 0xd7, 0x95, 0xd7, 0xaa,
0x20, 0xd7, 0x90, 0xd7, 0x9c, 0xd7, 0x94, 0xd7,
0x9b, 0xd7, 0x9d, 0x20, 0xd7, 0xaa, 0xd7, 0xa2,
0xd7, 0x91, 0xd7, 0x93, 0xd7, 0x95, 0xd7, 0x9f,
# Bitcoin block hash 0000000000000000000b1f8e1c17b0133d439174e52efbb0c41c3583a8aa66b0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0b, 0x1f, 0x8e, 0x1c, 0x17, 0xb0, 0x13,
0x3d, 0x43, 0x91, 0x74, 0xe5, 0x2e, 0xfb, 0xb0,
0xc4, 0x1c, 0x35, 0x83, 0xa8, 0xaa, 0x66, 0xb0,
# Checkpoint block hash 0fca37ca667c2d550a6c4416dad9717e50927128c424fa4edbebc436ab13aeef
0x0f, 0xca, 0x37, 0xca, 0x66, 0x7c, 0x2d, 0x55,
0x0a, 0x6c, 0x44, 0x16, 0xda, 0xd9, 0x71, 0x7e,
0x50, 0x92, 0x71, 0x28, 0xc4, 0x24, 0xfa, 0x4e,
0xdb, 0xeb, 0xc4, 0x36, 0xab, 0x13, 0xae, 0xef,])# Bitcoin explorer link: # https://blockstream.info/block/0000000000000000000b1f8e1c17b0133d439174e52efbb0c41c3583a8aa66b0# # Kaspad version release: # https://github.com/kaspanet/kaspad/releases/tag/v0.11.5-2 assert(bytes.fromhex('0000000000000000000b1f8e1c17b0133d439174e52efbb0c41c3583a8aa66b0') ==
bytes([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0b, 0x1f, 0x8e, 0x1c, 0x17, 0xb0, 0x13,
0x3d, 0x43, 0x91, 0x74, 0xe5, 0x2e, 0xfb, 0xb0,
0xc4, 0x1c, 0x35, 0x83, 0xa8, 0xaa, 0x66, 0xb0,]))assert(bytes.fromhex('0fca37ca667c2d550a6c4416dad9717e50927128c424fa4edbebc436ab13aeef') ==
bytes([
0x0f, 0xca, 0x37, 0xca, 0x66, 0x7c, 0x2d, 0x55,
0x0a, 0x6c, 0x44, 0x16, 0xda, 0xd9, 0x71, 0x7e,
0x50, 0x92, 0x71, 0x28, 0xc4, 0x24, 0xfa, 0x4e,
0xdb, 0xeb, 0xc4, 0x36, 0xab, 0x13, 0xae, 0xef,]))
Next, we manually construct a coinbase transaction with the hash of the data above as a payload. Here, again, you could either verify that the computation is identical to the client code, or appeal to the fact that if it isn't, but the verification still passes, then we have identified a collision in blake2b.
In [6]:
# Build genesis's coinbase tx. # Golang ref: # https://github.com/kaspanet/kaspad/blob/master/domain/dagconfig/genesis.go#L51genesis_coinbase_tx = type('Transaction', (object,), {})()genesis_coinbase_tx.version = 0genesis_coinbase_tx.subnetworkID = type('SubnetworkId', (object,), {})()genesis_coinbase_tx.subnetworkID.subnetworkId = bytes.fromhex(
'0100000000000000000000000000000000000000')genesis_coinbase_tx.inputs = []genesis_coinbase_tx.outputs = []genesis_coinbase_tx.lockTime = 0genesis_coinbase_tx.gas = 0genesis_coinbase_tx.payload = genesis_tx_payload
The next block loads the ledger data from the synchronized node into the current_store
variable, as well as the snapshot data into the pre_checkpoint_store
variable.
In [7]:
# Comment these lines if using Google colab. Otherwise, update them to the correct pathspre_checkpoint_store = Store(r'/home/pool/data/kaspa-data-22-11-21-correct-utxo-commit')current_store = Store(r'/home/pool/.kaspad/kaspa-mainnet/datadir2')# Uncomment below if using Google Colab# pre_checkpoint_store = Store(r'./pre-checkpoint-data')# current_store = Store(r'./datadir2')
With all the required data and logic in place, we can start the verification process.
First, we obtain the genesis block and see that it hashes correctly.
In [8]:
genesis_header = current_store.get_raw_header(genesis_hash)# Assert the genesis hash is correctassert(header_hash(genesis_header) == genesis_hash)
We next verify that the hash of the coinbase transaction of the hardwired genesis block does match the hash of the coinbase transaction constructed above. Combined with the above, this completes checks 2-4.
In [9]:
# This shows that indeed current genesis refrences the checkpoint via the coinbase tx payloadassert(
transaction_hash(genesis_coinbase_tx) == genesis_header.hashMerkleRoot.hash)print(transaction_hash(genesis_coinbase_tx).hex()) print(genesis_header.hashMerkleRoot.hash.hex())
8ec898568c6801d13df4ee6e2a1b54b7e6236f671f20954f05306410518eeb32
8ec898568c6801d13df4ee6e2a1b54b7e6236f671f20954f05306410518eeb32
We next verify that the current chain of pruning points taken from the node leads to the hardwired genesis block. Completing check 1. above.
In [10]:
# Show that tips from current database link to genesistips, hst = current_store.tips()assert_cryptographic_hash_chain_to_genesis(current_store, tips[0], genesis_hash)
Reached the queried genesis block:
58c2d4199e21f910d1571d114969cecef48f09f934d42ccb6a281a15868f2999 via 210 pruning points
We now see that the checkpoint block from the snapshot has the same UTXO state commitment as the genesis block, completing check 5.
In [11]:
# Now we move to the pre-halt database and show that the checkpoint was mined# over the original genesis (with an empty UTXO-set commitment)checkpoint_hash = bytes.fromhex(
'0fca37ca667c2d550a6c4416dad9717e50927128c424fa4edbebc436ab13aeef')checkpoint_header = pre_checkpoint_store.get_raw_header(checkpoint_hash)# Assert the checkpoint hash is correctassert(header_hash(checkpoint_header) == checkpoint_hash)# Show that genesis and the checkpoint share the same UTXO commitmentassert(genesis_header.utxoCommitment.hash == checkpoint_header.utxoCommitment.hash)print(genesis_header.utxoCommitment.hash.hex()) print(checkpoint_header.utxoCommitment.hash.hex())# In order to obtain a clean datadir with the checkpoint UTXO-set, one can download# the post-halt binary from:# https://github.com/kaspanet/kaspad/releases/tag/v0.11.5-2# and run it with the following command line:# kaspad --outpeers=0 --listen=0.0.0.0:16333 --norpc --appdir=/home/pool/data/mainnet-restart-dir# This version has the UTXO-set embedded in it. Turning the node on loads the UTXO set into the# database and verifies it versus the genesis/checkpoint UTXO commitment
710f27df423e63aa6cdb72b89ea5a06cffa399d66f167704455b5af59def8e20
710f27df423e63aa6cdb72b89ea5a06cffa399d66f167704455b5af59def8e20
Next, we recover the hash of the original genesis block, reconstruct the commitment in the payload of its coinbase, and see that it hashes correctly, completing check 6.
We can now look up the hardwired Bitcoin header hash to see that it has timestamp 2021-11-07 16:55:30 GMT +2
corresponding to the launch date of the mainnet, completing check 7.
In [12]:
# Golang ref from initial mainnet version: # https://github.com/kaspanet/kaspad/blob/v0.11.0/domain/dagconfig/genesis.go#L53C2-L56C49original_genesis = bytes([
0xca, 0xeb, 0x97, 0x96, 0x0a, 0x16, 0x0c, 0x21,
0x1a, 0x6b, 0x21, 0x96, 0xbd, 0x78, 0x39, 0x9f,
0xd4, 0xc4, 0xcc, 0x5b, 0x50, 0x9f, 0x55, 0xc1,
0x2c, 0x8a, 0x7d, 0x81, 0x5f, 0x75, 0x36, 0xea,])original_genesis_tx_payload = bytes([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # Blue score
0x00, 0xE1, 0xF5, 0x05, 0x00, 0x00, 0x00, 0x00, # Subsidy
0x00, 0x00, # Script version
0x01, # Varint
0x00, # OP-FALSE
# ומה די עליך ועל אחיך ייטב בשאר כספא ודהבה למעבד כרעות אלהכם תעבדון
0xd7, 0x95, 0xd7, 0x9e, 0xd7, 0x94, 0x20, 0xd7,
0x93, 0xd7, 0x99, 0x20, 0xd7, 0xa2, 0xd7, 0x9c,
0xd7, 0x99, 0xd7, 0x9a, 0x20, 0xd7, 0x95, 0xd7,
0xa2, 0xd7, 0x9c, 0x20, 0xd7, 0x90, 0xd7, 0x97,
0xd7, 0x99, 0xd7, 0x9a, 0x20, 0xd7, 0x99, 0xd7,
0x99, 0xd7, 0x98, 0xd7, 0x91, 0x20, 0xd7, 0x91,
0xd7, 0xa9, 0xd7, 0x90, 0xd7, 0xa8, 0x20, 0xd7,
0x9b, 0xd7, 0xa1, 0xd7, 0xa4, 0xd7, 0x90, 0x20,
0xd7, 0x95, 0xd7, 0x93, 0xd7, 0x94, 0xd7, 0x91,
0xd7, 0x94, 0x20, 0xd7, 0x9c, 0xd7, 0x9e, 0xd7,
0xa2, 0xd7, 0x91, 0xd7, 0x93, 0x20, 0xd7, 0x9b,
0xd7, 0xa8, 0xd7, 0xa2, 0xd7, 0x95, 0xd7, 0xaa,
0x20, 0xd7, 0x90, 0xd7, 0x9c, 0xd7, 0x94, 0xd7,
0x9b, 0xd7, 0x9d, 0x20, 0xd7, 0xaa, 0xd7, 0xa2,
0xd7, 0x91, 0xd7, 0x93, 0xd7, 0x95, 0xd7, 0x9f,
# Bitcoin block hash 00000000000000000001733c62adb19f1b77fa0735d0e11f25af36fc9ca908a5
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x73, 0x3c, 0x62, 0xad, 0xb1, 0x9f,
0x1b, 0x77, 0xfa, 0x07, 0x35, 0xd0, 0xe1, 0x1f,
0x25, 0xaf, 0x36, 0xfc, 0x9c, 0xa9, 0x08, 0xa5,])# Bitcoin explorer link: # https://blockstream.info/block/00000000000000000001733c62adb19f1b77fa0735d0e11f25af36fc9ca908a5# # Kaspad mainnet launch version release: # https://github.com/kaspanet/kaspad/releases/tag/v0.11.0 assert(bytes.fromhex('00000000000000000001733c62adb19f1b77fa0735d0e11f25af36fc9ca908a5') ==
bytes([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x73, 0x3c, 0x62, 0xad, 0xb1, 0x9f,
0x1b, 0x77, 0xfa, 0x07, 0x35, 0xd0, 0xe1, 0x1f,
0x25, 0xaf, 0x36, 0xfc, 0x9c, 0xa9, 0x08, 0xa5,]))# Load the full genesis header from the pre-halt storeoriginal_genesis_header = pre_checkpoint_store.get_raw_header(original_genesis)assert(header_hash(original_genesis_header) == original_genesis)# Build genesis's coinbase tx. # Golang ref: # https://github.com/kaspanet/kaspad/blob/v0.11.0/domain/dagconfig/genesis.go#L47tx = type('Transaction', (object,), {})()tx.version = 0tx.subnetworkID = type('SubnetworkId', (object,), {})()tx.subnetworkID.subnetworkId = bytes.fromhex(
'0100000000000000000000000000000000000000')tx.inputs = []tx.outputs = []tx.lockTime = 0tx.gas = 0tx.payload = original_genesis_tx_payload# This shows that indeed the original genesis references the# bitcoin block mined a few minutes before launch via the coinbase tx payloadassert(
transaction_hash(tx) == original_genesis_header.hashMerkleRoot.hash)
Next, we verify that the pruning headers chain starting from the checkpoint block does indeed lead to the original genesis block, completing check 8.
In [13]:
assert_cryptographic_hash_chain_to_genesis(
pre_checkpoint_store,
checkpoint_hash,
original_genesis)
Reached the queried genesis block:
caeb97960a160c211a6b2196bd78399fd4c4cc5b509f55c12c8a7d815f7536ea via 5 pruning points
Finally, we create a fresh MuHash containing an empty set, and verify that the UTXO commitment in the original genesis block matches this hash. This completes check 9. and the verification process.
In [14]:
# Show that original genesis has an empty UTXO-set commitment# Golang ref: https://github.com/kaspanet/go-muhash/blob/main/muhash.go#L32empty_muhash_hash = bytes([
0x54, 0x4e, 0xb3, 0x14, 0x2c, 0x0, 0xf, 0xa,
0xd2, 0xc7, 0x6a, 0xc4, 0x1f, 0x42, 0x22, 0xab,
0xba, 0xba, 0xbe, 0xd8, 0x30, 0xee, 0xaf, 0xee,
0x4b, 0x6d, 0xc5, 0x6b, 0x52, 0xd5, 0xca, 0xc0])assert(original_genesis_header.utxoCommitment.hash == empty_muhash_hash)print(original_genesis_header.utxoCommitment.hash.hex()) print(empty_muhash_hash.hex())
544eb3142c000f0ad2c76ac41f4222abbababed830eeafee4b6dc56b52d5cac0
544eb3142c000f0ad2c76ac41f4222abbababed830eeafee4b6dc56b52d5cac0
Thank you for taking the time to authenticate the integrity of Kaspa.
In [15]:
# Close the opened resourcespre_checkpoint_store.close()current_store.close()
感动 | 同情 | 无聊 | 愤怒 | 搞笑 | 难过 | 高兴 | 路过 |
相关文章
-
没有相关内容