-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added SHA3 and Keccak-256 #3
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,9 @@ module Hash { | |
reset() : (); | ||
// Returns the number of bytes that sum will return. | ||
size() : Nat; | ||
// Return the hash. | ||
// TODO: Should probably rename this | ||
checkSum() : [Nat8]; | ||
Comment on lines
+9
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It there are reason this was added to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added it to avoid repeating myself in the SHA3_* by reusing |
||
// Adds the current hash to the resulting slice. | ||
// The underlying hash is not modified. | ||
sum(bs : [Nat8]) : [Nat8]; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
import Array "mo:base-0.7.3/Array"; | ||
import Nat "mo:base-0.7.3/Nat"; | ||
import Nat8 "mo:base-0.7.3/Nat8"; | ||
import Nat64 "mo:base-0.7.3/Nat64"; | ||
import Int "mo:base-0.7.3/Int"; | ||
import Iter "mo:base-0.7.3/Iter"; | ||
|
||
import Hash "../Hash"; | ||
|
||
import Hex "mo:encoding/Hex"; | ||
import Debug "mo:base-0.7.3/Debug"; | ||
|
||
module { | ||
|
||
// Keccak constants | ||
private let keccakf_rndc : [Nat64] = [ | ||
0x0000000000000001, 0x0000000000008082, 0x800000000000808a, | ||
0x8000000080008000, 0x000000000000808b, 0x0000000080000001, | ||
0x8000000080008081, 0x8000000000008009, 0x000000000000008a, | ||
0x0000000000000088, 0x0000000080008009, 0x000000008000000a, | ||
0x000000008000808b, 0x800000000000008b, 0x8000000000008089, | ||
0x8000000000008003, 0x8000000000008002, 0x8000000000000080, | ||
0x000000000000800a, 0x800000008000000a, 0x8000000080008081, | ||
0x8000000000008080, 0x0000000080000001, 0x8000000080008008 | ||
]; | ||
|
||
// Keccak constants | ||
private let keccakf_rotc : [Nat64] = [ | ||
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, | ||
27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 | ||
]; | ||
|
||
// Keccak constants | ||
private let keccakf_piln : [Nat] = [ | ||
10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, | ||
15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1 | ||
]; | ||
|
||
public class Keccak( | ||
initialState : [Nat64], // unused for now | ||
hashSize : Nat, | ||
delimitedSuffix: Nat8, | ||
) : Hash.Hash = { | ||
// Keccak l-parameter selects "bus size". For SHA3, l = 6 and | ||
// this is the only value supported at this time. | ||
private let keccakf_l : Nat = 6; | ||
|
||
// Number of rounds | ||
private let n_rounds: Nat = 12 + 2*keccakf_l; | ||
|
||
// Bus sizes in bits | ||
private let bsize: Nat = 25 * 2**keccakf_l; | ||
|
||
// Capactiy in bits | ||
private let cap: Nat = hashSize * 2; | ||
|
||
// Block size in bytes | ||
assert(bsize > cap+8); | ||
private let rsize: Nat = (bsize - cap)/8; | ||
|
||
// Internal state | ||
private let state: [var Nat64] = Array.init<Nat64>(bsize/64, 0); | ||
|
||
// Current write state index | ||
private var pt: Nat = 0; | ||
|
||
// blockSize in bytes | ||
public func blockSize() : Nat { rsize; }; | ||
|
||
// Function to initialize | ||
public func reset() : () { | ||
for (i in Iter.range(0, state.size()-1)) { | ||
state[i] := 0; | ||
}; | ||
pt := 0; | ||
}; | ||
|
||
// Return size of hash in bits | ||
public func size() : Nat { hashSize; }; | ||
|
||
// Turn array of Nat8s into Nat64, LSB first | ||
private func pack64(data : [Nat8]) : Nat64 { | ||
let dsize = data.size(); | ||
assert(dsize <= 8 and dsize > 0); | ||
|
||
var q: Nat64 = 0; | ||
// Avoid reverse() here to optimize? Maybe unroll? | ||
for (i in Iter.range(0, dsize-1:Nat)) { | ||
q |= Nat64.fromNat(Nat8.toNat(data[dsize-1:Nat-i])); | ||
if (i < (dsize-1:Nat)) { | ||
q <<= 8; | ||
}; | ||
}; | ||
return q; | ||
}; | ||
|
||
// Unpack Nat64 into array of Nat8s, LSB first | ||
private func unpack64(q : Nat64) : [Nat8] { | ||
var t = q; | ||
let qbuf = Array.init<Nat8>(8, 0); | ||
for (i in Iter.range(0, qbuf.size()-1)) { | ||
qbuf[i] := Nat8.fromNat(Nat64.toNat(t & 0xff)); | ||
t >>= 8; | ||
}; | ||
return Array.freeze<Nat8>(qbuf); | ||
}; | ||
|
||
private func dump() { | ||
Debug.print("State"); | ||
for (i in Iter.range(0, state.size()-1)) { | ||
let b = Array.reverse<Nat8>(unpack64(state[i])); | ||
Debug.print(Hex.encode(b)); | ||
}; | ||
}; | ||
Comment on lines
+108
to
+114
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can be removed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thought I'd leave it, in case more testing (and debugging) need to be done. Easier to have it and ask to remove IMHO. Can remove it no problem. |
||
|
||
// Repeated from SHA2. Candidate for DRY principle refactor? | ||
public func sum(bs : [Nat8]) : [Nat8] { | ||
let cs = checkSum(); | ||
let size = bs.size(); | ||
Array.tabulate<Nat8>( | ||
size + cs.size(), | ||
func (x : Nat) { | ||
if (x < size) return bs[x]; | ||
cs[x - size]; | ||
} | ||
); | ||
}; | ||
|
||
// Function to finalize hashing | ||
public func checkSum() : [Nat8] { | ||
state[pt/8] ^= Nat64.fromNat(Nat8.toNat(delimitedSuffix)) << Nat64.fromNat((pt%8)*8); | ||
state[(rsize-1:Nat)/8] ^= 0x80 : Nat64 << Nat64.fromNat(((rsize-1):Nat%8)*8); | ||
|
||
//dump(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Idem. |
||
keccak_f(state); | ||
|
||
let md = Array.init<Nat8>(hashSize/8, 0); | ||
|
||
var i = 0; | ||
label done for (q in state.vals()) { | ||
for (b in unpack64(q).vals()) { | ||
if (i >= md.size()) { | ||
break done; | ||
}; | ||
md[i] := b; | ||
i += 1; | ||
}; | ||
}; | ||
return Array.freeze<Nat8>(md); | ||
}; | ||
|
||
// Function to update internal state with content | ||
// TODO: should data be a blob? Sticking with Array from Hash type | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once you can get individual bytes out of a blob, the whole package will migrate to it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good to know. I'll drop the comment. |
||
public func write(data : [Nat8]) : () { | ||
if (data.size() == 0) { | ||
return; | ||
}; | ||
|
||
// Creating qbuf to apply against the entire Nat64 to | ||
// avoid repacking because we don't have unions in | ||
// Motoko. First time might require leading zeros, so it's | ||
// initialized to zero | ||
let qbuf = Array.init<Nat8>(8, 0); | ||
let dlast = data.size()-1:Nat; | ||
|
||
for (i in Iter.range(0, dlast)) { | ||
qbuf[pt % 8] := data[i]; | ||
|
||
// Set zeros at the end of our quad-word before submitting | ||
if (i >= dlast and (pt+1) % 8 != 0) { | ||
for (j in Iter.range((pt+1) % 8, 7)) { | ||
qbuf[j] := 0; | ||
}; | ||
}; | ||
|
||
// Finished 8 bytes (64-bits) chunk to apply to state | ||
if (i >= dlast or (pt+1) % 8 == 0) { | ||
// Debug.print(Nat.toText(pt/8)); | ||
// Debug.print(Hex.encode(Array.freeze<Nat8>(qbuf))); | ||
// let b = Array.reverse<Nat8>(unpack64(state[pt/8])); | ||
// Debug.print(Hex.encode(b)); | ||
state[pt/8] ^= pack64(Array.freeze<Nat8>(qbuf)); | ||
}; | ||
pt += 1; | ||
|
||
if (pt >= rsize) { | ||
keccak_f(state); | ||
pt := 0; | ||
}; | ||
}; | ||
//dump(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Idem. |
||
|
||
// Sanitize buffer for sensitive information | ||
for (j in Iter.range(0, 7)) { | ||
qbuf[j] := 0; | ||
}; | ||
}; | ||
|
||
// Main Keccak hashing function. See: | ||
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf | ||
func keccak_f(st : [var Nat64]) { | ||
var t : Nat64 = 0; | ||
let bc = Array.init<Nat64>(5, 0); | ||
var j: Nat = 0; | ||
|
||
for (r in Iter.range(0, n_rounds - 1)) { | ||
// Theta | ||
for (i in Iter.range(0, 4)) { | ||
bc[i] := st[i] ^ st[i + 5] ^ st[i + 10] ^ st[i + 15] ^ st[i + 20]; | ||
}; | ||
for (i in Iter.range(0, 4)) { | ||
t := bc[(i + 4) % 5] ^ Nat64.bitrotLeft(bc[(i + 1) % 5], 1); | ||
for (j in Iter.range(0, 4)) { | ||
st[j*5 + i] ^= t; | ||
}; | ||
}; | ||
|
||
// Rho Pi | ||
t := st[1]; | ||
for (i in Iter.range(0, keccakf_piln.size()-1)) { | ||
j := keccakf_piln[i]; | ||
bc[0] := st[j]; | ||
st[j] := Nat64.bitrotLeft(t, keccakf_rotc[i]); | ||
t := bc[0]; | ||
}; | ||
|
||
// Chi | ||
for (j in Iter.range(0, 4)) { | ||
for (i in Iter.range(0, 4)) { | ||
bc[i] := st[j*5 + i]; | ||
}; | ||
for (i in Iter.range(0, 4)) { | ||
st[j*5 + i] ^= (^bc[(i + 1) % 5]) & bc[(i + 2) % 5]; | ||
}; | ||
}; | ||
|
||
// Iota | ||
st[0] ^= keccakf_rndc[r]; | ||
}; | ||
}; | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Keccak "Keccak"; | ||
|
||
import Hash "../Hash"; | ||
|
||
module Keccak_256 { | ||
|
||
public func New() : Hash.Hash { Keccak.Keccak([], 256, 0x01); }; | ||
|
||
/// Returns the Keccak-256 checksum of the data. | ||
public func sum(bs : [Nat8]) : [Nat8] { | ||
let h = New(); | ||
h.write(bs); | ||
h.checkSum(); | ||
}; | ||
|
||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Keccak "Keccak"; | ||
|
||
import Hash "../Hash"; | ||
|
||
module SHA3_224 { | ||
|
||
public func New() : Hash.Hash { Keccak.Keccak([], 224, 0x06); }; | ||
|
||
/// Returns the SHA3-224 checksum of the data. | ||
public func sum(bs : [Nat8]) : [Nat8] { | ||
let h = New(); | ||
h.write(bs); | ||
h.checkSum(); | ||
}; | ||
|
||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Keccak "Keccak"; | ||
|
||
import Hash "../Hash"; | ||
|
||
module SHA3_256 { | ||
|
||
public func New() : Hash.Hash { Keccak.Keccak([], 256, 0x06); }; | ||
|
||
/// Returns the SHA3-256 checksum of the data. | ||
public func sum(bs : [Nat8]) : [Nat8] { | ||
let h = New(); | ||
h.write(bs); | ||
h.checkSum(); | ||
}; | ||
|
||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Keccak "Keccak"; | ||
|
||
import Hash "../Hash"; | ||
|
||
module SHA3_384 { | ||
|
||
public func New() : Hash.Hash { Keccak.Keccak([], 384, 0x06); }; | ||
|
||
/// Returns the SHA3-384 checksum of the data. | ||
public func sum(bs : [Nat8]) : [Nat8] { | ||
let h = New(); | ||
h.write(bs); | ||
h.checkSum(); | ||
}; | ||
|
||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Keccak "Keccak"; | ||
|
||
import Hash "../Hash"; | ||
|
||
module SHA3_512 { | ||
|
||
public func New() : Hash.Hash { Keccak.Keccak([], 512, 0x06); }; | ||
|
||
/// Returns the SHA3-512 checksum of the data. | ||
public func sum(bs : [Nat8]) : [Nat8] { | ||
let h = New(); | ||
h.write(bs); | ||
h.checkSum(); | ||
}; | ||
|
||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import Blob "mo:base-0.7.3/Blob"; | ||
import Hex "mo:encoding/Hex"; | ||
import Text "mo:base-0.7.3/Text"; | ||
|
||
import Debug "mo:base-0.7.3/Debug"; | ||
|
||
import Keccak_256 "../src/SHA/Keccak_256"; | ||
|
||
//Debug.print("Testing Keccak_256"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should either be removed or uncommented (same for other print statements). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To me, these served more as comments about describing the tests and to break them up. Safe to assume you want them removed as the other tests don't emit any messages? |
||
let shasum = Keccak_256.sum(Blob.toArray(Text.encodeUtf8("hello world\n"))); | ||
//Debug.print(Hex.encode(shasum)); | ||
assert(Hex.encode(shasum) == "70e3788906c57c18999ba6b0389a768ff3333e3d6136fdf85743e66a03bc29f9"); | ||
|
||
//Debug.print("Testing Keccak_256 sum"); | ||
let h = Keccak_256.New(); | ||
h.write(Blob.toArray(Text.encodeUtf8("hello world\n"))); | ||
assert(Hex.encode(shasum) == Hex.encode(h.sum([]))); | ||
|
||
//Debug.print("Testing Keccak_256 vectors"); | ||
for (v in [ | ||
("", "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"), | ||
("a", "3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb"), | ||
("ab", "67fad3bfa1e0321bd021ca805ce14876e50acac8ca8532eda8cbf924da565160"), | ||
("abc", "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45"), | ||
("abcd", "48bed44d1bcd124a28c27f343a817e5f5243190d3c52bf347daf876de1dbbf77"), | ||
("abcde", "6377c7e66081cb65e473c1b95db5195a27d04a7108b468890224bedbe1a8a6eb"), | ||
("abcdef", "acd0c377fe36d5b209125185bc3ac41155ed1bf7103ef9f0c2aff4320460b6df"), | ||
("abcdefg", "a82aec019867b7307551dc397acde18b541e742fa1a4e53df4ce3b02d462f524"), | ||
("abcdefgh", "48624fa43c68d5c552855a4e2919e74645f683f5384f72b5b051b71ea41d4f2d"), | ||
("abcdefghi", "34fb2702da7001bf4dbf26a1e4cf31044bd95b85e1017596ee2d23aedc90498b"), | ||
("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "9f475334b0dafde0fdbe358fbe2b65b7129e0b52e242e2e1317479f9b590f581"), | ||
].vals()) { | ||
let hv = Hex.encode(Keccak_256.sum(Blob.toArray(Text.encodeUtf8(v.0)))); | ||
//Debug.print(hv); | ||
assert(hv == v.1); | ||
}; | ||
|
||
//Debug.print("Testing Keccak_256 write pieces"); | ||
do { | ||
let h = Keccak_256.New(); | ||
h.write(Blob.toArray(Text.encodeUtf8("hello"))); | ||
h.write(Blob.toArray(Text.encodeUtf8(" "))); | ||
h.write(Blob.toArray(Text.encodeUtf8("world\n"))); | ||
assert(Hex.encode(h.sum([])) == "70e3788906c57c18999ba6b0389a768ff3333e3d6136fdf85743e66a03bc29f9"); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be removed.