Go, хэш SHA-256 среднего штата

Имея 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

При обсуждении этого вопроса на биткойн-форуме кто-то упомянул, что могут быть некоторые проблемы с получением этого среднего хэша.

Как рассчитать хэш SHA-256 среднего состояния в Google Go?


person ThePiachu    schedule 12.02.2012    source источник
comment
В Go 1.0.3 Sum принимает байтовый срез, поэтому вы должны написать h.Sum([]byte{}) для возврата   -  person emicklei    schedule 19.02.2013
comment
h.Sum(nil) тоже подходит, @emicklei :-)   -  person elimisteve    schedule 30.03.2013


Ответы (4)


Операции с байтами, связанные с биткойнами, немного сложны, поскольку они имеют тенденцию переключать порядок байтов по прихоти. Во-первых, мы берем исходный массив []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 байтов, и у нас будет правильное «среднее состояние», используемое биткойн-пулами и майнерами (до тех пор, пока оно больше не будет нужно из-за того, что оно устарело).

person ThePiachu    schedule 17.02.2012
comment
Не могли бы вы рассказать, как вы перешли от 0100000076C470C5F0B3AD4A9F619598B80090549E781AB575EA587F977000000000000064A03C10396CC7F820F8830614E94330C4FCA76642BC6E0ED8C2BC8F к 69FC72E76DB0E764615A858F483E3566E42D56B2BC7A03ADCE9492887010EDA8 для тех, кто не знает Go? Похоже, вы перебираете шестнадцатеричный код (преобразованный из строкового шестнадцатеричного в необработанный шестнадцатеричный), делаете что-то с целыми числами без знака и конвертируете обратно в шестнадцатеричный. Я не уверен, откуда берутся целые числа или что вы с ними делаете... буду очень признателен за любую помощь! - person JacobEvelyn; 07.10.2012
comment
@ConstableJoe Извините, я беспокоюсь, что часть магии может быть сделана внутри алгоритма SHA. Более того, я давно не играл с этим. Давайте посмотрим, я поместил шестнадцатеричный массив в функцию записи, чтобы загрузить его в SHA. Алгоритм обрабатывает числа в функции Write и сохраняет их в массиве целых чисел без знака. Я получаю их значения и превращаю их в массив гексов. Я думаю, это все. - person ThePiachu; 07.10.2012
comment
Спасибо за помощь. Я буду продолжать пыхтеть. - person JacobEvelyn; 08.10.2012

Код Go, который у вас есть, — это правильный способ вычислить sha256 потока байтов.

Скорее всего, ответ заключается в том, что вы хотите сделать не sha256. Конкретно:

нужно было бы разделить его на два 64-битных данных и хешировать их по отдельности, прежде чем хешировать результаты вместе. Если бы нужно было часто менять некоторые биты во второй половине данных, можно было бы упростить вычисления и хэшировать первую половину данных только один раз.

не является допустимым способом вычисления sha256 (прочитайте http://doc.golang.org/src/pkg/crypto/sha256/sha256.go, чтобы, например, увидеть, что sha256 выполняет свою работу с блоками данных, которые должны быть дополнены и т. д.).

Описанный вами алгоритм что-то вычисляет, но не ша256.

Поскольку вы знаете ожидаемое значение, у вас, вероятно, есть эталонная реализация вашего алгоритма на другом языке, поэтому просто сделайте построчный порт на Go.

Наконец, сомнительная оптимизация в любом случае. 128 бит это 16 байт. Стоимость хеширования обычно пропорциональна размеру данных. При 16 байтах стоимость настолько мала, что дополнительная работа по умному разбиению данных на 8-байтовые части, вероятно, будет стоить больше, чем то, что вы сэкономили.

person Krzysztof Kowalczyk    schedule 12.02.2012
comment
Извините, я имел в виду байты, а не биты. Реализация, которая будет использовать эти данные, предназначена только для хеширования данных вместе, изменяя вторую половину строки, поэтому вычисление промежуточного состояния экономит значительную часть вычислительной задачи. - person ThePiachu; 12.02.2012
comment
Я бы сравнил значительную часть. Для меня 64 против 128 байт не имеет значения, особенно учитывая, как работает sha256. Sha256 работает с блоками не менее 56 байт. Вместо того, чтобы выполнять последовательный sha256 на 128 байтах, вы хотите сделать это на 64 байтах (дополненных до 2 * 56, т.е. 112) для sha256 половины данных плюс снова 32 + 32 (32 - размер контрольной суммы sha256) плюс накладные расходы настройка дополнительного процесса sha256. На самом деле делать это на 128 байтах должно быть быстрее (и результаты не эквивалентны). - person Krzysztof Kowalczyk; 12.02.2012

В sha256.go в начале функции Sum() реализация делает копия состояния SHA256. Базовый тип данных SHA256 (структура digest) является частным для пакета sha256.

Я бы предложил сделать вашу собственную копию файла sha256.go (это небольшой файл). Затем добавьте функцию Copy() для сохранения текущего состояния дайджеста:

func (d *digest) Copy() hash.Hash {
    d_copy := *d
    return &d_copy
}

Затем просто вызовите функцию Copy(), чтобы сохранить хэш среднего состояния SHA256.

person Community    schedule 12.02.2012
comment
На самом деле, нужно также изменить файл hash.go, чтобы изменить интерфейс hash.Hash, чтобы можно было вызывать Copy(), но даже после этого я получаю результат [BE7E1F35 A19D5938 D8E6A17B 6BDF4738 603BB417 9AE97BCE FAED662 C11CB2F7]. - person ThePiachu; 13.02.2012
comment
Хорошо, давайте посмотрим... Оказывается, биткойны немного сложны с их порядком байтов, и происходит много странного обмена байтами, прежде чем входные данные будут переданы в функцию SHA. В конце концов, это решение с некоторыми изменениями будет работать. - person ThePiachu; 17.02.2012

Я провел два теста Go на ваших 128 байтах данных, используя процессор Intel i5 2,70 ГГц. Сначала я 1000 раз записал все 128 байт в хеш SHA256 и прочитал сумму, что в общей сложности заняло около 9 285 000 наносекунд. Во-вторых, я записал первые 64 байта в хеш SHA256 один раз, а затем 1000 раз записал вторые 64 байта в копию хэша SHA256 и прочитал сумму, что заняло в общей сложности около 6 492 371 наносекунд. Второй тест, предполагающий, что первые 64 байта неизменны, выполнялся на 30% быстрее, чем первый тест.

Используя первый метод, вы можете вычислить около 9 305 331 179 128-байтных сумм SHA256 в день, прежде чем покупать более быстрый процессор. Используя второй метод, вы можете вычислить 13 307 927 103 128-байтовых сумм SHA256 в день, предполагая, что первые 64 байта неизменны 1000 раз подряд, прежде чем покупать более быстрый процессор. Сколько 128-байтных сумм SHA256 в день вам нужно рассчитать? Для скольких 128-байтовых сумм SHA256 в день первые 64 байта являются инвариантными?

Какие бенчмарки вы проводили и каковы были результаты?

person peterSO    schedule 12.02.2012
comment
В основном я ухожу от ответа по теме здесь - bitcointalk.org/index.php ?topic=51281.msg612224#msg612224 . Как правило, реализация связана с майнингом биткойнов, в частности, с протоколом getwork (также объясняется в связанной теме). Если бы он использовался на стороне клиента и Go поддерживал OpenCL, порядок операций мог бы быть порядка 10^9 хэшей в секунду (если бы он достиг скорости текущего используемого программного обеспечения для майнинга) — en.bitcoin.it/wiki/Mining_hardware_comparison#Multi-Card_Setups. - person ThePiachu; 12.02.2012