From e8b6d4b19c16351cfd47a8512ce1c5fe57dc630e Mon Sep 17 00:00:00 2001 From: bsdevlin Date: Thu, 28 Feb 2019 13:07:24 -0500 Subject: [PATCH] Updates to SHA256 core and a difficulty module --- ip_cores/sha256/src/rtl/sha256_pkg.sv | 45 ++++- ip_cores/sha256/src/rtl/sha256_top.sv | 145 +++++++------- ip_cores/sha256/src/tb/sha256_top_tb.sv | 57 +++--- .../rtl/zcash_verif_equihash_difficulty.sv | 177 ++++++++++++++++++ zcash_verif/src/rtl/zcash_verif_pkg.sv | 1 + 5 files changed, 326 insertions(+), 99 deletions(-) create mode 100644 zcash_verif/src/rtl/zcash_verif_equihash_difficulty.sv diff --git a/ip_cores/sha256/src/rtl/sha256_pkg.sv b/ip_cores/sha256/src/rtl/sha256_pkg.sv index b3dda68..6467f2b 100644 --- a/ip_cores/sha256/src/rtl/sha256_pkg.sv +++ b/ip_cores/sha256/src/rtl/sha256_pkg.sv @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -*/ +*/ 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 \ No newline at end of file diff --git a/ip_cores/sha256/src/rtl/sha256_top.sv b/ip_cores/sha256/src/rtl/sha256_top.sv index 808a953..520e136 100644 --- a/ip_cores/sha256/src/rtl/sha256_top.sv +++ b/ip_cores/sha256/src/rtl/sha256_top.sv @@ -19,26 +19,28 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - + 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 \ No newline at end of file diff --git a/ip_cores/sha256/src/tb/sha256_top_tb.sv b/ip_cores/sha256/src/tb/sha256_top_tb.sv index 87ff33a..7eef046 100644 --- a/ip_cores/sha256/src/tb/sha256_top_tb.sv +++ b/ip_cores/sha256/src/tb/sha256_top_tb.sv @@ -16,22 +16,22 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - -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 \ No newline at end of file diff --git a/zcash_verif/src/rtl/zcash_verif_equihash_difficulty.sv b/zcash_verif/src/rtl/zcash_verif_equihash_difficulty.sv new file mode 100644 index 0000000..6806d5d --- /dev/null +++ b/zcash_verif/src/rtl/zcash_verif_equihash_difficulty.sv @@ -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 . + */ + +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 \ No newline at end of file diff --git a/zcash_verif/src/rtl/zcash_verif_pkg.sv b/zcash_verif/src/rtl/zcash_verif_pkg.sv index 83a7a66..2547070 100644 --- a/zcash_verif/src/rtl/zcash_verif_pkg.sv +++ b/zcash_verif/src/rtl/zcash_verif_pkg.sv @@ -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