diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+# Version 0.1
+
+* Initial version.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,219 @@
+Copyright 2020, Renzo Carbonara.
+
+Except where otherwise explicitly mentioned, all of contents of the
+"bitcoin-hash" project are licensed under the Apache License,
+Version 2.0 (the "License"); you may not use this file except in
+compliance with the License. You may obtain a copy of the License at the
+end of this file or at http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+-------------------------------------------------------------------------------
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,17 @@
+# bitcoin-hash
+
+Bitcoin hashing primitives.
+
+This library builds on GHC and GHCJS.
+
+
+## Developing in GHCJS
+
+* `cabal --ghcjs build` will build the Haskell `bitcoin-hash` library with the
+  JavaScript dependencies already compiled at `js/index.compiled.js`.
+
+* If the `js/index.js` file changes, run `npx webpack` to regenerate
+  `js/index.compiled.js` and commit both files to the repository.
+
+* If the `package.json` file changes, run `npm install -D` to get new JS
+  dependencies, and then run `npx webpack` as above.
diff --git a/bitcoin-hash.cabal b/bitcoin-hash.cabal
new file mode 100644
--- /dev/null
+++ b/bitcoin-hash.cabal
@@ -0,0 +1,47 @@
+cabal-version: 2.4
+name: bitcoin-hash
+version: 0.1
+license: Apache-2.0
+license-file: LICENSE
+extra-source-files: README.md CHANGELOG.md
+author: Renzo Carbonara
+maintainer: renλren.zone
+copyright: Copyright (c) Renzo Carbonara 2020
+category: Cryptography
+build-type: Simple
+synopsis: Bitcoin hash primitives
+description:
+  Bitcoin hash primitives.
+  .
+  This library builds in GHC and GHCJS.
+homepage: https://gitlab.com/k0001/hs-bitcoin-hash
+bug-reports: https://gitlab.com/k0001/hs-bitcoin-hash/issues
+tested-with: GHC == 8.8.3, GHC == 8.6.5, GHCJS == 8.6.0
+
+common basic
+  default-language: Haskell2010
+  ghc-options: -O2 -Wall -Werror=incomplete-patterns
+  ghcjs-options: -O3 -Wall -Werror=incomplete-patterns
+  build-depends: base == 4.*, bytestring
+
+library
+  import: basic
+  hs-source-dirs: lib
+  exposed-modules: Bitcoin.Hash, Bitcoin.Hash.Prim
+  other-modules: Bitcoin.Hash.Internal
+  if impl(ghcjs)
+    other-modules: Bitcoin.Hash.GHCJS
+    build-depends: ghcjs-base
+    js-sources:
+      -- This file is generated. See README.md.
+      js/index.compiled.js
+  else
+    other-modules: Bitcoin.Hash.GHC
+    build-depends: cryptonite, memory
+
+test-suite test
+  import: basic
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is: Main.hs
+  build-depends: bitcoin-hash, tasty, tasty-hunit, time
diff --git a/js/index.compiled.js b/js/index.compiled.js
new file mode 100644
--- /dev/null
+++ b/js/index.compiled.js
@@ -0,0 +1,1 @@
+var h$bitcoin_hash=function(t){var n={};function i(r){if(n[r])return n[r].exports;var e=n[r]={i:r,l:!1,exports:{}};return t[r].call(e.exports,e,e.exports,i),e.l=!0,e.exports}return i.m=t,i.c=n,i.d=function(t,n,r){i.o(t,n)||Object.defineProperty(t,n,{enumerable:!0,get:r})},i.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.t=function(t,n){if(1&n&&(t=i(t)),8&n)return t;if(4&n&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&n&&"string"!=typeof t)for(var e in t)i.d(r,e,function(n){return t[n]}.bind(null,e));return r},i.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(n,"a",n),n},i.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},i.p="",i(i.s=7)}([function(t,n,i){"use strict";var r=i(1),e=i(8);function h(t,n){return 55296==(64512&t.charCodeAt(n))&&(!(n<0||n+1>=t.length)&&56320==(64512&t.charCodeAt(n+1)))}function s(t){return(t>>>24|t>>>8&65280|t<<8&16711680|(255&t)<<24)>>>0}function o(t){return 1===t.length?"0"+t:t}function u(t){return 7===t.length?"0"+t:6===t.length?"00"+t:5===t.length?"000"+t:4===t.length?"0000"+t:3===t.length?"00000"+t:2===t.length?"000000"+t:1===t.length?"0000000"+t:t}n.inherits=e,n.toArray=function(t,n){if(Array.isArray(t))return t.slice();if(!t)return[];var i=[];if("string"==typeof t)if(n){if("hex"===n)for((t=t.replace(/[^a-z0-9]+/gi,"")).length%2!=0&&(t="0"+t),e=0;e<t.length;e+=2)i.push(parseInt(t[e]+t[e+1],16))}else for(var r=0,e=0;e<t.length;e++){var s=t.charCodeAt(e);s<128?i[r++]=s:s<2048?(i[r++]=s>>6|192,i[r++]=63&s|128):h(t,e)?(s=65536+((1023&s)<<10)+(1023&t.charCodeAt(++e)),i[r++]=s>>18|240,i[r++]=s>>12&63|128,i[r++]=s>>6&63|128,i[r++]=63&s|128):(i[r++]=s>>12|224,i[r++]=s>>6&63|128,i[r++]=63&s|128)}else for(e=0;e<t.length;e++)i[e]=0|t[e];return i},n.toHex=function(t){for(var n="",i=0;i<t.length;i++)n+=o(t[i].toString(16));return n},n.htonl=s,n.toHex32=function(t,n){for(var i="",r=0;r<t.length;r++){var e=t[r];"little"===n&&(e=s(e)),i+=u(e.toString(16))}return i},n.zero2=o,n.zero8=u,n.join32=function(t,n,i,e){var h=i-n;r(h%4==0);for(var s=new Array(h/4),o=0,u=n;o<s.length;o++,u+=4){var a;a="big"===e?t[u]<<24|t[u+1]<<16|t[u+2]<<8|t[u+3]:t[u+3]<<24|t[u+2]<<16|t[u+1]<<8|t[u],s[o]=a>>>0}return s},n.split32=function(t,n){for(var i=new Array(4*t.length),r=0,e=0;r<t.length;r++,e+=4){var h=t[r];"big"===n?(i[e]=h>>>24,i[e+1]=h>>>16&255,i[e+2]=h>>>8&255,i[e+3]=255&h):(i[e+3]=h>>>24,i[e+2]=h>>>16&255,i[e+1]=h>>>8&255,i[e]=255&h)}return i},n.rotr32=function(t,n){return t>>>n|t<<32-n},n.rotl32=function(t,n){return t<<n|t>>>32-n},n.sum32=function(t,n){return t+n>>>0},n.sum32_3=function(t,n,i){return t+n+i>>>0},n.sum32_4=function(t,n,i,r){return t+n+i+r>>>0},n.sum32_5=function(t,n,i,r,e){return t+n+i+r+e>>>0},n.sum64=function(t,n,i,r){var e=t[n],h=r+t[n+1]>>>0,s=(h<r?1:0)+i+e;t[n]=s>>>0,t[n+1]=h},n.sum64_hi=function(t,n,i,r){return(n+r>>>0<n?1:0)+t+i>>>0},n.sum64_lo=function(t,n,i,r){return n+r>>>0},n.sum64_4_hi=function(t,n,i,r,e,h,s,o){var u=0,a=n;return u+=(a=a+r>>>0)<n?1:0,u+=(a=a+h>>>0)<h?1:0,t+i+e+s+(u+=(a=a+o>>>0)<o?1:0)>>>0},n.sum64_4_lo=function(t,n,i,r,e,h,s,o){return n+r+h+o>>>0},n.sum64_5_hi=function(t,n,i,r,e,h,s,o,u,a){var c=0,f=n;return c+=(f=f+r>>>0)<n?1:0,c+=(f=f+h>>>0)<h?1:0,c+=(f=f+o>>>0)<o?1:0,t+i+e+s+u+(c+=(f=f+a>>>0)<a?1:0)>>>0},n.sum64_5_lo=function(t,n,i,r,e,h,s,o,u,a){return n+r+h+o+a>>>0},n.rotr64_hi=function(t,n,i){return(n<<32-i|t>>>i)>>>0},n.rotr64_lo=function(t,n,i){return(t<<32-i|n>>>i)>>>0},n.shr64_hi=function(t,n,i){return t>>>i},n.shr64_lo=function(t,n,i){return(t<<32-i|n>>>i)>>>0}},function(t,n){function i(t,n){if(!t)throw new Error(n||"Assertion failed")}t.exports=i,i.equal=function(t,n,i){if(t!=n)throw new Error(i||"Assertion failed: "+t+" != "+n)}},function(t,n,i){"use strict";var r=i(0),e=i(1);function h(){this.pending=null,this.pendingTotal=0,this.blockSize=this.constructor.blockSize,this.outSize=this.constructor.outSize,this.hmacStrength=this.constructor.hmacStrength,this.padLength=this.constructor.padLength/8,this.endian="big",this._delta8=this.blockSize/8,this._delta32=this.blockSize/32}n.BlockHash=h,h.prototype.update=function(t,n){if(t=r.toArray(t,n),this.pending?this.pending=this.pending.concat(t):this.pending=t,this.pendingTotal+=t.length,this.pending.length>=this._delta8){var i=(t=this.pending).length%this._delta8;this.pending=t.slice(t.length-i,t.length),0===this.pending.length&&(this.pending=null),t=r.join32(t,0,t.length-i,this.endian);for(var e=0;e<t.length;e+=this._delta32)this._update(t,e,e+this._delta32)}return this},h.prototype.digest=function(t){return this.update(this._pad()),e(null===this.pending),this._digest(t)},h.prototype._pad=function(){var t=this.pendingTotal,n=this._delta8,i=n-(t+this.padLength)%n,r=new Array(i+this.padLength);r[0]=128;for(var e=1;e<i;e++)r[e]=0;if(t<<=3,"big"===this.endian){for(var h=8;h<this.padLength;h++)r[e++]=0;r[e++]=0,r[e++]=0,r[e++]=0,r[e++]=0,r[e++]=t>>>24&255,r[e++]=t>>>16&255,r[e++]=t>>>8&255,r[e++]=255&t}else for(r[e++]=255&t,r[e++]=t>>>8&255,r[e++]=t>>>16&255,r[e++]=t>>>24&255,r[e++]=0,r[e++]=0,r[e++]=0,r[e++]=0,h=8;h<this.padLength;h++)r[e++]=0;return r}},function(t,n,i){var r=n;r.utils=i(0),r.common=i(2),r.sha=i(9),r.ripemd=i(13),r.hmac=i(14),r.sha1=r.sha.sha1,r.sha256=r.sha.sha256,r.sha224=r.sha.sha224,r.sha384=r.sha.sha384,r.sha512=r.sha.sha512,r.ripemd160=r.ripemd.ripemd160},function(t,n,i){"use strict";var r=i(0).rotr32;function e(t,n,i){return t&n^~t&i}function h(t,n,i){return t&n^t&i^n&i}function s(t,n,i){return t^n^i}n.ft_1=function(t,n,i,r){return 0===t?e(n,i,r):1===t||3===t?s(n,i,r):2===t?h(n,i,r):void 0},n.ch32=e,n.maj32=h,n.p32=s,n.s0_256=function(t){return r(t,2)^r(t,13)^r(t,22)},n.s1_256=function(t){return r(t,6)^r(t,11)^r(t,25)},n.g0_256=function(t){return r(t,7)^r(t,18)^t>>>3},n.g1_256=function(t){return r(t,17)^r(t,19)^t>>>10}},function(t,n,i){"use strict";var r=i(0),e=i(2),h=i(4),s=i(1),o=r.sum32,u=r.sum32_4,a=r.sum32_5,c=h.ch32,f=h.maj32,l=h.s0_256,p=h.s1_256,g=h.g0_256,d=h.g1_256,_=e.BlockHash,v=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function m(){if(!(this instanceof m))return new m;_.call(this),this.h=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],this.k=v,this.W=new Array(64)}r.inherits(m,_),t.exports=m,m.blockSize=512,m.outSize=256,m.hmacStrength=192,m.padLength=64,m.prototype._update=function(t,n){for(var i=this.W,r=0;r<16;r++)i[r]=t[n+r];for(;r<i.length;r++)i[r]=u(d(i[r-2]),i[r-7],g(i[r-15]),i[r-16]);var e=this.h[0],h=this.h[1],_=this.h[2],v=this.h[3],m=this.h[4],y=this.h[5],b=this.h[6],S=this.h[7];for(s(this.k.length===i.length),r=0;r<i.length;r++){var k=a(S,p(m),c(m,y,b),this.k[r],i[r]),x=o(l(e),f(e,h,_));S=b,b=y,y=m,m=o(v,k),v=_,_=h,h=e,e=o(k,x)}this.h[0]=o(this.h[0],e),this.h[1]=o(this.h[1],h),this.h[2]=o(this.h[2],_),this.h[3]=o(this.h[3],v),this.h[4]=o(this.h[4],m),this.h[5]=o(this.h[5],y),this.h[6]=o(this.h[6],b),this.h[7]=o(this.h[7],S)},m.prototype._digest=function(t){return"hex"===t?r.toHex32(this.h,"big"):r.split32(this.h,"big")}},function(t,n,i){"use strict";var r=i(0),e=i(2),h=i(1),s=r.rotr64_hi,o=r.rotr64_lo,u=r.shr64_hi,a=r.shr64_lo,c=r.sum64,f=r.sum64_hi,l=r.sum64_lo,p=r.sum64_4_hi,g=r.sum64_4_lo,d=r.sum64_5_hi,_=r.sum64_5_lo,v=e.BlockHash,m=[1116352408,3609767458,1899447441,602891725,3049323471,3964484399,3921009573,2173295548,961987163,4081628472,1508970993,3053834265,2453635748,2937671579,2870763221,3664609560,3624381080,2734883394,310598401,1164996542,607225278,1323610764,1426881987,3590304994,1925078388,4068182383,2162078206,991336113,2614888103,633803317,3248222580,3479774868,3835390401,2666613458,4022224774,944711139,264347078,2341262773,604807628,2007800933,770255983,1495990901,1249150122,1856431235,1555081692,3175218132,1996064986,2198950837,2554220882,3999719339,2821834349,766784016,2952996808,2566594879,3210313671,3203337956,3336571891,1034457026,3584528711,2466948901,113926993,3758326383,338241895,168717936,666307205,1188179964,773529912,1546045734,1294757372,1522805485,1396182291,2643833823,1695183700,2343527390,1986661051,1014477480,2177026350,1206759142,2456956037,344077627,2730485921,1290863460,2820302411,3158454273,3259730800,3505952657,3345764771,106217008,3516065817,3606008344,3600352804,1432725776,4094571909,1467031594,275423344,851169720,430227734,3100823752,506948616,1363258195,659060556,3750685593,883997877,3785050280,958139571,3318307427,1322822218,3812723403,1537002063,2003034995,1747873779,3602036899,1955562222,1575990012,2024104815,1125592928,2227730452,2716904306,2361852424,442776044,2428436474,593698344,2756734187,3733110249,3204031479,2999351573,3329325298,3815920427,3391569614,3928383900,3515267271,566280711,3940187606,3454069534,4118630271,4000239992,116418474,1914138554,174292421,2731055270,289380356,3203993006,460393269,320620315,685471733,587496836,852142971,1086792851,1017036298,365543100,1126000580,2618297676,1288033470,3409855158,1501505948,4234509866,1607167915,987167468,1816402316,1246189591];function y(){if(!(this instanceof y))return new y;v.call(this),this.h=[1779033703,4089235720,3144134277,2227873595,1013904242,4271175723,2773480762,1595750129,1359893119,2917565137,2600822924,725511199,528734635,4215389547,1541459225,327033209],this.k=m,this.W=new Array(160)}function b(t,n,i,r,e){var h=t&i^~t&e;return h<0&&(h+=4294967296),h}function S(t,n,i,r,e,h){var s=n&r^~n&h;return s<0&&(s+=4294967296),s}function k(t,n,i,r,e){var h=t&i^t&e^i&e;return h<0&&(h+=4294967296),h}function x(t,n,i,r,e,h){var s=n&r^n&h^r&h;return s<0&&(s+=4294967296),s}function z(t,n){var i=s(t,n,28)^s(n,t,2)^s(n,t,7);return i<0&&(i+=4294967296),i}function w(t,n){var i=o(t,n,28)^o(n,t,2)^o(n,t,7);return i<0&&(i+=4294967296),i}function A(t,n){var i=s(t,n,14)^s(t,n,18)^s(n,t,9);return i<0&&(i+=4294967296),i}function H(t,n){var i=o(t,n,14)^o(t,n,18)^o(n,t,9);return i<0&&(i+=4294967296),i}function j(t,n){var i=s(t,n,1)^s(t,n,8)^u(t,n,7);return i<0&&(i+=4294967296),i}function L(t,n){var i=o(t,n,1)^o(t,n,8)^a(t,n,7);return i<0&&(i+=4294967296),i}function O(t,n){var i=s(t,n,19)^s(n,t,29)^u(t,n,6);return i<0&&(i+=4294967296),i}function B(t,n){var i=o(t,n,19)^o(n,t,29)^a(t,n,6);return i<0&&(i+=4294967296),i}r.inherits(y,v),t.exports=y,y.blockSize=1024,y.outSize=512,y.hmacStrength=192,y.padLength=128,y.prototype._prepareBlock=function(t,n){for(var i=this.W,r=0;r<32;r++)i[r]=t[n+r];for(;r<i.length;r+=2){var e=O(i[r-4],i[r-3]),h=B(i[r-4],i[r-3]),s=i[r-14],o=i[r-13],u=j(i[r-30],i[r-29]),a=L(i[r-30],i[r-29]),c=i[r-32],f=i[r-31];i[r]=p(e,h,s,o,u,a,c,f),i[r+1]=g(e,h,s,o,u,a,c,f)}},y.prototype._update=function(t,n){this._prepareBlock(t,n);var i=this.W,r=this.h[0],e=this.h[1],s=this.h[2],o=this.h[3],u=this.h[4],a=this.h[5],p=this.h[6],g=this.h[7],v=this.h[8],m=this.h[9],y=this.h[10],j=this.h[11],L=this.h[12],O=this.h[13],B=this.h[14],W=this.h[15];h(this.k.length===i.length);for(var P=0;P<i.length;P+=2){var T=B,C=W,M=A(v,m),U=H(v,m),E=b(v,m,y,j,L),q=S(v,m,y,j,L,O),I=this.k[P],$=this.k[P+1],D=i[P],F=i[P+1],G=d(T,C,M,U,E,q,I,$,D,F),J=_(T,C,M,U,E,q,I,$,D,F);T=z(r,e),C=w(r,e),M=k(r,e,s,o,u),U=x(r,e,s,o,u,a);var K=f(T,C,M,U),N=l(T,C,M,U);B=L,W=O,L=y,O=j,y=v,j=m,v=f(p,g,G,J),m=l(g,g,G,J),p=u,g=a,u=s,a=o,s=r,o=e,r=f(G,J,K,N),e=l(G,J,K,N)}c(this.h,0,r,e),c(this.h,2,s,o),c(this.h,4,u,a),c(this.h,6,p,g),c(this.h,8,v,m),c(this.h,10,y,j),c(this.h,12,L,O),c(this.h,14,B,W)},y.prototype._digest=function(t){return"hex"===t?r.toHex32(this.h,"big"):r.split32(this.h,"big")}},function(t,n,i){"use strict";i.r(n),i.d(n,"ripemd160",(function(){return e})),i.d(n,"sha256",(function(){return h})),i.d(n,"hmacSHA512",(function(){return s}));var r=i(3);function e(t){return new Uint8Array(r.ripemd160().update(t).digest())}function h(t){return new Uint8Array(r.sha256().update(t).digest())}function s(t,n){return new Uint8Array(r.hmac(r.sha512,t).update(n).digest())}},function(t,n){"function"==typeof Object.create?t.exports=function(t,n){n&&(t.super_=n,t.prototype=Object.create(n.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}))}:t.exports=function(t,n){if(n){t.super_=n;var i=function(){};i.prototype=n.prototype,t.prototype=new i,t.prototype.constructor=t}}},function(t,n,i){"use strict";n.sha1=i(10),n.sha224=i(11),n.sha256=i(5),n.sha384=i(12),n.sha512=i(6)},function(t,n,i){"use strict";var r=i(0),e=i(2),h=i(4),s=r.rotl32,o=r.sum32,u=r.sum32_5,a=h.ft_1,c=e.BlockHash,f=[1518500249,1859775393,2400959708,3395469782];function l(){if(!(this instanceof l))return new l;c.call(this),this.h=[1732584193,4023233417,2562383102,271733878,3285377520],this.W=new Array(80)}r.inherits(l,c),t.exports=l,l.blockSize=512,l.outSize=160,l.hmacStrength=80,l.padLength=64,l.prototype._update=function(t,n){for(var i=this.W,r=0;r<16;r++)i[r]=t[n+r];for(;r<i.length;r++)i[r]=s(i[r-3]^i[r-8]^i[r-14]^i[r-16],1);var e=this.h[0],h=this.h[1],c=this.h[2],l=this.h[3],p=this.h[4];for(r=0;r<i.length;r++){var g=~~(r/20),d=u(s(e,5),a(g,h,c,l),p,i[r],f[g]);p=l,l=c,c=s(h,30),h=e,e=d}this.h[0]=o(this.h[0],e),this.h[1]=o(this.h[1],h),this.h[2]=o(this.h[2],c),this.h[3]=o(this.h[3],l),this.h[4]=o(this.h[4],p)},l.prototype._digest=function(t){return"hex"===t?r.toHex32(this.h,"big"):r.split32(this.h,"big")}},function(t,n,i){"use strict";var r=i(0),e=i(5);function h(){if(!(this instanceof h))return new h;e.call(this),this.h=[3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428]}r.inherits(h,e),t.exports=h,h.blockSize=512,h.outSize=224,h.hmacStrength=192,h.padLength=64,h.prototype._digest=function(t){return"hex"===t?r.toHex32(this.h.slice(0,7),"big"):r.split32(this.h.slice(0,7),"big")}},function(t,n,i){"use strict";var r=i(0),e=i(6);function h(){if(!(this instanceof h))return new h;e.call(this),this.h=[3418070365,3238371032,1654270250,914150663,2438529370,812702999,355462360,4144912697,1731405415,4290775857,2394180231,1750603025,3675008525,1694076839,1203062813,3204075428]}r.inherits(h,e),t.exports=h,h.blockSize=1024,h.outSize=384,h.hmacStrength=192,h.padLength=128,h.prototype._digest=function(t){return"hex"===t?r.toHex32(this.h.slice(0,12),"big"):r.split32(this.h.slice(0,12),"big")}},function(t,n,i){"use strict";var r=i(0),e=i(2),h=r.rotl32,s=r.sum32,o=r.sum32_3,u=r.sum32_4,a=e.BlockHash;function c(){if(!(this instanceof c))return new c;a.call(this),this.h=[1732584193,4023233417,2562383102,271733878,3285377520],this.endian="little"}function f(t,n,i,r){return t<=15?n^i^r:t<=31?n&i|~n&r:t<=47?(n|~i)^r:t<=63?n&r|i&~r:n^(i|~r)}function l(t){return t<=15?0:t<=31?1518500249:t<=47?1859775393:t<=63?2400959708:2840853838}function p(t){return t<=15?1352829926:t<=31?1548603684:t<=47?1836072691:t<=63?2053994217:0}r.inherits(c,a),n.ripemd160=c,c.blockSize=512,c.outSize=160,c.hmacStrength=192,c.padLength=64,c.prototype._update=function(t,n){for(var i=this.h[0],r=this.h[1],e=this.h[2],a=this.h[3],c=this.h[4],m=i,y=r,b=e,S=a,k=c,x=0;x<80;x++){var z=s(h(u(i,f(x,r,e,a),t[g[x]+n],l(x)),_[x]),c);i=c,c=a,a=h(e,10),e=r,r=z,z=s(h(u(m,f(79-x,y,b,S),t[d[x]+n],p(x)),v[x]),k),m=k,k=S,S=h(b,10),b=y,y=z}z=o(this.h[1],e,S),this.h[1]=o(this.h[2],a,k),this.h[2]=o(this.h[3],c,m),this.h[3]=o(this.h[4],i,y),this.h[4]=o(this.h[0],r,b),this.h[0]=z},c.prototype._digest=function(t){return"hex"===t?r.toHex32(this.h,"little"):r.split32(this.h,"little")};var g=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13],d=[5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11],_=[11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6],v=[8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]},function(t,n,i){"use strict";var r=i(0),e=i(1);function h(t,n,i){if(!(this instanceof h))return new h(t,n,i);this.Hash=t,this.blockSize=t.blockSize/8,this.outSize=t.outSize/8,this.inner=null,this.outer=null,this._init(r.toArray(n,i))}t.exports=h,h.prototype._init=function(t){t.length>this.blockSize&&(t=(new this.Hash).update(t).digest()),e(t.length<=this.blockSize);for(var n=t.length;n<this.blockSize;n++)t.push(0);for(n=0;n<t.length;n++)t[n]^=54;for(this.inner=(new this.Hash).update(t),n=0;n<t.length;n++)t[n]^=106;this.outer=(new this.Hash).update(t)},h.prototype.update=function(t,n){return this.inner.update(t,n),this},h.prototype.digest=function(t){return this.outer.update(this.inner.digest()),this.outer.digest(t)}}]);
diff --git a/lib/Bitcoin/Hash.hs b/lib/Bitcoin/Hash.hs
new file mode 100644
--- /dev/null
+++ b/lib/Bitcoin/Hash.hs
@@ -0,0 +1,20 @@
+-- | Custom cryptographic hash constructions used by Bitcoin.
+module Bitcoin.Hash
+  ( -- * Bitcoin constructions
+    check32
+  , I.hash160
+  , I.hash256
+  ) where
+
+import qualified Data.ByteString as B
+import qualified Bitcoin.Hash.Internal as I
+
+--------------------------------------------------------------------------------
+
+-- | @'check32' == 'B.take' 4 . 'I.hash256'@
+check32
+  :: B.ByteString  -- ^ Data to hash.
+  -> B.ByteString  -- ^ 32-bit long digest.
+check32 = B.take 4 . I.hash256
+{-# INLINE check32 #-}
+
diff --git a/lib/Bitcoin/Hash/GHC.hs b/lib/Bitcoin/Hash/GHC.hs
new file mode 100644
--- /dev/null
+++ b/lib/Bitcoin/Hash/GHC.hs
@@ -0,0 +1,66 @@
+{-# LANGUAGE TypeApplications #-}
+{-# OPTIONS_HADDOCK not-home #-}
+
+-- | Implementation to be used when compiled with GHC
+module Bitcoin.Hash.GHC
+  ( hash160
+  , hash256
+  , ripemd160
+  , sha256
+  , hmacSHA512
+  ) where
+
+import qualified Crypto.Hash as Hash
+import qualified Crypto.MAC.HMAC as HMAC
+import qualified Data.ByteArray as BA
+import qualified Data.ByteString as B
+
+--------------------------------------------------------------------------------
+
+-- | @'hash160' == 'ripemd160' . 'sha256'@
+hash160
+  :: B.ByteString  -- ^ Data to hash.
+  -> B.ByteString  -- ^ 160-bit long digest.
+{-# INLINE hash160 #-}
+hash160 = BA.convert
+        . Hash.hash @(Hash.Digest Hash.SHA256) @Hash.RIPEMD160
+        . Hash.hash @B.ByteString @Hash.SHA256
+
+--------------------------------------------------------------------------------
+
+-- | @'hash256' == 'sha256' . 'sha256'@
+hash256
+  :: B.ByteString  -- ^ Data to hash.
+  -> B.ByteString  -- ^ 256-bit long digest.
+{-# INLINE hash256 #-}
+hash256 = BA.convert
+        . Hash.hash @(Hash.Digest Hash.SHA256) @Hash.SHA256
+        . Hash.hash @B.ByteString @Hash.SHA256
+
+--------------------------------------------------------------------------------
+
+ripemd160
+  :: B.ByteString  -- ^ Data to hash.
+  -> B.ByteString  -- ^ 20-byte long digest.
+{-# INLINE ripemd160 #-}
+ripemd160 = BA.convert
+          . Hash.hash @B.ByteString @Hash.RIPEMD160
+
+--------------------------------------------------------------------------------
+
+sha256
+  :: B.ByteString  -- ^ Data to hash.
+  -> B.ByteString  -- ^ 32-byte long digest.
+{-# INLINE sha256 #-}
+sha256 = BA.convert
+       . Hash.hash @B.ByteString @Hash.SHA256
+
+--------------------------------------------------------------------------------
+
+hmacSHA512
+  :: B.ByteString  -- ^ Key.
+  -> B.ByteString  -- ^ Data to hash.
+  -> B.ByteString  -- ^ 64-byte long digest.
+{-# INLINE hmacSHA512 #-}
+hmacSHA512 k d = BA.convert (HMAC.hmac k d :: HMAC.HMAC Hash.SHA512)
+
diff --git a/lib/Bitcoin/Hash/GHCJS.hs b/lib/Bitcoin/Hash/GHCJS.hs
new file mode 100644
--- /dev/null
+++ b/lib/Bitcoin/Hash/GHCJS.hs
@@ -0,0 +1,105 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE JavaScriptFFI #-}
+{-# OPTIONS_HADDOCK not-home #-}
+
+-- | Implementation to be used when compiled with GHCJS
+module Bitcoin.Hash.GHCJS
+  ( hash160
+  , hash256
+  , ripemd160
+  , sha256
+  , hmacSHA512
+  ) where
+
+import qualified Data.ByteString as B
+import qualified GHCJS.Buffer as Buf
+import qualified JavaScript.TypedArray.ArrayBuffer as AB
+import qualified JavaScript.TypedArray as TA
+
+--------------------------------------------------------------------------------
+
+-- | @'hash160' == 'ripemd160' . 'sha256'@
+hash160
+  :: B.ByteString  -- ^ Data to hash.
+  -> B.ByteString  -- ^ 160-bit long digest.
+{-# INLINE hash160 #-}
+hash160 = byteStringFromUint8Array
+        . js_ripemd160
+        . js_sha256
+        . byteStringToUint8Array
+
+--------------------------------------------------------------------------------
+
+-- | @'hash256' == 'sha256' . 'sha256'@
+hash256
+  :: B.ByteString  -- ^ Data to hash.
+  -> B.ByteString  -- ^ 256-bit long digest.
+{-# INLINE hash256 #-}
+hash256 = byteStringFromUint8Array
+        . js_sha256
+        . js_sha256
+        . byteStringToUint8Array
+
+--------------------------------------------------------------------------------
+
+-- | RIPEMD-160
+ripemd160
+  :: B.ByteString  -- ^ Data to hash.
+  -> B.ByteString  -- ^ 160-bits long digest.
+{-# INLINE ripemd160 #-}
+ripemd160 = byteStringFromUint8Array . js_ripemd160 . byteStringToUint8Array
+
+foreign import javascript unsafe "h$bitcoin_hash.ripemd160($1)"
+  js_ripemd160 :: TA.Uint8Array -> TA.Uint8Array
+
+--------------------------------------------------------------------------------
+
+-- | SHA256
+sha256
+  :: B.ByteString  -- ^ Data to hash.
+  -> B.ByteString  -- ^ 256-bits long digest.
+{-# INLINE sha256 #-}
+sha256 = byteStringFromUint8Array . js_sha256 . byteStringToUint8Array
+
+foreign import javascript unsafe "h$bitcoin_hash.sha256($1)"
+  js_sha256 :: TA.Uint8Array -> TA.Uint8Array
+
+--------------------------------------------------------------------------------
+
+-- | HMAC-SHA512
+hmacSHA512
+  :: B.ByteString  -- ^ Arbitrary length key.
+  -> B.ByteString  -- ^ Data to hash.
+  -> B.ByteString  -- ^ 64-byte long digest.
+{-# INLINE hmacSHA512 #-}
+hmacSHA512 k d =
+  byteStringFromUint8Array $ js_hmacSHA512 (byteStringToUint8Array k)
+                                           (byteStringToUint8Array d)
+
+foreign import javascript unsafe "h$bitcoin_hash.hmacSHA512($1, $2)"
+  js_hmacSHA512 :: TA.Uint8Array -> TA.Uint8Array -> TA.Uint8Array
+
+--------------------------------------------------------------------------------
+
+byteStringToUint8Array :: B.ByteString -> TA.Uint8Array
+{-# INLINE byteStringToUint8Array #-}
+byteStringToUint8Array = \x -> case B.length x of
+  0 -> js_emptyUint8Array
+  _ -> let (buf, off, len) = Buf.fromByteString x
+       in js_newUint8Array (Buf.getArrayBuffer buf) off len
+
+foreign import javascript unsafe "new Uint8Array($1, $2, $3)"
+  js_newUint8Array :: AB.ArrayBuffer -> Int -> Int -> TA.Uint8Array
+
+foreign import javascript unsafe "new Uint8Array(0)"
+  js_emptyUint8Array :: TA.Uint8Array
+
+--------------------------------------------------------------------------------
+
+byteStringFromUint8Array :: TA.Uint8Array -> B.ByteString
+{-# INLINE byteStringFromUint8Array #-}
+byteStringFromUint8Array = \x ->
+  Buf.toByteString (TA.byteOffset x)
+                   (Just (TA.byteLength x))
+                   (Buf.createFromArrayBuffer (TA.buffer x))
+
diff --git a/lib/Bitcoin/Hash/Internal.hs b/lib/Bitcoin/Hash/Internal.hs
new file mode 100644
--- /dev/null
+++ b/lib/Bitcoin/Hash/Internal.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE CPP #-}
+{-# OPTIONS_HADDOCK not-home #-}
+
+module Bitcoin.Hash.Internal
+  ( G.hash160
+  , G.hash256
+  , G.ripemd160
+  , G.sha256
+  , G.hmacSHA512
+  ) where
+
+#ifdef ghcjs_HOST_OS
+import Bitcoin.Hash.GHCJS as G
+#else
+import Bitcoin.Hash.GHC as G
+#endif
diff --git a/lib/Bitcoin/Hash/Prim.hs b/lib/Bitcoin/Hash/Prim.hs
new file mode 100644
--- /dev/null
+++ b/lib/Bitcoin/Hash/Prim.hs
@@ -0,0 +1,10 @@
+-- | Cryptographic hash primitives used by Bitcoin.
+module Bitcoin.Hash.Prim
+  ( -- * Hash
+    I.ripemd160
+  , I.sha256
+    -- * HMAC
+  , I.hmacSHA512
+  ) where
+
+import qualified Bitcoin.Hash.Internal as I
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,245 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main (main) where
+
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Builder as BB
+import qualified Data.ByteString.Lazy as BL
+import qualified Test.Tasty as Tasty
+import qualified Test.Tasty.Runners as Tasty
+import Test.Tasty (TestTree, testGroup)
+import Test.Tasty.HUnit (testCase, (@=?))
+
+-- We don't use the 'time' library expicitly, but if we don't import it
+-- GHCJS fails to run the tests because an undefined 'h$current_time'.
+import qualified Data.Time as Time ()
+
+import qualified Bitcoin.Hash as BH
+import qualified Bitcoin.Hash.Prim as BH
+
+--------------------------------------------------------------------------------
+
+main :: IO ()
+main = Tasty.defaultMainWithIngredients
+  [ Tasty.consoleTestReporter
+  , Tasty.listingTests
+  ] tt
+
+tt :: TestTree
+tt = testGroup "bitcoin-hash"
+  [ tt_ripemd160
+  , tt_sha256
+  , tt_hash160
+  , tt_hash256
+  , tt_check32
+  , tt_hmacSHA512
+  ]
+
+msg :: Int -> B.ByteString
+msg len = B.replicate len 0x27
+
+fh :: (B.ByteString -> B.ByteString) -> Int -> B.ByteString -> TestTree
+fh f len out = testCase (show len) $ out @=? toBase16 (f (msg len))
+
+tt_ripemd160 :: TestTree
+tt_ripemd160 = testGroup "ripemd160"
+  [ h    0 "9c1185a5c5e9fc54612808977ee8f548b2258d31"
+  , h    1 "e4ee5c96e6a059ff08d441d7667e7b3936ed8089"
+  , h    2 "327a5811faaf39601554f1e9fa75cefea719bf1b"
+  , h    8 "c9d553b3329ea7ade32c89ce2dbe5b5d3af2c02c"
+  , h   16 "d97869cab890738068f4759ff03056a323cf7da6"
+  , h   32 "47ba9ae144d982c09bb68046fd87731fbe4d050a"
+  , h   33 "a22128456832e574624248d2cfe9cc0e7081134e"
+  , h   64 "f3e39a2c916b85183405babf88ba19f28d46b296"
+  , h  128 "c5467ad5008bbdc3e27740f7b847aa427502d23e"
+  , h  256 "696f2fac6dc837936b3c07684c6fa557a75c8986"
+  , h  257 "c18821b44674454e716b2ca71945960862e11f07"
+  , h  512 "e6b39d5ac1be9462d17d0c2c329fca10207804c9"
+  , h  519 "c199d39af3ec736c347a72e6065a8479fac00050"
+  , h 1024 "07048736bb4be170aa405d7702f05951bf91cd38"
+  , h 1029 "be98cf421089c1581eca74ae3d8c351467754be7"
+  , h 2048 "aab6cf47c7e46d3fab7f797afdceca60123b1d8d"
+  , h 2055 "56eecf9bdae3a3a094884c6ad4bca1cdf7125600"
+  ]
+  where
+    h :: Int -> B.ByteString -> TestTree
+    h = fh BH.ripemd160
+
+tt_sha256 :: TestTree
+tt_sha256 = testGroup "sha256"
+  [ h    0 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+  , h    1 "265fda17a34611b1533d8a281ff680dc5791b0ce0a11c25b35e11c8e75685509"
+  , h    2 "6f49cdbd80e1b95d5e6427e1501fc217790daee87055fa5b4e71064288bddede"
+  , h    8 "93b50054bf138761edd58b09cf99bab521dbe00fa703bb055941df80c9a043cf"
+  , h   16 "e160ec46a8f3e2066cf88c72c7545812b2cc847e30bda671942cd7cdaa063cf7"
+  , h   32 "4ce65e91582a44819c8f17dfe5225e3b79269ec1ad92dfef3137b98a2bfff551"
+  , h   33 "8fd6eba95e8d1ba2c691d0f7ed8ff815d573c7aa28d3bde13a86558f6df348aa"
+  , h   64 "59af513c29d2fc3728126da90c84905c6ea451840a3099a3e3959e753b38a2fc"
+  , h  128 "32932efae95e19aab9862ce6dac38680a5e5e9fe949c7abfef3916fab53532b0"
+  , h  256 "18a8d14bb05697d9c779dd795992945f345b6e4f10ff713b989da307693dbd62"
+  , h  257 "6c19b0feb3ecdcefff3c3b1e9bf80437c76165a3bb4c143789288173060e6954"
+  , h  512 "746cba66a1c2e5ad7ef4fe64195b126df69fe8c0fdbf5a0d14487546f2d059c3"
+  , h  519 "2d86b63c625ffcfb417af2ee06e8d97acf820b9d151f1592deaccb06eafa347a"
+  , h 1024 "4f1f3e5c43da7c45cf4f2e7690fcd99dbb6a3794ee4ec4ec3604cfd930a23409"
+  , h 1029 "e8ac7ceee762a9fd98b37815797cbdf6eba215ad232aac025bb3d25a8e73828d"
+  , h 2048 "0265b8f82954fb56e5ea876c19c8bc5d289519fabb67682e9723cca0da72892f"
+  , h 2055 "babaa339695587c4eb34ff10077e9080e6feab85cc730ccf580431c9feb78130"
+  ]
+  where
+    h :: Int -> B.ByteString -> TestTree
+    h = fh BH.sha256
+
+tt_hash160 :: TestTree
+tt_hash160 = testGroup "hash160"
+  [ h    0 "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"
+  , h    1 "64f5b99153eb4a63d32416d4269f1c346d6e8ac4"
+  , h    2 "7b6f57c8b7d72d7bad3fb916b066519fb693efdb"
+  , h    8 "cd2f0c4d5fe5279327549883d95f8276a953ebc8"
+  , h   16 "c94dd81c7d9b3b08c81bf154a40a87fc9b0d4988"
+  , h   32 "a5fb1acecd47e7b3d31d2f45df590509d118ed51"
+  , h   33 "2032ad3938ef12ed1e92b9c9bf306c4ee72aa58f"
+  , h   64 "8e4def42bee7f44e6f344b45874c705f8ad26774"
+  , h  128 "c08c5dd5942ad358db28ae4872a0ab49676df8f1"
+  , h  160 "b9c16a0844be5f3b72ddfac7df514f1876bc4578"
+  , h  257 "8ac52cfe67ffc5db834fb075ab21433712386f54"
+  , h  160 "b9c16a0844be5f3b72ddfac7df514f1876bc4578"
+  , h  519 "35d768b1bd36543e7d3366e22098b3ef3fcc48da"
+  , h 1024 "8ac122cc8956b0b4ec4d6e204eb88e752ce735fc"
+  , h 1029 "577a6eff788bf44829e0aeeb02ffdc74e9cffb3d"
+  , h 2048 "8204ed56a4076e190b7affb27859de88d67c12e5"
+  , h 2055 "cddc7258d480b001aa11e1c07c9903a4298b2cab"
+  ]
+  where
+    h :: Int -> B.ByteString -> TestTree
+    h = fh BH.hash160
+
+tt_hash256 :: TestTree
+tt_hash256 = testGroup "hash256"
+  [ h    0 "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456"
+  , h    1 "e368b5f8bac32462da14cda3a2c944365cbf7f34a5db8aa4e9d85abc21cf8f8a"
+  , h    2 "4a9b8de6233de14e76d4146a43030920a0ce43302b0ef885c9b3982d51ff81e4"
+  , h    8 "7f3a9bc8e135e559f9c5a388cc3db5c017227984fe031177af25f719205c70b5"
+  , h   16 "cdc6f516ac0d520f9827dd29a1eba16846d5effb1c799a0a43aaa49e32de5631"
+  , h   32 "10f11f8f76f95726b51a69ad03e6ddc0ea3ffcd350d897040a9dc80cc87b5af4"
+  , h   33 "27bb0754c0ede411da787aef7b46e3499d17902c7d8c078dd0dbd083678b5828"
+  , h   64 "a8cac40994107d52033ed5a2e476318ea6919aad27cadb59666d15f33f5c6408"
+  , h  128 "baaf81d32565dcc44b027157f9dca8a2b07dbead96965de38d854da06e3ea07b"
+  , h  512 "60c5d80dd8300576bbab8ca8c8aae25bb4402bc0bae85ddb5f68b78f53d0871e"
+  , h  257 "13b859a80f7be290337b6068d65f8706efd855d5eb193178a15636b29728d696"
+  , h  512 "60c5d80dd8300576bbab8ca8c8aae25bb4402bc0bae85ddb5f68b78f53d0871e"
+  , h  519 "458c1a7847d44161bff4846acd78f6b44b489b517736f0ec5d4ad4b3b9a856d7"
+  , h 1024 "f34efcd0bd9f6e3f1119d38420b0f066c147463ebab34ae8fd3902aae1f369df"
+  , h 1029 "037147c77ee9e7b3b5bd4fd321749406e4e422cf26e206c7c0d9b2acec0b04a6"
+  , h 2048 "6533648135aeb3d636285d92fd0c9aaac0b67905a01e5e2b89b8601e67869b8c"
+  , h 2055 "f40a20f40caeffc575e63f5fc31b4beefeb83d7c3f09a7029e25996b92953dd0"
+  ]
+  where
+    h :: Int -> B.ByteString -> TestTree
+    h = fh BH.hash256
+
+tt_check32 :: TestTree
+tt_check32 = testGroup "check32"
+  [ h    0 "5df6e0e2"
+  , h    1 "e368b5f8"
+  , h    2 "4a9b8de6"
+  , h    8 "7f3a9bc8"
+  , h   16 "cdc6f516"
+  , h   32 "10f11f8f"
+  , h   33 "27bb0754"
+  , h   64 "a8cac409"
+  , h  128 "baaf81d3"
+  , h  512 "60c5d80d"
+  , h  257 "13b859a8"
+  , h  512 "60c5d80d"
+  , h  519 "458c1a78"
+  , h 1024 "f34efcd0"
+  , h 1029 "037147c7"
+  , h 2048 "65336481"
+  , h 2055 "f40a20f4"
+  ]
+  where
+    h :: Int -> B.ByteString -> TestTree
+    h = fh BH.check32
+
+tt_hmacSHA512 :: TestTree
+tt_hmacSHA512 = testGroup "hmacSHA512"
+  [ h 0      0 "b936cee86c9f87aa5d3c6f2e84cb5a4239a5fe50480a6ec66b70ab5b1f4ac6730c6c515421b327ec1d69402e53dfb49ad7381eb067b338fd7b0cb22247225d47"
+  , h 0      1 "3e6bb8a8d21abae18f5646d5d87a57962121e0a9b3edccdf09864925df56d728a29e550142f48d210a7e38f8737aedbaecfa843dfc8b562876710618912a7a8a"
+  , h 0      2 "87ac25bb3d233467ae449498712e80256a24117689c3178fce62b713bd3e5cfe65dcdbe2e79504c9780d8240ec24bf1cea427ef629359ad6aa150a7fb4b42243"
+  , h 0      8 "c9e3c0b53a8e0fe887d26e53ef0f222bd18d253cfe1830f791ab185bb6a7b7237562c54bbf2c282ce7a08b7dfd90eca87364a66e48002ca4d0183af7fcfd8f7b"
+  , h 0     16 "d6dfb859ae31641b08345990cc198400255a8eae50fec01d605188ceaba371755c8c77cd227f6b58e5d93057ad446d92de88035221a5ab14115a2840ddd8d219"
+  , h 0     32 "87b354778869a66c0eb2f245ff79cb24f991aebf7f6378ff3f6414d4a70b26d214d9d3a6abaf821c06787564938745a4ed4a5b882ed14bff10388cdfc35842ab"
+  , h 0     33 "99f23de02aeffc7f30d1b09bd42a3efa4c46dd84125f4b733da56cbaaea437e122f491ffe728491b3435a3f8ad70d1c262d5a8b5aad0d48758e789bae28debfa"
+  , h 0     64 "d188d6c6eb9e5803a5c2e3d6145c22b2535b16d24bff3158a76ac4d06ed0f1a49494f2463063ef38c1cc3c841ae85ba8ef9dd30ddcdd0129455813709b155edf"
+  , h 0    128 "687fd0bfe55617b0b58b2f932319cf5c1938612c13fe75499ac2b76f126a8ed06e3add557101a60d64a42d6166a0581907f3d9a881ec96dfc315cb30edb2d4d1"
+  , h 0    512 "cdc9855f3aa1915f96f8724342794df73abd9d23d7677befc006299b50a7af7ed5e48b1ded7795cd7cdeb99fb1a62a0891a9ca22d42fa6c374125b8c73348cd9"
+  , h 0    257 "b67d36bd251747fcf0b0765c307ee45fa2e0ecc615c0f857dc76b8d3a1991d5f57a2035c7b6134cc76af50db3e9f4f38fd16b4c86246ca6931b3a5655f36dcef"
+  , h 0    512 "cdc9855f3aa1915f96f8724342794df73abd9d23d7677befc006299b50a7af7ed5e48b1ded7795cd7cdeb99fb1a62a0891a9ca22d42fa6c374125b8c73348cd9"
+  , h 0    519 "c2dff40a5512833b01e4091e641c3d453e687f138b1ef520fe82f05851122617a6aae2948f92635139b8592ebad4f2cb9a38f34aaa6c8e102051cddbd86921bf"
+  , h 0   1024 "e5baff56465a850aa6d8421e3980c92967c7d40a28e25555d0f65710ca3e6ed8bb19e8452508304d48a2c6849e619c5604cac109ebb3d53bb4833b26254b142e"
+  , h 0   1029 "181a965c021c454eb2e93387903f743c0f6d8d45142c179529bfee13b5ccae71a41eb53c8c027e8f48e15ce3e25617660d68e07924af3a38592ff65d5a68b0f9"
+  , h 0   2048 "34e5a9698b83334e50ce0c9909b09db6387e983be2a3542ac34424ef7a1a7c06ecb235eb02d8a0aaf85dcda8b590c991a2d603a951ed96470f0da3196fbdeef2"
+  , h 0   2055 "d552ce5181075a22f63aa161ad9ce4583c66106fa81cf6a5954e2d03e34a5488d92fde43e06ca97d9db0acaa560a77e785610bf7950b6ea144bd79094d22f7f3"
+  , h 9      0 "6f7f51d7f2d33020b808c3e2766fd4a529f21541a18a58629cc04276485e500624d1c46d9541a33c45a865b1797e66f50f49fd0492a74d30302c1e7b01d6e78b"
+  , h 9      1 "3d673c1273b8c426cb4e48119161030ce7fadeaeaf2ce47fec8d3332b982094a349e7f175530597a4c37090c1bd97ae6b1a81224b72b56e7a7db4927b48c32ee"
+  , h 9      2 "a3ec5f33bebd3abe8829f579902a183b4193a4b50476ebb9305179b6b606f30cfb60a90f5a0935e4bc0b9efe24ce9c1f07efc8fd9002d439284565b955cef675"
+  , h 9      8 "a9ef661eb3497ccc3313cae92fb8eeb333023e783507d1d9540758b9fe06e066cacaf6a4ef4a10bb672ba729efc77d9e17050bccde9f6f44fa2c5bd6e4d4bbd4"
+  , h 9     16 "2717db66432712373cc0e01a126823582d790012fb97224c1325f6325d94955161a0d1cef086e84997482544100e192c99d420ecf382d3e045066f6400d60f97"
+  , h 9     32 "0b693970829e55517fabf65671dcfa22891b93db8416f816e7339ab648142d7769ff60b1519d09783b5c947f890e7ae47d44a8aa3d4e5b73ae63854e090ae1a2"
+  , h 9     33 "f07e3deb713e1cb5ee4173a6f1de95f68e86d6be12fb9cf81aa0ee4f5cf1e9eb50a9070b6ae57b7dd4cf538f53fad19fe05d5c4bfa6ae5fba4b4b77115658002"
+  , h 9     64 "5bda29660053e5dbfbff1ec36b9c0294de195cf7632a44fb9d5f0e49e00f1a80832285cfdfc6cab5487807ee87151bb10abff5e5fd1a40967d804b2c1b8353e0"
+  , h 9    128 "5cc7d80c552b0a348c5a82e1b3e3e65b5895882e025e79a99ceb0ed7949fad3d44e8ac4dac7eb0dcf552825ca9aabc9378d6b403bbdeb2325bf7ec00a044e860"
+  , h 9    512 "3781b3ffeba43195c3428a52572f4643e53d7e9375e4b9a9079d692acb05471cc0dae04a60a74ad3ddd7ceb7866644ff57a74ff5b57068f2cbd3f6bc9ca791d7"
+  , h 9    257 "96b9f9371d5cafd2044aa7268605d3e04db8c9e3e72391c80f649bbb84879a29238682520708ed06053efbff74fbe5c8b81fcc7bc2b24b3867915cfea627fed7"
+  , h 9    512 "3781b3ffeba43195c3428a52572f4643e53d7e9375e4b9a9079d692acb05471cc0dae04a60a74ad3ddd7ceb7866644ff57a74ff5b57068f2cbd3f6bc9ca791d7"
+  , h 9    519 "160ace1bc82c82fae5b1b8215dde77618f901fef61b709aa15d5231ddd91eac3472a53be6442837fe49831c92a8c26bc7fe952386e5c3b52b280643f10b9bdc1"
+  , h 9   1024 "3246fa08831a3aec3c2bacbc6c282f6004bc6dd3914862bdaad7eb0e64e704f6d7449c12b7c839f637830a866bacdc6fb91df725b887f18a5066175e12c3e085"
+  , h 9   1029 "ff0feeceb96b59fdb8af4fd67c82e8c2131a09d921c272b24e24005d80bc88f731308081c3a45e4492f3c4ada0905994b55343dd1fd3d1c46d12225aef734ba9"
+  , h 9   2048 "06c6eda47336e49d5b1e3c97a5fef99455489944516087b5f196fcb35410ce800a2b34b38a70dd2a8b9e8ef29822eadf37d9538d7541d976e5a099f2cf6aecc9"
+  , h 9   2055 "c60a8d5f1e48dbc24b36fa4b3a5123c9cdd1a2722ebead76815c75e6fde08047bcb11023b573ff83ec4b2f48ff99b09a728b0494d875036cfce7fedfb29110b3"
+  , h 128    0 "9dfb363b8d730179112f28091209f54ebf8a66e3da33735022a5b7494d5d3a86d12decc70a0098e2ef42e3f9f559031fd63135293a9e39e854a7c40e3e47bc00"
+  , h 128    1 "df0986089c4a8a3f9d2b4a9302acc5cf340f65998c4d3ee307665e42c8a428b95f63fd1af56b589561284daef3845d407013ed5d43201ffa5f5be466191e4798"
+  , h 128    2 "0c8e909c834da24e26dd943e2442bbead5310501288d0f17a4544d98ae9f7b8a45f232ed5dbd73696a093fca53ec3c6c6fb4bc903c259a19b1d20b51b7c108e7"
+  , h 128    8 "79712905e4ee972ddcf87f01da98dd97a0c4888ae069c635fc21033159d32170ef81f43a67863ba6e955fc071f75e902312718e1ba7248694d69b82044cbc62b"
+  , h 128   16 "f590da64d2e527443fef642fc122436346d5e8c94222241241c03a80b5b4b353f510bf451a1aa78a764e9f60dda0d8ce3f8950b46d55b3bc131aa9a34f6b6b47"
+  , h 128   32 "aa80eccc3f9095af38b7cf5e918a791fa4140daa9d2e360c076c414a2e45a5965883d987678a8d640f2f96724df194e4333b50c35bf2939eca34fe16b936c4d3"
+  , h 128   33 "d02c457f1e69c6d666a704ac9da3d6f66eaa12b0143e73b3aaadd8fc49cfabd12f2b157dbe778349898473611eb491ff194fd329fbe6ceef2b9f2b07e9c46b47"
+  , h 128   64 "8485a89797fc2e6dc7ed081a546266460d9d5e0b4e5d7accc9ec37b5265380d69b9270175958c666a50d3ed26612e2be67760d0778ad5e1f2e8736c6b626b929"
+  , h 128  128 "38e8988acceaaa6c55ab8530d67f2d3c78a2e025fa7181cef04e8571f0c6074a5c4b9c3eb492f2535fafad8cb9efc2ea72778a227988fb946f21a21807138974"
+  , h 128  512 "8e7a4c1b37b4d4734839047da81981635b86f75d4fae4ddbb17828db95b3aed15cef6f5dcee607c6adcc4ae33c463b040020448a31cf4a4a5edc9eb6e862ced9"
+  , h 128  257 "ec28dadc2d7268dae0eb10b95f223b69c17363a16c66f39aee82e5dab7add23c10078ea8cde9d6a7b9d783b2d5535abd1f327beb4efe36ef273952f7dfbe1b2e"
+  , h 128  512 "8e7a4c1b37b4d4734839047da81981635b86f75d4fae4ddbb17828db95b3aed15cef6f5dcee607c6adcc4ae33c463b040020448a31cf4a4a5edc9eb6e862ced9"
+  , h 128  519 "7b2ec73f5eaf45a924b71284372c80dd80d55af1790e8bbd927ea786b303f267cffe7bf2ebad5b946f9b5fd5db38d215212275eda37b55103134f05c2e3a9660"
+  , h 128 1024 "583760f7e25f55eb70c726788e01ac7c3935801c8030a76ec537f5ed5713717f2bfbc8bd5681fd316e7ce6cc933b37309352c4ad954bf4288edc552a4f0e4b9e"
+  , h 128 1029 "1afcf9bcb0ced816fd2349f5e2210fe82a2c229535faa72f80f72710a713adddaa569a9f12676f2a6f3b594d78629ce290c41a471bf7fcce802bda10cfacad09"
+  , h 128 2048 "ea7802e4883bf132bfe9bcfea71dc0c062c0e5e3227e9c1d5f4c571b3ae57b7246eee27f15ee1e938f8eec4f01d3416d43859fee97051ac89781d92de682e38c"
+  , h 128 2055 "dd40777cd3f188d92eff50b84bac51743d5c6c936cce818a75b2d3afa8333c662254b58285891f2aa52ac98c081eb72d40646acbbb1485e97961df9eba77eae6"
+  , h 333    0 "5a0587467c13160baf251b07f8204c05effcf994511967de8fd25a2885ab4c12eba333bdfd6588b40d0d2d381e1fbde86dbb08ad366b8e9bb34881967a27c64d"
+  , h 333    1 "a54e69dabe1925f8058d2f50fea6e1118b92a14313517b5428c144deed119086196ff7ecc183aaf6eb5033e6ed3eb2522d311bf193fe866a7078cbf8f2361813"
+  , h 333    2 "8183755464f02e6033be551700482d173827e87e2e4be0aa6b0a08b7632c0a1eb75323e36a679cc0ca86a62e58b3613cc904d77ee5c35934e03400402706b9ac"
+  , h 333    8 "991f92b8d05d1077be011d27a80c3113d0ffda137ee3056af67445f41395865760e91eebcb7e7ad5e3b81554e6133b0dd247f5700d2b5d2038d4bf4c3f04ffe5"
+  , h 333   16 "a59d4fc369e70a3c108d5a325b6f803f2a396692a28aec11b6d1908ebf8d102387df769687e97d58d83587ace176323e5029b1e0861f8d582f7bdb38264c9c1a"
+  , h 333   32 "ceeafbbf65152805680fdca1759899a56d0e9d9cd54b631d765c56547278d4e665ae200c3d08eb5e89bcacd989f17dbf27cbcb482c9a364351272eb2b5b2fb53"
+  , h 333   33 "ec1e70824fca85e798dfb4b1ce804428db8d555b8c289d5b37c4c1c6a087bd34851c9148d2cd544311aca6f5a8374dc3193011f3265de7aeded06f526278a16d"
+  , h 333   64 "3bc90c0f0ab7a911f49d2905d24b3fb37da23c2b3c3bdf9e930c77fa783e0293848a5e8784247548a9216e35d35991ad8ec4c007d27872df14b6091cb5b3d4fe"
+  , h 333  128 "5975ce0895b6af8f8cc918ec75d070fc6363100e41cd08768ded0a8237b64081fa4f18120f2baacd3b458e22ee3ef03d9be707561830b0f4e72fce4f08e37959"
+  , h 333  512 "ff2367fa11dc37194b1c4c8954dc48506d781081c5b0dd541391e3b85fb837c83b978f88ec600635eaa0453227b6d5b9ac19d0c12ae23339fdf49b1b68986707"
+  , h 333  257 "768f5dc2eaf82f55796a2b6276c5c2dfbcc5d23fb88b1673ff6431bdd7cd8c95670d22467f785054e1c5b1490de0256f91e4f0da4cd6d2fe5be7eb9c37645bff"
+  , h 333  512 "ff2367fa11dc37194b1c4c8954dc48506d781081c5b0dd541391e3b85fb837c83b978f88ec600635eaa0453227b6d5b9ac19d0c12ae23339fdf49b1b68986707"
+  , h 333  519 "9c5b3a7c6c86df637ae741c4fdf87a3e6b9bc9d930ad89e50cfb7e314fe69bb67a76a84a9372e74eb330436d5fcb1f028a6e0b69f2336ab65dbbcfec7c9470c3"
+  , h 333 1024 "03155b65dfd55c24d8583fbff21564db15372f99fe134872e09977657dc4d3a42921b596b0ea545ae8be6f75b40eefd22405ce49014c27e2b032dfaf545d111a"
+  , h 333 1029 "dd18cc2e732b6ff27fb154dc92a8d4c73bb2184d1e4e71aa87a8412aa4ecafe8cf37014ad0186a08d0a10145e4d99da51361ec20b427c2169c9ddbd4d2f38737"
+  , h 333 2048 "2a843042c773dbd4d4aee3bd5108f9e0077d71fc97c575309be804f3931886c955c60261165e9800cb8c1ce9ad6e2a37f316186c603d95253c5dc97f6f8a9d8d"
+  , h 333 2055 "41061265f084c010340eea293f58e5ba331da17b84918fdb25ffd3c0a01ea8d7e403a4f45df43742a47d4bccb0311df4e05cd039756546ac33bf66b8e0017457"
+  ]
+  where
+    h :: Int -> Int -> B.ByteString -> TestTree
+    h klen dlen out =
+      testCase ("key len " <> show klen <> ", msg len " <> show dlen) $
+        out @=? toBase16 (BH.hmacSHA512 (msg klen) (msg dlen))
+
+--------------------------------------------------------------------------------
+
+toBase16 :: B.ByteString -> B.ByteString
+toBase16 = BL.toStrict . BB.toLazyByteString . BB.byteStringHex
+
