Add missing SHA224, SHA384, and SHA512 (#63)

* Add SHA384 and SHA512

Sha384 and Sha512 share the same implementation and differ only in the
initailization vectors and the digest concatentation. The algorithm from
from rfc6234 calls for 64bit words and operations and to remain dart2js
friendly, BigInt was used.

INPUT REQUESTED:
  I'm not sure where the test vectors came from; but a simple comparison
  to sha384 and sha512 command line methods shows it matching. If you
  can point me to them, then I can write a better test.

* Add Sha224 + Refactor

Do something less stupid; use _Sha32BitSink and _Sha64BitSink to have
class heirarchies make better sense.

* Support 32bit and 64bit operations for SHA384/512

Two modes of operation: if you're in a browser, you get the slower 32bit
algorithm because you only have 2^53 bits. If you are on the VM /
Flutter, you'll have 64bit operations wich is *much* faster:

32BIT NUMBERS: ~20MB/s hashing
Removing BigInt has some good results:
  Instance of 'Sha224' warmup: 0:00:00.015599
  Instance of 'Sha256' warmup: 0:00:00.002325
  Instance of 'Sha384' warmup: 0:00:00.019082
  Instance of 'Sha512' warmup: 0:00:00.010288
  Instance of 'Sha224' real: 0:00:00.092928
  Instance of 'Sha256' real: 0:00:00.093426
  Instance of 'Sha384' real: 0:00:00.823335
  Instance of 'Sha512' real: 0:00:00.807871

64BIT NUMBERS: ~236MB/s hashing
On the VM, this is much faster with 64bit operations.
  Instance of 'Sha224' warmup: 0:00:00.013285
  Instance of 'Sha256' warmup: 0:00:00.002443
  Instance of 'Sha384' warmup: 0:00:00.020954
  Instance of 'Sha512' warmup: 0:00:00.005616
  Instance of 'Sha224' real: 0:00:00.097196
  Instance of 'Sha256' real: 0:00:00.094167
  Instance of 'Sha384' real: 0:00:00.067605
  Instance of 'Sha512' real: 0:00:00.067564

NOTE:
Compiles with dart2js - cannot reference 64bit hex as the compiler just
vomits... but you can left shift by 32.

* Fix comment

* Add conditional imports

* De-listify 32bit allocations

Speed is still meh in dart2js:

384/512 numbers:
Instance of 'minified:a2' real: 0:00:02.203820
Instance of 'minified:aO' real: 0:00:02.192515

* Add sha monte tests for 224,256,384, and 512

RSP values came from http://csrc.nist.gov/groups/STM/cavp/documents/shs/shabytetestvectors.zip

* Fix formattting of lib/crypto.dart

* Bump version (adding new features) + min sdk

* Bump travis (hidden file)

* Rework monte test to function-only.
diff --git a/.travis.yml b/.travis.yml
index 8be7e2d..d7285be 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,8 @@
 language: dart
 
 dart:
-  - 2.0.0
-  - dev
+  - 2.1.0
+  - stable 
 
 dart_task:
   - test: -p vm
diff --git a/README.md b/README.md
index c31a1b8..63c97eb 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,10 @@
 The following hashing algorithms are supported:
 
 * SHA-1
+* SHA-224
 * SHA-256
+* SHA-384
+* SHA-512
 * MD5
 * HMAC (i.e. HMAC-MD5, HMAC-SHA1, HMAC-SHA256)
 
@@ -105,7 +108,10 @@
 [Hmac]: https://www.dartdocs.org/documentation/crypto/latest/crypto/Hmac-class.html
 [MD5]: https://www.dartdocs.org/documentation/crypto/latest/crypto/MD5-class.html
 [Sha1]: https://www.dartdocs.org/documentation/crypto/latest/crypto/Sha1-class.html
+[Sha224]: https://www.dartdocs.org/documentation/crypto/latest/crypto/Sha224-class.html
 [Sha256]: https://www.dartdocs.org/documentation/crypto/latest/crypto/Sha256-class.html
+[Sha384]: https://www.dartdocs.org/documentation/crypto/latest/crypto/Sha384-class.html
+[Sha512]: https://www.dartdocs.org/documentation/crypto/latest/crypto/Sha512-class.html
 [md5-obj]: https://www.dartdocs.org/documentation/crypto/latest/crypto/md5.html
 [sha1-obj]: https://www.dartdocs.org/documentation/crypto/latest/crypto/sha1.html
 [sha256-obj]: https://www.dartdocs.org/documentation/crypto/latest/crypto/sha256.html
diff --git a/lib/crypto.dart b/lib/crypto.dart
index 990349a..d0e1a76 100644
--- a/lib/crypto.dart
+++ b/lib/crypto.dart
@@ -8,3 +8,4 @@
 export 'src/md5.dart';
 export 'src/sha1.dart';
 export 'src/sha256.dart';
+export 'src/sha512.dart';
diff --git a/lib/src/sha256.dart b/lib/src/sha256.dart
index 6f0e40b..6040d5e 100644
--- a/lib/src/sha256.dart
+++ b/lib/src/sha256.dart
@@ -17,6 +17,13 @@
 /// [rfc]: http://tools.ietf.org/html/rfc6234
 final sha256 = Sha256._();
 
+/// An instance of [Sha224].
+///
+/// This instance provides convenient access to the [Sha224][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+final sha224 = Sha224._();
+
 /// An implementation of the [SHA-256][rfc] hash function.
 ///
 /// [rfc]: http://tools.ietf.org/html/rfc6234
@@ -36,6 +43,25 @@
       ByteConversionSink.from(_Sha256Sink(sink));
 }
 
+/// An implementation of the [SHA-224][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+///
+/// Note that it's almost always easier to use [sha224] rather than creating a
+/// new instance.
+class Sha224 extends Hash {
+  @override
+  final int blockSize = 16 * bytesPerWord;
+
+  Sha224._();
+
+  Sha224 newInstance() => Sha224._();
+
+  @override
+  ByteConversionSink startChunkedConversion(Sink<Digest> sink) =>
+      ByteConversionSink.from(_Sha224Sink(sink));
+}
+
 /// Data from a non-linear function that functions as reproducible noise.
 const List<int> _noise = [
   0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, //
@@ -51,34 +77,16 @@
   0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
 ];
 
-/// The concrete implementation of [Sha256].
-///
-/// This is separate so that it can extend [HashSink] without leaking additional
-/// public members.
-class _Sha256Sink extends HashSink {
-  @override
-  final digest = Uint32List(8);
+abstract class _Sha32BitSink extends HashSink {
+  final Uint32List _digest;
 
   /// The sixteen words from the original chunk, extended to 64 words.
   ///
   /// This is an instance variable to avoid re-allocating, but its data isn't
   /// used across invocations of [updateHash].
-  final Uint32List _extended;
+  final _extended = Uint32List(64);
 
-  _Sha256Sink(Sink<Digest> sink)
-      : _extended = Uint32List(64),
-        super(sink, 16) {
-    // Initial value of the hash parts. First 32 bits of the fractional parts
-    // of the square roots of the first 8 prime numbers.
-    digest[0] = 0x6a09e667;
-    digest[1] = 0xbb67ae85;
-    digest[2] = 0x3c6ef372;
-    digest[3] = 0xa54ff53a;
-    digest[4] = 0x510e527f;
-    digest[5] = 0x9b05688c;
-    digest[6] = 0x1f83d9ab;
-    digest[7] = 0x5be0cd19;
-  }
+  _Sha32BitSink(Sink<Digest> sink, this._digest) : super(sink, 16);
 
   // The following helper functions are taken directly from
   // http://tools.ietf.org/html/rfc6234.
@@ -105,14 +113,14 @@
     }
 
     // Shuffle around the bits.
-    var a = digest[0];
-    var b = digest[1];
-    var c = digest[2];
-    var d = digest[3];
-    var e = digest[4];
-    var f = digest[5];
-    var g = digest[6];
-    var h = digest[7];
+    var a = _digest[0];
+    var b = _digest[1];
+    var c = _digest[2];
+    var d = _digest[3];
+    var e = _digest[4];
+    var f = _digest[5];
+    var g = _digest[6];
+    var h = _digest[7];
 
     for (var i = 0; i < 64; i++) {
       var temp1 = add32(add32(h, _bsig1(e)),
@@ -129,13 +137,61 @@
     }
 
     // Update hash values after iteration.
-    digest[0] = add32(a, digest[0]);
-    digest[1] = add32(b, digest[1]);
-    digest[2] = add32(c, digest[2]);
-    digest[3] = add32(d, digest[3]);
-    digest[4] = add32(e, digest[4]);
-    digest[5] = add32(f, digest[5]);
-    digest[6] = add32(g, digest[6]);
-    digest[7] = add32(h, digest[7]);
+    _digest[0] = add32(a, _digest[0]);
+    _digest[1] = add32(b, _digest[1]);
+    _digest[2] = add32(c, _digest[2]);
+    _digest[3] = add32(d, _digest[3]);
+    _digest[4] = add32(e, _digest[4]);
+    _digest[5] = add32(f, _digest[5]);
+    _digest[6] = add32(g, _digest[6]);
+    _digest[7] = add32(h, _digest[7]);
   }
 }
+
+/// The concrete implementation of [Sha256].
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class _Sha256Sink extends _Sha32BitSink {
+  @override
+  Uint32List get digest => _digest;
+
+  // Initial value of the hash parts. First 32 bits of the fractional parts
+  // of the square roots of the first 8 prime numbers.
+  _Sha256Sink(Sink<Digest> sink)
+      : super(
+            sink,
+            Uint32List.fromList([
+              0x6a09e667,
+              0xbb67ae85,
+              0x3c6ef372,
+              0xa54ff53a,
+              0x510e527f,
+              0x9b05688c,
+              0x1f83d9ab,
+              0x5be0cd19,
+            ]));
+}
+
+/// The concrete implementation of [Sha224].
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class _Sha224Sink extends _Sha32BitSink {
+  @override
+  Uint32List get digest => _digest.buffer.asUint32List(0, 7);
+
+  _Sha224Sink(Sink<Digest> sink)
+      : super(
+            sink,
+            Uint32List.fromList([
+              0xc1059ed8,
+              0x367cd507,
+              0x3070dd17,
+              0xf70e5939,
+              0xffc00b31,
+              0x68581511,
+              0x64f98fa7,
+              0xbefa4fa4,
+            ]));
+}
diff --git a/lib/src/sha512.dart b/lib/src/sha512.dart
new file mode 100644
index 0000000..45a4ac2
--- /dev/null
+++ b/lib/src/sha512.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
+import 'digest.dart';
+import 'hash.dart';
+import 'sha512_fastsinks.dart' if (dart.library.js) 'sha512_slowsinks.dart';
+//import 'sha512_slowsinks.dart';
+import 'utils.dart';
+
+/// An instance of [Sha2Sha384].
+///
+/// This instance provides convenient access to the [Sha384][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+final sha384 = Sha384._();
+
+/// An instance of [Sha2Sha512].
+///
+/// This instance provides convenient access to the [Sha512][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+final sha512 = Sha512._();
+
+/// An implementation of the [SHA-384][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+///
+/// Note that it's almost always easier to use [sha384] rather than creating a
+/// new instance.
+class Sha384 extends Hash {
+  @override
+  final int blockSize = 16 * bytesPerWord;
+
+  Sha384._();
+
+  Sha384 newInstance() => Sha384._();
+
+  @override
+  ByteConversionSink startChunkedConversion(Sink<Digest> sink) =>
+      ByteConversionSink.from(Sha384Sink(sink));
+}
+
+/// An implementation of the [SHA-512][rfc] hash function.
+///
+/// [rfc]: http://tools.ietf.org/html/rfc6234
+///
+/// Note that it's almost always easier to use [sha512] rather than creating a
+/// new instance.
+class Sha512 extends Sha384 {
+  Sha512._() : super._();
+
+  Sha512 newInstance() => Sha512._();
+
+  @override
+  ByteConversionSink startChunkedConversion(Sink<Digest> sink) =>
+      ByteConversionSink.from(Sha512Sink(sink));
+}
diff --git a/lib/src/sha512_fastsinks.dart b/lib/src/sha512_fastsinks.dart
new file mode 100644
index 0000000..4bcf31e
--- /dev/null
+++ b/lib/src/sha512_fastsinks.dart
@@ -0,0 +1,227 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:typed_data';
+
+import 'digest.dart';
+import 'hash_sink.dart';
+
+abstract class _Sha64BitSink extends HashSink {
+  int get digestBytes;
+
+  @override
+  Uint32List get digest {
+    var unordered = _digest.buffer.asUint32List();
+    var ordered = Uint32List(digestBytes);
+    for (int i = 0; i < digestBytes; i++) {
+      ordered[i] = unordered[i + (i.isEven ? 1 : -1)];
+    }
+    return ordered;
+  }
+
+  // Initial value of the hash parts. First 64 bits of the fractional parts
+  // of the square roots of the ninth through sixteenth prime numbers.
+  final Uint64List _digest;
+
+  /// The sixteen words from the original chunk, extended to 64 words.
+  ///
+  /// This is an instance variable to avoid re-allocating, but its data isn't
+  /// used across invocations of [updateHash].
+  final _extended = Uint64List(80);
+
+  _Sha64BitSink(Sink<Digest> sink, this._digest) : super(sink, 32);
+  // The following helper functions are taken directly from
+  // http://tools.ietf.org/html/rfc6234.
+
+  static int _rotr64(int n, int x) => _shr64(n, x) | (x << (64 - n));
+  static int _shr64(int n, int x) => (x >> n) & ~(-1 << (64 - n));
+
+  static int _ch(int x, int y, int z) => (x & y) ^ (~x & z);
+  static int _maj(int x, int y, int z) => (x & y) ^ (x & z) ^ (y & z);
+  static int _bsig0(int x) => _rotr64(28, x) ^ _rotr64(34, x) ^ _rotr64(39, x);
+  static int _bsig1(int x) => _rotr64(14, x) ^ _rotr64(18, x) ^ _rotr64(41, x);
+  static int _ssig0(int x) => _rotr64(1, x) ^ _rotr64(8, x) ^ _shr64(7, x);
+  static int _ssig1(int x) => _rotr64(19, x) ^ _rotr64(61, x) ^ _shr64(6, x);
+
+  @override
+  void updateHash(Uint32List chunk) {
+    assert(chunk.length == 32);
+
+    // Prepare message schedule.
+    for (var i = 0, x = 0; i < 32; i += 2, x++) {
+      _extended[x] = (chunk[i] << 32) | chunk[i + 1];
+    }
+
+    for (var t = 16; t < 80; t++) {
+      _extended[t] = _ssig1(_extended[t - 2]) +
+          _extended[t - 7] +
+          _ssig0(_extended[t - 15]) +
+          _extended[t - 16];
+    }
+
+    // Shuffle around the bits.
+    var a = _digest[0];
+    var b = _digest[1];
+    var c = _digest[2];
+    var d = _digest[3];
+    var e = _digest[4];
+    var f = _digest[5];
+    var g = _digest[6];
+    var h = _digest[7];
+
+    for (var i = 0; i < 80; i++) {
+      var temp1 = h + _bsig1(e) + _ch(e, f, g) + _noise64[i] + _extended[i];
+      var temp2 = _bsig0(a) + _maj(a, b, c);
+      h = g;
+      g = f;
+      f = e;
+      e = d + temp1;
+      d = c;
+      c = b;
+      b = a;
+      a = temp1 + temp2;
+    }
+
+    // Update hash values after iteration.
+    _digest[0] += a;
+    _digest[1] += b;
+    _digest[2] += c;
+    _digest[3] += d;
+    _digest[4] += e;
+    _digest[5] += f;
+    _digest[6] += g;
+    _digest[7] += h;
+  }
+}
+
+/// The concrete implementation of [Sha384].
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class Sha384Sink extends _Sha64BitSink {
+  @override
+  final digestBytes = 12;
+
+  Sha384Sink(Sink<Digest> sink)
+      : super(
+            sink,
+            Uint64List.fromList([
+              0xcbbb9d5dc1059ed8,
+              0x629a292a367cd507,
+              0x9159015a3070dd17,
+              0x152fecd8f70e5939,
+              0x67332667ffc00b31,
+              0x8eb44a8768581511,
+              0xdb0c2e0d64f98fa7,
+              0x47b5481dbefa4fa4,
+            ]));
+}
+
+/// The concrete implementation of [Sha512].
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class Sha512Sink extends _Sha64BitSink {
+  @override
+  final digestBytes = 16;
+
+  Sha512Sink(Sink<Digest> sink)
+      : super(
+            sink,
+            Uint64List.fromList([
+              // Initial value of the hash parts. First 64 bits of the fractional
+              // parts of the square roots of the first eight prime numbers.
+              0x6a09e667f3bcc908,
+              0xbb67ae8584caa73b,
+              0x3c6ef372fe94f82b,
+              0xa54ff53a5f1d36f1,
+              0x510e527fade682d1,
+              0x9b05688c2b3e6c1f,
+              0x1f83d9abfb41bd6b,
+              0x5be0cd19137e2179,
+            ]));
+}
+
+final _noise64 = Uint64List.fromList([
+  0x428a2f98d728ae22,
+  0x7137449123ef65cd,
+  0xb5c0fbcfec4d3b2f,
+  0xe9b5dba58189dbbc,
+  0x3956c25bf348b538,
+  0x59f111f1b605d019,
+  0x923f82a4af194f9b,
+  0xab1c5ed5da6d8118,
+  0xd807aa98a3030242,
+  0x12835b0145706fbe,
+  0x243185be4ee4b28c,
+  0x550c7dc3d5ffb4e2,
+  0x72be5d74f27b896f,
+  0x80deb1fe3b1696b1,
+  0x9bdc06a725c71235,
+  0xc19bf174cf692694,
+  0xe49b69c19ef14ad2,
+  0xefbe4786384f25e3,
+  0x0fc19dc68b8cd5b5,
+  0x240ca1cc77ac9c65,
+  0x2de92c6f592b0275,
+  0x4a7484aa6ea6e483,
+  0x5cb0a9dcbd41fbd4,
+  0x76f988da831153b5,
+  0x983e5152ee66dfab,
+  0xa831c66d2db43210,
+  0xb00327c898fb213f,
+  0xbf597fc7beef0ee4,
+  0xc6e00bf33da88fc2,
+  0xd5a79147930aa725,
+  0x06ca6351e003826f,
+  0x142929670a0e6e70,
+  0x27b70a8546d22ffc,
+  0x2e1b21385c26c926,
+  0x4d2c6dfc5ac42aed,
+  0x53380d139d95b3df,
+  0x650a73548baf63de,
+  0x766a0abb3c77b2a8,
+  0x81c2c92e47edaee6,
+  0x92722c851482353b,
+  0xa2bfe8a14cf10364,
+  0xa81a664bbc423001,
+  0xc24b8b70d0f89791,
+  0xc76c51a30654be30,
+  0xd192e819d6ef5218,
+  0xd69906245565a910,
+  0xf40e35855771202a,
+  0x106aa07032bbd1b8,
+  0x19a4c116b8d2d0c8,
+  0x1e376c085141ab53,
+  0x2748774cdf8eeb99,
+  0x34b0bcb5e19b48a8,
+  0x391c0cb3c5c95a63,
+  0x4ed8aa4ae3418acb,
+  0x5b9cca4f7763e373,
+  0x682e6ff3d6b2b8a3,
+  0x748f82ee5defb2fc,
+  0x78a5636f43172f60,
+  0x84c87814a1f0ab72,
+  0x8cc702081a6439ec,
+  0x90befffa23631e28,
+  0xa4506cebde82bde9,
+  0xbef9a3f7b2c67915,
+  0xc67178f2e372532b,
+  0xca273eceea26619c,
+  0xd186b8c721c0c207,
+  0xeada7dd6cde0eb1e,
+  0xf57d4f7fee6ed178,
+  0x06f067aa72176fba,
+  0x0a637dc5a2c898a6,
+  0x113f9804bef90dae,
+  0x1b710b35131c471b,
+  0x28db77f523047d84,
+  0x32caab7b40c72493,
+  0x3c9ebe0a15c9bebc,
+  0x431d67c49c100d4c,
+  0x4cc5d4becb3e42b6,
+  0x597f299cfc657e2a,
+  0x5fcb6fab3ad6faec,
+  0x6c44198c4a475817,
+]);
diff --git a/lib/src/sha512_slowsinks.dart b/lib/src/sha512_slowsinks.dart
new file mode 100644
index 0000000..a2af606
--- /dev/null
+++ b/lib/src/sha512_slowsinks.dart
@@ -0,0 +1,323 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:typed_data';
+
+import 'digest.dart';
+import 'hash_sink.dart';
+
+/// Data from a non-linear function that functions as reproducible noise.
+///
+/// [rfc]: https://tools.ietf.org/html/rfc6234#section-5.2
+final _noise32 = Uint32List.fromList([
+  0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, //
+  0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc,
+  0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019,
+  0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118,
+  0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe,
+  0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2,
+  0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1,
+  0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694,
+  0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3,
+  0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65,
+  0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483,
+  0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5,
+  0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210,
+  0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4,
+  0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725,
+  0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70,
+  0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926,
+  0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df,
+  0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8,
+  0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b,
+  0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001,
+  0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30,
+  0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910,
+  0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8,
+  0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53,
+  0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8,
+  0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb,
+  0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3,
+  0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60,
+  0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec,
+  0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9,
+  0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b,
+  0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207,
+  0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178,
+  0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6,
+  0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b,
+  0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493,
+  0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c,
+  0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a,
+  0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817,
+]);
+
+abstract class _Sha64BitSink extends HashSink {
+  int get digestBytes;
+
+  @override
+  Uint32List get digest {
+    return Uint32List.view(_digest.buffer, 0, digestBytes);
+  }
+
+  // Initial value of the hash parts. First 64 bits of the fractional parts
+  // of the square roots of the ninth through sixteenth prime numbers.
+  final Uint32List _digest;
+
+  /// The sixteen words from the original chunk, extended to 64 words.
+  ///
+  /// This is an instance variable to avoid re-allocating, but its data isn't
+  /// used across invocations of [updateHash].
+  final _extended = Uint32List(160);
+
+  _Sha64BitSink(Sink<Digest> sink, this._digest) : super(sink, 32);
+  // The following helper functions are taken directly from
+  // http://tools.ietf.org/html/rfc6234.
+
+  _shr(int bits, Uint32List word, int offset, Uint32List ret, int offsetR) {
+    ret[0 + offsetR] =
+        ((bits < 32) && (bits >= 0)) ? (word[0 + offset] >> (bits)) : 0;
+    ret[1 + offsetR] = (bits > 32)
+        ? (word[0 + offset] >> (bits - 32))
+        : (bits == 32)
+            ? word[0 + offset]
+            : (bits >= 0)
+                ? ((word[0 + offset] << (32 - bits)) |
+                    (word[1 + offset] >> bits))
+                : 0;
+  }
+
+  _shl(int bits, Uint32List word, int offset, Uint32List ret, int offsetR) {
+    ret[0 + offsetR] = (bits > 32)
+        ? (word[1 + offset] << (bits - 32))
+        : (bits == 32)
+            ? word[1 + offset]
+            : (bits >= 0)
+                ? ((word[0 + offset] << bits) |
+                    (word[1 + offset] >> (32 - bits)))
+                : 0;
+    ret[1 + offsetR] =
+        ((bits < 32) && (bits >= 0)) ? (word[1 + offset] << bits) : 0;
+  }
+
+  _or(Uint32List word1, int offset1, Uint32List word2, int offset2,
+      Uint32List ret, int offsetR) {
+    ret[0 + offsetR] = word1[0 + offset1] | word2[0 + offset2];
+    ret[1 + offsetR] = word1[1 + offset1] | word2[1 + offset2];
+  }
+
+  _xor(Uint32List word1, int offset1, Uint32List word2, int offset2,
+      Uint32List ret, int offsetR) {
+    ret[0 + offsetR] = word1[0 + offset1] ^ word2[0 + offset2];
+    ret[1 + offsetR] = word1[1 + offset1] ^ word2[1 + offset2];
+  }
+
+  _add(Uint32List word1, int offset1, Uint32List word2, int offset2,
+      Uint32List ret, int offsetR) {
+    ret[1 + offsetR] = (word1[1 + offset1] + word2[1 + offset2]);
+    ret[0 + offsetR] = word1[0 + offset1] +
+        word2[0 + offset2] +
+        (ret[1 + offsetR] < word1[1 + offset1] ? 1 : 0);
+  }
+
+  _addTo2(Uint32List word1, int offset1, Uint32List word2, int offset2) {
+    int _addTemp;
+    _addTemp = word1[1 + offset1];
+    word1[1 + offset1] += word2[1 + offset2];
+    word1[0 + offset1] +=
+        word2[0 + offset2] + (word1[1 + offset1] < _addTemp ? 1 : 0);
+  }
+
+  static const _rotrIndex1 = 0;
+  static const _rotrIndex2 = _rotrIndex1 + 2;
+  static const _sigIndex1 = _rotrIndex2 + 2;
+  static const _sigIndex2 = _sigIndex1 + 2;
+  static const _sigIndex3 = _sigIndex2 + 2;
+  static const _sigIndex4 = _sigIndex3 + 2;
+  static const _aIndex = _sigIndex4 + 2;
+  static const _bIndex = _aIndex + 2;
+  static const _cIndex = _bIndex + 2;
+  static const _dIndex = _cIndex + 2;
+  static const _eIndex = _dIndex + 2;
+  static const _fIndex = _eIndex + 2;
+  static const _gIndex = _fIndex + 2;
+  static const _hIndex = _gIndex + 2;
+  static const _tmp1 = _hIndex + 2;
+  static const _tmp2 = _tmp1 + 2;
+  static const _tmp3 = _tmp2 + 2;
+  static const _tmp4 = _tmp3 + 2;
+  static const _tmp5 = _tmp4 + 2;
+  final _nums = Uint32List(12 + 16 + 10);
+
+  // SHA rotate   ((word >> bits) | (word << (64-bits)))
+  _rotr(int bits, Uint32List word, int offset, Uint32List ret, int offsetR) {
+    _shr(bits, word, offset, _nums, _rotrIndex1);
+    _shl(64 - bits, word, offset, _nums, _rotrIndex2);
+    _or(_nums, _rotrIndex1, _nums, _rotrIndex2, ret, offsetR);
+  }
+
+  _bsig0(Uint32List word, int offset, Uint32List ret, int offsetR) {
+    _rotr(28, word, offset, _nums, _sigIndex1);
+    _rotr(34, word, offset, _nums, _sigIndex2);
+    _rotr(39, word, offset, _nums, _sigIndex3);
+    _xor(_nums, _sigIndex2, _nums, _sigIndex3, _nums, _sigIndex4);
+    _xor(_nums, _sigIndex1, _nums, _sigIndex4, ret, offsetR);
+  }
+
+  _bsig1(Uint32List word, int offset, Uint32List ret, int offsetR) {
+    _rotr(14, word, offset, _nums, _sigIndex1);
+    _rotr(18, word, offset, _nums, _sigIndex2);
+    _rotr(41, word, offset, _nums, _sigIndex3);
+    _xor(_nums, _sigIndex2, _nums, _sigIndex3, _nums, _sigIndex4);
+    _xor(_nums, _sigIndex1, _nums, _sigIndex4, ret, offsetR);
+  }
+
+  _ssig0(Uint32List word, int offset, Uint32List ret, int offsetR) {
+    _rotr(1, word, offset, _nums, _sigIndex1);
+    _rotr(8, word, offset, _nums, _sigIndex2);
+    _shr(7, word, offset, _nums, _sigIndex3);
+    _xor(_nums, _sigIndex2, _nums, _sigIndex3, _nums, _sigIndex4);
+    _xor(_nums, _sigIndex1, _nums, _sigIndex4, ret, offsetR);
+  }
+
+  _ssig1(Uint32List word, int offset, Uint32List ret, int offsetR) {
+    _rotr(19, word, offset, _nums, _sigIndex1);
+    _rotr(61, word, offset, _nums, _sigIndex2);
+    _shr(6, word, offset, _nums, _sigIndex3);
+    _xor(_nums, _sigIndex2, _nums, _sigIndex3, _nums, _sigIndex4);
+    _xor(_nums, _sigIndex1, _nums, _sigIndex4, ret, offsetR);
+  }
+
+  _ch(Uint32List x, int offsetX, Uint32List y, int offsetY, Uint32List z,
+      int offsetZ, Uint32List ret, int offsetR) {
+    ret[0 + offsetR] =
+        ((x[0 + offsetX] & (y[0 + offsetY] ^ z[0 + offsetZ])) ^ z[0 + offsetZ]);
+    ret[1 + offsetR] =
+        ((x[1 + offsetX] & (y[1 + offsetY] ^ z[1 + offsetZ])) ^ z[1 + offsetZ]);
+  }
+
+  _maj(Uint32List x, int offsetX, Uint32List y, int offsetY, Uint32List z,
+      int offsetZ, Uint32List ret, int offsetR) {
+    ret[0 + offsetR] = ((x[0 + offsetX] & (y[0 + offsetY] | z[0 + offsetZ])) |
+        (y[0 + offsetY] & z[0 + offsetZ]));
+    ret[1 + offsetR] = ((x[1 + offsetX] & (y[1 + offsetY] | z[1 + offsetZ])) |
+        (y[1 + offsetY] & z[1 + offsetZ]));
+  }
+
+  @override
+  void updateHash(Uint32List chunk) {
+    assert(chunk.length == 32);
+
+    // Prepare message schedule.
+    for (var i = 0; i < 32; i++) {
+      _extended[i] = chunk[i];
+    }
+
+    for (var i = 32; i < 160; i += 2) {
+      _ssig1(_extended, i - 2 * 2, _nums, _tmp1);
+      _add(_nums, _tmp1, _extended, i - 7 * 2, _nums, _tmp2);
+      _ssig0(_extended, i - 15 * 2, _nums, _tmp1);
+      _add(_nums, _tmp1, _extended, i - 16 * 2, _nums, _tmp3);
+      _add(_nums, _tmp2, _nums, _tmp3, _extended, i);
+    }
+
+    // Shuffle around the bits.
+    _nums.setRange(_aIndex, _hIndex + 2, _digest);
+
+    for (var i = 0; i < 160; i += 2) {
+      // temp1 = H + SHA512_SIGMA1(E) + SHA_Ch(E,F,G) + K[t] + W[t];
+      _bsig1(_nums, _eIndex, _nums, _tmp1);
+      _add(_nums, _hIndex, _nums, _tmp1, _nums, _tmp2);
+      _ch(_nums, _eIndex, _nums, _fIndex, _nums, _gIndex, _nums, _tmp3);
+      _add(_nums, _tmp2, _nums, _tmp3, _nums, _tmp4);
+      _add(_noise32, i, _extended, i, _nums, _tmp5);
+      _add(_nums, _tmp4, _nums, _tmp5, _nums, _tmp1);
+
+      // temp2 = SHA512_SIGMA0(A) + SHA_Maj(A,B,C);
+      _bsig0(_nums, _aIndex, _nums, _tmp3);
+      _maj(_nums, _aIndex, _nums, _bIndex, _nums, _cIndex, _nums, _tmp4);
+      _add(_nums, _tmp3, _nums, _tmp4, _nums, _tmp2);
+
+      _nums[_hIndex] = _nums[_gIndex];
+      _nums[_hIndex + 1] = _nums[_gIndex + 1];
+      _nums[_gIndex] = _nums[_fIndex];
+      _nums[_gIndex + 1] = _nums[_fIndex + 1];
+      _nums[_fIndex] = _nums[_eIndex];
+      _nums[_fIndex + 1] = _nums[_eIndex + 1];
+      _add(_nums, _dIndex, _nums, _tmp1, _nums, _eIndex);
+      _nums[_dIndex] = _nums[_cIndex];
+      _nums[_dIndex + 1] = _nums[_cIndex + 1];
+      _nums[_cIndex] = _nums[_bIndex];
+      _nums[_cIndex + 1] = _nums[_bIndex + 1];
+      _nums[_bIndex] = _nums[_aIndex];
+      _nums[_bIndex + 1] = _nums[_aIndex + 1];
+
+      _add(_nums, _tmp1, _nums, _tmp2, _nums, _aIndex);
+    }
+
+    // Update hash values after iteration.
+    _addTo2(_digest, 0, _nums, _aIndex);
+    _addTo2(_digest, 2, _nums, _bIndex);
+    _addTo2(_digest, 4, _nums, _cIndex);
+    _addTo2(_digest, 6, _nums, _dIndex);
+    _addTo2(_digest, 8, _nums, _eIndex);
+    _addTo2(_digest, 10, _nums, _fIndex);
+    _addTo2(_digest, 12, _nums, _gIndex);
+    _addTo2(_digest, 14, _nums, _hIndex);
+  }
+}
+
+/// The concrete implementation of [Sha384].
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class Sha384Sink extends _Sha64BitSink {
+  final digestBytes = 12;
+
+  Sha384Sink(Sink<Digest> sink)
+      : super(
+            sink,
+            Uint32List.fromList([
+              0xcbbb9d5d,
+              0xc1059ed8,
+              0x629a292a,
+              0x367cd507,
+              0x9159015a,
+              0x3070dd17,
+              0x152fecd8,
+              0xf70e5939,
+              0x67332667,
+              0xffc00b31,
+              0x8eb44a87,
+              0x68581511,
+              0xdb0c2e0d,
+              0x64f98fa7,
+              0x47b5481d,
+              0xbefa4fa4,
+            ]));
+}
+
+/// The concrete implementation of [Sha512].
+///
+/// This is separate so that it can extend [HashSink] without leaking additional
+/// public members.
+class Sha512Sink extends _Sha64BitSink {
+  final digestBytes = 16;
+
+  Sha512Sink(Sink<Digest> sink)
+      : super(
+            sink,
+            Uint32List.fromList([
+              // Initial value of the hash parts. First 64 bits of the fractional
+              // parts of the square roots of the first eight prime numbers.
+              0x6a09e667, 0xf3bcc908,
+              0xbb67ae85, 0x84caa73b,
+              0x3c6ef372, 0xfe94f82b,
+              0xa54ff53a, 0x5f1d36f1,
+              0x510e527f, 0xade682d1,
+              0x9b05688c, 0x2b3e6c1f,
+              0x1f83d9ab, 0xfb41bd6b,
+              0x5be0cd19, 0x137e2179,
+            ]));
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 1955238..1f971fc 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,11 +1,11 @@
 name: crypto
-version: 2.0.6
+version: 2.1.0
 author: Dart Team <misc@dartlang.org>
 description: Library of cryptographic functions.
 homepage: https://www.github.com/dart-lang/crypto
 
 environment:
-  sdk: '>=2.0.0 <3.0.0'
+  sdk: '>=2.1.0 <3.0.0'
 
 dependencies:
   collection: '^1.0.0'
diff --git a/test/sha256_test.dart b/test/sha256_test.dart
index 1e0a3ce..babf6db 100644
--- a/test/sha256_test.dart
+++ b/test/sha256_test.dart
@@ -9,34 +9,82 @@
 import 'package:crypto/crypto.dart';
 
 void main() {
-  group('with a chunked converter', () {
-    test('add may not be called after close', () {
-      var sink = sha256.startChunkedConversion(StreamController<Digest>().sink);
-      sink.close();
-      expect(() => sink.add([0]), throwsStateError);
+  group('SHA2-256', () {
+    group('with a chunked converter', () {
+      test('add may not be called after close', () {
+        var sink =
+            sha256.startChunkedConversion(StreamController<Digest>().sink);
+        sink.close();
+        expect(() => sink.add([0]), throwsStateError);
+      });
+
+      test('close may be called multiple times', () {
+        var sink =
+            sha256.startChunkedConversion(StreamController<Digest>().sink);
+        sink.close();
+        sink.close();
+        sink.close();
+        sink.close();
+      });
+
+      test('close closes the underlying sink', () {
+        var inner = ChunkedConversionSink<Digest>.withCallback(
+            expectAsync1((accumulated) {
+          expect(accumulated.length, equals(1));
+          expect(
+              accumulated.first.toString(),
+              equals(
+                  'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8'
+                  '55'));
+        }));
+
+        var outer = sha256.startChunkedConversion(inner);
+        outer.close();
+      });
     });
 
-    test('close may be called multiple times', () {
-      var sink = sha256.startChunkedConversion(StreamController<Digest>().sink);
-      sink.close();
-      sink.close();
-      sink.close();
-      sink.close();
+    test('vectors', () {
+      expect('${sha256.convert('this is a test'.codeUnits)}',
+          '2e99758548972a8e8822ad47fa1017ff72f06f3ff6a016851f45c398732bc50c');
+    });
+  });
+
+  group('SHA2-224', () {
+    group('with a chunked converter', () {
+      test('add may not be called after close', () {
+        var sink =
+            sha224.startChunkedConversion(StreamController<Digest>().sink);
+        sink.close();
+        expect(() => sink.add([0]), throwsStateError);
+      });
+
+      test('close may be called multiple times', () {
+        var sink =
+            sha224.startChunkedConversion(StreamController<Digest>().sink);
+        sink.close();
+        sink.close();
+        sink.close();
+        sink.close();
+      });
+
+      test('close closes the underlying sink', () {
+        var inner = ChunkedConversionSink<Digest>.withCallback(
+            expectAsync1((accumulated) {
+          expect(accumulated.length, equals(1));
+          expect(
+              accumulated.first.toString(),
+              equals(
+                  'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f'));
+        }));
+
+        var outer = sha224.startChunkedConversion(inner);
+        outer.close();
+      });
     });
 
-    test('close closes the underlying sink', () {
-      var inner = ChunkedConversionSink<Digest>.withCallback(
-          expectAsync1((accumulated) {
-        expect(accumulated.length, equals(1));
-        expect(
-            accumulated.first.toString(),
-            equals(
-                'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8'
-                '55'));
-      }));
-
-      var outer = sha256.startChunkedConversion(inner);
-      outer.close();
+    test('vectors', () {
+      expect('${sha224.convert('this is a test'.codeUnits)}',
+          '52fa5d621db1c9f11602fc92d1e8d1115a9018f191de948944c4ac39');
     });
   });
 
diff --git a/test/sha512_test.dart b/test/sha512_test.dart
new file mode 100644
index 0000000..351a8b7
--- /dev/null
+++ b/test/sha512_test.dart
@@ -0,0 +1,79 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:test/test.dart';
+import 'package:crypto/crypto.dart';
+
+void main() {
+  group('SHA2-384', () {
+    group('with a chunked converter', () {
+      test('add may not be called after close', () {
+        var sink =
+            sha384.startChunkedConversion(StreamController<Digest>().sink);
+        sink.close();
+        expect(() => sink.add([0]), throwsStateError);
+      });
+
+      test('close may be called multiple times', () {
+        var sink =
+            sha384.startChunkedConversion(StreamController<Digest>().sink);
+        sink.close();
+        sink.close();
+        sink.close();
+        sink.close();
+      });
+
+      test('close closes the underlying sink', () {
+        var inner = ChunkedConversionSink<Digest>.withCallback(
+            expectAsync1((accumulated) {
+          expect(accumulated.length, equals(1));
+          expect(
+              accumulated.first.toString(),
+              equals(
+                  '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b'));
+        }));
+
+        var outer = sha384.startChunkedConversion(inner);
+        outer.close();
+      });
+    });
+  });
+
+  group('SHA2-512', () {
+    group('with a chunked converter', () {
+      test('add may not be called after close', () {
+        var sink =
+            sha512.startChunkedConversion(StreamController<Digest>().sink);
+        sink.close();
+        expect(() => sink.add([0]), throwsStateError);
+      });
+
+      test('close may be called multiple times', () {
+        var sink =
+            sha512.startChunkedConversion(StreamController<Digest>().sink);
+        sink.close();
+        sink.close();
+        sink.close();
+        sink.close();
+      });
+
+      test('close closes the underlying sink', () {
+        var inner = ChunkedConversionSink<Digest>.withCallback(
+            expectAsync1((accumulated) {
+          expect(accumulated.length, equals(1));
+          expect(
+              accumulated.first.toString(),
+              equals(
+                  'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e'));
+        }));
+
+        var outer = sha512.startChunkedConversion(inner);
+        outer.close();
+      });
+    });
+  });
+}
diff --git a/test/sha_monte_test.dart b/test/sha_monte_test.dart
new file mode 100644
index 0000000..5a947d1
--- /dev/null
+++ b/test/sha_monte_test.dart
@@ -0,0 +1,90 @@
+import 'dart:typed_data';
+
+import 'package:test/test.dart';
+import 'package:crypto/crypto.dart';
+
+main() {
+  group("Monte Vectors", () {
+    monteTest(
+      'sha224',
+      sha224,
+      'ed2b70d575d9d0b4196ae84a03eed940057ea89cdd729b95b7d4e6a5',
+      [
+        'cd94d7da13c030208b2d0d78fcfe9ea22fa8906df66aa9a1f42afa70',
+        '555846e884633639565d5e0c01dd93ba58edb01ee18e68ccca28f7b8',
+        '44d5f4a179b33231f24cc209ed2542ddb931391f2a2d604f80ed460b',
+        '18678e3c151f05f92a89fc5b2ec56bfc6fafa66d73ffc1937fcab4d0',
+        'b285f829b0499ff45f8454eda2d4e0997b3f438c2728f1a25cfbb05a',
+      ],
+    );
+    monteTest(
+      'sha256',
+      sha256,
+      '6d1e72ad03ddeb5de891e572e2396f8da015d899ef0e79503152d6010a3fe691',
+      [
+        'e93c330ae5447738c8aa85d71a6c80f2a58381d05872d26bdd39f1fcd4f2b788',
+        '2e78f8c8772ea7c9331d41ed3f9cdf27d8f514a99342ee766ee3b8b0d0b121c0',
+        'd6a23dff1b7f2eddc1a212f8a218397523a799b07386a30692fd6fe9d2bf0944',
+        'fb0099a964fad5a88cf12952f2991ce256a4ac3049f3d389c3b9e6c00e585db4',
+        'f9eba2a4cf6263826beaf6150057849eb975a9513c0b76ecad0f1c19ebbad89b',
+      ],
+    );
+    monteTest(
+        'sha384',
+        sha384,
+        'edff07255c71b54a9beae52cdfa083569a08be89949cbba73ddc8acf429359ca5e5be7a673633ca0d9709848f522a9df',
+        [
+          'e81b86c49a38feddfd185f71ca7da6732a053ed4a2640d52d27f53f9f76422650b0e93645301ac99f8295d6f820f1035',
+          '1d6bd21713bffd50946a10c39a7742d740e8f271f0c8f643d4c95375094fd9bf29d89ee61a76053f22e44a4b058a64ed',
+          '425167b66ae965bd7d68515b54ebfa16f33d2bdb2147a4eac515a75224cd19cea564d692017d2a1c41c1a3f68bb5a209',
+          '9e7477ffd4baad1fcca035f4687b35ed47a57832fb27d131eb8018fcb41edf4d5e25874466d2e2d61ae3accdfc7aa364',
+          'd7b4d4e779ca70c8d065630db1f9128ee43b4bde08a81bce13d48659b6ef47b6cfc802af6d8756f6cd43c709bb445bab',
+        ]);
+    monteTest(
+        'sha512',
+        sha512,
+        '5c337de5caf35d18ed90b5cddfce001ca1b8ee8602f367e7c24ccca6f893802fb1aca7a3dae32dcd60800a59959bc540d63237876b799229ae71a2526fbc52cd',
+        [
+          'ada69add0071b794463c8806a177326735fa624b68ab7bcab2388b9276c036e4eaaff87333e83c81c0bca0359d4aeebcbcfd314c0630e0c2af68c1fb19cc470e',
+          'ef219b37c24ae507a2b2b26d1add51b31fb5327eb8c3b19b882fe38049433dbeccd63b3d5b99ba2398920bcefb8aca98cd28a1ee5d2aaf139ce58a15d71b06b4',
+          'c3d5087a62db0e5c6f5755c417f69037308cbce0e54519ea5be8171496cc6d18023ba15768153cfd74c7e7dc103227e9eed4b0f82233362b2a7b1a2cbcda9daf',
+          'bb3a58f71148116e377505461d65d6c89906481fedfbcfe481b7aa8ceb977d252b3fe21bfff6e7fbf7575ceecf5936bd635e1cf52698c36ef6908ddbd5b6ae05',
+          'b68f0cd2d63566b3934a50666dec6d62ca1db98e49d7733084c1f86d91a8a08c756fa7ece815e20930dd7cb66351bad8c087c2f94e8757cb98e7f4b86b21a8a8',
+        ]);
+  });
+}
+
+monteTest(String name, Hash hash, String seed, List<String> expected) {
+  test(name, () {
+    Iterable<String> run() sync* {
+      var _seed = bytesFromHexString(seed);
+      for (int j = 0; j < expected.length; j++) {
+        Uint8List md0, md1, md2;
+        md0 = (Uint8List.fromList(_seed));
+        md1 = (Uint8List.fromList(_seed));
+        md2 = (Uint8List.fromList(_seed));
+        Digest mdI;
+        for (int i = 3; i < 1003; i++) {
+          var mI = <int>[]..addAll(md0)..addAll(md1)..addAll(md2);
+          mdI = hash.convert(mI);
+          md0.setAll(0, md1);
+          md1.setAll(0, md2);
+          md2.setAll(0, mdI.bytes);
+        }
+        yield '$mdI';
+        _seed.setAll(0, md2);
+      }
+    }
+
+    expect(run().toList(), expected);
+  });
+}
+
+final toupleMatch = RegExp('([0-9a-fA-F]{2})');
+Uint8List bytesFromHexString(String message) {
+  var seed = <int>[];
+  for (var match in toupleMatch.allMatches(message)) {
+    seed.add(int.parse(match.group(0), radix: 16));
+  }
+  return Uint8List.fromList(seed);
+}