Updates to SHA256 core and a difficulty module

This commit is contained in:
bsdevlin 2019-02-28 13:07:24 -05:00
parent 547a557a14
commit e8b6d4b19c
5 changed files with 326 additions and 99 deletions

View File

@ -15,7 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
*/
package sha256_pkg;
@ -37,4 +37,47 @@ package sha256_pkg;
32'ha54ff53a, 32'h3c6ef372, 32'hbb67ae85, 32'h6a09e667
};
// Functions used in bit manipulations
function [31:0] little_sig0(input logic [31:0] in);
little_sig0 = {rotr(in, 7)} ^ {rotr(in, 18)} ^ {shr(in, 3)};
endfunction
function [31:0] little_sig1(input logic [31:0] in);
little_sig1 = {rotr(in, 17)} ^ {rotr(in, 19)} ^ {shr(in, 10)};
endfunction
function [31:0] big_sig0(input logic [31:0] in);
big_sig0 = {rotr(in, 2)} ^ {rotr(in, 13)} ^ {rotr(in, 22)};
endfunction
function [31:0] big_sig1(input logic [31:0] in);
big_sig1 = {rotr(in, 6)} ^ {rotr(in, 11)} ^ {rotr(in, 25)};
endfunction
function [31:0] ch(input logic [31:0] x, y, z);
ch = (x & y) ^ (~x & z);
endfunction
function [31:0] maj(input logic [31:0] x, y, z);
maj = (x & y) ^ (x & z) ^ (y & z);
endfunction
function [31:0] rotr(input logic [31:0] in, input int bits);
for (int i = 0; i < 32; i++) rotr[i] = in[(i+bits) % 32];
endfunction
function [31:0] shr(input logic [31:0] in, input int bits);
shr = 0;
shr = in >> bits;
endfunction
// Swap bytes (used to convert between little and big endian)
function [31:0] bs32(input logic [31:0] in);
for (int i = 0; i < 4; i++) bs32[i*8 +: 8] = in[(4-1-i)*8 +: 8];
endfunction
function [63:0] bs64(input logic [63:0] in);
for (int i = 0; i < 8; i++) bs64[i*8 +: 8] = in[(8-1-i)*8 +: 8];
endfunction
endpackage

View File

@ -19,26 +19,28 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
module sha256_top
import sha256_pkg::*;
(
input i_clk, i_rst,
if_axi_stream.sink i_block, // Message stream to be hashed, must be 512 bits
if_axi_stream.source o_hash // Resulting hash digest (32 bytes)
);
localparam DAT_BYTS = 64;
localparam DAT_BITS = DAT_BYTS*8; // Must be 512
localparam ROUNDS = 64;
logic [7:0][31:0] V; // Internal state, V[0] == A, V[7] == H
logic [31:0] T1, T2;
logic [15:0][31:0] W;
logic [15:0] W_nxt;
logic eop_l, padding_only, final_block;
logic [7:0][0:31] V, H; // Internal states, V[0] == A, V[7] == H (in big endian)
logic [0:31] T1, T2;
logic [15:0][0:31] W;
logic [0:31] W_nxt;
logic padding_only, final_block;
logic [63:0] bit_len;
logic [$clog2(64)-1:0] rnd_cntr;
logic [$clog2(ROUNDS)-1:0] rnd_cntr;
logic [1:0][0:31] bit_len_c;
enum {SHA_IDLE = 0,
SHA_ROUNDS = 1,
@ -46,14 +48,14 @@ enum {SHA_IDLE = 0,
SHA_FINAL = 3} sha_state;
// Used to make compression function easier to read
localparam A = 0;
localparam B = 1;
localparam C = 2;
localparam D = 3;
localparam E = 4;
localparam F = 5;
localparam G = 6;
localparam H = 7;
localparam VAR_A = 0;
localparam VAR_B = 1;
localparam VAR_C = 2;
localparam VAR_D = 3;
localparam VAR_E = 4;
localparam VAR_F = 5;
localparam VAR_G = 6;
localparam VAR_H = 7;
always_ff @ (posedge i_clk) begin
@ -64,7 +66,6 @@ always_ff @ (posedge i_clk) begin
i_block.rdy <= 0;
bit_len <= 0;
W <= 0;
eop_l <= 0;
padding_only <= 0;
final_block <= 0;
end else begin
@ -75,7 +76,6 @@ always_ff @ (posedge i_clk) begin
update_HV(1);
rnd_cntr <= 0;
final_block <= 0;
eop_l <= 0;
padding_only <= 0;
i_block.rdy <= 1;
// As soon as we have one write on the input we can start
@ -89,26 +89,29 @@ always_ff @ (posedge i_clk) begin
end
end
SHA_ROUNDS: begin
for (int i = 0; i < 16; i++) W[i] <= W[i+i];
for (int i = 0; i < 15; i++)
W[i] <= W[i+1];
W[15] <= W_nxt;
compress();
rnd_cntr <= rnd_cntr + 1;
if (rnd_cntr == 62) begin
if (rnd_cntr == ROUNDS - 1) begin
rnd_cntr <= 0;
i_block.rdy <= ~(final_block || padding_only);
sha_state <= SHA_UPDATE_HV;
end
end
SHA_UPDATE_HV: begin
update_H(0);
update_HV(0);
if (final_block) begin
sha_state <= SHA_FINAL;
i_block.rdy <= 0;
end else if (padding_only) begin
final_block <= 1;
W <= 0;
W[(64-8)*8 +: 64] <= bit_len;
W[(bit_len/8) % 64 +: 8] <= 1;
W[15:14] <= {bit_len_c[0], bit_len_c[1]};
if (bit_len % 512 == 0)
W[0] <= sha256_pkg::bs32(32'd1);
sha_state <= SHA_ROUNDS;
end else if (i_block.rdy && i_block.val) begin
W <= i_block.dat;
bit_len <= bit_len + DAT_BITS;
@ -121,9 +124,15 @@ always_ff @ (posedge i_clk) begin
end
SHA_FINAL: begin
o_hash.val <= 1;
o_hash.dat <= H;
for (int i = 0; i < 8; i++)
o_hash.dat[i*32 +: 32] <= sha256_pkg::bs32(H[i]); // Shift back to little endian
if (o_hash.val && o_hash.rdy) begin
o_hash.val <= 0;
rnd_cntr <= 0;
bit_len <= 0;
W <= 0;
padding_only <= 0;
final_block <= 0;
sha_state <= SHA_IDLE;
end
end
@ -135,7 +144,6 @@ end
// wise set a flag so next time we call we only add the padding block
task msg_eop();
eop_l <= 1;
bit_len <= bit_len + (i_block.mod == 0 ? DAT_BITS : i_block.mod*8);
if (i_block.mod == 0 || i_block.mod > 64-9) begin
padding_only <= 1; // Means we need one block extra with only len (and possibly the terminating 0x1)
@ -143,41 +151,52 @@ task msg_eop();
final_block <= 1; // This is the final block and includes padding
end
for (int i = 0; i < 64; i++)
M[i*8 +: 8] <= (i_block.mod == 0 || i < i_block.mod) ? i_block.dat[i*8 +: 8] : 0;
// Every 32 bit word needs to be swapped to big endian
for (int i = 0; i < 16; i++)
W[i] <= (i_block.mod == 0 || i < i_block.mod) ? sha256_pkg::bs32(i_block.dat[i*32 +: 32]) : 0;
if (i_block.mod != 0)
M[i_block.mod*8 +: 8] <= 1;
W[i_block.mod/4][8*(i_block.mod % 4) +: 8] <= 8'h80; // Since we operate in big endian
if (i_block.mod < 64-9)
M[(64-8)*8 +: 64] <= bit_len + i_block.mod*8;
W[15:14] <= {bit_len_c[0], bit_len_c[1]};
endtask
always_comb begin
W_nxt = little_sig1(W[14]) + W[9] + little_sig0(W[1]) + W[0];
end
always_comb begin
T1 = V[H] + big_sig1(V[E]) + ch(V[E], V[F], V[G]) + sha256_pkg::K[rnd_cntr] + W[0];
T2 = big_sig0(V[A]) + maj(V[A], V[B], V[C]);
bit_len_c = (bit_len + i_block.mod*8);
W_nxt = sha256_pkg::little_sig1(W[14]) +
W[9] +
sha256_pkg::little_sig0(W[1]) +
W[0];
T1 = V[VAR_H] +
sha256_pkg::big_sig1(V[VAR_E]) +
sha256_pkg::ch(V[VAR_E], V[VAR_F], V[VAR_G]) +
sha256_pkg::K[rnd_cntr] +
W[0];
T2 = sha256_pkg::big_sig0(V[VAR_A]) +
sha256_pkg::maj(V[VAR_A], V[VAR_B], V[VAR_C]);
end
task compress();
V[H] <= V[G];
V[G] <= V[F];
V[F] <= V[E];
V[E] <= V[D] + T1;
V[D] <= V[C];
V[C] <= V[B];
V[B] <= V[A];
V[A] <= T1 + T2;
V[VAR_H] <= V[VAR_G];
V[VAR_G] <= V[VAR_F];
V[VAR_F] <= V[VAR_E];
V[VAR_E] <= V[VAR_D] + T1;
V[VAR_D] <= V[VAR_C];
V[VAR_C] <= V[VAR_B];
V[VAR_B] <= V[VAR_A];
V[VAR_A] <= T1 + T2;
endtask
task update_HV(logic init);
task update_HV(input logic init);
if (init) begin
for (int i = 0; i < 8; i++) begin
H[i] <= sha256_pkg::IV[i];
V[i] <= sha256_pkg::IV[i];
H[i] <= (sha256_pkg::IV[i]);
V[i] <= (sha256_pkg::IV[i]);
end
end else begin
for (int i = 0; i < 8; i++) begin
@ -187,38 +206,10 @@ task update_HV(logic init);
end
endtask
function [31:0] litte_sig0(input logic [31:0] in);
litte_sig0 = {rotr(in, 7)} ^ {rotr(in, 18)} ^ {shr(in, 3)};
endfunction
// Check that input size is correct
initial begin
assert ($bits(i_block.dat) == DAT_BITS) else $fatal(1, "%m %t ERROR: sha256_top DAT_BITS (%d) does not match interface .dat (%d)", $time, DAT_BITS, $bits(i_block.dat));
end
function [31:0] litte_sig1(input logic [31:0] in);
litte_sig1 = {rotr(in, 17)} ^ {rotr(in, 19)} ^ {shr(in, 10)};
endfunction
function [31:0] big_sig0(input logic [31:0] in);
big_sig0 = {rotr(in, 2)} ^ {rotr(in, 13)} ^ {rotr(in, 22)};
endfunction
function [31:0] big_sig1(input logic [31:0] in);
big_sig1 = {rotr(in, 6)} ^ {rotr(in, 11)} ^ {rotr(in, 25)};
endfunction
function [31:0] ch(input logic [31:0] x, y, z);
ch = (x & y) ^ (~x & z);
endfunction
function [31:0] maj(input logic [31:0] x, y, z);
maj = (x & y) ^ (x & z) ^ (y & z);
endfunction
function [31:0] rotr(input logic [31:0] in, input int bits);
for (int i = 0; i < 32; i++) rotr[i] = in[(i+bits) % 32];
endfunction
function [31:0] shr(input logic [31:0] in, input int bits);
shr = 0;
shr = in >> bits;
endfunction
endmodule

View File

@ -16,22 +16,22 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
module sha256_top_tb();
import common_pkg::*;
logic clk, rst;
module sha256_top_tb();
import common_pkg::*;
logic clk, rst;
logic [255:0] expected;
if_axi_stream #(.DAT_BYTS(64)) i_block(clk);
if_axi_stream #(.DAT_BYTS(32)) out_hash(clk);
initial begin
rst = 0;
#100ns rst = 1;
#100ns rst = 0;
end
initial begin
clk = 0;
forever #10ns clk = ~clk;
@ -44,33 +44,48 @@ sha256_top DUT (
.o_hash ( out_hash )
);
// This test runs the hash which is shown in the RFC, for "abc"
task rfc_test();
// NIST testcase for single 512 bit block "abc"
task nist_single_block_test();
begin
integer signed get_len;
logic [common_pkg::MAX_SIM_BYTS*8-1:0] get_dat;
$display("Running rfc_test...\n");
expected = 'h239900d4ed8623b95a92f1dba88ad31895cc3345ded552c22d79ab2a39c5877dd1a2ffdb6fbb124bb7c45a68142f214ce9f6129fb697276a0d4d1c983fa580ba;
i_block.put_stream("cba", 3);
$display("Running nist_single_block_test...\n");
expected = 'had1500f261ff10b49c7a1796a36103b02322ae5dde404141eacf018fbf1678ba; // Both in little endian
i_block.put_stream("cba", 3); // abc in little endian
out_hash.get_stream(get_dat, get_len);
common_pkg::compare_and_print(get_dat, expected);
$display("rfc_test PASSED");
$display("nist_single_block_test PASSED");
end
endtask
// NIST testcase for double 512 bit block "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
task nist_double_block_test();
begin
integer signed get_len;
logic [common_pkg::MAX_SIM_BYTS*8-1:0] get_dat;
$display("Running nist_double_block_test...\n");
expected = 'hc106db19d4edecf66721ff6459e43ca339603e0c9326c0e5b83806d2616a8d24; // Both in little endian
i_block.put_stream("qponponmonmlnmlkmlkjlkjikjihjihgihgfhgfegfedfedcedcbdcba", 56);
out_hash.get_stream(get_dat, get_len);
common_pkg::compare_and_print(get_dat, expected);
$display("nist_double_block_test PASSED");
end
endtask
// Main testbench calls
initial begin
i_block.reset_source();
out_hash.rdy = 1;
#200ns;
rfc_test();
nist_single_block_test();
nist_double_block_test();
#10us $finish();
end
endmodule

View File

@ -0,0 +1,177 @@
/*
This verifies that a Zcash equihash solution has the correct difficulty.
Take input stream of entire block header (including equihash solution and size) and
calculate the SHA256d (double SHA256)
We take in the stream in DAT_BYTS in a FIFO, and load the output into 512 bit words
for the SHA256 block. Then the 256 bit output it inputted into the same SHA256 block.
Copyright (C) 2019 Benjamin Devlin and Zcash Foundation
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
module zcash_verif_equihash_difficulty
import zcash_verif_pkg::*;
#(
parameter DAT_BYTS = 8,
)(
input i_clk, i_rst,
if_axi_stream.sink i_axi,
input logic [31:0] i_difficulty, // Must be valid on .sop && .val
output logic o_difficulty_fail,
output logic o_val
);
logic [$clog2($bits(cblockheader_sol_t)/8)-1:0] byt_cnt;
logic o_fifo_full, o_fifo_emp;
logic [31:0] difficulty;
if_axi_stream #(.DAT_BYTS(DAT_BYTS)) o_fifo(clk);
if_axi_stream #(.DAT_BYTS(64)) i_block(clk);
if_axi_stream #(.DAT_BYTS(32)) o_hash(clk);
enum {IDLE = 0,
SHA256_0 = 1,
SHA256_1 = 2,
FINISHED = 3} state;
always_ff @ (posedge i_clk) begin
if (i_rst) begin
o_difficulty_fail <= 0;
o_val <= 0;
byt_cnt <= 0;
i_block.reset_source();
o_hash.rdy <= 0;
o_fifo.rdy <= 0;
difficulty <= 0;
state <= IDLE;
end else begin
o_val <= 0;
o_hash.rdy <= 1;
case(state)
IDLE: begin
i_block.reset_source();
o_fifo.rdy <= 0;
byt_cnt <= 0;
if (i_axi.rdy && i_axi.val && i_axi.sop) begin
difficulty <= i_difficulty;
state <= SHA256_0;
o_fifo.rdy <= 1;
end
end
// Convert data to 512 bit wide
SHA256_0: begin
if (o_fifo.rdy && o_fifo.val) begin
i_block.val <= 0;
byt_cnt <= byt_cnt + DAT_BYTS;
i_block.dat[byt_cnt +: DAT_BYTS] <= o_fifo.dat;
end
if (((byt_cnt + DAT_BYTS) % 64 == 0) ||
(byt_cnt + DAT_BYTS) == $bits(cblockheader_sol_t)/8) begin
i_block.val <= 1;
i_block.sop <= (byt_cnt + DAT_BYTS)/64 == 1;
i_block.eop <= 0;
i_block.mod <= (byt_cnt + DAT_BYTS);
o_fifo.rdy <= i_block.rdy;
if ((byt_cnt + DAT_BYTS) == $bits(cblockheader_sol_t)/8) begin
i_block.eop <= 1;
state <= SHA256_1;
end
end else begin
o_fifo.rdy <= 1;
end
end
SHA256_1: begin
if (i_block.val && i_block.rdy) begin
i_block.val <= 0;
end
if (o_hash.val && o_hash.rdy) begin
i_block.val <= 1;
i_block.dat <= o_hash.dat;
i_block.sop <= 1;
i_block.eop <= 1;
i_block.mod <= 32;
state <= FINISHED;
end
end
FINISHED: begin
if (i_block.val && i_block.rdy) begin
i_block.val <= 0;
end
if (o_hash.val && o_hash.rdy) begin
o_difficulty_fail <= check_difficulty(o_hash.dat, difficulty);
o_val <= 1;
state <= IDLE;
end
end
endcase
if ( o_fifo_full ) begin
o_difficulty_fail <= 1;
o_val <= 1;
o_hash.rdy <= 1;
o_fifo.rdy <= 1;
i_block.reset_source();
if ( o_fifo_emp )
state <= IDLE;
end
end
end
// Function to check if difficulty passes - bits is the number of 0s we
// need
// TODO target function
function check_difficulty(input logic [255:0] hash, logic [31:0] bits);
check_difficulty = 0;
for (int i = 0; i < 64; i++)
if (i > bits && hash[(64-1-i)*8 +: 8] != 0)
check_difficulty = 1;
endfunction
// FIFO for storing input stream
axi_stream_fifo #(
.SIZE ( ($bits(cblockheader_sol_t)/8)/DAT_BYTS ),
.DAT_BITS ( DAT_BYTS/8 ),
.USE_BRAM ( 1 )
)
axi_stream_fifo (
.i_clk ( i_clk ),
.i_rst ( i_rst ),
.i_axi ( i_axi ),
.o_axi ( o_fifo ),
.o_full ( o_fifo_full ),
.o_emp ( o_fifo_emp )
);
// SHA256 block
sha256_top sha256_top (
.i_clk ( i_clk ),
.i_rst ( i_rst ),
.i_block ( i_block ),
.o_hash ( o_hash )
);
endmodule

View File

@ -37,6 +37,7 @@ package zcash_verif_pkg;
logic BAD_ZERO_ORDER;
logic BAD_IDX_ORDER;
logic XOR_NON_ZERO;
logic DIFFICULTY_FAIL;
} equihash_bm_t;
// Format for equihash input - should be 144 bytes