前往,中间状态SHA-256哈希

4
例如,有128字节的数据:
00000001c570c4764aadb3f09895619f549000b8b51a789e7f58ea750000709700000000103ca064f8c76c390683f8203043e91466a7fcc40e6ebc428fbcc2d89b574a864db8345b1b00b5ac00000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000

如果想在它上面执行SHA-256哈希,需要将其分成两个64字节的数据并对它们进行单独哈希,然后再将结果一起哈希。如果经常更改数据的后半部分中的某些位,可以简化计算并仅对数据的前半部分进行一次哈希。在Google Go语言中怎样实现呢?我尝试了调用

func SingleSHA(b []byte)([]byte){
    var h hash.Hash = sha256.New()
    h.Write(b)
    return h.Sum()
}

但是,不是得到正确的答案

e772fc6964e7b06d8f855a6166353e48b2562de4ad037abc889294cea8ed1070

我收到了

12E84A43CBC7689AE9916A30E1AA0F3CA12146CBF886B60103AEC21A5CFAA268

在讨论比特币论坛的问题时,有人提到获取中间状态哈希可能会出现一些问题。

如何在Google Go中计算中间状态SHA-256哈希?


1
在Go 1.0.3中,Sum接受一个字节切片,因此您必须编写h.Sum([]byte{})来返回结果。 - emicklei
3
h.Sum(nil)也是有效的,@emicklei :-) - elimisteve
4个回答

6

比特币相关的字节操作有点棘手,因为它们倾向于随意切换字节顺序。首先,我们需要取出代表的初始[]byte数组。

00000001c570c4764aadb3f09895619f549000b8b51a789e7f58ea750000709700000000103ca064f8c76c390683f8203043e91466a7fcc40e6ebc428fbcc2d89b574a864db8345b1b00b5ac00000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000

然后,我们将数组的前一半分离出来,得到:

00000001c570c4764aadb3f09895619f549000b8b51a789e7f58ea750000709700000000103ca06 4f8c76c390683f8203043e91466a7fcc40e6ebc428fbcc2d8

接下来,我们需要交换一些字节。我们将每个4字节的片段中的字节顺序翻转,从而得到:

0100000076C470C5F0B3AD4A9F619598B80090549E781AB575EA587F977000000000000064A03C10396CC7F820F8830614E94330C4FCA76642BC6E0ED8C2BC8F

这是我们将用于计算中间状态的数组。现在,我们需要修改文件hash.go,添加到type Hash interface中:

Midstate() []byte

请修改文件sha256.go,添加以下函数:

func (d *digest) Midstate() []byte {
    var answer []byte
    for i:=0;i<len(d.h);i++{
        answer=append(answer[:], Uint322Hex(d.h[i])...)
    }
    return answer
}

在这里,Uint322Hex将一个 uint32 变量转换成一个 []byte 变量。有了这些,我们就可以调用:

var h BitSHA.Hash = BitSHA.New()
h.Write(Str2Hex("0100000076C470C5F0B3AD4A9F619598B80090549E781AB575EA587F977000000000000064A03C10396CC7F820F8830614E94330C4FCA76642BC6E0ED8C2BC8F"))
log.Printf("%X", h.Midstate())

Str2Hex函数将string转换为[]byte。结果如下:

69FC72E76DB0E764615A858F483E3566E42D56B2BC7A03ADCE9492887010EDA8

记住正确答案:

e772fc6964e7b06d8f855a6166353e48b2562de4ad037abc889294cea8ed1070

我们可以进行比较:
69FC72E7 6DB0E764 615A858F 483E3566 E42D56B2 BC7A03AD CE949288 7010EDA8
e772fc69 64e7b06d 8f855a61 66353e48 b2562de4 ad037abc 889294ce a8ed1070

因此,我们可以看到,我们只需要在每个4字节的片段中交换字节顺序,就可以得到比特币池和矿工使用的正确“中间状态”(直到它由于被弃用而不再需要)。


你介意详细说明一下如何从0100000076C470C5F0B3AD4A9F619598B80090549E781AB575EA587F977000000000000064A03C10396CC7F820F8830614E94330C4FCA76642BC6E0ED8C2BC8F转换为69FC72E76DB0E764615A858F483E3566E42D56B2BC7A03ADCE9492887010EDA8,对于不懂Go的人来说。看起来你正在迭代十六进制(从字符串十六进制转换为原始十六进制),使用无符号整数进行某些操作,并将其转换回十六进制。我不确定这些整数来自哪里,也不知道你在做什么...任何帮助都将不胜感激! - JacobEvelyn
@ConstableJoe 对不起,我担心一些魔法可能是由SHA算法内部完成的。此外,我已经很久没有玩过这个了。让我们看看,我将十六进制数组放入Write函数中以将其加载到SHA中。算法在Write函数中计算数字,并将它们存储在无符号整数数组中。我获取它们的值并将它们转换为十六进制数组。我想那就是这样了。 - ThePiachu
谢谢你的帮助。我会继续努力。 - JacobEvelyn
@ThePiachu 反转每 4 字节的字节顺序是必要的步骤吗?看起来结果会是每 4 个字节反转一次。 - xtrinch

3
您拥有的Go代码是计算字节流的sha256的正确方法。
最有可能的情况是,您想要做的不是sha256。 具体来说:
将其分为两个64位数据,并在对结果进行哈希之前单独对它们进行哈希。 如果经常更改数据的后半部分中的某些位,则可以简化计算并仅一次哈希数据的前半部分。
这不是计算sha256的有效方法(例如,请阅读http://doc.golang.org/src/pkg/crypto/sha256/sha256.go以查看sha256是如何在数据块上执行其工作的,必须填充等)。
您所描述的算法计算了某些内容,但不是sha256。
由于您知道预期值,因此您可能在另一种语言中拥有算法的某些参考实现,因此只需逐行将其移植到Go即可。
最后,这是一个可疑的优化。128位等于16字节。哈希成本通常与数据大小成比例。在16字节时,成本非常小,尝试聪明地将数据分成8字节部分的额外工作可能会比您节省的成本更高。

抱歉,我是指字节而不是比特。将使用此数据的实现仅用于对数据进行哈希,更改字符串的后半部分,因此计算中间状态可节省相当大的计算任务。 - ThePiachu
我会对“相当大的部分”进行基准测试。对我来说,64字节与128字节之间的差别微不足道,特别是考虑到sha256的工作方式。Sha256在最小56字节的块上操作。你不想在128字节上顺序执行sha256,而是想在64字节上执行(填充为2*56即112),以便对一半数据的sha256进行操作,再对32+32(32是sha256校验和的大小)进行操作,加上设置额外sha256进程的开销。实际上,在128字节上执行它应该更快(结果不等同)。 - Krzysztof Kowalczyk

2
sha256.go 中,在函数Sum()的开头,实现正在复制SHA256状态。 SHA256的底层数据类型(结构体digest)是私有的,只在sha256包中可见。
建议您制作自己的sha256.go文件副本(这是一个小文件)。然后添加一个Copy()函数来保存digest的当前状态:
func (d *digest) Copy() hash.Hash {
    d_copy := *d
    return &d_copy
}

然后只需调用Copy()函数保存中间状态SHA256哈希。


实际上,要调用Copy()函数,还需要更改hash.go文件以更改hash.Hash接口,但即使在那之后,我得到的结果仍然是[BE7E1F35 A19D5938 D8E6A17B 6BDF4738 603BB417 9AE97BCE FAED662 C11CB2F7]。 - ThePiachu
好的,让我们看看...事实证明比特币在它们的字节序方面有些棘手,在输入被馈送到SHA函数之前会进行很多奇怪的字节交换。最终,这个解决方案经过一些修改就可以工作了。 - ThePiachu

0

我在使用Intel i5 2.70 GHz CPU时对你的128字节数据进行了两个Go基准测试。第一,我将所有128字节写入SHA256哈希函数并读取总和,重复1,000次,总共约需要9,285,000纳秒。第二,我首先将前64字节写入SHA256哈希函数,然后将后64字节写入SHA256哈希函数的一份副本中,并读取总和,重复1,000次,总共约需6,492,371纳秒。第二个基准测试假设前64个字节不变,比第一个基准测试运行时间缩短了30%。

使用第一种方法,在购买更快的CPU之前,您可以每天计算约9,305,331,179个SHA256 128字节的哈希值。使用第二种方法,在假设前64个字节连续1,000次不变的情况下,每天可以计算约13,307,927,103个SHA256 128字节的哈希值,而无需购买更快的CPU。您需要计算多少个SHA256 128字节的哈希值?有多少个SHA256 128字节的哈希值的前64个字节是不变的?

您运行了哪些基准测试,结果如何?


我主要是根据这里的一个话题回复 - https://bitcointalk.org/index.php?topic=51281.msg612224#msg612224。一般来说,实现与比特币挖掘有关,具体涉及到getwork协议(也在链接的话题中解释)。如果它被用于客户端,并且Go支持OpenCL,则操作顺序可以达到每秒10^9个哈希(如果达到当前使用的挖掘软件的速度)- https://en.bitcoin.it/wiki/Mining_hardware_comparison#Multi-Card_Setups。 - ThePiachu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接