diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66175ed --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vessel/ diff --git a/README.md b/README.md index b1eb3c5..09d3cfe 100644 --- a/README.md +++ b/README.md @@ -1 +1,19 @@ # Crypto Package for Motoko + +## Packages + +| Package Name | Description | Path | +|--------------|-------------|------| +| SHA | SHA224 and SHA256 hash algorithms as defined in FIPS 180-4 | crypto/SHA | + +## Usage + +```motoko +SHA256.sum(Blob.toArray(Text.encodeUtf8("hello world\n")) +// "A948904F2F0F479B8F8197694B30184B0D2ED1C1CD2A1EC0FB85D299A192A447" + +let h = SHA256.New(); +h.write(Blob.toArray(Text.encodeUtf8("hello world\n"))); +h.sum([]); +// "A948904F2F0F479B8F8197694B30184B0D2ED1C1CD2A1EC0FB85D299A192A447" +``` diff --git a/package-set.dhall b/package-set.dhall new file mode 100644 index 0000000..0827038 --- /dev/null +++ b/package-set.dhall @@ -0,0 +1,2 @@ +let upstream = https://github.com/aviate-labs/package-set/releases/download/v0.1.3/package-set.dhall sha256:ca68dad1e4a68319d44c587f505176963615d533b8ac98bdb534f37d1d6a5b47 +in upstream diff --git a/src/Hash.mo b/src/Hash.mo new file mode 100644 index 0000000..c2a0e82 --- /dev/null +++ b/src/Hash.mo @@ -0,0 +1,17 @@ +module Hash { + public type Hash = { + // Returns the block size of the hash. + blockSize() : Nat; + // Returns the checksum data. + checkSum() : [Nat8]; + // Resets the hash to its initial state. + reset() : (); + // Returns the number of bytes that sum will return. + size() : Nat; + // Adds the current hash to the resulting slice. + // The underlying hash is not modified. + sum(bs : [Nat8]) : [Nat8]; + // Adds data to the running hash. + write(bs : [Nat8]) : (); + }; +}; diff --git a/src/SHA/SHA2.mo b/src/SHA/SHA2.mo new file mode 100644 index 0000000..e20aaa5 --- /dev/null +++ b/src/SHA/SHA2.mo @@ -0,0 +1,138 @@ +import Array "mo:base/Array"; +import Binary "mo:encoding/Binary"; +import Iter "mo:base/Iter"; +import Nat32 "mo:base/Nat32"; +import Nat64 "mo:base/Nat64"; + +import Hash "../Hash"; +import Util "../Utilities"; + +/// For internal use only. +module { + // First thirty-two bits of the fractional parts of the cube roots of the + // first sixty-four prime numbers. + private let K : [Nat32] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + ]; + + // Initial hash value, H(0). + private let H256 : [Nat32] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19, + ]; + + public class SHA2( + initialState : [Nat32], + hashSize : Nat, + ) : Hash.Hash = { + private var h : [var Nat32] = Array.thaw(initialState); + private var x : [var Nat8] = Array.init(64, 0); + private var nx : Nat = 0; + private var len : Nat64 = 0; + + public func blockSize() : Nat { 64; }; + + public func reset() : () { + h := Array.thaw(initialState); + x := Array.init(64, 0); + nx := 0; + len := 0; + }; + + public func size() : Nat { hashSize; }; + + public func sum(bs : [Nat8]) : [Nat8] { + Array.append(bs, checkSum()); + }; + + public func checkSum() : [Nat8] { + let n = len; + var tmp = Array.init(64, 0); + tmp[0] := 0x80; + if (Nat64.toNat(len) % 64 < 56) { + write(Util.takeN( + 56 - Nat64.toNat(len) % 64, + Array.freeze(tmp), + )); + } else { + write(Util.takeN( + 64 + 56 - Nat64.toNat(len) % 64, + Array.freeze(tmp), + )); + }; + write(Binary.BigEndian.fromNat64(n << 3)); + var digest : [Nat8] = []; + label l for (i in h.keys()) { + if (i == 7 and hashSize == 28) { break l; }; + digest := Array.append(digest, Binary.BigEndian.fromNat32(h[i])); + }; + digest; + }; + + public func write(bs : [Nat8]) : () { + var p = bs; + len +%= Nat64.fromNat(bs.size()); + if (0 < nx) { + let n = Util.copy(nx, x, p); + nx += n; + if (nx == 64) { + block(Array.freeze(x)); + nx := 0; + }; + p := Util.removeN(n, p); + }; + if (64 <= p.size()) { + let n = Nat64.toNat(Nat64.fromNat(p.size()) & (^63)); + block(Util.takeN(n, p)); + p := Util.removeN(n, p); + }; + if (0 < p.size()) { + nx := Util.copy(0, x, p); + }; + }; + + private func block(bs : [Nat8]) { + var p = bs; + var w : [var Nat32] = Array.init(64, 0); + var h0 = h[0]; var h1 = h[1]; var h2 = h[2]; var h3 = h[3]; + var h4 = h[4]; var h5 = h[5]; var h6 = h[6]; var h7 = h[7]; + while (64 <= p.size()) { + for (i in Iter.range(0, 15)) { + let j = i * 4; + w[i] := Util.nat8to32(p[j]) << 24 | Util.nat8to32(p[j+1]) << 16 + | Util.nat8to32(p[j+2]) << 8 | Util.nat8to32(p[j+3]); + }; + for (i in Iter.range(16, 63)) { + let v1 = w[i-2]; + let t1 = (Nat32.bitrotRight(v1, 17) ^ Nat32.bitrotRight(v1, 19)) ^ (v1 >> 10); + let v2 = w[i-15]; + let t2 = (Nat32.bitrotRight(v2, 7) ^ Nat32.bitrotRight(v2, 18)) ^ (v2 >> 3); + w[i] := t1 +% w[i-7] +% t2 +% w[i - 16]; + }; + var a = h0; var b = h1; var c = h2; var d = h3; + var e = h4; var f = h5; var g = h6; var h = h7; + for (i in Iter.range(0, 63)) { + let t1 = h +% (Nat32.bitrotRight(e, 6) ^ Nat32.bitrotRight(e, 11) ^ Nat32.bitrotRight(e, 25)) +% ((e & f) ^ (^e & g)) +% K[i] +% w[i]; + let t2 = (Nat32.bitrotRight(a, 2) ^ Nat32.bitrotRight(a, 13) ^ Nat32.bitrotRight(a, 22)) +% ((a & b) ^ (a & c) ^ (b & c)); + h := g; g := f; f := e; e := d +% t1; + d := c; c := b; b := a; a := t1 +% t2; + }; + h0 +%= a; h1 +%= b; h2 +%= c; h3 +%= d; + h4 +%= e; h5 +%= f; h6 +%= g; h7 +%= h; + p := Util.removeN(64, p); + }; + h[0] := h0; h[1] := h1; h[2] := h2; h[3] := h3; + h[4] := h4; h[5] := h5; h[6] := h6; h[7] := h7; + }; + }; +}; diff --git a/src/SHA/SHA224.mo b/src/SHA/SHA224.mo new file mode 100644 index 0000000..da94dd9 --- /dev/null +++ b/src/SHA/SHA224.mo @@ -0,0 +1,20 @@ +import SHA2 "SHA2"; + +import Hash "../Hash"; + +module SHA224 { + // Initial hash value, H(0). + private let H224 : [Nat32] = [ + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, + 0x64f98fa7, 0xbefa4fa4, + ]; + + /// Returns the SHA224 checkum of the data. + public func sum(bs : [Nat8]) : [Nat8] { + let h = New(); + h.write(bs); + h.checkSum() + }; + + public func New() : Hash.Hash { SHA2.SHA2(H224, 28); }; +}; diff --git a/src/SHA/SHA256.mo b/src/SHA/SHA256.mo new file mode 100644 index 0000000..faff330 --- /dev/null +++ b/src/SHA/SHA256.mo @@ -0,0 +1,20 @@ +import SHA2 "SHA2"; + +import Hash "../Hash"; + +module SHA224 { + // Initial hash value, H(0). + private let H256 : [Nat32] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19, + ]; + + /// Returns the SHA256 checksum of the data. + public func sum(bs : [Nat8]) : [Nat8] { + let h = New(); + h.write(bs); + h.checkSum(); + }; + + public func New() : Hash.Hash { SHA2.SHA2(H256, 32); }; +}; diff --git a/src/Utilities.mo b/src/Utilities.mo new file mode 100644 index 0000000..eb77c59 --- /dev/null +++ b/src/Utilities.mo @@ -0,0 +1,46 @@ +import Array "mo:base/Array"; +import Nat8 "mo:base/Nat8"; +import Nat32 "mo:base/Nat32"; + +module Utilities { + public func copy( + n : Nat, // Position to start writing. + dst : [var T], + src : [T], + ) : Nat { + let l = dst.size(); + for (i in src.keys()) { + if (l <= i) return l; + dst[n + i] := src[i]; + }; + src.size(); + }; + + public func removeN( + n : Nat, // Number to remove. + xs : [T], + ) : [T] { + Array.tabulate( + xs.size() - n, + func (i : Nat) : T { + xs[i + n]; + }, + ); + }; + + public func takeN( + n : Nat, // Number to take. + xs : [T], + ) : [T] { + Array.tabulate( + n, + func (i : Nat) : T { + xs[i]; + }, + ); + }; + + public func nat8to32(n : Nat8) : Nat32 { + Nat32.fromNat(Nat8.toNat(n)); + }; +}; diff --git a/test/SHA224.mo b/test/SHA224.mo new file mode 100644 index 0000000..13342d0 --- /dev/null +++ b/test/SHA224.mo @@ -0,0 +1,24 @@ +import Blob "mo:base/Blob"; +import Hex "mo:encoding/Hex"; +import Text "mo:base/Text"; + +import SHA224 "../src/SHA/SHA224"; + +let sum224 = SHA224.sum(Blob.toArray(Text.encodeUtf8("hello world\n"))); +assert(Hex.encode(sum224) == "95041DD60AB08C0BF5636D50BE85FE9790300F39EB84602858A9B430"); + +for (v in [ + ("", "D14A028C2A3A2BC9476102BB288234C415A2B01F828EA62AC5B3E42F"), + ("a", "ABD37534C7D9A2EFB9465DE931CD7055FFDB8879563AE98078D6D6D5"), + ("ab", "DB3CDA86D4429A1D39C148989566B38F7BDA0156296BD364BA2F878B"), + ("abc", "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7"), + ("abcd", "A76654D8E3550E9A2D67A0EEB6C67B220E5885EDDD3FDE135806E601"), + ("abcde", "BDD03D560993E675516BA5A50638B6531AC2AC3D5847C61916CFCED6"), + ("abcdef", "7043631CB415556A275A4EBECB802C74EE9F6153908E1792A90B6A98"), + ("abcdefg", "D1884E711701AD81ABE0C77A3B0EA12E19BA9AF64077286C72FC602D"), + ("abcdefgh", "17EB7D40F0356F8598E89EAFAD5F6C759B1F822975D9C9B737C8A517"), + ("abcdefghi", "AEB35915346C584DB820D2DE7AF3929FFAFEF9222A9BCB26516C7334"), + ("abcdefghij", "D35E1E5AF29DDB0D7E154357DF4AD9842AFEE527C689EE547F753188"), +].vals()) { + assert(Hex.encode(SHA224.sum(Blob.toArray(Text.encodeUtf8(v.0)))) == v.1); +}; diff --git a/test/SHA256.mo b/test/SHA256.mo new file mode 100644 index 0000000..a22e5f2 --- /dev/null +++ b/test/SHA256.mo @@ -0,0 +1,38 @@ +import Blob "mo:base/Blob"; +import Hex "mo:encoding/Hex"; +import Text "mo:base/Text"; + +import Debug "mo:base/Debug"; + +import SHA256 "../src/SHA/SHA256"; + +let sum256 = SHA256.sum(Blob.toArray(Text.encodeUtf8("hello world\n"))); +assert(Hex.encode(sum256) == "A948904F2F0F479B8F8197694B30184B0D2ED1C1CD2A1EC0FB85D299A192A447"); + +let h = SHA256.New(); +h.write(Blob.toArray(Text.encodeUtf8("hello world\n"))); +assert(Hex.encode(sum256) == Hex.encode(h.sum([]))); + +for (v in [ + ("", "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"), + ("a", "CA978112CA1BBDCAFAC231B39A23DC4DA786EFF8147C4E72B9807785AFEE48BB"), + ("ab", "FB8E20FC2E4C3F248C60C39BD652F3C1347298BB977B8B4D5903B85055620603"), + ("abc", "BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD"), + ("abcd", "88D4266FD4E6338D13B845FCF289579D209C897823B9217DA3E161936F031589"), + ("abcde", "36BBE50ED96841D10443BCB670D6554F0A34B761BE67EC9C4A8AD2C0C44CA42C"), + ("abcdef", "BEF57EC7F53A6D40BEB640A780A639C83BC29AC8A9816F1FC6C5C6DCD93C4721"), + ("abcdefg", "7D1A54127B222502F5B79B5FB0803061152A44F92B37E23C6527BAF665D4DA9A"), + ("abcdefgh", "9C56CC51B374C3BA189210D5B6D4BF57790D351C96C47C02190ECF1E430635AB"), + ("abcdefghi", "19CC02F26DF43CC571BC9ED7B0C4D29224A3EC229529221725EF76D021C8326F"), + ("abcdefghij", "72399361DA6A7754FEC986DCA5B7CBAF1C810A28DED4ABAF56B2106D06CB78B0"), +].vals()) { + assert(Hex.encode(SHA256.sum256(Blob.toArray(Text.encodeUtf8(v.0)))) == v.1); +}; + +do { + let h = SHA256.New(); + h.write(Blob.toArray(Text.encodeUtf8("hello"))); + h.write(Blob.toArray(Text.encodeUtf8(" "))); + h.write(Blob.toArray(Text.encodeUtf8("world"))); + assert(Hex.encode(h.sum([])) == "B94D27B9934D3E08A52E52D7DA7DABFAC484EFE37A5380EE9088F7ACE2EFCDE9"); +}; diff --git a/test/Utilities.mo b/test/Utilities.mo new file mode 100644 index 0000000..c56b0a7 --- /dev/null +++ b/test/Utilities.mo @@ -0,0 +1,18 @@ +import Array "mo:base/Array"; +import Iter "mo:base/Iter"; + +import Util "../src/Utilities"; + +let fifty = Iter.toArray(Iter.range(0, 49)); +let hundred = Iter.toArray(Iter.range(0, 99)); + +// Remove the first 50. +assert(Util.removeN(50, hundred) == Iter.toArray(Iter.range(50, 99))); + +// Take the first 50. +assert(Util.takeN(50, hundred) == fifty); + +// Copy the first 50 starting at position 50. +let copyTo = Array.thaw(hundred); +assert(Util.copy(50, copyTo, Util.takeN(50, hundred)) == 50); +assert(Array.freeze(copyTo) == Array.append(fifty, fifty)); diff --git a/vessel.dhall b/vessel.dhall new file mode 100644 index 0000000..3068d14 --- /dev/null +++ b/vessel.dhall @@ -0,0 +1,4 @@ +{ + dependencies = [ "base", "encoding" ], + compiler = Some "0.6.10" +} \ No newline at end of file