0. Intro
Всем привет.
Это новый выпуск передачи "Очумелые ручки" и сегодня мы будем увеличивать
хэшрейт наших майнинг ферм и майнинг ботнетов без дополнительных финансовых
вложений.
Каким же образом мы это сделаем? Для этого нам понадобятся исходники софта для
майнинга используемого в нашей ферме или нашем ботнете и много свободного
времени. Да, да, мои любимые подписчики. Мы будем править сорсы и
оптимизировать некоторые алгоритмы используемые в майнинге криптовалюты. В
качестве подопытного кролика я взял популярную и дорогую криптовалюту, дедушку
криптовалютного мира, детище Сатоши Накамото: всем известный биткоин.
Для тех кому не терпится узнать вышло ли из этого чего-то стоящее или же
хэшрейт приподнялся на 1-2% - загляните в раздел 6 этого экскурса в глубины
SHA и узнайте как глубока кролячья нора.
Как многим из нас известно, в биткоине для подтверждения блока используется
хэш в начале которого присутствует целевое количество нулевых бит. Хэш
вычисляется из заголовка блока по алгоритму SHA256. И в один прекрасный день у
меня родилась мысль: а нельзя ли как-либо ускорить процесс вычисления хэша.
Почитав немного информации о структуре заголовка блока и алгоритме SHA256 я
понял - можно. И даже сразу увидел несколько потенциальных возможностей
сделать задуманное. Проведя несколько дней в состояние глубокого исследования
задачи мной были найдены пути и решения которые могли бы увеличить хэшрейт. Но
назревал
следующий вопрос: а не реализовал ли кто-то это уже до меня? Пришлось найти
исходники некоторых популярных и не очень майнеров на CPU и GPU, а затем
покопаться в них. Некоторые из найденных образцов использовали простое
хэширование без каких-либо попыток ускорить процесс, другие же старались
оптимизировать код SHA256 как только можно. Одним из майнеров старающихся
интегрировать как можно больше методов ускорения хэшрейта за счет кода
оказался CUDA майнер. Выдаваемый им хэшрейт было решено взять за базовый и
стараться улучшить это значение всеми возможными способами.
Так как моим любимым языком программирования является Delphi, то весь код в
дальнейшем будет именно на нём.
1. Клонируем код CUDA
Мной были найдены исходники CUDA майнера и первой поставленной задачей было
провести объективный тест замеряющий хэшрейт данного софта. Так как на C-ях я
кодить не люблю и весь дальнейших код будет именно на Delphi, то кодовую базу
CUDA пришлось транслировать на тот язык с которым буду работать в данной
статье.
Предоставляю этот код дабы желающие могли убедиться, что я не пытаюсь как-либо
замедлить его работу и специально получить более низкий хэшрейт.
Код майнера:
Code:Copy to clipboard
unit CUDA;
interface
uses SysUtils,Dialogs,Windows;
type
TUINT64 = record
case Byte of
0:(Value64:UINT64);
1:(LoDWord, HiDWord: Cardinal);
end;
const
UnixStartDate: TDateTime = 25569.0;
var
cpu_H256:array [0..7] of Cardinal=($6A09E667, $BB67AE85, $3C6EF372, $A54FF53A,$510E527F, $9B05688C, $1F83D9AB, $5BE0CD19);
cpu_K:array [0..63] of Cardinal=($428a2f98,$71374491,$b5c0fbcf,$e9b5dba5,$3956c25b,$59f111f1,$923f82a4,$ab1c5ed5,
$d807aa98,$12835b01,$243185be,$550c7dc3,$72be5d74,$80deb1fe,$9bdc06a7,$c19bf174,
$e49b69c1,$efbe4786,$0fc19dc6,$240ca1cc,$2de92c6f,$4a7484aa,$5cb0a9dc,$76f988da,
$983e5152,$a831c66d,$b00327c8,$bf597fc7,$c6e00bf3,$d5a79147,$06ca6351,$14292967,
$27b70a85,$2e1b2138,$4d2c6dfc,$53380d13,$650a7354,$766a0abb,$81c2c92e,$92722c85,
$a2bfe8a1,$a81a664b,$c24b8b70,$c76c51a3,$d192e819,$d6990624,$f40e3585,$106aa070,
$19a4c116,$1e376c08,$2748774c,$34b0bcb5,$391c0cb3,$4ed8aa4a,$5b9cca4f,$682e6ff3,
$748f82ee,$78a5636f,$84c87814,$8cc70208,$90befffa,$a4506ceb,$bef9a3f7,$c67178f2);
c_midstate76:array [0..7] of Cardinal;
c_dataEnd80:array [0..3] of Cardinal;
c_target:array [0..1] of Cardinal;
d_target:TUINT64;
function sha256d_hash_80(pdata,ptarget:array of Cardinal;var hash:array of Cardinal;var nonce:Cardinal):boolean;
implementation
function DateTimeToUnix(ConvDate: TDateTime): Longint;
begin
Result := Round((ConvDate - UnixStartDate) * 86400);
end;
function ROTR(x:Cardinal;n:byte):Cardinal;
begin
result:=(x shr n) or (x shl (32-n));
end;
function cuda_swab32(x:Cardinal):Cardinal;
begin
result:=((x shl 24) and $ff000000) or ((x shl 8) and $00ff0000) or ((x shr 8) and $0000ff00) or ((x shr 24) and $000000ff);
end;
procedure sha256_step1_host(a:Cardinal;b:Cardinal;c:Cardinal;var d:Cardinal;e:Cardinal;f:Cardinal;g:Cardinal;var h:Cardinal;a_in:Cardinal;Kshared:Cardinal);
var t1,t2,vxandx,bsg21,bsg20,andorv:Cardinal;
begin
vxandx := ((f xor g) and e) xor g;
bsg21 := ROTR(e, 6) xor ROTR(e, 11) xor ROTR(e, 25);
bsg20 := ROTR(a, 2) xor ROTR(a, 13) xor ROTR(a, 22);
andorv := (b and c) or ((b or c) and a);
t1 := h + bsg21 + vxandx + Kshared + a_in;
t2 := bsg20 + andorv;
d := d + t1;
h := t1 + t2;
end;
procedure sha256_step2_host(a:Cardinal;b:Cardinal;c:Cardinal;var d:Cardinal;e:Cardinal;f:Cardinal;g:Cardinal;var h:Cardinal;var a_in:array of Cardinal;pc:Cardinal;Kshared:Cardinal);
var
t1,t2:Cardinal;
pcidx1,pcidx2,pcidx3:integer;
inx0,inx1,inx2,inx3:Cardinal;
ssg21,ssg20,vxandx,bsg21,bsg20,andorv:Cardinal;
begin
pcidx1 := (pc-2) and $F;
pcidx2 := (pc-7) and $F;
pcidx3 := (pc-15) and $F;
inx0 := a_in[pc];
inx1 := a_in[pcidx1];
inx2 := a_in[pcidx2];
inx3 := a_in[pcidx3];
ssg21 := ROTR(inx1, 17) xor ROTR(inx1, 19) xor (inx1 shr 10);
ssg20 := ROTR(inx3, 7) xor ROTR(inx3, 18) xor (inx3 shr 3);
vxandx := ((f xor g) and e) xor g;
bsg21 := ROTR(e, 6) xor ROTR(e, 11) xor ROTR(e, 25);
bsg20 := ROTR(a, 2) xor ROTR(a, 13) xor ROTR(a, 22);
andorv := (b and c) or ((b or c) and a);
a_in[pc] := ssg21 + inx2 + ssg20 + inx0;
t1 := h + bsg21 + vxandx + Kshared + a_in[pc];
t2 := bsg20 + andorv;
d := d + t1;
h := t1 + t2;
end;
procedure sha256_round_body_host(a_in:array of Cardinal;var state:array of Cardinal;Kshared:array of Cardinal);
var a,b,c,d,e,f,g,h:Cardinal;
i:integer;
begin
a:=state[0];
b:=state[1];
c:=state[2];
d:=state[3];
e:=state[4];
f:=state[5];
g:=state[6];
h:=state[7];
sha256_step1_host(a,b,c,d,e,f,g,h,a_in[0], Kshared[0]);
sha256_step1_host(h,a,b,c,d,e,f,g,a_in[1], Kshared[1]);
sha256_step1_host(g,h,a,b,c,d,e,f,a_in[2], Kshared[2]);
sha256_step1_host(f,g,h,a,b,c,d,e,a_in[3], Kshared[3]);
sha256_step1_host(e,f,g,h,a,b,c,d,a_in[4], Kshared[4]);
sha256_step1_host(d,e,f,g,h,a,b,c,a_in[5], Kshared[5]);
sha256_step1_host(c,d,e,f,g,h,a,b,a_in[6], Kshared[6]);
sha256_step1_host(b,c,d,e,f,g,h,a,a_in[7], Kshared[7]);
sha256_step1_host(a,b,c,d,e,f,g,h,a_in[8], Kshared[8]);
sha256_step1_host(h,a,b,c,d,e,f,g,a_in[9], Kshared[9]);
sha256_step1_host(g,h,a,b,c,d,e,f,a_in[10], Kshared[10]);
sha256_step1_host(f,g,h,a,b,c,d,e,a_in[11], Kshared[11]);
sha256_step1_host(e,f,g,h,a,b,c,d,a_in[12], Kshared[12]);
sha256_step1_host(d,e,f,g,h,a,b,c,a_in[13], Kshared[13]);
sha256_step1_host(c,d,e,f,g,h,a,b,a_in[14], Kshared[14]);
sha256_step1_host(b,c,d,e,f,g,h,a,a_in[15], Kshared[15]);
for i:=0 to 2 do
begin
sha256_step2_host(a,b,c,d,e,f,g,h,a_in,0, Kshared[16+16*i]);
sha256_step2_host(h,a,b,c,d,e,f,g,a_in,1, Kshared[17+16*i]);
sha256_step2_host(g,h,a,b,c,d,e,f,a_in,2, Kshared[18+16*i]);
sha256_step2_host(f,g,h,a,b,c,d,e,a_in,3, Kshared[19+16*i]);
sha256_step2_host(e,f,g,h,a,b,c,d,a_in,4, Kshared[20+16*i]);
sha256_step2_host(d,e,f,g,h,a,b,c,a_in,5, Kshared[21+16*i]);
sha256_step2_host(c,d,e,f,g,h,a,b,a_in,6, Kshared[22+16*i]);
sha256_step2_host(b,c,d,e,f,g,h,a,a_in,7, Kshared[23+16*i]);
sha256_step2_host(a,b,c,d,e,f,g,h,a_in,8, Kshared[24+16*i]);
sha256_step2_host(h,a,b,c,d,e,f,g,a_in,9, Kshared[25+16*i]);
sha256_step2_host(g,h,a,b,c,d,e,f,a_in,10,Kshared[26+16*i]);
sha256_step2_host(f,g,h,a,b,c,d,e,a_in,11,Kshared[27+16*i]);
sha256_step2_host(e,f,g,h,a,b,c,d,a_in,12,Kshared[28+16*i]);
sha256_step2_host(d,e,f,g,h,a,b,c,a_in,13,Kshared[29+16*i]);
sha256_step2_host(c,d,e,f,g,h,a,b,a_in,14,Kshared[30+16*i]);
sha256_step2_host(b,c,d,e,f,g,h,a,a_in,15,Kshared[31+16*i]);
end;
state[0] := state[0]+a;
state[1] := state[1]+b;
state[2] := state[2]+c;
state[3] := state[3]+d;
state[4] := state[4]+e;
state[5] := state[5]+f;
state[6] := state[6]+g;
state[7] := state[7]+h;
end;
procedure sha256d_setBlock_80(pdata,ptarget:array of Cardinal);
var
a_in:array [0..15] of Cardinal;
a_buf:array [0..7] of Cardinal;
a_end:array [0..3] of Cardinal;
i:integer;
begin
for i:=0 to 15 do
a_in[i] := cuda_swab32(pdata[i]);
for i:=0 to 7 do
a_buf[i] := cpu_H256[i];
for i:=0 to 3 do
a_end[i] := cuda_swab32(pdata[16+i]);
sha256_round_body_host(a_in, a_buf, cpu_K);
Move(a_buf[0],c_midstate76[0],32);
Move(a_end[0],c_dataEnd80,16);
Move(ptarget[6],c_target[0],8);
Move(ptarget[6],d_target,8);
end;
function xandx(a,b,c:Cardinal):Cardinal;
begin
result:=((b xor c) and a) xor c;
end;
function xor3b(a,b,c:Cardinal):Cardinal;
begin
result:=a xor b xor c;
end;
function bsg2_1(x:Cardinal):Cardinal;
begin
result:=xor3b(ROTR(x,6),ROTR(x,11),ROTR(x,25));
end;
function bsg2_0(x:Cardinal):Cardinal;
begin
result:=xor3b(ROTR(x,2),ROTR(x,13),ROTR(x,22));
end;
function andor32(a,b,c:Cardinal):Cardinal;
begin
result:=(b and c) or ((b or c) and a);
end;
procedure sha2_step1(a:Cardinal;b:Cardinal;c:Cardinal;var d:Cardinal;e:Cardinal;f:Cardinal;g:Cardinal;var h:Cardinal;a_in:Cardinal;Kshared:Cardinal);
var
t1,t2,vxandx,bsg21,bsg20,andorv:Cardinal;
begin
vxandx := xandx(e, f, g);
bsg21 := bsg2_1(e);
bsg20 := bsg2_0(a);
andorv := andor32(a,b,c);
t1 := h + bsg21 + vxandx + Kshared + a_in;
t2 := bsg20 + andorv;
d := d + t1;
h := t1 + t2;
end;
function ssg2_0(x:Cardinal):Cardinal;
begin
result:=xor3b(ROTR(x,7),ROTR(x,18),x shr 3);
end;
function ssg2_1(x:Cardinal):Cardinal;
begin
result:=xor3b(ROTR(x,17),ROTR(x,19),x shr 10);
end;
procedure sha2_step2(a:Cardinal;b:Cardinal; c:Cardinal;var d:Cardinal; e:Cardinal; f:Cardinal; g:Cardinal; var h:Cardinal;var a_in:array of Cardinal; pc:Cardinal; Kshared:Cardinal);
var
t1,t2:Cardinal;
pcidx1,pcidx2,pcidx3:integer;
inx0,inx1,inx2,inx3:Cardinal;
ssg21,ssg20,vxandx,bsg21,bsg20,andorv:Cardinal;
begin
pcidx1 := (pc-2) and $F;
pcidx2 := (pc-7) and $F;
pcidx3 := (pc-15) and $F;
inx0 := a_in[pc];
inx1 := a_in[pcidx1];
inx2 := a_in[pcidx2];
inx3 := a_in[pcidx3];
ssg21 := ssg2_1(inx1);
ssg20 := ssg2_0(inx3);
vxandx := xandx(e, f, g);
bsg21 := bsg2_1(e);
bsg20 := bsg2_0(a);
andorv := andor32(a,b,c);
a_in[pc] := ssg21 + inx2 + ssg20 + inx0;
t1 := h + bsg21 + vxandx + Kshared + a_in[pc];
t2 := bsg20 + andorv;
d := d + t1;
h := t1 + t2;
end;
procedure sha256_round_body(var a_in:array of Cardinal;var state:array of Cardinal;var Kshared:array of Cardinal);
var
a,b,c,d,e,f,g,h:Cardinal;
i:integer;
begin
a:=state[0];
b:=state[1];
c:=state[2];
d:=state[3];
e:=state[4];
f:=state[5];
g:=state[6];
h:=state[7];
sha2_step1(a,b,c,d,e,f,g,h,a_in[0], Kshared[0]);
sha2_step1(h,a,b,c,d,e,f,g,a_in[1], Kshared[1]);
sha2_step1(g,h,a,b,c,d,e,f,a_in[2], Kshared[2]);
sha2_step1(f,g,h,a,b,c,d,e,a_in[3], Kshared[3]);
sha2_step1(e,f,g,h,a,b,c,d,a_in[4], Kshared[4]);
sha2_step1(d,e,f,g,h,a,b,c,a_in[5], Kshared[5]);
sha2_step1(c,d,e,f,g,h,a,b,a_in[6], Kshared[6]);
sha2_step1(b,c,d,e,f,g,h,a,a_in[7], Kshared[7]);
sha2_step1(a,b,c,d,e,f,g,h,a_in[8], Kshared[8]);
sha2_step1(h,a,b,c,d,e,f,g,a_in[9], Kshared[9]);
sha2_step1(g,h,a,b,c,d,e,f,a_in[10], Kshared[10]);
sha2_step1(f,g,h,a,b,c,d,e,a_in[11], Kshared[11]);
sha2_step1(e,f,g,h,a,b,c,d,a_in[12], Kshared[12]);
sha2_step1(d,e,f,g,h,a,b,c,a_in[13], Kshared[13]);
sha2_step1(c,d,e,f,g,h,a,b,a_in[14], Kshared[14]);
sha2_step1(b,c,d,e,f,g,h,a,a_in[15], Kshared[15]);
for i:=0 to 2 do
begin
sha2_step2(a,b,c,d,e,f,g,h,a_in,0, Kshared[16+16*i]);
sha2_step2(h,a,b,c,d,e,f,g,a_in,1, Kshared[17+16*i]);
sha2_step2(g,h,a,b,c,d,e,f,a_in,2, Kshared[18+16*i]);
sha2_step2(f,g,h,a,b,c,d,e,a_in,3, Kshared[19+16*i]);
sha2_step2(e,f,g,h,a,b,c,d,a_in,4, Kshared[20+16*i]);
sha2_step2(d,e,f,g,h,a,b,c,a_in,5, Kshared[21+16*i]);
sha2_step2(c,d,e,f,g,h,a,b,a_in,6, Kshared[22+16*i]);
sha2_step2(b,c,d,e,f,g,h,a,a_in,7, Kshared[23+16*i]);
sha2_step2(a,b,c,d,e,f,g,h,a_in,8, Kshared[24+16*i]);
sha2_step2(h,a,b,c,d,e,f,g,a_in,9, Kshared[25+16*i]);
sha2_step2(g,h,a,b,c,d,e,f,a_in,10,Kshared[26+16*i]);
sha2_step2(f,g,h,a,b,c,d,e,a_in,11,Kshared[27+16*i]);
sha2_step2(e,f,g,h,a,b,c,d,a_in,12,Kshared[28+16*i]);
sha2_step2(d,e,f,g,h,a,b,c,a_in,13,Kshared[29+16*i]);
sha2_step2(c,d,e,f,g,h,a,b,a_in,14,Kshared[30+16*i]);
sha2_step2(b,c,d,e,f,g,h,a,a_in,15,Kshared[31+16*i]);
end;
state[0] := state[0]+a;
state[1] := state[1]+b;
state[2] := state[2]+c;
state[3] := state[3]+d;
state[4] := state[4]+e;
state[5] := state[5]+f;
state[6] := state[6]+g;
state[7] := state[7]+h;
end;
procedure sha256_round_last(var a_in:array of Cardinal;var state:array of Cardinal;var Kshared:array of Cardinal);
var
a,b,c,d,e,f,g,h:Cardinal;
i:integer;
begin
a := state[0];
b := state[1];
c := state[2];
d := state[3];
e := state[4];
f := state[5];
g := state[6];
h := state[7];
sha2_step1(a,b,c,d, e,f,g,h, a_in[ 0], Kshared[ 0]);
sha2_step1(h,a,b,c, d,e,f,g, a_in[ 1], Kshared[ 1]);
sha2_step1(g,h,a,b, c,d,e,f, a_in[ 2], Kshared[ 2]);
sha2_step1(f,g,h,a, b,c,d,e, a_in[ 3], Kshared[ 3]);
sha2_step1(e,f,g,h, a,b,c,d, a_in[ 4], Kshared[ 4]);
sha2_step1(d,e,f,g, h,a,b,c, a_in[ 5], Kshared[ 5]);
sha2_step1(c,d,e,f, g,h,a,b, a_in[ 6], Kshared[ 6]);
sha2_step1(b,c,d,e, f,g,h,a, a_in[ 7], Kshared[ 7]);
sha2_step1(a,b,c,d, e,f,g,h, a_in[ 8], Kshared[ 8]);
sha2_step1(h,a,b,c, d,e,f,g, a_in[ 9], Kshared[ 9]);
sha2_step1(g,h,a,b, c,d,e,f, a_in[10], Kshared[10]);
sha2_step1(f,g,h,a, b,c,d,e, a_in[11], Kshared[11]);
sha2_step1(e,f,g,h, a,b,c,d, a_in[12], Kshared[12]);
sha2_step1(d,e,f,g, h,a,b,c, a_in[13], Kshared[13]);
sha2_step1(c,d,e,f, g,h,a,b, a_in[14], Kshared[14]);
sha2_step1(b,c,d,e, f,g,h,a, a_in[15], Kshared[15]);
for i:=0 to 1 do
begin
sha2_step2(a,b,c,d, e,f,g,h, a_in, 0, Kshared[16+16*i]);
sha2_step2(h,a,b,c, d,e,f,g, a_in, 1, Kshared[17+16*i]);
sha2_step2(g,h,a,b, c,d,e,f, a_in, 2, Kshared[18+16*i]);
sha2_step2(f,g,h,a, b,c,d,e, a_in, 3, Kshared[19+16*i]);
sha2_step2(e,f,g,h, a,b,c,d, a_in, 4, Kshared[20+16*i]);
sha2_step2(d,e,f,g, h,a,b,c, a_in, 5, Kshared[21+16*i]);
sha2_step2(c,d,e,f, g,h,a,b, a_in, 6, Kshared[22+16*i]);
sha2_step2(b,c,d,e, f,g,h,a, a_in, 7, Kshared[23+16*i]);
sha2_step2(a,b,c,d, e,f,g,h, a_in, 8, Kshared[24+16*i]);
sha2_step2(h,a,b,c, d,e,f,g, a_in, 9, Kshared[25+16*i]);
sha2_step2(g,h,a,b, c,d,e,f, a_in,10, Kshared[26+16*i]);
sha2_step2(f,g,h,a, b,c,d,e, a_in,11, Kshared[27+16*i]);
sha2_step2(e,f,g,h, a,b,c,d, a_in,12, Kshared[28+16*i]);
sha2_step2(d,e,f,g, h,a,b,c, a_in,13, Kshared[29+16*i]);
sha2_step2(c,d,e,f, g,h,a,b, a_in,14, Kshared[30+16*i]);
sha2_step2(b,c,d,e, f,g,h,a, a_in,15, Kshared[31+16*i]);
end;
sha2_step2(a,b,c,d, e,f,g,h, a_in, 0, Kshared[16+16*2]);
sha2_step2(h,a,b,c, d,e,f,g, a_in, 1, Kshared[17+16*2]);
sha2_step2(g,h,a,b, c,d,e,f, a_in, 2, Kshared[18+16*2]);
sha2_step2(f,g,h,a, b,c,d,e, a_in, 3, Kshared[19+16*2]);
sha2_step2(e,f,g,h, a,b,c,d, a_in, 4, Kshared[20+16*2]);
sha2_step2(d,e,f,g, h,a,b,c, a_in, 5, Kshared[21+16*2]);
sha2_step2(c,d,e,f, g,h,a,b, a_in, 6, Kshared[22+16*2]);
sha2_step2(b,c,d,e, f,g,h,a, a_in, 7, Kshared[23+16*2]);
sha2_step2(a,b,c,d, e,f,g,h, a_in, 8, Kshared[24+16*2]);
sha2_step2(h,a,b,c, d,e,f,g, a_in, 9, Kshared[25+16*2]);
sha2_step2(g,h,a,b, c,d,e,f, a_in,10, Kshared[26+16*2]);
sha2_step2(f,g,h,a, b,c,d,e, a_in,11, Kshared[27+16*2]);
sha2_step2(e,f,g,h, a,b,c,d, a_in,12, Kshared[28+16*2]);
sha2_step2(d,e,f,g, h,a,b,c, a_in,13, Kshared[29+16*2]);
state[6]:=state[6]+g;
state[7]:=state[7]+h;
end;
function cuda_swab32ll(x:TUINT64):TUINT64;
begin
result.LoDWord:=cuda_swab32(x.LoDWord);
result.HiDWord:=cuda_swab32(x.HiDWord)
end;
function sha256d_gpu_hash_shared(nonce:Cardinal):boolean;
var
dat:array [0..15] of Cardinal;
buf:array [0..7] of Cardinal;
i:integer;
high:TUINT64;
begin
dat[0]:=c_dataEnd80[0];
dat[1]:=c_dataEnd80[1];
dat[2]:=c_dataEnd80[2];
dat[3]:=nonce;
dat[4]:=2147483648;
dat[15]:=640;
for i:=5 to 14 do
dat[i] := 0;
for i:=0 to 7 do
buf[i]:=c_midstate76[i];
sha256_round_body(dat, buf, cpu_K);
for i:=0 to 7 do
dat[i]:=buf[i];
dat[8]:=2147483648;
for i:=9 to 14 do
dat[i]:=0;
dat[15]:=256;
for i:=0 to 7 do
buf[i]:=cpu_H256[i];
sha256_round_last(dat, buf, cpu_K);
high.HiDWord:=buf[6];
high.LoDWord:=buf[7];
high := cuda_swab32ll(high);
if high.Value64<=d_target.Value64 then
result:=true
else
result:=false;
end;
function sha256d_hash_80(pdata,ptarget:array of Cardinal;var hash:array of Cardinal;var nonce:Cardinal):boolean;
var
dt:longint;
begin
result:=true;
dt:=Now;
sha256d_setBlock_80(pdata,ptarget);
for nonce:=0 to 4294967295 do
begin
if sha256d_gpu_hash_shared(nonce) then
Exit;
if nonce=10000001 then
Showmessage(inttostr(DateTimeToUnix(now)-dt));
end;
result:=false;
end;
end.
В самом майнере я добавил код подсчитывающий время майнинга 10000000 хэшей. На
этой основе будет расчитан хэшрейт.
Теперь нужно подготовить тестовый заголовок блока, назначить цель и запустить
майнинг. Так как формирование заголовка блока из транзакций и расчет цели
выходят за рамки этой статьи, то я их установлю в качестве фиксированных
значений. В качестве образца был взят реально существующий блок цепи блокчейна
биткоина под номером 125552. Nonce удовлетворяющий условиям цели для данной
транзакции 1117865621. Я специально не менял таймстамп метку начала майнинга
для текущего блока чтобы можно было проверить правильность вычисления золотой
шары для указанного выше блока с имеющимся nonce.
Code:Copy to clipboard
function SwapString(s:string):string;
var
i:integer;
begin
result:='';
i:=length(s)-1;
while i>0 do
begin
result:=result+copy(s,i,2);
i:=i-2;
end;
end;
function HexToInt(Str : string): integer;
var
i, r : integer;
begin
val('$'+Trim(Str),r, i);
if i<>0 then
HexToInt := 0
else
HexToInt := r;
end;
function HexToString(s:string):string;
var
i:integer;
begin
result:='';
i:=1;
while i<=length(s) do
begin
result:=result+char(HexToInt(s[i]+s[i+1]));
i:=i+2;
end;
end;
procedure InitAndStart;
var
data:array of Cardinal;
hash:array of Cardinal;
prevblock,rootHash:string;
nonce:Cardinal;
target:array of Cardinal;
begin
setlength(hash,8);
setlength(target,8);
target[0]:=0;
target[1]:=0;
target[2]:=0;
target[3]:=0;
target[4]:=0;
target[5]:=4060086272;
target[6]:=17593;
target[7]:=0;
prevblock:=HEXToString(SwapString('00000000000008a3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81'));
rootHash:=HEXToString(SwapString('2b12fcf1b09288fcaff797d71e950e71ae42b91e8bdb2304758dfcffc2b620e3'));
setlength(data,20);
data[0]:=1;
Move(prevblock[1],data[1],32);
Move(rootHash[1],data[9],32);
data[17]:=1305998791;
data[18]:=440711666;
data[19]:=0;
if sha256d_hash_80(data,target,hash,nonce) then
Showmessage(inttostr(nonce))
else
Showmessage('Not found');
end;
Результат замера скорости CUDA майнера в одном потоке на CPU: 107526 хэша в
секунду.
2. Базовая теоретическая информация: алгоритм SHA256, Merkle Root, block
header, golden share
2.0 Вводная в алгоритм майнинга
Для дальнейшего понимания что я делаю и как вообще всё это работает надо
разобраться с структурой блока биткоина и процессом вычисления подтверждения
блока.
Подтверждение блока делится на несколько этапов:
а) Наращиваем extranonce в coinbase транзакции, обнуляем nonce
б) Вычисляем Merkle Root с новой coinbase и формируем заголовок блока
в) Вычисляем двойной хэш от заголовка блока. Если хэш удовлетворяет заданным
условиям - выходим из функции.
Если хэш не удовлетворяет заданным условиям - в заголовке блока наращиваем
nonce на 1 и повторяем пункт в). Если после наращивания nonce стало больше чем
4294967295, то возвращаемся к пункту а)
В рамках данной статьи я ограничусь методами ускорения вычисления
подтверждения блока на этапе перебора nonce и не буду рассказывать о том как
меняется coinbase транзакция если nonce превышает пределы допустимого
диапазона, как вычисляется Merkle Root и как можно ещё сильней увеличить
скорость майнинга. Т.е. мы сейчас работаем только с пунктом в) описанного выше
алгоритма. Хотя в пунктах а) и б) тоже есть векторы для исследования.
2.1 Block header и golden share
Как я уже писал выше, подтверждение блока это SHA256 хэш в начале которого
присутствует нужное количество нулевых бит или больше. Но это не простой хэш,
а хэш вычисленный из хэша от заголовка блока. Выглядит это так:
sha256(sha256(block_header)). Хэш удовлетворяющий найденным условиям
называется золотой шарой. Для того чтобы найти такую золотую шару нужно
провести множество вычислений меняя некий nonce постоянно наращивая его на 1 и
вычисляя новый двойной хэш, а затем проверять его на условие достижения цели.
Но где же прячется этот nonce и что он из себя представляет?
Nonce находится внутри block_header и представляет из себя целое беззнаковое
число размером 32 бита. Для далеких от кодинга людей, а также для "кодеров"
умеющих двигать только кнопочки по форме разжую: nonce может из себя
представлять число в диапазоне от 0 до 4294967295. В начале поиска золотой
шары nonce равен 0 и с каждым новым вычисленным хэшем не удовлетворяющим
заданному условию он увеличивается на 1.
Теперь посмотрим во внутренности заголовка блока и узнаем из чего он состоит:
а) Версия - версия актуальной сети биткоина. Размер 4 байта
б) Хэш прошлого блока - размер 32 байта
в) Merkle Root - тоже хэш, но о нем поговорим позже. Размер 32 байта
г) Таймстамп - UTC время начала майнинга. Полные ноды не принимают блоки с
хэадерами в которых таймстамп отличен от их времени более чем на 2 часа.
Среднее время майнинга блока должно составлять 10 минут. Размер 4 байта
д) Сложность - Сколько нулевых бит должно быть в начале золотой шары.
Размер 4 байта
ж) Nonce - выбирается перебором. Размер 4 байта
2.2 Merkle Root
Не буду давать полное описание этого хэша. Скажу только что Merkle Root - это
вычисленный по определенным правилам хэш из всех транзакций присутствующих в
блоке.
2.3 SHA256
Для дальнейшей разработки нам потребуется изучить сам алгоритм хэширования
SHA256 и подготовить его исходники для того чтобы было с чем работать дальше.
Также не буду давать полное описание данного алгоритма, так как это сильно
раздует статью. Для тех кому интерено: tproger.ru/translations/sha-2-step-by-
step/
3. CUDA: борьба за хэшрейт
Майнинг на классическом алгоритме SHA подразумевает из себя полноценное
вычисление хэша из заголовка блока и рассчет хэша из прошлого хэша. Далее
происходит тестирование на достижение цели. Первое хэширование имеет два
раунда особенность которых в том, что первый раунд заканчивается всегда одним
и тем-же результатом. Это происходит за счет того, что в заголовке блока
хэшируемая в первом раунде часть не меняется пока не будет изменен сам
заголовок блока. Из-за этой особенности можно вынести первый раунд хэширования
за пределы цикла перебора nonce и вычислить это значение всего один раз.
Разработчики CUDA учли эту возможность и тем самым подняли хэшрейт
приблизительно в 1.33 раза от того что могло бы быть при классической
реализации. Но
по неизвестным мне причинам многие другие софты для майнинга не используют
этот маленький легальный чит. Кроме того в CUDA хэширование ускоряется за счет
не полного рассчета хэша, а окончательное вычисление лишь последних двух
двойных слов и тестирование их на достижение цели. Это можно увидеть в
последних двух строчках процедуры рассчета второго хэша sha256_round_last:
Code:Copy to clipboard
state[6]:=state[6]+g;
state[7]:=state[7]+h;
И последнее что реализовано в CUDA майнере - стабильная генерация расширенного
блока. Алгоритм SHA подразумевает изменение входных данных перед тем как
начать процесс рассчета хэша. Во-первых, в конец хэшируемых данных добавляется
бит 1. Во-вторых, к хэшируемым данным добавляются нулевые биты пока длинна
данных не станет кратна 512. И в-третьих, в последние 64 бита расширенного
массива данных записывается длинна хэшируемых данных без изменений в битах.
Таким образом при использование чистого SHA у нас происходят множество
операций с памятью, рассчеты длинны, операции деления и умножения которые
совершенно не нужны. Размер заголовка блока у нас фиксирован и всегда равен 80
байтам. Т.е. после расширения хэшируемый массив данных будет иметь длинну 32
32-битных беззнаковых числа. Позиция единичного бита будет фиксирована и
располагаться он будет в 20 элементе расширенного массива. Также длинна
хэшируемых данных фиксирована и равна 640 битам. Длинну записываем также в
фиксированное место, а именно в 31 элемент.
Во время второго хэширования у нас также есть статические данные. Длинна
хэшируемых данных 32 байта, Длинна массива после расширения 16 32-битных
беззнаковых числа. Единичный бит записывается в позицию 8. Размер хэшируемых
данных 256 бит и он записывается в позицию 15 расширенного массива.
Таким образом это всё можно рассчитать всего один раз и выделить нужный объем
памяти без лишних телодвижений. В CUDA майнере это всё реализованно.
За счет выше описанных методов хэшрейт CUDA майнера держится на уровне 107526
х/с. Можно ли улучшить это значение? Давайте постараемся в этом разобраться.
4. Обнаруженные методы бустинга хэшрейта
В процессе долгого и кропотливого исследования мне удалось обнаружить ещё
несколько мест которые можно оптимизировать и выдавить из SHA дополнительные
процентики к столь желанному хэшрейту. Для реализации задуманного пришлось
отказаться от кода хэширования предложенного в CUDA майнере и реализовать
SHA256 с нуля в его классическом виде, а уже затем постепенно интегрировать
все уже имеющиеся и новые задумки влияющие на скорость.
Самое первое на что я обратил внимание ещё до того как нашел код CUDA, так это
то, что нет смысла вычислять второй хэш в полном объеме. Но если в CUDA
вычисляются последние 2 части хэша, то я считаю, что в этом нет смысла и
хватит одной.
Обосную:
В последнее время сложность майнинга уже на столько высока, что требуемая цель
сильно превышает 32 нулевых бита. Вероятность того, что сложность упадет до
значения эквивалентного менее чем 32 битам стремится к нулю. Значит мы можем
спокойно вычислять только последний элемент хэша и сравнивать его с 0. Если он
больше 0, то этот хэш гарантированно не является искомым и дальнейшие
вычисления, а также последующий тест на достижения цели являются
бессмысленными и следует сразу же брать новый nonce.
Также в процессе каждого раунда хэширования заголовка блока соответствующая
функция должна генерировать очередь сообщений размером 64 32-битных
беззнаковых числа. При вычисление первого хэша четверть этой очереди берется
из хэшируемых данных, а оставшаяся рассчитывается в процессе. Так как мы знаем
как будут меняться хэшируемые данные на каждом этапе перебора nonce, то мы
можем предсказать как будет меняться очередь сообщений. Таким образом теряется
смысл в постоянной генерации очереди сообщений в полном объеме. Её мы можем
сгенерировать один раз и передавать в нужные функции. Этим мы избавляемся от
некоторых операций выделения, копирования и изменения памяти.
Более детальное изучение процесса генерации очереди сообщений для хэширования
второй части заголовка выявило, что при неизменном timestamp 16 и 17 элементы
данной очереди будут фиксированы и неизменны на длительном промежутке времени.
Так как таймстамп нет смысла менять каждую интерацию цикла смены nonce и даже
нет смысла менять его каждую секунду, а вполне хватит замены времени начала
майнинга в периоде к примеру раз в пол часа или и того реже, то эти элементы
очереди также можно пересчитывать только в момент изменения timestamp, а в
остальное время просто брать их из памяти.
Кроме того посмотрим на код вычисления элементов очереди сообщений в
традиционном SHA:
Code:Copy to clipboard
for i:=16 to 63 do
begin
s0 := rightrotate(w[i-15],7) xor rightrotate(w[i-15],18) xor (w[i-15] shr 3);
s1 := rightrotate(w[i-2],17) xor rightrotate(w[i-2],19) xor (w[i-2] shr 10);
w [i] := w[i-16] + s0 + w[i-7] + s1;
end;
Для рассчета каждого следующего элемента очереди производится калькуляция
промежуточных значений s0 и s1 которые формируются из прошлых элементов
очереди сообщений w[i-15] и w[i-2]. Для нас интересен тот факт, что при
неизменном timestamp для рассчета 18 и 19 элементов очереди сообщений
генерируемой для хэширования второй части заголовка s1 будет всегда
постоянным. А для рассчета элементов с 19 по 32 постоянными будут s0. Тогда
теряется смысл делать их постоянную калькуляцию. Это можно делать также один
раз при изменение timestamp.
Есть ещё один момент влияющий на хэшрейт. После формирования полной очереди
сообщений используемой для рассчета хэша из второй части заголовка блока
выполняются 64 раунда самого хэширования. Код классического хэширования будет
выглядеть так:
Code:Copy to clipboard
for i:=0 to 63 do
begin
S1 := rightrotate(e,6) xor rightrotate(e,11) xor rightrotate(e,25);
ch := (e and f) xor ((not e) and g);
temp1 := h + S1 + ch + k[i] + w[i];
S0 := rightrotate(a,2) xor rightrotate(a,13) xor rightrotate(a,22);
maj := (a and b) xor (a and c) xor (b and c);
temp2 := S0 + maj;
h := g;
g := f;
f := e;
e := d + temp1;
d := c;
c := b;
b := a;
a := temp1 + temp2;
end;
При неизменном в длительном периоде timestamp первые три элемента очереди
сообщений у нас одинаковы и независимы от nonce, а значит результат вычисления
в первых трех раундах будет одинаковым. Но, кроме того, на четвертом раунде
nonce играет не значительную роль и его изменением можно пренебречь (считать
nonce равным 0) и добавить текущий nonce к переменным a и e уже после основных
вычислений в этом раунде.
Таким образом можно вычислить состояние переменных a, b, c, d, e, f, g и h для
первых трех раундов, провести для них четвертый раунд хэширования считая что
w[3] равно 0 и отправлять их в функцию хэширования второй части заголовка
блока в неизменном виде. Уже в этой функции добавлять к a и e текущий nonce и
начинать хэширование с 5-ого раунда тем самым экономя 1/16 времени работы
этого куска кода.
Ещё одно отличие от кода CUDA внесенное в мою наработку - изменение timestamp
и перерасчет всех фиксированных значений при изменение global_timestamp.
global_timestamp должен будет инициализироваться при запуске майнинга и
меняться раз в пол часа в отдельном потоке, но в предоставленном ниже коде код
потока изменяющего global_timestamp не указан. Для проверки правильности
хэширования вы можете изменить nonce на nonce золотой шары тестового блока и
установить global_timestamp в timestamp метку указанную в заголовке тестового
блока.
5. Код ускоренного майнинга
Модуль для майнинга после всех внесенных изменений будет выглядеть так:
Code:Copy to clipboard
unit BoostedMiner;
interface
uses Windows,SysUtils,Dialogs;
type
Tdata=array of Cardinal;
const
UnixStartDate: TDateTime = 25569.0;
var
k:array [0..63] of Cardinal=($428a2f98,$71374491,$b5c0fbcf,$e9b5dba5,$3956c25b,$59f111f1,$923f82a4,$ab1c5ed5,
$d807aa98,$12835b01,$243185be,$550c7dc3,$72be5d74,$80deb1fe,$9bdc06a7,$c19bf174,
$e49b69c1,$efbe4786,$0fc19dc6,$240ca1cc,$2de92c6f,$4a7484aa,$5cb0a9dc,$76f988da,
$983e5152,$a831c66d,$b00327c8,$bf597fc7,$c6e00bf3,$d5a79147,$06ca6351,$14292967,
$27b70a85,$2e1b2138,$4d2c6dfc,$53380d13,$650a7354,$766a0abb,$81c2c92e,$92722c85,
$a2bfe8a1,$a81a664b,$c24b8b70,$c76c51a3,$d192e819,$d6990624,$f40e3585,$106aa070,
$19a4c116,$1e376c08,$2748774c,$34b0bcb5,$391c0cb3,$4ed8aa4a,$5b9cca4f,$682e6ff3,
$748f82ee,$78a5636f,$84c87814,$8cc70208,$90befffa,$a4506ceb,$bef9a3f7,$c67178f2);
fs0:array [0..13] of Cardinal;
fs1:array [0..1] of Cardinal;
round4:array [0..7] of Cardinal;
global_timestamp:Cardinal;
function nonce_search(inp:Tdata;target:Tdata;var nonce:Cardinal):boolean;
function cuda_swab32(x:Cardinal):Cardinal;
function DateTimeToUnix(ConvDate: TDateTime): Cardinal;
implementation
function DateTimeToUnix(ConvDate: TDateTime): Cardinal;
begin
Result := Round((ConvDate - UnixStartDate) * 86400);
end;
function cuda_swab32(x:Cardinal):Cardinal;
begin
result:=((x shl 24) and $ff000000) or ((x shl 8) and $00ff0000) or ((x shr 8) and $0000ff00) or ((x shr 24) and $000000ff);
end;
function RightRotate(w:Cardinal;l:byte):Cardinal;
begin
result:=(w shr l)+(w shl (32-l));
end;
function CheckToTarget(var hash:array of Cardinal;var target:Tdata):boolean;
var i:integer;
begin
result:=true;
for i:=7 downto 0 do
begin
if hash[i]<target[i] then
Exit;
if hash[i]>target[i] then
begin
result:=false;
Exit;
end;
end;
end;
procedure SHA_dynamic_header_hashing(w:array of Cardinal;var buf:Tdata;timestamp:Cardinal;nonce:Cardinal;var hash:array of Cardinal);
var
h0,h1,h2,h3,h4,h5,h6,h7:Cardinal;
i:integer;
s0,s1,ch,temp1,temp2,maj:Cardinal;
a,b,c,d,e,f,g,h:Cardinal;
begin
w[1]:=Cardinal(timestamp);
w[3]:=nonce;
h0 := buf[0];
h1 := buf[1];
h2 := buf[2];
h3 := buf[3];
h4 := buf[4];
h5 := buf[5];
h6 := buf[6];
h7 := buf[7];
s0 := rightrotate(w[3],7) xor rightrotate(w[3],18) xor (w[3] shr 3);
w [18] := w[2] + s0 + w[11] + fs1[0];
w [19] := w[3] + fs0[0] + w[12] + fs1[1];
for i:=20 to 32 do
begin
s1 := rightrotate(w[i-2],17) xor rightrotate(w[i-2],19) xor (w[i-2] shr 10);
w[i] := w[i-16] + fs0[i-19] + w[i-7] + s1;
end;
for i:=33 to 63 do
begin
s0 := rightrotate(w[i-15],7) xor rightrotate(w[i-15],18) xor (w[i-15] shr 3);
s1 := rightrotate(w[i-2],17) xor rightrotate(w[i-2],19) xor (w[i-2] shr 10);
w[i] := w[i-16] + s0 + w[i-7] + s1;
end;
a:=round4[0];
b:=round4[1];
c:=round4[2];
d:=round4[3];
e:=round4[4];
f:=round4[5];
g:=round4[6];
h:=round4[7];
a:=a+nonce;
e:=e+nonce;
for i:=4 to 63 do
begin
S1 := rightrotate(e,6) xor rightrotate(e,11) xor rightrotate(e,25);
ch := (e and f) xor ((not e) and g);
temp1 := h + S1 + ch + k[i] + w[i];
S0 := rightrotate(a,2) xor rightrotate(a,13) xor rightrotate(a,22);
maj := (a and b) xor (a and c) xor (b and c);
temp2 := S0 + maj;
h := g;
g := f;
f := e;
e := d + temp1;
d := c;
c := b;
b := a;
a := temp1 + temp2;
end;
hash[0]:=h0+a;
hash[1]:=h1+b;
hash[2]:=h2+c;
hash[3]:=h3+d;
hash[4]:=h4+e;
hash[5]:=h5+f;
hash[6]:=h6+g;
hash[7]:=h7+h;
end;
function SHA_second_hashing(var w:array of Cardinal):boolean;
var
h0,h1,h2,h3,h4,h5,h6,h7:Cardinal;
i:integer;
s0,s1,ch,temp1,temp2,maj:Cardinal;
a,b,c,d,e,f,g,h:Cardinal;
begin
h0 := $6a09e667;
h1 := $bb67ae85;
h2 := $3c6ef372;
h3 := $a54ff53a;
h4 := $510e527f;
h5 := $9b05688c;
h6 := $1f83d9ab;
h7 := $5be0cd19;
for i:=16 to 63 do
begin
s0 := rightrotate(w[i-15],7) xor rightrotate(w[i-15],18) xor (w[i-15] shr 3);
s1 := rightrotate(w[i-2],17) xor rightrotate(w[i-2],19) xor (w[i-2] shr 10);
w[i] := w[i-16] + s0 + w[i-7] + s1;
end;
a:=h0;
b:=h1;
c:=h2;
d:=h3;
e:=h4;
f:=h5;
g:=h6;
h:=h7;
for i:=0 to 63 do
begin
S1 := rightrotate(e,6) xor rightrotate(e,11) xor rightrotate(e,25);
ch := (e and f) xor ((not e) and g);
temp1 := h + S1 + ch + k[i] + w[i];
S0 := rightrotate(a,2) xor rightrotate(a,13) xor rightrotate(a,22);
maj := (a and b) xor (a and c) xor (b and c);
temp2 := S0 + maj;
h := g;
g := f;
f := e;
e := d + temp1;
d := c;
c := b;
b := a;
a := temp1 + temp2;
end;
w[7]:=h7+h;
if w[7]<>0 then
begin
result:=false;
Exit;
end;
w[6]:=h6+g;
w[5]:=h6+f;
w[4]:=h6+e;
w[3]:=h6+d;
w[2]:=h6+c;
w[1]:=h6+b;
w[0]:=h6+a;
result:=true;
end;
function sha256_doublehash(var buf,target:Tdata;var w2:array of Cardinal;w3:array of Cardinal;timestamp:Cardinal;nonce:Cardinal):boolean;
begin
SHA_dynamic_header_hashing(w2,buf,timestamp,nonce,w3);
if not SHA_second_hashing(w3) then
result:=false
else
if CheckToTarget(w3,target) then
result:=true
else
result:=false;
end;
procedure SHA_stable_header_hashing(var w:array of Cardinal;var buf:Tdata);
var
h0,h1,h2,h3,h4,h5,h6,h7:Cardinal;
i:integer;
s0,s1,ch,temp1,temp2,maj:Cardinal;
a,b,c,d,e,f,g,h:Cardinal;
begin
h0 := $6a09e667;
h1 := $bb67ae85;
h2 := $3c6ef372;
h3 := $a54ff53a;
h4 := $510e527f;
h5 := $9b05688c;
h6 := $1f83d9ab;
h7 := $5be0cd19;
for i:=16 to 63 do
begin
s0 := rightrotate(w[i-15],7) xor rightrotate(w[i-15],18) xor (w[i-15] shr 3);
s1 := rightrotate(w[i-2],17) xor rightrotate(w[i-2],19) xor (w[i-2] shr 10);
w[i] := w[i-16] + s0 + w[i-7] + s1;
end;
a:=h0;
b:=h1;
c:=h2;
d:=h3;
e:=h4;
f:=h5;
g:=h6;
h:=h7;
for i:=0 to 63 do
begin
S1 := rightrotate(e,6) xor rightrotate(e,11) xor rightrotate(e,25);
ch := (e and f) xor ((not e) and g);
temp1 := h + S1 + ch + k[i] + w[i];
S0 := rightrotate(a,2) xor rightrotate(a,13) xor rightrotate(a,22);
maj := (a and b) xor (a and c) xor (b and c);
temp2 := S0 + maj;
h := g;
g := f;
f := e;
e := d + temp1;
d := c;
c := b;
b := a;
a := temp1 + temp2;
end;
buf[0]:=h0+a;
buf[1]:=h1+b;
buf[2]:=h2+c;
buf[3]:=h3+d;
buf[4]:=h4+e;
buf[5]:=h5+f;
buf[6]:=h6+g;
buf[7]:=h7+h;
end;
procedure get_round4_calculation(var w:array of Cardinal;buf:Tdata;timestamp:Cardinal);
var
a,b,c,d,e,f,g,h:Cardinal;
i:integer;
s0,s1,ch,maj,temp1,temp2:Cardinal;
begin
w[1]:=timestamp;
w[3]:=0;
a:=buf[0];
b:=buf[1];
c:=buf[2];
d:=buf[3];
e:=buf[4];
f:=buf[5];
g:=buf[6];
h:=buf[7];
for i:=0 to 3 do
begin
S1 := rightrotate(e,6) xor rightrotate(e,11) xor rightrotate(e,25);
ch := (e and f) xor ((not e) and g);
temp1 := h + S1 + ch + k[i] + w[i];
S0 := rightrotate(a,2) xor rightrotate(a,13) xor rightrotate(a,22);
maj := (a and b) xor (a and c) xor (b and c);
temp2 := S0 + maj;
h := g;
g := f;
f := e;
e := d + temp1;
d := c;
c := b;
b := a;
a := temp1 + temp2;
end;
round4[0]:=a;
round4[1]:=b;
round4[2]:=c;
round4[3]:=d;
round4[4]:=e;
round4[5]:=f;
round4[6]:=g;
round4[7]:=h;
end;
function nonce_search(inp:Tdata;target:Tdata;var nonce:Cardinal):boolean;
var
dt:Cardinal;
buf:Tdata;
w1,w2,w3:array [0..63] of Cardinal;
timestamp:Cardinal;
s0,s1:Cardinal;
i:integer;
begin
result:=true;
timestamp:=0;
dt:=DateTimeToUnix(Now);
setlength(buf,8);
Move(inp[0],w1[0],64);
ZeroMemory(@w2[0],256);
Move(inp[16],w2[0],16);
w2[4]:=2147483648;
w2[15]:=640;
ZeroMemory(@w3[0],256);
w3[8]:=2147483648;
w3[15]:=256;
SHA_stable_header_hashing(w1,buf);
for nonce:=0 to 4294967295 do
begin
if timestamp<>global_timestamp then
begin
timestamp:=global_timestamp;
for i:=16 to 17 do
begin
s0 := rightrotate(w2[i-15],7) xor rightrotate(w2[i-15],18) xor (w2[i-15] shr 3);
s1 := rightrotate(w2[i-2],17) xor rightrotate(w2[i-2],19) xor (w2[i-2] shr 10);
w2 [i] := w2[i-16] + s0 + w2[i-7] + s1;
end;
for i:=0 to 13 do
fs0[i] := rightrotate(w2[i+4],7) xor rightrotate(w2[i+4],18) xor (w2[i+4] shr 3);
for i:=0 to 1 do
fs1[i]:=rightrotate(w2[i+16],17) xor rightrotate(w2[i+16],19) xor (w2[i+16] shr 10);
get_round4_calculation(w2,buf,timestamp);
end;
if sha256_doublehash(buf,target,w2,w3,timestamp,nonce) then
Exit;
if nonce=10000001 then
Showmessage(inttostr(DateTimeToUnix(now)-dt));
end;
result:=false;
end;
end.
Код инициализации и запуска майнинга:
Code:Copy to clipboard
function SwapString(s:string):string;
var
i:integer;
begin
result:='';
i:=length(s)-1;
while i>0 do
begin
result:=result+copy(s,i,2);
i:=i-2;
end;
end;
function HexToInt(Str : string): integer;
var
i, r : integer;
begin
val('$'+Trim(Str),r, i);
if i<>0 then
HexToInt := 0
else
HexToInt := r;
end;
function HexToString(s:string):string;
var
i:integer;
begin
result:='';
i:=1;
while i<=length(s) do
begin
result:=result+char(HexToInt(s[i]+s[i+1]));
i:=i+2;
end;
end;
procedure InitAndStart;
var
data:Tdata;
prevblock,rootHash:string;
nonce:Cardinal;
target:Tdata;
i:integer;
begin
global_timestamp:=DateTimeToUnix(now);
setlength(target,8);
target[0]:=0;
target[1]:=0;
target[2]:=0;
target[3]:=0;
target[4]:=0;
target[5]:=4060086272;
target[6]:=17593;
target[7]:=0;
prevblock:=HEXToString(SwapString('00000000000008a3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81'));
rootHash:=HEXToString(SwapString('2b12fcf1b09288fcaff797d71e950e71ae42b91e8bdb2304758dfcffc2b620e3'));
setlength(data,20);
data[0]:=1;
Move(prevblock[1],data[1],32);
Move(rootHash[1],data[9],32);
data[17]:=1305998791;
data[18]:=440711666;
data[19]:=0;
for i:=0 to 19 do
data[i]:=cuda_swab32(data[i]);
if nonce_search(data,target,nonce) then
Showmessage(inttostr(nonce))
else
Showmessage('Not found');
end;
6. Замеры и выводы
После запуска и замеров моей вариации майнера при тех же условиях и на том же
железе был получен результат 144927 х/c. Таким образом коэффициент увеличения
скорости майнинга на этапе перебора nonce составил x1.3478 от результата CUDA,
что можно считать вполне приемлемым исходом исследования. Если же посчитать
коэффициент буста хэшрейта по сравнению с классической реализацией SHA, то он
составит приблизительно x1.7246. Думаю дополнительные исследования позволят
ещё немного увеличить скорость. Кроме
того ресерч надо проводить и для этапов хэширования codebase транзакции вместе
с формированием Merkle Root, так как эти вычисления при больших мощностях
будут проходить достаточно часто и могут тормозить основной процесс майнинга.
Более того, подобные методы ускорения майнинга могут найтись и в других
криптовалютах.
Хотите чтобы я провел дополнительные исследования и выложил результаты на
форуме? Голосуйте за меня и да пусть победит темная сторона силы.
7. Дополнение 1: ещё один обнаруженный способ
Всякие интересные решения часто приходят перед сном и это не исключение. Кто-
то пытаясь заснуть считает овец. Я же думал об алгоритме хэширования. И
обнаружил ещё один интересующий меня участок кода. Функция SHA_second_hashing
рассчитывает второй хэш из первого и, как мы ранее обсуждали, в ней нет смысла
делать рассчет хэша до кноца. В CUDA свою окончательную форму принимают лишь
последние 2 32-битных элемента хэша. У меня же проводится тестирование
последнего элемента на равенство 0 и вылету из функции при не соблюдение
данного условия.. Но в процессе 64 раундов хэширования очереди сообщений так
сильно необходимый в состояние 0 последний элемент не меняет своё состояние в
последних 3-ех раундах. Он лишь меняет свою позицию.
Посмотрите сюда:
Code:Copy to clipboard
for i:=0 to 63 do
begin
S1 := rightrotate(e,6) xor rightrotate(e,11) xor rightrotate(e,25);
ch := (e and f) xor ((not e) and g);
temp1 := h + S1 + ch + k[i] + w[i];
S0 := rightrotate(a,2) xor rightrotate(a,13) xor rightrotate(a,22);
maj := (a and b) xor (a and c) xor (b and c);
temp2 := S0 + maj;
h := g;
g := f;
f := e;
e := d + temp1;
d := c;
c := b;
b := a;
a := temp1 + temp2;
end;
w[7]:=h7+h;
if w[7]<>0 then
begin
result:=false;
Exit;
end;
На последнем 64-ом раунде h принимает свое окончательное значение, затем уже
вне цикла складывается с изначальным состоянием h7 и тестируется на
соответствие нулю. Но посмотрите что же такое h на последнем раунде на самом
деле. Это состояние g на предпоследнем раунде. Т.е. h по факту не изменился,
но изменилось его положение в памяти. Последнее фактическое изменение h
происходит на 60-ом раунде. Тогда зачем нам нужно ещё 3 раунда хэширования
если на 60-ом раунде сравнение с целью не даст нужный результат? Добавим в
функцию тест на достижение цели после 60-ого раунда учитывая тот факт, что
реальное местоположение h находится в области памяти выделенной под e.
Внесем изменения в исследуемую функцию:
Code:Copy to clipboard
function SHA_second_hashing(var w:array of Cardinal):boolean;
var
h0,h1,h2,h3,h4,h5,h6,h7:Cardinal;
i:integer;
s0,s1,ch,temp1,temp2,maj:Cardinal;
a,b,c,d,e,f,g,h:Cardinal;
begin
h0 := $6a09e667;
h1 := $bb67ae85;
h2 := $3c6ef372;
h3 := $a54ff53a;
h4 := $510e527f;
h5 := $9b05688c;
h6 := $1f83d9ab;
h7 := $5be0cd19;
for i:=16 to 63 do
begin
s0 := rightrotate(w[i-15],7) xor rightrotate(w[i-15],18) xor (w[i-15] shr 3);
s1 := rightrotate(w[i-2],17) xor rightrotate(w[i-2],19) xor (w[i-2] shr 10);
w[i] := w[i-16] + s0 + w[i-7] + s1;
end;
a:=h0;
b:=h1;
c:=h2;
d:=h3;
e:=h4;
f:=h5;
g:=h6;
h:=h7;
for i:=0 to 60 do
begin
S1 := rightrotate(e,6) xor rightrotate(e,11) xor rightrotate(e,25);
ch := (e and f) xor ((not e) and g);
temp1 := h + S1 + ch + k[i] + w[i];
S0 := rightrotate(a,2) xor rightrotate(a,13) xor rightrotate(a,22);
maj := (a and b) xor (a and c) xor (b and c);
temp2 := S0 + maj;
h := g;
g := f;
f := e;
e := d + temp1;
d := c;
c := b;
b := a;
a := temp1 + temp2;
end;
if h7+e<>0 then
begin
result:=false;
Exit;
end;
for i:=61 to 63 do
begin
S1 := rightrotate(e,6) xor rightrotate(e,11) xor rightrotate(e,25);
ch := (e and f) xor ((not e) and g);
temp1 := h + S1 + ch + k[i] + w[i];
S0 := rightrotate(a,2) xor rightrotate(a,13) xor rightrotate(a,22);
maj := (a and b) xor (a and c) xor (b and c);
temp2 := S0 + maj;
h := g;
g := f;
f := e;
e := d + temp1;
d := c;
c := b;
b := a;
a := temp1 + temp2;
end;
w[7]:=h7+h;
if w[7]<>0 then
begin
result:=false;
Exit;
end;
w[6]:=h6+g;
w[5]:=h6+f;
w[4]:=h6+e;
w[3]:=h6+d;
w[2]:=h6+c;
w[1]:=h6+b;
w[0]:=h6+a;
result:=true;
end;
Делаем замер и получаем прирост к хэшрейту до состояния 147058 х/с.
Коэффициент увеличения хэшрейта х1.3676
Автор @BlackDog
источник exploit.in