From 827102212c4403e5c454b77bc44b40310f23fa34 Mon Sep 17 00:00:00 2001
From: projectuser <projectuser@leon056.com>
Date: 星期一, 08 七月 2019 17:39:18 +0800
Subject: [PATCH] 类库新建

---
 src/modules/Crypto/utility/ProgMemUtil.h                              |   52 
 src/modules/Crypto/AES128.cpp                                         |  356 
 src/modules/Crypto/examples/TestGCM/TestGCM.ino                       |  584 +
 src/modules/Crypto/ChaCha.h                                           |   64 
 src/modules/Crypto/examples/TestSHAKE128/TestSHAKE128.ino             |  369 
 src/modules/Crypto/Ed25519.h                                          |   71 
 src/modules/Crypto/examples/TestRNG/TestRNG.ino                       |   72 
 src/modules/Crypto/GCM.h                                              |   80 
 src/modules/Crypto/examples/TestEAX/TestEAX.ino                       |  558 +
 src/modules/Crypto/BLAKE2b.h                                          |   61 
 src/modules/Crypto/BigNumberUtil.cpp                                  |  769 +
 src/modules/Crypto/examples/TestPoly1305/TestPoly1305.ino             |  238 
 src/modules/Crypto/Crypto.cpp                                         |  114 
 src/modules/Crypto/BigNumberUtil.h                                    |  110 
 src/modules/Crypto/OMAC.h                                             |   50 
 src/modules/Crypto/SHA512.h                                           |   62 
 src/modules/Crypto/SHA512.cpp                                         |  274 
 src/modules/Crypto/AESCommon.cpp                                      |  363 
 src/modules/Crypto/examples/TestAESTiny/TestAESTiny.ino               |  150 
 src/modules/Crypto/AES256.cpp                                         |  401 
 src/modules/Crypto/SHAKE.cpp                                          |  137 
 src/modules/Crypto/Hash.h                                             |   51 
 src/modules/Crypto/SHAKE.h                                            |   66 
 src/modules/Crypto/OMAC.cpp                                           |  200 
 src/modules/Crypto/BLAKE2s.h                                          |   60 
 src/modules/Crypto/utility/EndianUtil.h                               |   77 
 library.properties                                                    |    9 
 src/modules/Crypto/examples/TestChaChaPoly/TestChaChaPoly.ino         |  452 
 src/modules/Crypto/examples/TestXTS/TestXTS.ino                       |  480 +
 src/modules/Crypto/CTR.h                                              |   69 
 src/modules/Crypto/examples/TestEd25519/TestEd25519.ino               |  185 
 src/modules/Crypto/Curve25519.cpp                                     | 1610 +++
 src/modules/Crypto/examples/TestGHASH/TestGHASH.ino                   |  245 
 src/modules/Crypto/examples/TestCurve25519/TestCurve25519.ino         |  221 
 examples/ESP8266/ESP8266.ino                                          |   56 
 src/modules/Crypto/AES192.cpp                                         |  102 
 src/modules/Crypto/Poly1305.cpp                                       |  345 
 src/modules/Crypto/XOF.h                                              |   51 
 src/modules/Crypto/ChaChaPoly.cpp                                     |  170 
 src/modules/Crypto/Hash.cpp                                           |  180 
 src/modules/Crypto/XOF.cpp                                            |  216 
 src/modules/Crypto/Cipher.cpp                                         |  154 
 src/modules/Crypto/examples/TestSHA512/TestSHA512.ino                 |  266 
 src/modules/Crypto/CTR.cpp                                            |  220 
 src/modules/Crypto/P521.h                                             |  112 
 src/modules/Crypto/BlockCipher.cpp                                    |  124 
 src/modules/Crypto/examples/TestSHAKE256/TestSHAKE256.ino             |  365 
 src/aliIotLink.h                                                      |   77 
 src/modules/Crypto/examples/TestCTR/TestCTR.ino                       |  231 
 src/modules/Crypto/ChaCha.cpp                                         |  281 
 src/modules/Crypto/utility/RotateUtil.h                               |  696 +
 src/modules/Crypto/AES.h                                              |  256 
 src/modules/Crypto/ChaChaPoly.h                                       |   65 
 src/modules/Crypto/AuthenticatedCipher.cpp                            |  130 
 src/modules/Crypto/GF128.h                                            |   42 
 src/modules/mqtt/PubSubClient.cpp                                     |  607 +
 src/modules/Crypto/examples/TestP521Math/TestP521Math.ino             |  612 +
 src/modules/Crypto/SHA256.cpp                                         |  258 
 src/modules/Crypto/RNG.h                                              |   74 
 src/modules/Crypto/BLAKE2b.cpp                                        |  330 
 src/modules/Crypto/XTS.cpp                                            |  437 
 src/modules/Crypto/examples/TestP521/TestP521.ino                     |  523 +
 src/modules/Crypto/examples/TestBLAKE2s/TestBLAKE2s.ino               |  429 
 src/modules/Crypto/examples/TestSHA3_256/TestSHA3_256.ino             |  318 
 src/modules/Crypto/library.json                                       |   22 
 README.md                                                             |    7 
 src/modules/Crypto/examples/TestAES/TestAES.ino                       |  175 
 src/modules/Crypto/RNG.cpp                                            | 1004 ++
 src/modules/Crypto/SHA3.h                                             |   73 
 examples/w5500/w5500.ino                                              |   54 
 src/modules/Crypto/utility/LimbUtil.h                                 |   70 
 src/modules/Crypto/GHASH.cpp                                          |  153 
 src/modules/Crypto/GHASH.h                                            |   51 
 src/modules/Crypto/examples/TestChaCha/TestChaCha.ino                 |  422 
 src/modules/Crypto/KeccakCore.cpp                                     | 2013 ++++
 src/modules/Crypto/examples/TestBLAKE2b/TestBLAKE2b.ino               |  410 
 src/modules/Crypto/examples/TestCurve25519Math/TestCurve25519Math.ino |  683 +
 src/modules/Crypto/GCM.cpp                                            |  335 
 src/modules/Crypto/examples/TestBigNumberUtil/TestBigNumberUtil.ino   |  378 
 src/modules/Crypto/NoiseSource.cpp                                    |  120 
 src/modules/Crypto/keywords.txt                                       |   71 
 src/modules/Crypto/P521.cpp                                           | 1642 +++
 src/modules/Crypto/examples/TestSHA256/TestSHA256.ino                 |  323 
 src/modules/Crypto/SHA256.h                                           |   57 
 src/modules/Crypto/AESEsp32.cpp                                       |   88 
 src/modules/Crypto/examples/TestSHA3_512/TestSHA3_512.ino             |  318 
 src/modules/Crypto/BLAKE2s.cpp                                        |  322 
 src/modules/Crypto/Cipher.h                                           |   47 
 src/modules/Crypto/EAX.h                                              |   85 
 src/modules/Crypto/SHA3.cpp                                           |  168 
 src/modules/Crypto/XTS.h                                              |  101 
 src/modules/Crypto/Crypto.h                                           |   46 
 src/modules/Crypto/Ed25519.cpp                                        |  642 +
 src/modules/Crypto/GF128.cpp                                          |  571 +
 src/modules/Crypto/KeccakCore.h                                       |   63 
 src/modules/Crypto/Poly1305.h                                         |   54 
 src/modules/Crypto/AuthenticatedCipher.h                              |   42 
 src/modules/mqtt/PubSubClient.h                                       |  145 
 src/modules/Crypto/BlockCipher.h                                      |   46 
 src/modules/Crypto/examples/TestAESSmall/TestAESSmall.ino             |  170 
 src/aliIotLink.cpp                                                    |  215 
 src/modules/Crypto/EAX.cpp                                            |  293 
 src/modules/Crypto/NoiseSource.h                                      |   44 
 src/modules/Crypto/Curve25519.h                                       |   77 
 104 files changed, 27,984 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 6fa5697..d1af0ea 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-## aliIotLink
-
-连接阿里MQTT服务的通用类库
+## 阿里云物联网平台的 arduino连接工具包
+* 目前实现了订阅消息与平台推送消息的回调
+* 只能发布QoS 0消息,可以订阅QoS 0或QoS 1
+* 目前已支持 arduino UNO  ,MEGA2560 (基于W5100,W5500),esp8266,esp8285,esp32 (wifi)
 
diff --git a/examples/ESP8266/ESP8266.ino b/examples/ESP8266/ESP8266.ino
new file mode 100644
index 0000000..4e187b6
--- /dev/null
+++ b/examples/ESP8266/ESP8266.ino
@@ -0,0 +1,56 @@
+#include <aliIotLink.h>
+#include <ESP8266WiFi.h>
+
+const char* ssid = "WIFI-SSID";
+const char* password = "12345678";
+
+static const char ProductKey[] PROGMEM = "a1BllGqYfBO";
+static const char DeviceName[] PROGMEM = "QNfNlOW1l9uTY2V0Dw2E";  
+static const char DeviceSecret[] PROGMEM = "jIM4j0tphyiXhx4yVzuv2BcF1bz916Oo";
+
+static const char postTopic[]  PROGMEM = "";   //上报消息topic
+static const char setTopic[]  PROGMEM = "";    //服务器消息topic
+
+WiFiClient espClient;   //实例化 wifi网络
+PubSubClient client(espClient); //将网络传入MQTT
+AliIotLink aliLink(client);  //将mqtt传入服务
+
+//回调函数
+void Callbacks(char* topic, byte* payload, unsigned int length)
+{
+	//回调功能演示,将监听到的消息直接打印到串口
+  Serial.print(topic);
+  Serial.print(":");
+  for (int i = 0; i < length; i++)
+  {
+    Serial.print((char)payload[i]);
+  }
+  Serial.println();
+}
+
+void setup()
+{
+
+  Serial.begin(115200);
+  WiFi.mode(WIFI_STA);   //配置为客户端模式
+  WiFi.begin(ssid, password);  //初始化并且链接wifi
+  
+  while (WiFi.status() != WL_CONNECTED)  //等待wifi连接
+  {  
+    delay(500);
+    Serial.print(".");
+  }
+
+  aliLink.subTopic(FPSTR(setTopic));   //订阅服务器下行消息
+
+  aliLink.setCallback(Callbacks);   //注册下发消息回调函数
+  aliLink.begin(FPSTR(DeviceName),FPSTR(ProductKey),FPSTR(DeviceSecret));  //完成初始化配置 三元素(DeviceName,ProductKey,DeviceSecret)
+}
+void loop()
+{
+  if (aliLink.state() != 0) // 显示连接错误码,实际项目不需要
+  {
+    Serial.println(aliLink.state());
+  }
+  aliLink.loop();  //循环维持心跳与消息触发,应尽可能多的执行
+}
diff --git a/examples/w5500/w5500.ino b/examples/w5500/w5500.ino
new file mode 100644
index 0000000..00c6f23
--- /dev/null
+++ b/examples/w5500/w5500.ino
@@ -0,0 +1,54 @@
+#include <aliIotLink.h>
+#include <SPI.h>
+#include <Ethernet2.h>
+//W5500 链接阿里云
+
+byte mac[] = {0x00, 0xED, 0xBA, 0xFE, 0xFE, 0xED };  //MAC地址
+
+
+static const char ProductKey[] PROGMEM = "a1BllGqYfBO";
+static const char DeviceName[] PROGMEM = "QNfNlOW1l9uTY2V0Dw2E";  
+static const char DeviceSecret[] PROGMEM = "jIM4j0tphyiXhx4yVzuv2BcF1bz916Oo";
+
+static const char postTopic[]  PROGMEM = "";   //上报消息topic
+static const char setTopic[]  PROGMEM = "";    //服务器消息topic
+
+EthernetClient ethClient;  //实例化以太网
+PubSubClient client(ethClient); //将网络传入MQTT
+AliIotLink aliLink(client);   //将mqtt传入服务
+
+
+//回调函数
+void Callbacks(char* topic, byte* payload, unsigned int length)
+{
+	//回调功能演示,将监听到的消息直接打印到串口
+  Serial.print(topic);
+  Serial.print(":");
+  for (int i = 0; i < length; i++)
+  {
+    Serial.print((char)payload[i]);
+  }
+  Serial.println();
+}
+
+void setup() 
+{
+  Serial.begin(115200);
+  Ethernet.begin(mac);  //初始化以太网
+
+  
+  aliLink.subTopic(FPSTR(setTopic));   //订阅服务器下行消息
+  aliLink.setCallback(Callbacks);   //注册下发消息回调函数
+  aliLink.begin(FPSTR(DeviceName),FPSTR(ProductKey),FPSTR(DeviceSecret));  //完成初始化配置 三元素(DeviceName,ProductKey,DeviceSecret)
+}
+void loop() 
+{
+  
+  if (aliLink.state() != 0) // 显示连接错误码,实际项目不需要
+  {
+    Serial.println(aliLink.state());
+  }
+  aliLink.loop();  //循环维持心跳与消息触发,应尽可能多的执行
+  
+}
+
diff --git a/library.properties b/library.properties
new file mode 100644
index 0000000..7cab6ef
--- /dev/null
+++ b/library.properties
@@ -0,0 +1,9 @@
+name=aliIotLink
+version=0.0.1
+author=MiNiQiang
+maintainer=MiNiQiang
+sentence=
+paragraph=Support Arduino, ESP8266, ESP8285, ESP32.
+category=Communication
+url=https://github.com/MINI-Qiang/aliIotLink
+architectures=*
diff --git a/src/aliIotLink.cpp b/src/aliIotLink.cpp
new file mode 100644
index 0000000..2e730af
--- /dev/null
+++ b/src/aliIotLink.cpp
@@ -0,0 +1,215 @@
+#include "aliIotLink.h"
+
+ 
+SHA256 sha256;
+
+AliIotLink::AliIotLink(PubSubClient& client)
+{
+	_client = &client;	
+}
+void AliIotLink::begin()
+{
+  //randomSeed(analogRead(A0));   //获取IO随机值【非安全的伪随机】作为种子
+  //delayMicroseconds(random(0,10000));   //延迟随机时间
+  
+  generateClientId();
+  readUrl();
+  readUser();
+  readPasswd();
+  _client->setServer(_URL.c_str(),_port); //写入服务器名称与端口号
+  
+}
+
+
+void AliIotLink::begin(String __DeviceName,String __ProductKey,String __DeviceSecret)
+{
+	_Id = __DeviceName;
+	_DeviceName = __DeviceName;
+	_ProductKey = __ProductKey;
+	_DeviceSecret = __DeviceSecret;
+	//randomSeed(analogRead(A0));
+    //delayMicroseconds(random(0,10000));
+	generateClientId();
+	readUrl();
+	readUser();
+	readPasswd();
+	_client->setServer(_URL.c_str(),_port);  //写入服务器名称与端口号
+}
+
+void AliIotLink::writeUrl(String __ServerUrl)    // 设置登录网址
+{
+	//_ServerUrl = __ServerUrl;
+}
+//服务器端口设置
+void AliIotLink::writePort(uint16_t __port)
+{
+	_port = __port;
+}
+//硬件设备名称
+void AliIotLink::writeID(String __Id)
+{
+	_Id = __Id;
+}
+// 设置设备硬件标签名称【阿里云提供】
+void AliIotLink::writeDeviceName(String __DeviceName)  
+{
+	_DeviceName = __DeviceName;
+}
+//【阿里云提供】
+void AliIotLink::writeProductKey(String __ProductKey)
+{
+	_ProductKey = __ProductKey;
+}
+//【阿里云提供】
+void AliIotLink::writeDeviceSecret(String __DeviceSecret)
+{
+	_DeviceSecret = __DeviceSecret;
+}
+
+void AliIotLink::generateClientId()
+{
+	//ID+参数+随机数+结尾符号
+	randomSeed(micros());
+	Times  =random(0, 65535);
+	_ClientId = _Id + FPSTR(_ClientIdSuffix) + Times +"|";
+}
+
+//获取合成好的登录用的ClientId
+String AliIotLink::readClientId()
+{
+	return _ClientId;
+}
+
+
+//合成访问网址
+ String AliIotLink::readUrl()
+ {
+	 _URL = _ProductKey + F(".") + FPSTR(_ServerUrl);
+	 return _URL;
+ }
+ 
+ //合成访问用户名
+ String AliIotLink::readUser()
+ {
+	 _Username = _DeviceName + "&" + _ProductKey ;
+	 return _Username;
+ }
+ 
+ 
+ //合成访问密码
+ String AliIotLink::readPasswd()
+ {
+	 String _Passwd =  "clientId" + _Id + "deviceName" + _DeviceName +"productKey"+_ProductKey + "timestamp" + Times;
+	 _PasswdHash = "";
+	 
+	 byte hashData[64];
+	 
+	 HMAC(_DeviceSecret.c_str(), 32, _Passwd.c_str(), _Passwd.length(), hashData);
+
+	 for (int i = 0; i < 32; i++)  //hash 固定长度
+	 {
+		_PasswdHash = _PasswdHash + "0123456789ABCDEF"[hashData[i] >> 4];
+	 	_PasswdHash = _PasswdHash + "0123456789ABCDEF"[hashData[i] & 0xf];
+	 }
+
+	 //
+	 return _PasswdHash;  
+	 //返回HASH密文
+ }
+ //===================mqtt部分=====================
+ 
+ bool AliIotLink::connect()
+ {
+
+
+	 return _client->connect(_ClientId.c_str(),_Username.c_str(),_PasswdHash.c_str());   //像服务器传递 ClientId,用户名,密码
+
+	 
+ }
+ 
+ //重连
+ void AliIotLink::reconnect()
+ {
+
+   while(!_client->connected()) //检查网络网络是否不正常,断开的网络会锁定重试
+   {
+
+	  if (connect())   //重连并判断是否成功
+	  {
+
+		  //成功
+		  for(byte a = 0;a<TopicNum;a++)
+		  {
+			  subscribe(TopicName[a].c_str());
+		  }
+		  
+		  //填写上线订阅,应该是个String数组传入循环订阅处理
+	  } 
+	else 
+		{
+			//链接失败
+
+		   delay(5000);   //暂停5秒钟重试
+		}
+  }
+ }
+ 
+ 
+ //循环检测
+ void AliIotLink::loop()
+ {
+   if (!_client->connected()) 
+   {
+    reconnect();
+   }
+	_client->loop();
+}
+ 
+ 
+int AliIotLink::state()    //错误消息返回
+{
+	return _client->state();
+}
+
+
+
+//回调函数
+void AliIotLink::setCallback(MQTT_CALLBACK_SIGNATURE)
+{
+	_client->setCallback(callback);
+	//SerialUSB.println(callback);
+}
+//监听Topic
+void AliIotLink::subscribe(const char* topic)
+{
+	_client->subscribe(topic);
+}
+//推送消息
+void AliIotLink::publish(const char* topic, const char* payload)
+{
+	_client->publish(topic,payload);
+}
+
+
+//登记topic
+void AliIotLink::subTopic(String topic)
+{
+	TopicName[TopicNum] = topic;
+	TopicNum++;
+	if(TopicNum > MQTT_Topic_Quantity)
+	{
+		TopicNum = MQTT_Topic_Quantity;
+	}
+}
+
+void AliIotLink::HMAC( char const *key, byte KeySize,  char const *data, byte dataSize, byte *hashData)
+{
+  uint8_t result[32];
+  sha256.resetHMAC(key, KeySize);  //传入KEY,和key长度
+  sha256.update(data, dataSize);// 传输 hash明文
+  sha256.finalizeHMAC(key, KeySize, result, sizeof(result)); // 计算
+  for (byte a = 0; a < sizeof(result); a++) //传出签名
+  {
+    *(hashData + a) = result[a];
+  }
+}
diff --git a/src/aliIotLink.h b/src/aliIotLink.h
new file mode 100644
index 0000000..769ec58
--- /dev/null
+++ b/src/aliIotLink.h
@@ -0,0 +1,77 @@
+#ifndef aliIotLink_H
+#define aliIotLink_H
+
+#include <Arduino.h>
+
+#include "modules/mqtt/PubSubClient.h"
+#include "modules/Crypto/SHA256.h"
+#include "modules/Crypto/Crypto.h"
+
+#define MQTT_Topic_Quantity  2
+
+static const char _ServerUrl[] PROGMEM = "iot-as-mqtt.cn-shanghai.aliyuncs.com";
+static const char _ClientIdSuffix[] PROGMEM = "|securemode=3,signmethod=hmacsha256,timestamp=";
+
+class AliIotLink
+{
+	public:
+		AliIotLink(PubSubClient& client);
+		void begin();
+		void begin(String __DeviceName,String __ProductKey,String __DeviceSecret);
+		void writeUrl(String __ServerUrl);    // 设置登录网址
+		void writePort(uint16_t __port);   //服务器端口
+		void writeID(String __Id);    //设备ID
+		void writeDeviceName(String __DeviceName);  // 设置设备名称
+		void writeProductKey(String __ProductKey);
+		void writeDeviceSecret(String __DeviceSecret);
+		
+		void  generateClientId();  //获取登录的设备ID
+
+		
+		
+		bool connect();   //链接服务器
+		void subTopic(String topic);    //配置订阅topic
+		void publish(const char* topic, const char * payload);  //推送消息
+		void setCallback(MQTT_CALLBACK_SIGNATURE);//注册回调函数
+		void reconnect();
+		void loop();  //心跳维持与重连
+	    int state();   //错误消息返回
+		
+	private:
+		//私有函数
+		
+		void HMAC(char const *key, byte KeySize, char const *data, byte dataSize, byte *hashData);
+
+		void subscribe(const char* topic);  //监听Topic
+		String readClientId();  //获取登录的设备ID
+		String readUrl();  
+		String readUser();
+		String readPasswd();
+		//登录用四元素
+		
+		//私有对象
+		PubSubClient* _client;    //mqtt接口
+		//私有变量
+		String TopicName[MQTT_Topic_Quantity];
+		//String _ServerUrl = "iot-as-mqtt.cn-shanghai.aliyuncs.com";
+		String _Id;
+		//String _ClientIdSuffix = "|securemode=3,signmethod=hmacsha256,timestamp=";
+		
+		uint16_t _port = 1883;
+		uint16_t Times;
+		uint8_t TopicNum =0;   //topic计数器
+		//
+		String _URL;
+		String _ClientId;
+		String _Username;
+		String _PasswdHash;
+		//三元素
+		String _DeviceName;	
+		String _ProductKey;
+		String _DeviceSecret;
+};
+
+
+
+
+#endif
diff --git a/src/modules/Crypto/AES.h b/src/modules/Crypto/AES.h
new file mode 100644
index 0000000..63adc6d
--- /dev/null
+++ b/src/modules/Crypto/AES.h
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2015,2018 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_AES_h
+#define CRYPTO_AES_h
+
+#include "BlockCipher.h"
+
+// Determine which AES implementation to export to applications.
+#if defined(ESP32)
+#define CRYPTO_AES_ESP32 1
+#else
+#define CRYPTO_AES_DEFAULT 1
+#endif
+
+#if defined(CRYPTO_AES_DEFAULT) || defined(CRYPTO_DOC)
+
+class AESTiny128;
+class AESTiny256;
+class AESSmall128;
+class AESSmall256;
+
+class AESCommon : public BlockCipher
+{
+public:
+    virtual ~AESCommon();
+
+    size_t blockSize() const;
+
+    void encryptBlock(uint8_t *output, const uint8_t *input);
+    void decryptBlock(uint8_t *output, const uint8_t *input);
+
+    void clear();
+
+protected:
+    AESCommon();
+
+    /** @cond aes_internal */
+    uint8_t rounds;
+    uint8_t *schedule;
+
+    static void subBytesAndShiftRows(uint8_t *output, const uint8_t *input);
+    static void inverseShiftRowsAndSubBytes(uint8_t *output, const uint8_t *input);
+    static void mixColumn(uint8_t *output, uint8_t *input);
+    static void inverseMixColumn(uint8_t *output, const uint8_t *input);
+    static void keyScheduleCore(uint8_t *output, const uint8_t *input, uint8_t iteration);
+    static void applySbox(uint8_t *output, const uint8_t *input);
+    /** @endcond */
+
+    friend class AESTiny128;
+    friend class AESTiny256;
+    friend class AESSmall128;
+    friend class AESSmall256;
+};
+
+class AES128 : public AESCommon
+{
+public:
+    AES128();
+    virtual ~AES128();
+
+    size_t keySize() const;
+
+    bool setKey(const uint8_t *key, size_t len);
+
+private:
+    uint8_t sched[176];
+};
+
+class AES192 : public AESCommon
+{
+public:
+    AES192();
+    virtual ~AES192();
+
+    size_t keySize() const;
+
+    bool setKey(const uint8_t *key, size_t len);
+
+private:
+    uint8_t sched[208];
+};
+
+class AES256 : public AESCommon
+{
+public:
+    AES256();
+    virtual ~AES256();
+
+    size_t keySize() const;
+
+    bool setKey(const uint8_t *key, size_t len);
+
+private:
+    uint8_t sched[240];
+};
+
+class AESTiny256 : public BlockCipher
+{
+public:
+    AESTiny256();
+    virtual ~AESTiny256();
+
+    size_t blockSize() const;
+    size_t keySize() const;
+
+    bool setKey(const uint8_t *key, size_t len);
+
+    void encryptBlock(uint8_t *output, const uint8_t *input);
+    void decryptBlock(uint8_t *output, const uint8_t *input);
+
+    void clear();
+
+private:
+    uint8_t schedule[32];
+};
+
+class AESSmall256 : public AESTiny256
+{
+public:
+    AESSmall256();
+    virtual ~AESSmall256();
+
+    bool setKey(const uint8_t *key, size_t len);
+
+    void decryptBlock(uint8_t *output, const uint8_t *input);
+
+    void clear();
+
+private:
+    uint8_t reverse[32];
+};
+
+class AESTiny128 : public BlockCipher
+{
+public:
+    AESTiny128();
+    virtual ~AESTiny128();
+
+    size_t blockSize() const;
+    size_t keySize() const;
+
+    bool setKey(const uint8_t *key, size_t len);
+
+    void encryptBlock(uint8_t *output, const uint8_t *input);
+    void decryptBlock(uint8_t *output, const uint8_t *input);
+
+    void clear();
+
+private:
+    uint8_t schedule[16];
+};
+
+class AESSmall128 : public AESTiny128
+{
+public:
+    AESSmall128();
+    virtual ~AESSmall128();
+
+    bool setKey(const uint8_t *key, size_t len);
+
+    void decryptBlock(uint8_t *output, const uint8_t *input);
+
+    void clear();
+
+private:
+    uint8_t reverse[16];
+};
+
+#endif // CRYPTO_AES_DEFAULT
+
+#if defined(CRYPTO_AES_ESP32)
+
+// "hwcrypto/aes.h" includes "rom/aes.h" which defines global enums for
+// AES128, AES192, and AES256.  The enum definitions interfere with the
+// definition of the same-named classes below.  The #define's and #undef's
+// here work around the problem by defining the enums to different names.
+#define AES128 AES128_enum
+#define AES192 AES192_enum
+#define AES256 AES256_enum
+#include "hwcrypto/aes.h"
+#undef AES128
+#undef AES192
+#undef AES256
+
+class AESCommon : public BlockCipher
+{
+public:
+    virtual ~AESCommon();
+
+    size_t blockSize() const;
+    size_t keySize() const;
+
+    bool setKey(const uint8_t *key, size_t len);
+
+    void encryptBlock(uint8_t *output, const uint8_t *input);
+    void decryptBlock(uint8_t *output, const uint8_t *input);
+
+    void clear();
+
+protected:
+    AESCommon(uint8_t keySize);
+
+private:
+    esp_aes_context ctx;
+};
+
+class AES128 : public AESCommon
+{
+public:
+    AES128() : AESCommon(16) {}
+    virtual ~AES128();
+};
+
+class AES192 : public AESCommon
+{
+public:
+    AES192() : AESCommon(24) {}
+    virtual ~AES192();
+};
+
+class AES256 : public AESCommon
+{
+public:
+    AES256() : AESCommon(32) {}
+    virtual ~AES256();
+};
+
+// The ESP32 AES context is so small that it already qualifies as "tiny".
+typedef AES128 AESTiny128;
+typedef AES256 AESTiny256;
+typedef AES128 AESSmall128;
+typedef AES256 AESSmall256;
+
+#endif // CRYPTO_AES_ESP32
+
+#endif
diff --git a/src/modules/Crypto/AES128.cpp b/src/modules/Crypto/AES128.cpp
new file mode 100644
index 0000000..533e193
--- /dev/null
+++ b/src/modules/Crypto/AES128.cpp
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2015,2018 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "AES.h"
+#include "Crypto.h"
+#include <string.h>
+
+#if defined(CRYPTO_AES_DEFAULT) || defined(CRYPTO_DOC)
+
+/**
+ * \class AES128 AES.h <AES.h>
+ * \brief AES block cipher with 128-bit keys.
+ *
+ * \sa AES192, AES256, AESTiny128, AESSmall128
+ */
+
+/**
+ * \brief Constructs an AES 128-bit block cipher with no initial key.
+ *
+ * This constructor must be followed by a call to setKey() before the
+ * block cipher can be used for encryption or decryption.
+ */
+AES128::AES128()
+{
+    rounds = 10;
+    schedule = sched;
+}
+
+AES128::~AES128()
+{
+    clean(sched);
+}
+
+/**
+ * \brief Size of a 128-bit AES key in bytes.
+ * \return Always returns 16.
+ */
+size_t AES128::keySize() const
+{
+    return 16;
+}
+
+bool AES128::setKey(const uint8_t *key, size_t len)
+{
+    if (len != 16)
+        return false;
+
+    // Copy the key itself into the first 16 bytes of the schedule.
+    uint8_t *schedule = sched;
+    memcpy(schedule, key, 16);
+
+    // Expand the key schedule until we have 176 bytes of expanded key.
+    uint8_t iteration = 1;
+    uint8_t n = 16;
+    uint8_t w = 4;
+    while (n < 176) {
+        if (w == 4) {
+            // Every 16 bytes (4 words) we need to apply the key schedule core.
+            keyScheduleCore(schedule + 16, schedule + 12, iteration);
+            schedule[16] ^= schedule[0];
+            schedule[17] ^= schedule[1];
+            schedule[18] ^= schedule[2];
+            schedule[19] ^= schedule[3];
+            ++iteration;
+            w = 0;
+        } else {
+            // Otherwise just XOR the word with the one 16 bytes previous.
+            schedule[16] = schedule[12] ^ schedule[0];
+            schedule[17] = schedule[13] ^ schedule[1];
+            schedule[18] = schedule[14] ^ schedule[2];
+            schedule[19] = schedule[15] ^ schedule[3];
+        }
+
+        // Advance to the next word in the schedule.
+        schedule += 4;
+        n += 4;
+        ++w;
+    }
+
+    return true;
+}
+
+/**
+ * \class AESTiny128 AES.h <AES.h>
+ * \brief AES block cipher with 128-bit keys and tiny memory usage.
+ *
+ * This class differs from the AES128 class in the following ways:
+ *
+ * \li RAM requirements are vastly reduced.  The key is stored directly
+ * and then expanded to the full key schedule round by round.  The setKey()
+ * method is very fast because of this.
+ * \li Performance of encryptBlock() is slower than for AES128 due to
+ * expanding the key on the fly rather than ahead of time.
+ * \li The decryptBlock() function is not supported, which means that CBC
+ * mode cannot be used but the CTR, CFB, OFB, EAX, and GCM modes can be used.
+ *
+ * This class is useful when RAM is at a premium, CBC mode is not required,
+ * and reduced encryption performance is not a hindrance to the application.
+ *
+ * The companion AESSmall128 class supports decryptBlock() at the cost of
+ * some additional memory and slower setKey() times.
+ *
+ * \sa AESSmall128, AES128
+ */
+
+/** @cond */
+
+// Helper macros.
+#define KCORE(n) \
+    do { \
+        AESCommon::keyScheduleCore(temp, schedule + 12, (n)); \
+        schedule[0] ^= temp[0]; \
+        schedule[1] ^= temp[1]; \
+        schedule[2] ^= temp[2]; \
+        schedule[3] ^= temp[3]; \
+    } while (0)
+#define KXOR(a, b) \
+    do { \
+        schedule[(a) * 4] ^= schedule[(b) * 4]; \
+        schedule[(a) * 4 + 1] ^= schedule[(b) * 4 + 1]; \
+        schedule[(a) * 4 + 2] ^= schedule[(b) * 4 + 2]; \
+        schedule[(a) * 4 + 3] ^= schedule[(b) * 4 + 3]; \
+    } while (0)
+
+/** @endcond */
+
+/**
+ * \brief Constructs an AES 128-bit block cipher with no initial key.
+ *
+ * This constructor must be followed by a call to setKey() before the
+ * block cipher can be used for encryption or decryption.
+ */
+AESTiny128::AESTiny128()
+{
+}
+
+AESTiny128::~AESTiny128()
+{
+    clean(schedule);
+}
+
+/**
+ * \brief Size of an AES block in bytes.
+ * \return Always returns 16.
+ */
+size_t AESTiny128::blockSize() const
+{
+    return 16;
+}
+
+/**
+ * \brief Size of a 128-bit AES key in bytes.
+ * \return Always returns 16.
+ */
+size_t AESTiny128::keySize() const
+{
+    return 16;
+}
+
+bool AESTiny128::setKey(const uint8_t *key, size_t len)
+{
+    if (len == 16) {
+        // Make a copy of the key - it will be expanded in encryptBlock().
+        memcpy(schedule, key, 16);
+        return true;
+    }
+    return false;
+}
+
+void AESTiny128::encryptBlock(uint8_t *output, const uint8_t *input)
+{
+    uint8_t schedule[16];
+    uint8_t posn;
+    uint8_t round;
+    uint8_t state1[16];
+    uint8_t state2[16];
+    uint8_t temp[4];
+
+    // Start with the key in the schedule buffer.
+    memcpy(schedule, this->schedule, 16);
+
+    // Copy the input into the state and XOR with the key schedule.
+    for (posn = 0; posn < 16; ++posn)
+        state1[posn] = input[posn] ^ schedule[posn];
+
+    // Perform the first 9 rounds of the cipher.
+    for (round = 1; round <= 9; ++round) {
+        // Expand the next 16 bytes of the key schedule.
+        KCORE(round);
+        KXOR(1, 0);
+        KXOR(2, 1);
+        KXOR(3, 2);
+
+        // Encrypt using the key schedule.
+        AESCommon::subBytesAndShiftRows(state2, state1);
+        AESCommon::mixColumn(state1,      state2);
+        AESCommon::mixColumn(state1 + 4,  state2 + 4);
+        AESCommon::mixColumn(state1 + 8,  state2 + 8);
+        AESCommon::mixColumn(state1 + 12, state2 + 12);
+        for (posn = 0; posn < 16; ++posn)
+            state1[posn] ^= schedule[posn];
+    }
+
+    // Expand the final 16 bytes of the key schedule.
+    KCORE(10);
+    KXOR(1, 0);
+    KXOR(2, 1);
+    KXOR(3, 2);
+
+    // Perform the final round.
+    AESCommon::subBytesAndShiftRows(state2, state1);
+    for (posn = 0; posn < 16; ++posn)
+        output[posn] = state2[posn] ^ schedule[posn];
+}
+
+void AESTiny128::decryptBlock(uint8_t *output, const uint8_t *input)
+{
+    // Decryption is not supported by AESTiny128.
+}
+
+void AESTiny128::clear()
+{
+    clean(schedule);
+}
+
+/**
+ * \class AESSmall128 AES.h <AES.h>
+ * \brief AES block cipher with 128-bit keys and reduced memory usage.
+ *
+ * This class differs from the AES128 class in that the RAM requirements are
+ * vastly reduced.  The key schedule is expanded round by round instead of
+ * being generated and stored by setKey().  The performance of encryption
+ * and decryption is slightly less because of this.
+ *
+ * This class is useful when RAM is at a premium and reduced encryption
+ * performance is not a hindrance to the application.
+ *
+ * The companion AESTiny128 class uses even less RAM but only supports the
+ * encryptBlock() operation.  Block cipher modes like CTR, EAX, and GCM
+ * do not need the decryptBlock() operation, so AESTiny128 may be a better
+ * option than AESSmall128 for many applications.
+ *
+ * \sa AESTiny128, AES128
+ */
+
+/**
+ * \brief Constructs an AES 128-bit block cipher with no initial key.
+ *
+ * This constructor must be followed by a call to setKey() before the
+ * block cipher can be used for encryption or decryption.
+ */
+AESSmall128::AESSmall128()
+{
+}
+
+AESSmall128::~AESSmall128()
+{
+    clean(reverse);
+}
+
+bool AESSmall128::setKey(const uint8_t *key, size_t len)
+{
+    uint8_t *schedule;
+    uint8_t round;
+    uint8_t temp[4];
+
+    // Set the encryption key first.
+    if (!AESTiny128::setKey(key, len))
+        return false;
+
+    // Expand the key schedule up to the last round which gives
+    // us the round keys to use for the final two rounds.  We can
+    // then work backwards from there in decryptBlock().
+    schedule = reverse;
+    memcpy(schedule, key, 16);
+    for (round = 1; round <= 10; ++round) {
+        KCORE(round);
+        KXOR(1, 0);
+        KXOR(2, 1);
+        KXOR(3, 2);
+    }
+
+    // Key is ready to go.
+    return true;
+}
+
+void AESSmall128::decryptBlock(uint8_t *output, const uint8_t *input)
+{
+    uint8_t schedule[16];
+    uint8_t round;
+    uint8_t posn;
+    uint8_t state1[16];
+    uint8_t state2[16];
+    uint8_t temp[4];
+
+    // Start with the end of the decryption schedule.
+    memcpy(schedule, reverse, 16);
+
+    // Copy the input into the state and reverse the final round.
+    for (posn = 0; posn < 16; ++posn)
+        state1[posn] = input[posn] ^ schedule[posn];
+    AESCommon::inverseShiftRowsAndSubBytes(state2, state1);
+    KXOR(3, 2);
+    KXOR(2, 1);
+    KXOR(1, 0);
+    KCORE(10);
+
+    // Perform the next 9 rounds of the decryption process.
+    for (round = 9; round >= 1; --round) {
+        // Decrypt using the key schedule.
+        for (posn = 0; posn < 16; ++posn)
+            state2[posn] ^= schedule[posn];
+        AESCommon::inverseMixColumn(state1,      state2);
+        AESCommon::inverseMixColumn(state1 + 4,  state2 + 4);
+        AESCommon::inverseMixColumn(state1 + 8,  state2 + 8);
+        AESCommon::inverseMixColumn(state1 + 12, state2 + 12);
+        AESCommon::inverseShiftRowsAndSubBytes(state2, state1);
+
+        // Expand the next 16 bytes of the key schedule in reverse.
+        KXOR(3, 2);
+        KXOR(2, 1);
+        KXOR(1, 0);
+        KCORE(round);
+    }
+
+    // Reverse the initial round and create the output words.
+    for (posn = 0; posn < 16; ++posn)
+        output[posn] = state2[posn] ^ schedule[posn];
+}
+
+void AESSmall128::clear()
+{
+    clean(reverse);
+    AESTiny128::clear();
+}
+
+#endif // CRYPTO_AES_DEFAULT
diff --git a/src/modules/Crypto/AES192.cpp b/src/modules/Crypto/AES192.cpp
new file mode 100644
index 0000000..57055d3
--- /dev/null
+++ b/src/modules/Crypto/AES192.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "AES.h"
+#include "Crypto.h"
+#include <string.h>
+
+#if defined(CRYPTO_AES_DEFAULT) || defined(CRYPTO_DOC)
+
+/**
+ * \class AES192 AES.h <AES.h>
+ * \brief AES block cipher with 192-bit keys.
+ *
+ * \sa AES128, AES256
+ */
+
+/**
+ * \brief Constructs an AES 192-bit block cipher with no initial key.
+ *
+ * This constructor must be followed by a call to setKey() before the
+ * block cipher can be used for encryption or decryption.
+ */
+AES192::AES192()
+{
+    rounds = 12;
+    schedule = sched;
+}
+
+AES192::~AES192()
+{
+    clean(sched);
+}
+
+/**
+ * \brief Size of a 192-bit AES key in bytes.
+ * \return Always returns 24.
+ */
+size_t AES192::keySize() const
+{
+    return 24;
+}
+
+bool AES192::setKey(const uint8_t *key, size_t len)
+{
+    if (len != 24)
+        return false;
+
+    // Copy the key itself into the first 24 bytes of the schedule.
+    uint8_t *schedule = sched;
+    memcpy(schedule, key, 24);
+
+    // Expand the key schedule until we have 208 bytes of expanded key.
+    uint8_t iteration = 1;
+    uint8_t n = 24;
+    uint8_t w = 6;
+    while (n < 208) {
+        if (w == 6) {
+            // Every 24 bytes (6 words) we need to apply the key schedule core.
+            keyScheduleCore(schedule + 24, schedule + 20, iteration);
+            schedule[24] ^= schedule[0];
+            schedule[25] ^= schedule[1];
+            schedule[26] ^= schedule[2];
+            schedule[27] ^= schedule[3];
+            ++iteration;
+            w = 0;
+        } else {
+            // Otherwise just XOR the word with the one 24 bytes previous.
+            schedule[24] = schedule[20] ^ schedule[0];
+            schedule[25] = schedule[21] ^ schedule[1];
+            schedule[26] = schedule[22] ^ schedule[2];
+            schedule[27] = schedule[23] ^ schedule[3];
+        }
+
+        // Advance to the next word in the schedule.
+        schedule += 4;
+        n += 4;
+        ++w;
+    }
+
+    return true;
+}
+
+#endif // CRYPTO_AES_DEFAULT
diff --git a/src/modules/Crypto/AES256.cpp b/src/modules/Crypto/AES256.cpp
new file mode 100644
index 0000000..1540d8f
--- /dev/null
+++ b/src/modules/Crypto/AES256.cpp
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2015,2018 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "AES.h"
+#include "Crypto.h"
+#include <string.h>
+
+#if defined(CRYPTO_AES_DEFAULT) || defined(CRYPTO_DOC)
+
+/**
+ * \class AES256 AES.h <AES.h>
+ * \brief AES block cipher with 256-bit keys.
+ *
+ * \sa AES128, AES192, AESTiny256, AESSmall256
+ */
+
+/**
+ * \brief Constructs an AES 256-bit block cipher with no initial key.
+ *
+ * This constructor must be followed by a call to setKey() before the
+ * block cipher can be used for encryption or decryption.
+ */
+AES256::AES256()
+{
+    rounds = 14;
+    schedule = sched;
+}
+
+AES256::~AES256()
+{
+    clean(sched);
+}
+
+/**
+ * \brief Size of a 256-bit AES key in bytes.
+ * \return Always returns 32.
+ */
+size_t AES256::keySize() const
+{
+    return 32;
+}
+
+bool AES256::setKey(const uint8_t *key, size_t len)
+{
+    if (len != 32)
+        return false;
+
+    // Copy the key itself into the first 32 bytes of the schedule.
+    uint8_t *schedule = sched;
+    memcpy(schedule, key, 32);
+
+    // Expand the key schedule until we have 240 bytes of expanded key.
+    uint8_t iteration = 1;
+    uint8_t n = 32;
+    uint8_t w = 8;
+    while (n < 240) {
+        if (w == 8) {
+            // Every 32 bytes (8 words) we need to apply the key schedule core.
+            keyScheduleCore(schedule + 32, schedule + 28, iteration);
+            schedule[32] ^= schedule[0];
+            schedule[33] ^= schedule[1];
+            schedule[34] ^= schedule[2];
+            schedule[35] ^= schedule[3];
+            ++iteration;
+            w = 0;
+        } else if (w == 4) {
+            // At the 16 byte mark we need to apply the S-box.
+            applySbox(schedule + 32, schedule + 28);
+            schedule[32] ^= schedule[0];
+            schedule[33] ^= schedule[1];
+            schedule[34] ^= schedule[2];
+            schedule[35] ^= schedule[3];
+        } else {
+            // Otherwise just XOR the word with the one 32 bytes previous.
+            schedule[32] = schedule[28] ^ schedule[0];
+            schedule[33] = schedule[29] ^ schedule[1];
+            schedule[34] = schedule[30] ^ schedule[2];
+            schedule[35] = schedule[31] ^ schedule[3];
+        }
+
+        // Advance to the next word in the schedule.
+        schedule += 4;
+        n += 4;
+        ++w;
+    }
+
+    return true;
+}
+
+/**
+ * \class AESTiny256 AES.h <AES.h>
+ * \brief AES block cipher with 256-bit keys and tiny memory usage.
+ *
+ * This class differs from the AES256 class in the following ways:
+ *
+ * \li RAM requirements are vastly reduced.  The key is stored directly
+ * and then expanded to the full key schedule round by round.  The setKey()
+ * method is very fast because of this.
+ * \li Performance of encryptBlock() is slower than for AES256 due to
+ * expanding the key on the fly rather than ahead of time.
+ * \li The decryptBlock() function is not supported, which means that CBC
+ * mode cannot be used but the CTR, CFB, OFB, EAX, and GCM modes can be used.
+ *
+ * This class is useful when RAM is at a premium, CBC mode is not required,
+ * and reduced encryption performance is not a hindrance to the application.
+ *
+ * The companion AESSmall256 class supports decryptBlock() at the cost of
+ * some additional memory and slower setKey() times.
+ *
+ * \sa AESSmall256, AES256
+ */
+
+/** @cond */
+
+// Helper macros.
+#define LEFT 0
+#define RIGHT 16
+#define ENCRYPT(phase) \
+    do { \
+        AESCommon::subBytesAndShiftRows(state2, state1); \
+        AESCommon::mixColumn(state1,      state2); \
+        AESCommon::mixColumn(state1 + 4,  state2 + 4); \
+        AESCommon::mixColumn(state1 + 8,  state2 + 8); \
+        AESCommon::mixColumn(state1 + 12, state2 + 12); \
+        for (posn = 0; posn < 16; ++posn) \
+            state1[posn] ^= schedule[posn + (phase)]; \
+    } while (0)
+#define DECRYPT(phase) \
+    do { \
+        for (posn = 0; posn < 16; ++posn) \
+            state2[posn] ^= schedule[posn + (phase)]; \
+        AESCommon::inverseMixColumn(state1,      state2); \
+        AESCommon::inverseMixColumn(state1 + 4,  state2 + 4); \
+        AESCommon::inverseMixColumn(state1 + 8,  state2 + 8); \
+        AESCommon::inverseMixColumn(state1 + 12, state2 + 12); \
+        AESCommon::inverseShiftRowsAndSubBytes(state2, state1); \
+    } while (0)
+#define KCORE(n) \
+    do { \
+        AESCommon::keyScheduleCore(temp, schedule + 28, (n)); \
+        schedule[0] ^= temp[0]; \
+        schedule[1] ^= temp[1]; \
+        schedule[2] ^= temp[2]; \
+        schedule[3] ^= temp[3]; \
+    } while (0)
+#define KXOR(a, b) \
+    do { \
+        schedule[(a) * 4] ^= schedule[(b) * 4]; \
+        schedule[(a) * 4 + 1] ^= schedule[(b) * 4 + 1]; \
+        schedule[(a) * 4 + 2] ^= schedule[(b) * 4 + 2]; \
+        schedule[(a) * 4 + 3] ^= schedule[(b) * 4 + 3]; \
+    } while (0)
+#define KSBOX() \
+    do { \
+        AESCommon::applySbox(temp, schedule + 12); \
+        schedule[16] ^= temp[0]; \
+        schedule[17] ^= temp[1]; \
+        schedule[18] ^= temp[2]; \
+        schedule[19] ^= temp[3]; \
+    } while (0)
+
+/** @endcond */
+
+/**
+ * \brief Constructs an AES 256-bit block cipher with no initial key.
+ *
+ * This constructor must be followed by a call to setKey() before the
+ * block cipher can be used for encryption or decryption.
+ */
+AESTiny256::AESTiny256()
+{
+}
+
+AESTiny256::~AESTiny256()
+{
+    clean(schedule);
+}
+
+/**
+ * \brief Size of an AES block in bytes.
+ * \return Always returns 16.
+ */
+size_t AESTiny256::blockSize() const
+{
+    return 16;
+}
+
+/**
+ * \brief Size of a 256-bit AES key in bytes.
+ * \return Always returns 32.
+ */
+size_t AESTiny256::keySize() const
+{
+    return 32;
+}
+
+bool AESTiny256::setKey(const uint8_t *key, size_t len)
+{
+    if (len == 32) {
+        // Make a copy of the key - it will be expanded in encryptBlock().
+        memcpy(schedule, key, 32);
+        return true;
+    }
+    return false;
+}
+
+void AESTiny256::encryptBlock(uint8_t *output, const uint8_t *input)
+{
+    uint8_t schedule[32];
+    uint8_t posn;
+    uint8_t round;
+    uint8_t state1[16];
+    uint8_t state2[16];
+    uint8_t temp[4];
+
+    // Start with the key in the schedule buffer.
+    memcpy(schedule, this->schedule, 32);
+
+    // Copy the input into the state and perform the first round.
+    for (posn = 0; posn < 16; ++posn)
+        state1[posn] = input[posn] ^ schedule[posn];
+    ENCRYPT(RIGHT);
+
+    // Perform the next 12 rounds of the cipher two at a time.
+    for (round = 1; round <= 6; ++round) {
+        // Expand the next 32 bytes of the key schedule.
+        KCORE(round);
+        KXOR(1, 0);
+        KXOR(2, 1);
+        KXOR(3, 2);
+        KSBOX();
+        KXOR(5, 4);
+        KXOR(6, 5);
+        KXOR(7, 6);
+
+        // Encrypt using the left and right halves of the key schedule.
+        ENCRYPT(LEFT);
+        ENCRYPT(RIGHT);
+    }
+
+    // Expand the final 16 bytes of the key schedule.
+    KCORE(7);
+    KXOR(1, 0);
+    KXOR(2, 1);
+    KXOR(3, 2);
+
+    // Perform the final round.
+    AESCommon::subBytesAndShiftRows(state2, state1);
+    for (posn = 0; posn < 16; ++posn)
+        output[posn] = state2[posn] ^ schedule[posn];
+}
+
+void AESTiny256::decryptBlock(uint8_t *output, const uint8_t *input)
+{
+    // Decryption is not supported by AESTiny256.
+}
+
+void AESTiny256::clear()
+{
+    clean(schedule);
+}
+
+/**
+ * \class AESSmall256 AES.h <AES.h>
+ * \brief AES block cipher with 256-bit keys and reduced memory usage.
+ *
+ * This class differs from the AES256 class in that the RAM requirements are
+ * vastly reduced.  The key schedule is expanded round by round instead of
+ * being generated and stored by setKey().  The performance of encryption
+ * and decryption is slightly less because of this.
+ *
+ * This class is useful when RAM is at a premium and reduced encryption
+ * performance is not a hindrance to the application.
+ *
+ * The companion AESTiny256 class uses even less RAM but only supports the
+ * encryptBlock() operation.  Block cipher modes like CTR, EAX, and GCM
+ * do not need the decryptBlock() operation, so AESTiny256 may be a better
+ * option than AESSmall256 for many applications.
+ *
+ * \sa AESTiny256, AES256
+ */
+
+/**
+ * \brief Constructs an AES 256-bit block cipher with no initial key.
+ *
+ * This constructor must be followed by a call to setKey() before the
+ * block cipher can be used for encryption or decryption.
+ */
+AESSmall256::AESSmall256()
+{
+}
+
+AESSmall256::~AESSmall256()
+{
+    clean(reverse);
+}
+
+bool AESSmall256::setKey(const uint8_t *key, size_t len)
+{
+    uint8_t *schedule;
+    uint8_t round;
+    uint8_t temp[4];
+
+    // Set the encryption key first.
+    if (!AESTiny256::setKey(key, len))
+        return false;
+
+    // Expand the key schedule up to the last round which gives
+    // us the round keys to use for the final two rounds.  We can
+    // then work backwards from there in decryptBlock().
+    schedule = reverse;
+    memcpy(schedule, key, 32);
+    for (round = 1; round <= 6; ++round) {
+        KCORE(round);
+        KXOR(1, 0);
+        KXOR(2, 1);
+        KXOR(3, 2);
+        KSBOX();
+        KXOR(5, 4);
+        KXOR(6, 5);
+        KXOR(7, 6);
+    }
+    KCORE(7);
+    KXOR(1, 0);
+    KXOR(2, 1);
+    KXOR(3, 2);
+
+    // Key is ready to go.
+    return true;
+}
+
+void AESSmall256::decryptBlock(uint8_t *output, const uint8_t *input)
+{
+    uint8_t schedule[32];
+    uint8_t round;
+    uint8_t posn;
+    uint8_t state1[16];
+    uint8_t state2[16];
+    uint8_t temp[4];
+
+    // Start with the end of the decryption schedule.
+    memcpy(schedule, reverse, 32);
+
+    // Copy the input into the state and reverse the final round.
+    for (posn = 0; posn < 16; ++posn)
+        state1[posn] = input[posn] ^ schedule[posn];
+    AESCommon::inverseShiftRowsAndSubBytes(state2, state1);
+    KXOR(3, 2);
+    KXOR(2, 1);
+    KXOR(1, 0);
+    KCORE(7);
+
+    // Perform the next 12 rounds of the decryption process two at a time.
+    for (round = 6; round >= 1; --round) {
+        // Decrypt using the right and left halves of the key schedule.
+        DECRYPT(RIGHT);
+        DECRYPT(LEFT);
+
+        // Expand the next 32 bytes of the key schedule in reverse.
+        KXOR(7, 6);
+        KXOR(6, 5);
+        KXOR(5, 4);
+        KSBOX();
+        KXOR(3, 2);
+        KXOR(2, 1);
+        KXOR(1, 0);
+        KCORE(round);
+    }
+
+    // Reverse the initial round and create the output words.
+    DECRYPT(RIGHT);
+    for (posn = 0; posn < 16; ++posn)
+        output[posn] = state2[posn] ^ schedule[posn];
+}
+
+void AESSmall256::clear()
+{
+    clean(reverse);
+    AESTiny256::clear();
+}
+
+#endif // CRYPTO_AES_DEFAULT
diff --git a/src/modules/Crypto/AESCommon.cpp b/src/modules/Crypto/AESCommon.cpp
new file mode 100644
index 0000000..aee6d32
--- /dev/null
+++ b/src/modules/Crypto/AESCommon.cpp
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "AES.h"
+#include "Crypto.h"
+#include "utility/ProgMemUtil.h"
+
+#if defined(CRYPTO_AES_DEFAULT) || defined(CRYPTO_DOC)
+
+/**
+ * \class AESCommon AES.h <AES.h>
+ * \brief Abstract base class for AES block ciphers.
+ *
+ * This class is abstract.  The caller should instantiate AES128,
+ * AES192, or AES256 to create an AES block cipher with a specific
+ * key size.
+ *
+ * \note This AES implementation does not have constant cache behaviour due
+ * to the use of table lookups.  It may not be safe to use this implementation
+ * in an environment where the attacker can observe the timing of encryption
+ * and decryption operations.  Unless AES compatibility is required,
+ * it is recommended that the ChaCha stream cipher be used instead.
+ *
+ * Reference: http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
+ *
+ * \sa ChaCha, AES128, AES192, AES256
+ */
+
+/** @cond sbox */
+
+// AES S-box (http://en.wikipedia.org/wiki/Rijndael_S-box)
+static uint8_t const sbox[256] PROGMEM = {
+    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5,     // 0x00
+    0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
+    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0,     // 0x10
+    0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
+    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC,     // 0x20
+    0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
+    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A,     // 0x30
+    0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
+    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0,     // 0x40
+    0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
+    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B,     // 0x50
+    0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
+    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85,     // 0x60
+    0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
+    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5,     // 0x70
+    0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
+    0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17,     // 0x80
+    0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
+    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88,     // 0x90
+    0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
+    0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C,     // 0xA0
+    0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
+    0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9,     // 0xB0
+    0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
+    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6,     // 0xC0
+    0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
+    0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E,     // 0xD0
+    0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
+    0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94,     // 0xE0
+    0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
+    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68,     // 0xF0
+    0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
+};
+
+// AES inverse S-box (http://en.wikipedia.org/wiki/Rijndael_S-box)
+static uint8_t const sbox_inverse[256] PROGMEM = {
+    0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38,     // 0x00
+    0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
+    0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87,     // 0x10
+    0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
+    0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D,     // 0x20
+    0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
+    0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2,     // 0x30
+    0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
+    0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16,     // 0x40
+    0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
+    0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA,     // 0x50
+    0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
+    0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A,     // 0x60
+    0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
+    0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02,     // 0x70
+    0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
+    0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA,     // 0x80
+    0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
+    0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85,     // 0x90
+    0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
+    0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89,     // 0xA0
+    0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
+    0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20,     // 0xB0
+    0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
+    0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31,     // 0xC0
+    0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
+    0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D,     // 0xD0
+    0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
+    0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0,     // 0xE0
+    0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
+    0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26,     // 0xF0
+    0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
+};
+
+/** @endcond */
+
+/**
+ * \brief Constructs an AES block cipher object.
+ */
+AESCommon::AESCommon()
+    : rounds(0), schedule(0)
+{
+}
+
+/**
+ * \brief Destroys this AES block cipher object after clearing
+ * sensitive information.
+ */
+AESCommon::~AESCommon()
+{
+}
+
+/**
+ * \brief Size of an AES block in bytes.
+ * \return Always returns 16.
+ */
+size_t AESCommon::blockSize() const
+{
+    return 16;
+}
+
+// Constants to correct Galois multiplication for the high bits
+// that are shifted out when multiplying by powers of two.
+static uint8_t const K[8] = {
+    0x00,
+    0x1B,
+    (0x1B << 1),
+    (0x1B << 1) ^ 0x1B,
+    (0x1B << 2),
+    (0x1B << 2) ^ 0x1B,
+    (0x1B << 2) ^ (0x1B << 1),
+    (0x1B << 2) ^ (0x1B << 1) ^ 0x1B
+};
+
+// Multiply x by 2 in the Galois field, to achieve the effect of the following:
+//
+//     if (x & 0x80)
+//         return (x << 1) ^ 0x1B;
+//     else
+//         return (x << 1);
+//
+// However, we don't want to use runtime conditionals if we can help it
+// to avoid leaking timing information from the implementation.
+// In this case, multiplication is slightly faster than table lookup on AVR.
+#define gmul2(x)    (t = ((uint16_t)(x)) << 1, \
+                     ((uint8_t)t) ^ (uint8_t)(0x1B * ((uint8_t)(t >> 8))))
+
+// Multiply x by 4 in the Galois field.
+#define gmul4(x)    (t = ((uint16_t)(x)) << 2, ((uint8_t)t) ^ K[t >> 8])
+
+// Multiply x by 8 in the Galois field.
+#define gmul8(x)    (t = ((uint16_t)(x)) << 3, ((uint8_t)t) ^ K[t >> 8])
+
+#define OUT(col, row)   output[(col) * 4 + (row)]
+#define IN(col, row)    input[(col) * 4 + (row)]
+
+/** @cond aes_funcs */
+
+void AESCommon::subBytesAndShiftRows(uint8_t *output, const uint8_t *input)
+{
+    OUT(0, 0) = pgm_read_byte(sbox + IN(0, 0));
+    OUT(0, 1) = pgm_read_byte(sbox + IN(1, 1));
+    OUT(0, 2) = pgm_read_byte(sbox + IN(2, 2));
+    OUT(0, 3) = pgm_read_byte(sbox + IN(3, 3));
+    OUT(1, 0) = pgm_read_byte(sbox + IN(1, 0));
+    OUT(1, 1) = pgm_read_byte(sbox + IN(2, 1));
+    OUT(1, 2) = pgm_read_byte(sbox + IN(3, 2));
+    OUT(1, 3) = pgm_read_byte(sbox + IN(0, 3));
+    OUT(2, 0) = pgm_read_byte(sbox + IN(2, 0));
+    OUT(2, 1) = pgm_read_byte(sbox + IN(3, 1));
+    OUT(2, 2) = pgm_read_byte(sbox + IN(0, 2));
+    OUT(2, 3) = pgm_read_byte(sbox + IN(1, 3));
+    OUT(3, 0) = pgm_read_byte(sbox + IN(3, 0));
+    OUT(3, 1) = pgm_read_byte(sbox + IN(0, 1));
+    OUT(3, 2) = pgm_read_byte(sbox + IN(1, 2));
+    OUT(3, 3) = pgm_read_byte(sbox + IN(2, 3));
+}
+
+void AESCommon::inverseShiftRowsAndSubBytes(uint8_t *output, const uint8_t *input)
+{
+    OUT(0, 0) = pgm_read_byte(sbox_inverse + IN(0, 0));
+    OUT(0, 1) = pgm_read_byte(sbox_inverse + IN(3, 1));
+    OUT(0, 2) = pgm_read_byte(sbox_inverse + IN(2, 2));
+    OUT(0, 3) = pgm_read_byte(sbox_inverse + IN(1, 3));
+    OUT(1, 0) = pgm_read_byte(sbox_inverse + IN(1, 0));
+    OUT(1, 1) = pgm_read_byte(sbox_inverse + IN(0, 1));
+    OUT(1, 2) = pgm_read_byte(sbox_inverse + IN(3, 2));
+    OUT(1, 3) = pgm_read_byte(sbox_inverse + IN(2, 3));
+    OUT(2, 0) = pgm_read_byte(sbox_inverse + IN(2, 0));
+    OUT(2, 1) = pgm_read_byte(sbox_inverse + IN(1, 1));
+    OUT(2, 2) = pgm_read_byte(sbox_inverse + IN(0, 2));
+    OUT(2, 3) = pgm_read_byte(sbox_inverse + IN(3, 3));
+    OUT(3, 0) = pgm_read_byte(sbox_inverse + IN(3, 0));
+    OUT(3, 1) = pgm_read_byte(sbox_inverse + IN(2, 1));
+    OUT(3, 2) = pgm_read_byte(sbox_inverse + IN(1, 2));
+    OUT(3, 3) = pgm_read_byte(sbox_inverse + IN(0, 3));
+}
+
+void AESCommon::mixColumn(uint8_t *output, uint8_t *input)
+{
+    uint16_t t; // Needed by the gmul2 macro.
+    uint8_t a = input[0];
+    uint8_t b = input[1];
+    uint8_t c = input[2];
+    uint8_t d = input[3];
+    uint8_t a2 = gmul2(a);
+    uint8_t b2 = gmul2(b);
+    uint8_t c2 = gmul2(c);
+    uint8_t d2 = gmul2(d);
+    output[0] = a2 ^ b2 ^ b ^ c ^ d;
+    output[1] = a ^ b2 ^ c2 ^ c ^ d;
+    output[2] = a ^ b ^ c2 ^ d2 ^ d;
+    output[3] = a2 ^ a ^ b ^ c ^ d2;
+}
+
+void AESCommon::inverseMixColumn(uint8_t *output, const uint8_t *input)
+{
+    uint16_t t; // Needed by the gmul2, gmul4, and gmul8 macros.
+    uint8_t a = input[0];
+    uint8_t b = input[1];
+    uint8_t c = input[2];
+    uint8_t d = input[3];
+    uint8_t a2 = gmul2(a);
+    uint8_t b2 = gmul2(b);
+    uint8_t c2 = gmul2(c);
+    uint8_t d2 = gmul2(d);
+    uint8_t a4 = gmul4(a);
+    uint8_t b4 = gmul4(b);
+    uint8_t c4 = gmul4(c);
+    uint8_t d4 = gmul4(d);
+    uint8_t a8 = gmul8(a);
+    uint8_t b8 = gmul8(b);
+    uint8_t c8 = gmul8(c);
+    uint8_t d8 = gmul8(d);
+    output[0] = a8 ^ a4 ^ a2 ^ b8 ^ b2 ^ b ^ c8 ^ c4 ^ c ^ d8 ^ d;
+    output[1] = a8 ^ a ^ b8 ^ b4 ^ b2 ^ c8 ^ c2 ^ c ^ d8 ^ d4 ^ d;
+    output[2] = a8 ^ a4 ^ a ^ b8 ^ b ^ c8 ^ c4 ^ c2 ^ d8 ^ d2 ^ d;
+    output[3] = a8 ^ a2 ^ a ^ b8 ^ b4 ^ b ^ c8 ^ c ^ d8 ^ d4 ^ d2;
+}
+
+/** @endcond */
+
+void AESCommon::encryptBlock(uint8_t *output, const uint8_t *input)
+{
+    const uint8_t *roundKey = schedule;
+    uint8_t posn;
+    uint8_t round;
+    uint8_t state1[16];
+    uint8_t state2[16];
+
+    // Copy the input into the state and XOR with the first round key.
+    for (posn = 0; posn < 16; ++posn)
+        state1[posn] = input[posn] ^ roundKey[posn];
+    roundKey += 16;
+
+    // Perform all rounds except the last.
+    for (round = rounds; round > 1; --round) {
+        subBytesAndShiftRows(state2, state1);
+        mixColumn(state1,      state2);
+        mixColumn(state1 + 4,  state2 + 4);
+        mixColumn(state1 + 8,  state2 + 8);
+        mixColumn(state1 + 12, state2 + 12);
+        for (posn = 0; posn < 16; ++posn)
+            state1[posn] ^= roundKey[posn];
+        roundKey += 16;
+    }
+
+    // Perform the final round.
+    subBytesAndShiftRows(state2, state1);
+    for (posn = 0; posn < 16; ++posn)
+        output[posn] = state2[posn] ^ roundKey[posn];
+}
+
+void AESCommon::decryptBlock(uint8_t *output, const uint8_t *input)
+{
+    const uint8_t *roundKey = schedule + rounds * 16;
+    uint8_t round;
+    uint8_t posn;
+    uint8_t state1[16];
+    uint8_t state2[16];
+
+    // Copy the input into the state and reverse the final round.
+    for (posn = 0; posn < 16; ++posn)
+        state1[posn] = input[posn] ^ roundKey[posn];
+    inverseShiftRowsAndSubBytes(state2, state1);
+
+    // Perform all other rounds in reverse.
+    for (round = rounds; round > 1; --round) {
+        roundKey -= 16;
+        for (posn = 0; posn < 16; ++posn)
+            state2[posn] ^= roundKey[posn];
+        inverseMixColumn(state1,      state2);
+        inverseMixColumn(state1 + 4,  state2 + 4);
+        inverseMixColumn(state1 + 8,  state2 + 8);
+        inverseMixColumn(state1 + 12, state2 + 12);
+        inverseShiftRowsAndSubBytes(state2, state1);
+    }
+
+    // Reverse the initial round and create the output words.
+    roundKey -= 16;
+    for (posn = 0; posn < 16; ++posn)
+        output[posn] = state2[posn] ^ roundKey[posn];
+}
+
+void AESCommon::clear()
+{
+    clean(schedule, (rounds + 1) * 16);
+}
+
+/** @cond aes_keycore */
+
+void AESCommon::keyScheduleCore(uint8_t *output, const uint8_t *input, uint8_t iteration)
+{
+    // Rcon(i), 2^i in the Rijndael finite field, for i = 0..10.
+    // http://en.wikipedia.org/wiki/Rijndael_key_schedule
+    static uint8_t const rcon[11] PROGMEM = {
+        0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,     // 0x00
+        0x80, 0x1B, 0x36
+    };
+    output[0] = pgm_read_byte(sbox + input[1]) ^ pgm_read_byte(rcon + iteration);
+    output[1] = pgm_read_byte(sbox + input[2]);
+    output[2] = pgm_read_byte(sbox + input[3]);
+    output[3] = pgm_read_byte(sbox + input[0]);
+}
+
+void AESCommon::applySbox(uint8_t *output, const uint8_t *input)
+{
+    output[0] = pgm_read_byte(sbox + input[0]);
+    output[1] = pgm_read_byte(sbox + input[1]);
+    output[2] = pgm_read_byte(sbox + input[2]);
+    output[3] = pgm_read_byte(sbox + input[3]);
+}
+
+/** @endcond */
+
+#endif // CRYPTO_AES_DEFAULT
diff --git a/src/modules/Crypto/AESEsp32.cpp b/src/modules/Crypto/AESEsp32.cpp
new file mode 100644
index 0000000..72a8646
--- /dev/null
+++ b/src/modules/Crypto/AESEsp32.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "AES.h"
+#include "Crypto.h"
+#include <string.h>
+
+// AES implementation for ESP32 using the hardware crypto module.
+
+#if defined(CRYPTO_AES_ESP32)
+
+AESCommon::AESCommon(uint8_t keySize)
+{
+    ctx.key_bytes = keySize;
+}
+
+AESCommon::~AESCommon()
+{
+    clean(ctx.key, sizeof(ctx.key));
+}
+
+size_t AESCommon::blockSize() const
+{
+    return 16;
+}
+
+size_t AESCommon::keySize() const
+{
+    return ctx.key_bytes;
+}
+
+bool AESCommon::setKey(const uint8_t *key, size_t len)
+{
+    if (len == ctx.key_bytes) {
+        // Do the effect of esp_aes_setkey() which is just a memcpy().
+        memcpy(ctx.key, key, len);
+        return true;
+    }
+    return false;
+}
+
+void AESCommon::encryptBlock(uint8_t *output, const uint8_t *input)
+{
+    esp_aes_encrypt(&ctx, input, output);
+}
+
+void AESCommon::decryptBlock(uint8_t *output, const uint8_t *input)
+{
+    esp_aes_decrypt(&ctx, input, output);
+}
+
+void AESCommon::clear()
+{
+    clean(ctx.key, sizeof(ctx.key));
+}
+
+AES128::~AES128()
+{
+}
+
+AES192::~AES192()
+{
+}
+
+AES256::~AES256()
+{
+}
+
+#endif // CRYPTO_AES_ESP32
diff --git a/src/modules/Crypto/AuthenticatedCipher.cpp b/src/modules/Crypto/AuthenticatedCipher.cpp
new file mode 100644
index 0000000..6184aa8
--- /dev/null
+++ b/src/modules/Crypto/AuthenticatedCipher.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "AuthenticatedCipher.h"
+
+/**
+ * \class AuthenticatedCipher AuthenticatedCipher.h <AuthenticatedCipher.h>
+ * \brief Abstract base class for authenticated ciphers.
+ *
+ * This class abstracts the details of algorithms that provide Authenticated
+ * Encryption with Associated Data (AEAD).  Such algorithms combine
+ * encryption with message authentication to provide a single primitive.
+ *
+ * Authenticated ciphers have four parameters: the secret key, an
+ * initialization vector (called a "nonce" in the literature), the
+ * plaintext, and some associated data which is to be authenticated
+ * with the plaintext but not encrypted.  Associated data might be
+ * sequence numbers, IP addresses, protocol versions, or other information
+ * that is not secret but is important and unique to the session.
+ *
+ * Subclasses encrypt the plaintext content and output the ciphertext.
+ * Once all plaintext has been processed, the caller should invoke
+ * computeTag() to obtain the authentication tag to transmit with
+ * the ciphertext.  When the ciphertext is later decrypted, the checkTag()
+ * function can be used to check that the data is authentic.
+ *
+ * Reference: <a href="http://tools.ietf.org/html/rfc5116">RFC 5116</a>
+ *
+ * \sa Cipher
+ */
+
+/**
+ * \brief Constructs a new authenticated cipher.
+ */
+AuthenticatedCipher::AuthenticatedCipher()
+{
+}
+
+/**
+ * \brief Destroys this authenticated cipher.
+ */
+AuthenticatedCipher::~AuthenticatedCipher()
+{
+}
+
+/**
+ * \fn size_t AuthenticatedCipher::tagSize() const
+ * \brief Returns the size of the authentication tag.
+ *
+ * \return The size of the authentication tag in bytes.
+ *
+ * By default this function should return the largest tag size supported
+ * by the authenticated cipher.
+ *
+ * \sa computeTag()
+ */
+
+/**
+ * \fn void AuthenticatedCipher::addAuthData(const void *data, size_t len)
+ * \brief Adds extra data that will be authenticated but not encrypted.
+ *
+ * \param data The extra data to be authenticated.
+ * \param len The number of bytes of extra data to be authenticated.
+ *
+ * This function must be called before the first call to encrypt() or
+ * decrypt().  That is, it is assumed that all extra data for authentication
+ * is available before the first payload data block and that it will be
+ * prepended to the payload for authentication.  If the subclass needs to
+ * process the extra data after the payload, then it is responsible for saving
+ * \a data away until it is needed during computeTag() or checkTag().
+ *
+ * This function can be called multiple times with separate extra data
+ * blocks for authentication.  All such data will be concatenated into a
+ * single block for authentication purposes.
+ */
+
+/**
+ * \fn void AuthenticatedCipher::computeTag(void *tag, size_t len)
+ * \brief Finalizes the encryption process and computes the authentication tag.
+ *
+ * \param tag Points to the buffer to write the tag to.
+ * \param len The length of the tag, which may be less than tagSize() to
+ * truncate the tag to the first \a len bytes.
+ *
+ * \sa checkTag()
+ */
+
+/**
+ * \fn bool AuthenticatedCipher::checkTag(const void *tag, size_t len)
+ * \brief Finalizes the decryption process and checks the authentication tag.
+ *
+ * \param tag The tag value from the incoming ciphertext to be checked.
+ * \param len The length of the tag value in bytes, which may be less
+ * than tagSize().
+ *
+ * \return Returns true if the \a tag is identical to the first \a len
+ * bytes of the authentication tag that was calculated during the
+ * decryption process.  Returns false otherwise.
+ *
+ * This function must be called after the final block of ciphertext is
+ * passed to decrypt() to determine if the data could be authenticated.
+ *
+ * \note Authenticated cipher modes usually require that if the tag could
+ * not be verified, then all of the data that was previously decrypted
+ * <i>must</i> be discarded.  It is unwise to use the decrypted data for
+ * any purpose before it can be verified.  Callers are responsible for
+ * ensuring that any data returned via previous calls to decrypt() is
+ * discarded if checkTag() returns false.
+ *
+ * \sa computeTag()
+ */
diff --git a/src/modules/Crypto/AuthenticatedCipher.h b/src/modules/Crypto/AuthenticatedCipher.h
new file mode 100644
index 0000000..de419d0
--- /dev/null
+++ b/src/modules/Crypto/AuthenticatedCipher.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_AUTHENTICATEDCIPHER_h
+#define CRYPTO_AUTHENTICATEDCIPHER_h
+
+#include "Cipher.h"
+
+class AuthenticatedCipher : public Cipher
+{
+public:
+    AuthenticatedCipher();
+    virtual ~AuthenticatedCipher();
+
+    virtual size_t tagSize() const = 0;
+
+    virtual void addAuthData(const void *data, size_t len) = 0;
+
+    virtual void computeTag(void *tag, size_t len) = 0;
+    virtual bool checkTag(const void *tag, size_t len) = 0;
+};
+
+#endif
diff --git a/src/modules/Crypto/BLAKE2b.cpp b/src/modules/Crypto/BLAKE2b.cpp
new file mode 100644
index 0000000..7352ec0
--- /dev/null
+++ b/src/modules/Crypto/BLAKE2b.cpp
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "BLAKE2b.h"
+#include "Crypto.h"
+#include "utility/EndianUtil.h"
+#include "utility/RotateUtil.h"
+#include "utility/ProgMemUtil.h"
+#include <string.h>
+
+/**
+ * \class BLAKE2b BLAKE2b.h <BLAKE2b.h>
+ * \brief BLAKE2b hash algorithm.
+ *
+ * BLAKE2b is a variation on the ChaCha stream cipher, designed for hashing,
+ * with a 512-bit hash output.  It is intended as a high performance
+ * replacement for SHA512 for when speed is critical but exact SHA512
+ * compatibility is not.
+ *
+ * This class supports two types of keyed hash.  The BLAKE2 keyed hash and
+ * traditional HMAC.  The BLAKE2 keyed hash is recommended unless there is
+ * some higher-level application need to be compatible with the HMAC
+ * construction.  The keyed hash is computed as follows:
+ *
+ * \code
+ * BLAKE2b blake;
+ * blake.reset(key, sizeof(key), outputLength);
+ * blake.update(data1, sizeof(data1));
+ * blake.update(data2, sizeof(data2));
+ * ...
+ * blake.update(dataN, sizeof(dataN));
+ * blake.finalize(hash, outputLength);
+ * \endcode
+ *
+ * The HMAC is computed as follows (the output length is always 64):
+ *
+ * \code
+ * BLAKE2b blake;
+ * blake.resetHMAC(key, sizeof(key));
+ * blake.update(data1, sizeof(data1));
+ * blake.update(data2, sizeof(data2));
+ * ...
+ * blake.update(dataN, sizeof(dataN));
+ * blake.finalizeHMAC(key, sizeof(key), hash, 32);
+ * \endcode
+ *
+ * References: https://blake2.net/,
+ * <a href="http://tools.ietf.org/html/rfc7693">RFC 7693</a>
+ *
+ * \sa BLAKE2s, SHA512, SHA3_512
+ */
+
+/**
+ * \brief Constructs a BLAKE2b hash object.
+ */
+BLAKE2b::BLAKE2b()
+{
+    reset();
+}
+
+/**
+ * \brief Destroys this BLAKE2b hash object after clearing
+ * sensitive information.
+ */
+BLAKE2b::~BLAKE2b()
+{
+    clean(state);
+}
+
+size_t BLAKE2b::hashSize() const
+{
+    return 64;
+}
+
+size_t BLAKE2b::blockSize() const
+{
+    return 128;
+}
+
+// Initialization vectors for BLAKE2b.
+#define BLAKE2b_IV0 0x6a09e667f3bcc908ULL
+#define BLAKE2b_IV1 0xbb67ae8584caa73bULL
+#define BLAKE2b_IV2 0x3c6ef372fe94f82bULL
+#define BLAKE2b_IV3 0xa54ff53a5f1d36f1ULL
+#define BLAKE2b_IV4 0x510e527fade682d1ULL
+#define BLAKE2b_IV5 0x9b05688c2b3e6c1fULL
+#define BLAKE2b_IV6 0x1f83d9abfb41bd6bULL
+#define BLAKE2b_IV7 0x5be0cd19137e2179ULL
+
+void BLAKE2b::reset()
+{
+    state.h[0] = BLAKE2b_IV0 ^ 0x01010040; // Default output length of 64.
+    state.h[1] = BLAKE2b_IV1;
+    state.h[2] = BLAKE2b_IV2;
+    state.h[3] = BLAKE2b_IV3;
+    state.h[4] = BLAKE2b_IV4;
+    state.h[5] = BLAKE2b_IV5;
+    state.h[6] = BLAKE2b_IV6;
+    state.h[7] = BLAKE2b_IV7;
+    state.chunkSize = 0;
+    state.lengthLow = 0;
+    state.lengthHigh = 0;
+}
+
+/**
+ * \brief Resets the hash ready for a new hashing process with a specified
+ * output length.
+ *
+ * \param outputLength The output length to use for the final hash in bytes,
+ * between 1 and 64.
+ */
+void BLAKE2b::reset(uint8_t outputLength)
+{
+    if (outputLength < 1)
+        outputLength = 1;
+    else if (outputLength > 64)
+        outputLength = 64;
+    state.h[0] = BLAKE2b_IV0 ^ 0x01010000 ^ outputLength;
+    state.h[1] = BLAKE2b_IV1;
+    state.h[2] = BLAKE2b_IV2;
+    state.h[3] = BLAKE2b_IV3;
+    state.h[4] = BLAKE2b_IV4;
+    state.h[5] = BLAKE2b_IV5;
+    state.h[6] = BLAKE2b_IV6;
+    state.h[7] = BLAKE2b_IV7;
+    state.chunkSize = 0;
+    state.lengthLow = 0;
+    state.lengthHigh = 0;
+}
+
+/**
+ * \brief Resets the hash ready for a new hashing process with a specified
+ * key and output length.
+ *
+ * \param key Points to the key.
+ * \param keyLen The length of the key in bytes, between 0 and 64.
+ * \param outputLength The output length to use for the final hash in bytes,
+ * between 1 and 64.
+ *
+ * If \a keyLen is greater than 64, then the \a key will be truncated to
+ * the first 64 bytes.
+ */
+void BLAKE2b::reset(const void *key, size_t keyLen, uint8_t outputLength)
+{
+    if (keyLen > 64)
+        keyLen = 64;
+    if (outputLength < 1)
+        outputLength = 1;
+    else if (outputLength > 64)
+        outputLength = 64;
+    state.h[0] = BLAKE2b_IV0 ^ 0x01010000 ^ (keyLen << 8) ^ outputLength;
+    state.h[1] = BLAKE2b_IV1;
+    state.h[2] = BLAKE2b_IV2;
+    state.h[3] = BLAKE2b_IV3;
+    state.h[4] = BLAKE2b_IV4;
+    state.h[5] = BLAKE2b_IV5;
+    state.h[6] = BLAKE2b_IV6;
+    state.h[7] = BLAKE2b_IV7;
+    if (keyLen > 0) {
+        // Set the first block to the key and pad with zeroes.
+        memcpy(state.m, key, keyLen);
+        memset(((uint8_t *)state.m) + keyLen, 0, 128 - keyLen);
+        state.chunkSize = 128;
+        state.lengthLow = 128;
+    } else {
+        // No key.  The first data block is the first hashed block.
+        state.chunkSize = 0;
+        state.lengthLow = 0;
+    }
+    state.lengthHigh = 0;
+}
+
+void BLAKE2b::update(const void *data, size_t len)
+{
+    // Break the input up into 1024-bit chunks and process each in turn.
+    const uint8_t *d = (const uint8_t *)data;
+    while (len > 0) {
+        if (state.chunkSize == 128) {
+            // Previous chunk was full and we know that it wasn't the
+            // last chunk, so we can process it now with f0 set to zero.
+            processChunk(0);
+            state.chunkSize = 0;
+        }
+        uint8_t size = 128 - state.chunkSize;
+        if (size > len)
+            size = len;
+        memcpy(((uint8_t *)state.m) + state.chunkSize, d, size);
+        state.chunkSize += size;
+        uint64_t temp = state.lengthLow;
+        state.lengthLow += size;
+        if (state.lengthLow < temp)
+            ++state.lengthHigh;
+        len -= size;
+        d += size;
+    }
+}
+
+void BLAKE2b::finalize(void *hash, size_t len)
+{
+    // Pad the last chunk and hash it with f0 set to all-ones.
+    memset(((uint8_t *)state.m) + state.chunkSize, 0, 128 - state.chunkSize);
+    processChunk(0xFFFFFFFFFFFFFFFFULL);
+
+    // Convert the hash into little-endian in the message buffer.
+    for (uint8_t posn = 0; posn < 8; ++posn)
+        state.m[posn] = htole64(state.h[posn]);
+
+    // Copy the hash to the caller's return buffer.
+    if (len > 64)
+        len = 64;
+    memcpy(hash, state.m, len);
+}
+
+void BLAKE2b::clear()
+{
+    clean(state);
+    reset();
+}
+
+void BLAKE2b::resetHMAC(const void *key, size_t keyLen)
+{
+    formatHMACKey(state.m, key, keyLen, 0x36);
+    state.lengthLow += 128;
+    processChunk(0);
+}
+
+void BLAKE2b::finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen)
+{
+    uint8_t temp[64];
+    finalize(temp, sizeof(temp));
+    formatHMACKey(state.m, key, keyLen, 0x5C);
+    state.lengthLow += 128;
+    processChunk(0);
+    update(temp, sizeof(temp));
+    finalize(hash, hashLen);
+    clean(temp);
+}
+
+// Permutation on the message input state for BLAKE2b.
+static const uint8_t sigma[12][16] PROGMEM = {
+    { 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15},
+    {14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3},
+    {11,  8, 12,  0,  5,  2, 15, 13, 10, 14,  3,  6,  7,  1,  9,  4},
+    { 7,  9,  3,  1, 13, 12, 11, 14,  2,  6,  5, 10,  4,  0, 15,  8},
+    { 9,  0,  5,  7,  2,  4, 10, 15, 14,  1, 11, 12,  6,  8,  3, 13},
+    { 2, 12,  6, 10,  0, 11,  8,  3,  4, 13,  7,  5, 15, 14,  1,  9},
+    {12,  5,  1, 15, 14, 13,  4, 10,  0,  7,  6,  3,  9,  2,  8, 11},
+    {13, 11,  7, 14, 12,  1,  3,  9,  5,  0, 15,  4,  8,  6,  2, 10},
+    { 6, 15, 14,  9, 11,  3,  0,  8, 12,  2, 13,  7,  1,  4, 10,  5},
+    {10,  2,  8,  4,  7,  6,  1,  5, 15, 11,  9, 14,  3, 12, 13 , 0},
+    { 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15},
+    {14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3},
+};
+
+// Perform a BLAKE2b quarter round operation.
+#define quarterRound(a, b, c, d, i)    \
+    do { \
+        uint64_t _b = (b); \
+        uint64_t _a = (a) + _b + state.m[pgm_read_byte(&(sigma[index][2 * (i)]))]; \
+        uint64_t _d = rightRotate32_64((d) ^ _a); \
+        uint64_t _c = (c) + _d; \
+        _b = rightRotate24_64(_b ^ _c); \
+        _a += _b + state.m[pgm_read_byte(&(sigma[index][2 * (i) + 1]))]; \
+        (d) = _d = rightRotate16_64(_d ^ _a); \
+        _c += _d; \
+        (a) = _a; \
+        (b) = rightRotate63_64(_b ^ _c); \
+        (c) = _c; \
+    } while (0)
+
+void BLAKE2b::processChunk(uint64_t f0)
+{
+    uint8_t index;
+    uint64_t v[16];
+
+    // Byte-swap the message buffer into little-endian if necessary.
+#if !defined(CRYPTO_LITTLE_ENDIAN)
+    for (index = 0; index < 16; ++index)
+        state.m[index] = le64toh(state.m[index]);
+#endif
+
+    // Format the block to be hashed.
+    memcpy(v, state.h, sizeof(state.h));
+    v[8]  = BLAKE2b_IV0;
+    v[9]  = BLAKE2b_IV1;
+    v[10] = BLAKE2b_IV2;
+    v[11] = BLAKE2b_IV3;
+    v[12] = BLAKE2b_IV4 ^ state.lengthLow;
+    v[13] = BLAKE2b_IV5 ^ state.lengthHigh;
+    v[14] = BLAKE2b_IV6 ^ f0;
+    v[15] = BLAKE2b_IV7;
+
+    // Perform the 12 BLAKE2b rounds.
+    for (index = 0; index < 12; ++index) {
+        // Column round.
+        quarterRound(v[0], v[4], v[8],  v[12], 0);
+        quarterRound(v[1], v[5], v[9],  v[13], 1);
+        quarterRound(v[2], v[6], v[10], v[14], 2);
+        quarterRound(v[3], v[7], v[11], v[15], 3);
+
+        // Diagonal round.
+        quarterRound(v[0], v[5], v[10], v[15], 4);
+        quarterRound(v[1], v[6], v[11], v[12], 5);
+        quarterRound(v[2], v[7], v[8],  v[13], 6);
+        quarterRound(v[3], v[4], v[9],  v[14], 7);
+    }
+
+    // Combine the new and old hash values.
+    for (index = 0; index < 8; ++index)
+        state.h[index] ^= (v[index] ^ v[index + 8]);
+}
diff --git a/src/modules/Crypto/BLAKE2b.h b/src/modules/Crypto/BLAKE2b.h
new file mode 100644
index 0000000..e3f7868
--- /dev/null
+++ b/src/modules/Crypto/BLAKE2b.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_BLAKE2B_H
+#define CRYPTO_BLAKE2B_H
+
+#include "Hash.h"
+
+class BLAKE2b : public Hash
+{
+public:
+    BLAKE2b();
+    virtual ~BLAKE2b();
+
+    size_t hashSize() const;
+    size_t blockSize() const;
+
+    void reset();
+    void reset(uint8_t outputLength);
+    void reset(const void *key, size_t keyLen, uint8_t outputLength = 64);
+
+    void update(const void *data, size_t len);
+    void finalize(void *hash, size_t len);
+
+    void clear();
+
+    void resetHMAC(const void *key, size_t keyLen);
+    void finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen);
+
+private:
+    struct {
+        uint64_t h[8];
+        uint64_t m[16];
+        uint64_t lengthLow;
+        uint64_t lengthHigh;
+        uint8_t chunkSize;
+    } state;
+
+    void processChunk(uint64_t f0);
+};
+
+#endif
diff --git a/src/modules/Crypto/BLAKE2s.cpp b/src/modules/Crypto/BLAKE2s.cpp
new file mode 100644
index 0000000..a707ff7
--- /dev/null
+++ b/src/modules/Crypto/BLAKE2s.cpp
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "BLAKE2s.h"
+#include "Crypto.h"
+#include "utility/EndianUtil.h"
+#include "utility/RotateUtil.h"
+#include "utility/ProgMemUtil.h"
+#include <string.h>
+
+/**
+ * \class BLAKE2s BLAKE2s.h <BLAKE2s.h>
+ * \brief BLAKE2s hash algorithm.
+ *
+ * BLAKE2s is a variation on the ChaCha stream cipher, designed for hashing,
+ * with a 256-bit hash output.  It is intended as a high performance
+ * replacement for SHA256 for when speed is critical but exact SHA256
+ * compatibility is not.
+ *
+ * This class supports two types of keyed hash.  The BLAKE2 keyed hash and
+ * traditional HMAC.  The BLAKE2 keyed hash is recommended unless there is
+ * some higher-level application need to be compatible with the HMAC
+ * construction.  The keyed hash is computed as follows:
+ *
+ * \code
+ * BLAKE2s blake;
+ * blake.reset(key, sizeof(key), outputLength);
+ * blake.update(data1, sizeof(data1));
+ * blake.update(data2, sizeof(data2));
+ * ...
+ * blake.update(dataN, sizeof(dataN));
+ * blake.finalize(hash, outputLength);
+ * \endcode
+ *
+ * The HMAC is computed as follows (the output length is always 32):
+ *
+ * \code
+ * BLAKE2s blake;
+ * blake.resetHMAC(key, sizeof(key));
+ * blake.update(data1, sizeof(data1));
+ * blake.update(data2, sizeof(data2));
+ * ...
+ * blake.update(dataN, sizeof(dataN));
+ * blake.finalizeHMAC(key, sizeof(key), hash, 32);
+ * \endcode
+ *
+ * References: https://blake2.net/,
+ * <a href="http://tools.ietf.org/html/rfc7693">RFC 7693</a>
+ *
+ * \sa BLAKE2b, SHA256, SHA3_256
+ */
+
+/**
+ * \brief Constructs a BLAKE2s hash object.
+ */
+BLAKE2s::BLAKE2s()
+{
+    reset();
+}
+
+/**
+ * \brief Destroys this BLAKE2s hash object after clearing
+ * sensitive information.
+ */
+BLAKE2s::~BLAKE2s()
+{
+    clean(state);
+}
+
+size_t BLAKE2s::hashSize() const
+{
+    return 32;
+}
+
+size_t BLAKE2s::blockSize() const
+{
+    return 64;
+}
+
+// Initialization vectors for BLAKE2s.
+#define BLAKE2s_IV0 0x6A09E667
+#define BLAKE2s_IV1 0xBB67AE85
+#define BLAKE2s_IV2 0x3C6EF372
+#define BLAKE2s_IV3 0xA54FF53A
+#define BLAKE2s_IV4 0x510E527F
+#define BLAKE2s_IV5 0x9B05688C
+#define BLAKE2s_IV6 0x1F83D9AB
+#define BLAKE2s_IV7 0x5BE0CD19
+
+void BLAKE2s::reset()
+{
+    state.h[0] = BLAKE2s_IV0 ^ 0x01010020; // Default output length of 32.
+    state.h[1] = BLAKE2s_IV1;
+    state.h[2] = BLAKE2s_IV2;
+    state.h[3] = BLAKE2s_IV3;
+    state.h[4] = BLAKE2s_IV4;
+    state.h[5] = BLAKE2s_IV5;
+    state.h[6] = BLAKE2s_IV6;
+    state.h[7] = BLAKE2s_IV7;
+    state.chunkSize = 0;
+    state.length = 0;
+}
+
+/**
+ * \brief Resets the hash ready for a new hashing process with a specified
+ * output length.
+ *
+ * \param outputLength The output length to use for the final hash in bytes,
+ * between 1 and 32.
+ */
+void BLAKE2s::reset(uint8_t outputLength)
+{
+    if (outputLength < 1)
+        outputLength = 1;
+    else if (outputLength > 32)
+        outputLength = 32;
+    state.h[0] = BLAKE2s_IV0 ^ 0x01010000 ^ outputLength;
+    state.h[1] = BLAKE2s_IV1;
+    state.h[2] = BLAKE2s_IV2;
+    state.h[3] = BLAKE2s_IV3;
+    state.h[4] = BLAKE2s_IV4;
+    state.h[5] = BLAKE2s_IV5;
+    state.h[6] = BLAKE2s_IV6;
+    state.h[7] = BLAKE2s_IV7;
+    state.chunkSize = 0;
+    state.length = 0;
+}
+
+/**
+ * \brief Resets the hash ready for a new hashing process with a specified
+ * key and output length.
+ *
+ * \param key Points to the key.
+ * \param keyLen The length of the key in bytes, between 0 and 32.
+ * \param outputLength The output length to use for the final hash in bytes,
+ * between 1 and 32.
+ *
+ * If \a keyLen is greater than 32, then the \a key will be truncated to
+ * the first 32 bytes.
+ */
+void BLAKE2s::reset(const void *key, size_t keyLen, uint8_t outputLength)
+{
+    if (keyLen > 32)
+        keyLen = 32;
+    if (outputLength < 1)
+        outputLength = 1;
+    else if (outputLength > 32)
+        outputLength = 32;
+    state.h[0] = BLAKE2s_IV0 ^ 0x01010000 ^ (keyLen << 8) ^ outputLength;
+    state.h[1] = BLAKE2s_IV1;
+    state.h[2] = BLAKE2s_IV2;
+    state.h[3] = BLAKE2s_IV3;
+    state.h[4] = BLAKE2s_IV4;
+    state.h[5] = BLAKE2s_IV5;
+    state.h[6] = BLAKE2s_IV6;
+    state.h[7] = BLAKE2s_IV7;
+    if (keyLen > 0) {
+        // Set the first block to the key and pad with zeroes.
+        memcpy(state.m, key, keyLen);
+        memset(((uint8_t *)state.m) + keyLen, 0, 64 - keyLen);
+        state.chunkSize = 64;
+        state.length = 64;
+    } else {
+        // No key.  The first data block is the first hashed block.
+        state.chunkSize = 0;
+        state.length = 0;
+    }
+}
+
+void BLAKE2s::update(const void *data, size_t len)
+{
+    // Break the input up into 512-bit chunks and process each in turn.
+    const uint8_t *d = (const uint8_t *)data;
+    while (len > 0) {
+        if (state.chunkSize == 64) {
+            // Previous chunk was full and we know that it wasn't the
+            // last chunk, so we can process it now with f0 set to zero.
+            processChunk(0);
+            state.chunkSize = 0;
+        }
+        uint8_t size = 64 - state.chunkSize;
+        if (size > len)
+            size = len;
+        memcpy(((uint8_t *)state.m) + state.chunkSize, d, size);
+        state.chunkSize += size;
+        state.length += size;
+        len -= size;
+        d += size;
+    }
+}
+
+void BLAKE2s::finalize(void *hash, size_t len)
+{
+    // Pad the last chunk and hash it with f0 set to all-ones.
+    memset(((uint8_t *)state.m) + state.chunkSize, 0, 64 - state.chunkSize);
+    processChunk(0xFFFFFFFF);
+
+    // Convert the hash into little-endian in the message buffer.
+    for (uint8_t posn = 0; posn < 8; ++posn)
+        state.m[posn] = htole32(state.h[posn]);
+
+    // Copy the hash to the caller's return buffer.
+    if (len > 32)
+        len = 32;
+    memcpy(hash, state.m, len);
+}
+
+void BLAKE2s::clear()
+{
+    clean(state);
+    reset();
+}
+
+void BLAKE2s::resetHMAC(const void *key, size_t keyLen)
+{
+    formatHMACKey(state.m, key, keyLen, 0x36);
+    state.length += 64;
+    processChunk(0);
+}
+
+void BLAKE2s::finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen)
+{
+    uint8_t temp[32];
+    finalize(temp, sizeof(temp));
+    formatHMACKey(state.m, key, keyLen, 0x5C);
+    state.length += 64;
+    processChunk(0);
+    update(temp, sizeof(temp));
+    finalize(hash, hashLen);
+    clean(temp);
+}
+
+// Permutation on the message input state for BLAKE2s.
+static const uint8_t sigma[10][16] PROGMEM = {
+    { 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15},
+    {14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3},
+    {11,  8, 12,  0,  5,  2, 15, 13, 10, 14,  3,  6,  7,  1,  9,  4},
+    { 7,  9,  3,  1, 13, 12, 11, 14,  2,  6,  5, 10,  4,  0, 15,  8},
+    { 9,  0,  5,  7,  2,  4, 10, 15, 14,  1, 11, 12,  6,  8,  3, 13},
+    { 2, 12,  6, 10,  0, 11,  8,  3,  4, 13,  7,  5, 15, 14,  1,  9},
+    {12,  5,  1, 15, 14, 13,  4, 10,  0,  7,  6,  3,  9,  2,  8, 11},
+    {13, 11,  7, 14, 12,  1,  3,  9,  5,  0, 15,  4,  8,  6,  2, 10},
+    { 6, 15, 14,  9, 11,  3,  0,  8, 12,  2, 13,  7,  1,  4, 10,  5},
+    {10,  2,  8,  4,  7,  6,  1,  5, 15, 11,  9, 14,  3, 12, 13 , 0}
+};
+
+// Perform a BLAKE2s quarter round operation.
+#define quarterRound(a, b, c, d, i)    \
+    do { \
+        uint32_t _b = (b); \
+        uint32_t _a = (a) + _b + state.m[pgm_read_byte(&(sigma[index][2 * (i)]))]; \
+        uint32_t _d = rightRotate16((d) ^ _a); \
+        uint32_t _c = (c) + _d; \
+        _b = rightRotate12(_b ^ _c); \
+        _a += _b + state.m[pgm_read_byte(&(sigma[index][2 * (i) + 1]))]; \
+        (d) = _d = rightRotate8(_d ^ _a); \
+        _c += _d; \
+        (a) = _a; \
+        (b) = rightRotate7(_b ^ _c); \
+        (c) = _c; \
+    } while (0)
+
+void BLAKE2s::processChunk(uint32_t f0)
+{
+    uint8_t index;
+    uint32_t v[16];
+
+    // Byte-swap the message buffer into little-endian if necessary.
+#if !defined(CRYPTO_LITTLE_ENDIAN)
+    for (index = 0; index < 16; ++index)
+        state.m[index] = le32toh(state.m[index]);
+#endif
+
+    // Format the block to be hashed.
+    memcpy(v, state.h, sizeof(state.h));
+    v[8]  = BLAKE2s_IV0;
+    v[9]  = BLAKE2s_IV1;
+    v[10] = BLAKE2s_IV2;
+    v[11] = BLAKE2s_IV3;
+    v[12] = BLAKE2s_IV4 ^ (uint32_t)(state.length);
+    v[13] = BLAKE2s_IV5 ^ (uint32_t)(state.length >> 32);
+    v[14] = BLAKE2s_IV6 ^ f0;
+    v[15] = BLAKE2s_IV7;
+
+    // Perform the 10 BLAKE2s rounds.
+    for (index = 0; index < 10; ++index) {
+        // Column round.
+        quarterRound(v[0], v[4], v[8],  v[12], 0);
+        quarterRound(v[1], v[5], v[9],  v[13], 1);
+        quarterRound(v[2], v[6], v[10], v[14], 2);
+        quarterRound(v[3], v[7], v[11], v[15], 3);
+
+        // Diagonal round.
+        quarterRound(v[0], v[5], v[10], v[15], 4);
+        quarterRound(v[1], v[6], v[11], v[12], 5);
+        quarterRound(v[2], v[7], v[8],  v[13], 6);
+        quarterRound(v[3], v[4], v[9],  v[14], 7);
+    }
+
+    // Combine the new and old hash values.
+    for (index = 0; index < 8; ++index)
+        state.h[index] ^= (v[index] ^ v[index + 8]);
+}
diff --git a/src/modules/Crypto/BLAKE2s.h b/src/modules/Crypto/BLAKE2s.h
new file mode 100644
index 0000000..3ebae2e
--- /dev/null
+++ b/src/modules/Crypto/BLAKE2s.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_BLAKE2S_H
+#define CRYPTO_BLAKE2S_H
+
+#include "Hash.h"
+
+class BLAKE2s : public Hash
+{
+public:
+    BLAKE2s();
+    virtual ~BLAKE2s();
+
+    size_t hashSize() const;
+    size_t blockSize() const;
+
+    void reset();
+    void reset(uint8_t outputLength);
+    void reset(const void *key, size_t keyLen, uint8_t outputLength = 32);
+
+    void update(const void *data, size_t len);
+    void finalize(void *hash, size_t len);
+
+    void clear();
+
+    void resetHMAC(const void *key, size_t keyLen);
+    void finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen);
+
+private:
+    struct {
+        uint32_t h[8];
+        uint32_t m[16];
+        uint64_t length;
+        uint8_t chunkSize;
+    } state;
+
+    void processChunk(uint32_t f0);
+};
+
+#endif
diff --git a/src/modules/Crypto/BigNumberUtil.cpp b/src/modules/Crypto/BigNumberUtil.cpp
new file mode 100644
index 0000000..976603c
--- /dev/null
+++ b/src/modules/Crypto/BigNumberUtil.cpp
@@ -0,0 +1,769 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "BigNumberUtil.h"
+#include "utility/EndianUtil.h"
+#include "utility/LimbUtil.h"
+#include <string.h>
+
+/**
+ * \class BigNumberUtil BigNumberUtil.h <BigNumberUtil.h>
+ * \brief Utilities to assist with implementing big number arithmetic.
+ *
+ * Big numbers are represented as arrays of limb_t words, which may be
+ * 8 bits, 16 bits, or 32 bits in size depending upon how the library
+ * was configured.  For AVR, 16 bit limbs usually give the best performance.
+ *
+ * Limb arrays are ordered from the least significant word to the most
+ * significant.
+ */
+
+/**
+ * \brief Unpacks the little-endian byte representation of a big number
+ * into a limb array.
+ *
+ * \param limbs The limb array, starting with the least significant word.
+ * \param count The number of elements in the \a limbs array.
+ * \param bytes The bytes to unpack.
+ * \param len The number of bytes to unpack.
+ *
+ * If \a len is shorter than the length of \a limbs, then the high bytes
+ * will be filled with zeroes.  If \a len is longer than the length of
+ * \a limbs, then the high bytes will be truncated and lost.
+ *
+ * \sa packLE(), unpackBE()
+ */
+void BigNumberUtil::unpackLE(limb_t *limbs, size_t count,
+                             const uint8_t *bytes, size_t len)
+{
+#if BIGNUMBER_LIMB_8BIT
+    if (len < count) {
+        memcpy(limbs, bytes, len);
+        memset(limbs + len, 0, count - len);
+    } else {
+        memcpy(limbs, bytes, count);
+    }
+#elif CRYPTO_LITTLE_ENDIAN
+    count *= sizeof(limb_t);
+    if (len < count) {
+        memcpy(limbs, bytes, len);
+        memset(((uint8_t *)limbs) + len, 0, count - len);
+    } else {
+        memcpy(limbs, bytes, count);
+    }
+#elif BIGNUMBER_LIMB_16BIT
+    while (count > 0 && len >= 2) {
+        *limbs++ = ((limb_t)(bytes[0])) |
+                  (((limb_t)(bytes[1])) << 8);
+        bytes += 2;
+        --count;
+        len -= 2;
+    }
+    if (count > 0 && len == 1) {
+        *limbs++ = ((limb_t)(bytes[0]));
+        --count;
+    }
+    while (count > 0) {
+        *limbs++ = 0;
+        --count;
+    }
+#elif BIGNUMBER_LIMB_32BIT
+    while (count > 0 && len >= 4) {
+        *limbs++ = ((limb_t)(bytes[0])) |
+                  (((limb_t)(bytes[1])) <<  8) |
+                  (((limb_t)(bytes[2])) << 16) |
+                  (((limb_t)(bytes[3])) << 24);
+        bytes += 4;
+        --count;
+        len -= 4;
+    }
+    if (count > 0 && len > 0) {
+        if (len == 3) {
+            *limbs++ = ((limb_t)(bytes[0])) |
+                      (((limb_t)(bytes[1])) <<  8) |
+                      (((limb_t)(bytes[2])) << 16);
+        } else if (len == 2) {
+            *limbs++ = ((limb_t)(bytes[0])) |
+                      (((limb_t)(bytes[1])) <<  8);
+        } else {
+            *limbs++ = ((limb_t)(bytes[0]));
+        }
+        --count;
+    }
+    while (count > 0) {
+        *limbs++ = 0;
+        --count;
+    }
+#elif BIGNUMBER_LIMB_64BIT
+    while (count > 0 && len >= 8) {
+        *limbs++ = ((limb_t)(bytes[0])) |
+                  (((limb_t)(bytes[1])) <<  8) |
+                  (((limb_t)(bytes[2])) << 16) |
+                  (((limb_t)(bytes[3])) << 24) |
+                  (((limb_t)(bytes[4])) << 32) |
+                  (((limb_t)(bytes[5])) << 40) |
+                  (((limb_t)(bytes[6])) << 48) |
+                  (((limb_t)(bytes[7])) << 56);
+        bytes += 8;
+        --count;
+        len -= 8;
+    }
+    if (count > 0 && len > 0) {
+        limb_t word = 0;
+        uint8_t shift = 0;
+        while (len > 0 && shift < 64) {
+            word |= (((limb_t)(*bytes++)) << shift);
+            shift += 8;
+            --len;
+        }
+        *limbs++ = word;
+        --count;
+    }
+    while (count > 0) {
+        *limbs++ = 0;
+        --count;
+    }
+#endif
+}
+
+/**
+ * \brief Unpacks the big-endian byte representation of a big number
+ * into a limb array.
+ *
+ * \param limbs The limb array, starting with the least significant word.
+ * \param count The number of elements in the \a limbs array.
+ * \param bytes The bytes to unpack.
+ * \param len The number of bytes to unpack.
+ *
+ * If \a len is shorter than the length of \a limbs, then the high bytes
+ * will be filled with zeroes.  If \a len is longer than the length of
+ * \a limbs, then the high bytes will be truncated and lost.
+ *
+ * \sa packBE(), unpackLE()
+ */
+void BigNumberUtil::unpackBE(limb_t *limbs, size_t count,
+                             const uint8_t *bytes, size_t len)
+{
+#if BIGNUMBER_LIMB_8BIT
+    while (count > 0 && len > 0) {
+        --count;
+        --len;
+        *limbs++ = bytes[len];
+    }
+    memset(limbs, 0, count);
+#elif BIGNUMBER_LIMB_16BIT
+    bytes += len;
+    while (count > 0 && len >= 2) {
+        --count;
+        bytes -= 2;
+        len -= 2;
+        *limbs++ = ((limb_t)(bytes[1])) |
+                  (((limb_t)(bytes[0])) << 8);
+    }
+    if (count > 0 && len == 1) {
+        --count;
+        --bytes;
+        *limbs++ = (limb_t)(bytes[0]);
+    }
+    memset(limbs, 0, count * sizeof(limb_t));
+#elif BIGNUMBER_LIMB_32BIT
+    bytes += len;
+    while (count > 0 && len >= 4) {
+        --count;
+        bytes -= 4;
+        len -= 4;
+        *limbs++ = ((limb_t)(bytes[3])) |
+                  (((limb_t)(bytes[2])) << 8) |
+                  (((limb_t)(bytes[1])) << 16) |
+                  (((limb_t)(bytes[0])) << 24);
+    }
+    if (count > 0) {
+        if (len == 3) {
+            --count;
+            bytes -= 3;
+            *limbs++ = ((limb_t)(bytes[2])) |
+                      (((limb_t)(bytes[1])) << 8) |
+                      (((limb_t)(bytes[0])) << 16);
+        } else if (len == 2) {
+            --count;
+            bytes -= 2;
+            *limbs++ = ((limb_t)(bytes[1])) |
+                      (((limb_t)(bytes[0])) << 8);
+        } else if (len == 1) {
+            --count;
+            --bytes;
+            *limbs++ = (limb_t)(bytes[0]);
+        }
+    }
+    memset(limbs, 0, count * sizeof(limb_t));
+#elif BIGNUMBER_LIMB_64BIT
+    bytes += len;
+    while (count > 0 && len >= 8) {
+        --count;
+        bytes -= 8;
+        len -= 8;
+        *limbs++ = ((limb_t)(bytes[7])) |
+                  (((limb_t)(bytes[6])) << 8) |
+                  (((limb_t)(bytes[5])) << 16) |
+                  (((limb_t)(bytes[4])) << 24) |
+                  (((limb_t)(bytes[3])) << 32) |
+                  (((limb_t)(bytes[2])) << 40) |
+                  (((limb_t)(bytes[1])) << 48) |
+                  (((limb_t)(bytes[0])) << 56);
+    }
+    if (count > 0 && len > 0) {
+        limb_t word = 0;
+        uint8_t shift = 0;
+        while (len > 0 && shift < 64) {
+            word |= (((limb_t)(*(--bytes))) << shift);
+            shift += 8;
+            --len;
+        }
+        *limbs++ = word;
+        --count;
+    }
+    memset(limbs, 0, count * sizeof(limb_t));
+#endif
+}
+
+/**
+ * \brief Packs the little-endian byte representation of a big number
+ * into a byte array.
+ *
+ * \param bytes The byte array to pack into.
+ * \param len The number of bytes in the destination \a bytes array.
+ * \param limbs The limb array representing the big number, starting with
+ * the least significant word.
+ * \param count The number of elements in the \a limbs array.
+ *
+ * If \a len is shorter than the length of \a limbs, then the number will
+ * be truncated to the least significant \a len bytes.  If \a len is longer
+ * than the length of \a limbs, then the high bytes will be filled with zeroes.
+ *
+ * \sa unpackLE(), packBE()
+ */
+void BigNumberUtil::packLE(uint8_t *bytes, size_t len,
+                           const limb_t *limbs, size_t count)
+{
+#if BIGNUMBER_LIMB_8BIT
+    if (len <= count) {
+        memcpy(bytes, limbs, len);
+    } else {
+        memcpy(bytes, limbs, count);
+        memset(bytes + count, 0, len - count);
+    }
+#elif CRYPTO_LITTLE_ENDIAN
+    count *= sizeof(limb_t);
+    if (len <= count) {
+        memcpy(bytes, limbs, len);
+    } else {
+        memcpy(bytes, limbs, count);
+        memset(bytes + count, 0, len - count);
+    }
+#elif BIGNUMBER_LIMB_16BIT
+    limb_t word;
+    while (count > 0 && len >= 2) {
+        word = *limbs++;
+        bytes[0] = (uint8_t)word;
+        bytes[1] = (uint8_t)(word >> 8);
+        --count;
+        len -= 2;
+        bytes += 2;
+    }
+    if (count > 0 && len == 1) {
+        bytes[0] = (uint8_t)(*limbs);
+        --len;
+        ++bytes;
+    }
+    memset(bytes, 0, len);
+#elif BIGNUMBER_LIMB_32BIT
+    limb_t word;
+    while (count > 0 && len >= 4) {
+        word = *limbs++;
+        bytes[0] = (uint8_t)word;
+        bytes[1] = (uint8_t)(word >> 8);
+        bytes[2] = (uint8_t)(word >> 16);
+        bytes[3] = (uint8_t)(word >> 24);
+        --count;
+        len -= 4;
+        bytes += 4;
+    }
+    if (count > 0) {
+        if (len == 3) {
+            word = *limbs;
+            bytes[0] = (uint8_t)word;
+            bytes[1] = (uint8_t)(word >> 8);
+            bytes[2] = (uint8_t)(word >> 16);
+            len -= 3;
+            bytes += 3;
+        } else if (len == 2) {
+            word = *limbs;
+            bytes[0] = (uint8_t)word;
+            bytes[1] = (uint8_t)(word >> 8);
+            len -= 2;
+            bytes += 2;
+        } else if (len == 1) {
+            bytes[0] = (uint8_t)(*limbs);
+            --len;
+            ++bytes;
+        }
+    }
+    memset(bytes, 0, len);
+#elif BIGNUMBER_LIMB_64BIT
+    limb_t word;
+    while (count > 0 && len >= 8) {
+        word = *limbs++;
+        bytes[0] = (uint8_t)word;
+        bytes[1] = (uint8_t)(word >> 8);
+        bytes[2] = (uint8_t)(word >> 16);
+        bytes[3] = (uint8_t)(word >> 24);
+        bytes[4] = (uint8_t)(word >> 32);
+        bytes[5] = (uint8_t)(word >> 40);
+        bytes[6] = (uint8_t)(word >> 48);
+        bytes[7] = (uint8_t)(word >> 56);
+        --count;
+        len -= 8;
+        bytes += 8;
+    }
+    if (count > 0) {
+        word = *limbs;
+        while (len > 0) {
+            *bytes++ = (uint8_t)word;
+            word >>= 8;
+            --len;
+        }
+    }
+    memset(bytes, 0, len);
+#endif
+}
+
+/**
+ * \brief Packs the big-endian byte representation of a big number
+ * into a byte array.
+ *
+ * \param bytes The byte array to pack into.
+ * \param len The number of bytes in the destination \a bytes array.
+ * \param limbs The limb array representing the big number, starting with
+ * the least significant word.
+ * \param count The number of elements in the \a limbs array.
+ *
+ * If \a len is shorter than the length of \a limbs, then the number will
+ * be truncated to the least significant \a len bytes.  If \a len is longer
+ * than the length of \a limbs, then the high bytes will be filled with zeroes.
+ *
+ * \sa unpackLE(), packBE()
+ */
+void BigNumberUtil::packBE(uint8_t *bytes, size_t len,
+                           const limb_t *limbs, size_t count)
+{
+#if BIGNUMBER_LIMB_8BIT
+    if (len > count) {
+        size_t size = len - count;
+        memset(bytes, 0, size);
+        len -= size;
+        bytes += size;
+    } else if (len < count) {
+        count = len;
+    }
+    limbs += count;
+    while (count > 0) {
+        --count;
+        *bytes++ = *(--limbs);
+    }
+#elif BIGNUMBER_LIMB_16BIT
+    size_t countBytes = count * sizeof(limb_t);
+    limb_t word;
+    if (len >= countBytes) {
+        size_t size = len - countBytes;
+        memset(bytes, 0, size);
+        len -= size;
+        bytes += size;
+        limbs += count;
+    } else {
+        count = len / sizeof(limb_t);
+        limbs += count;
+        if ((len & 1) != 0)
+            *bytes++ = (uint8_t)(*limbs);
+    }
+    while (count > 0) {
+        --count;
+        word = *(--limbs);
+        *bytes++ = (uint8_t)(word >> 8);
+        *bytes++ = (uint8_t)word;
+    }
+#elif BIGNUMBER_LIMB_32BIT
+    size_t countBytes = count * sizeof(limb_t);
+    limb_t word;
+    if (len >= countBytes) {
+        size_t size = len - countBytes;
+        memset(bytes, 0, size);
+        len -= size;
+        bytes += size;
+        limbs += count;
+    } else {
+        count = len / sizeof(limb_t);
+        limbs += count;
+        if ((len & 3) == 3) {
+            word = *limbs;
+            *bytes++ = (uint8_t)(word >> 16);
+            *bytes++ = (uint8_t)(word >> 8);
+            *bytes++ = (uint8_t)word;
+        } else if ((len & 3) == 2) {
+            word = *limbs;
+            *bytes++ = (uint8_t)(word >> 8);
+            *bytes++ = (uint8_t)word;
+        } else if ((len & 3) == 1) {
+            *bytes++ = (uint8_t)(*limbs);
+        }
+    }
+    while (count > 0) {
+        --count;
+        word = *(--limbs);
+        *bytes++ = (uint8_t)(word >> 24);
+        *bytes++ = (uint8_t)(word >> 16);
+        *bytes++ = (uint8_t)(word >> 8);
+        *bytes++ = (uint8_t)word;
+    }
+#elif BIGNUMBER_LIMB_64BIT
+    size_t countBytes = count * sizeof(limb_t);
+    limb_t word;
+    if (len >= countBytes) {
+        size_t size = len - countBytes;
+        memset(bytes, 0, size);
+        len -= size;
+        bytes += size;
+        limbs += count;
+    } else {
+        count = len / sizeof(limb_t);
+        limbs += count;
+        uint8_t size = len & 7;
+        uint8_t shift = size * 8;
+        word = *limbs;
+        while (size > 0) {
+            shift -= 8;
+            *bytes++ = (uint8_t)(word >> shift);
+            --size;
+        }
+    }
+    while (count > 0) {
+        --count;
+        word = *(--limbs);
+        *bytes++ = (uint8_t)(word >> 56);
+        *bytes++ = (uint8_t)(word >> 48);
+        *bytes++ = (uint8_t)(word >> 40);
+        *bytes++ = (uint8_t)(word >> 32);
+        *bytes++ = (uint8_t)(word >> 24);
+        *bytes++ = (uint8_t)(word >> 16);
+        *bytes++ = (uint8_t)(word >> 8);
+        *bytes++ = (uint8_t)word;
+    }
+#endif
+}
+
+/**
+ * \brief Adds two big numbers.
+ *
+ * \param result The result of the addition.  This can be the same
+ * as either \a x or \a y.
+ * \param x The first big number.
+ * \param y The second big number.
+ * \param size The size of the values in limbs.
+ *
+ * \return Returns 1 if there was a carry out or 0 if there was no carry out.
+ *
+ * \sa sub(), mul()
+ */
+limb_t BigNumberUtil::add(limb_t *result, const limb_t *x,
+                          const limb_t *y, size_t size)
+{
+    dlimb_t carry = 0;
+    while (size > 0) {
+        carry += *x++;
+        carry += *y++;
+        *result++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+        --size;
+    }
+    return (limb_t)carry;
+}
+
+/**
+ * \brief Subtracts one big number from another.
+ *
+ * \param result The result of the subtraction.  This can be the same
+ * as either \a x or \a y.
+ * \param x The first big number.
+ * \param y The second big number to subtract from \a x.
+ * \param size The size of the values in limbs.
+ *
+ * \return Returns 1 if there was a borrow, or 0 if there was no borrow.
+ *
+ * \sa add(), mul()
+ */
+limb_t BigNumberUtil::sub(limb_t *result, const limb_t *x,
+                          const limb_t *y, size_t size)
+{
+    dlimb_t borrow = 0;
+    while (size > 0) {
+        borrow = ((dlimb_t)(*x++)) - (*y++) - ((borrow >> LIMB_BITS) & 0x01);
+        *result++ = (limb_t)borrow;
+        --size;
+    }
+    return ((limb_t)(borrow >> LIMB_BITS)) & 0x01;
+}
+
+/**
+ * \brief Multiplies two big numbers.
+ *
+ * \param result The result of the multiplication.  The array must be
+ * \a xcount + \a ycount limbs in size.
+ * \param x Points to the first value to multiply.
+ * \param xcount The number of limbs in \a x.
+ * \param y Points to the second value to multiply.
+ * \param ycount The number of limbs in \a y.
+ *
+ * \sa mul_P()
+ */
+void BigNumberUtil::mul(limb_t *result, const limb_t *x, size_t xcount,
+                        const limb_t *y, size_t ycount)
+{
+    size_t i, j;
+    dlimb_t carry;
+    limb_t word;
+    const limb_t *xx;
+    limb_t *rr;
+
+    // Multiply the lowest limb of y by x.
+    carry = 0;
+    word = y[0];
+    xx = x;
+    rr = result;
+    for (i = 0; i < xcount; ++i) {
+        carry += ((dlimb_t)(*xx++)) * word;
+        *rr++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    *rr = (limb_t)carry;
+
+    // Multiply and add the remaining limbs of y by x.
+    for (i = 1; i < ycount; ++i) {
+        word = y[i];
+        carry = 0;
+        xx = x;
+        rr = result + i;
+        for (j = 0; j < xcount; ++j) {
+            carry += ((dlimb_t)(*xx++)) * word;
+            carry += *rr;
+            *rr++ = (limb_t)carry;
+            carry >>= LIMB_BITS;
+        }
+        *rr = (limb_t)carry;
+    }
+}
+
+/**
+ * \brief Reduces \a x modulo \a y using subtraction.
+ *
+ * \param result The result of the reduction.  This can be the
+ * same as \a x.
+ * \param x The number to be reduced.
+ * \param y The base to use for the modulo reduction.
+ * \param size The size of the values in limbs.
+ *
+ * It is assumed that \a x is less than \a y * 2 so that a single
+ * conditional subtraction will bring it down below \a y.  The reduction
+ * is performed in constant time.
+ *
+ * \sa reduceQuick_P()
+ */
+void BigNumberUtil::reduceQuick(limb_t *result, const limb_t *x,
+                                const limb_t *y, size_t size)
+{
+    // Subtract "y" from "x" and turn the borrow into an AND mask.
+    limb_t mask = sub(result, x, y, size);
+    mask = (~mask) + 1;
+
+    // Add "y" back to the result if the mask is non-zero.
+    dlimb_t carry = 0;
+    while (size > 0) {
+        carry += *result;
+        carry += (*y++ & mask);
+        *result++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+        --size;
+    }
+}
+
+/**
+ * \brief Adds two big numbers where one of them is in program memory.
+ *
+ * \param result The result of the addition.  This can be the same as \a x.
+ * \param x The first big number.
+ * \param y The second big number.  This must point into program memory.
+ * \param size The size of the values in limbs.
+ *
+ * \return Returns 1 if there was a carry out or 0 if there was no carry out.
+ *
+ * \sa sub_P(), mul_P()
+ */
+limb_t BigNumberUtil::add_P(limb_t *result, const limb_t *x,
+                            const limb_t *y, size_t size)
+{
+    dlimb_t carry = 0;
+    while (size > 0) {
+        carry += *x++;
+        carry += pgm_read_limb(y++);
+        *result++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+        --size;
+    }
+    return (limb_t)carry;
+}
+
+/**
+ * \brief Subtracts one big number from another where one is in program memory.
+ *
+ * \param result The result of the subtraction.  This can be the same as \a x.
+ * \param x The first big number.
+ * \param y The second big number to subtract from \a x.  This must point
+ * into program memory.
+ * \param size The size of the values in limbs.
+ *
+ * \return Returns 1 if there was a borrow, or 0 if there was no borrow.
+ *
+ * \sa add_P(), mul_P()
+ */
+limb_t BigNumberUtil::sub_P(limb_t *result, const limb_t *x,
+                            const limb_t *y, size_t size)
+{
+    dlimb_t borrow = 0;
+    while (size > 0) {
+        borrow = ((dlimb_t)(*x++)) - pgm_read_limb(y++) - ((borrow >> LIMB_BITS) & 0x01);
+        *result++ = (limb_t)borrow;
+        --size;
+    }
+    return ((limb_t)(borrow >> LIMB_BITS)) & 0x01;
+}
+
+/**
+ * \brief Multiplies two big numbers where one is in program memory.
+ *
+ * \param result The result of the multiplication.  The array must be
+ * \a xcount + \a ycount limbs in size.
+ * \param x Points to the first value to multiply.
+ * \param xcount The number of limbs in \a x.
+ * \param y Points to the second value to multiply.  This must point
+ * into program memory.
+ * \param ycount The number of limbs in \a y.
+ *
+ * \sa mul()
+ */
+void BigNumberUtil::mul_P(limb_t *result, const limb_t *x, size_t xcount,
+                          const limb_t *y, size_t ycount)
+{
+    size_t i, j;
+    dlimb_t carry;
+    limb_t word;
+    const limb_t *xx;
+    limb_t *rr;
+
+    // Multiply the lowest limb of y by x.
+    carry = 0;
+    word = pgm_read_limb(&(y[0]));
+    xx = x;
+    rr = result;
+    for (i = 0; i < xcount; ++i) {
+        carry += ((dlimb_t)(*xx++)) * word;
+        *rr++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    *rr = (limb_t)carry;
+
+    // Multiply and add the remaining limb of y by x.
+    for (i = 1; i < ycount; ++i) {
+        word = pgm_read_limb(&(y[i]));
+        carry = 0;
+        xx = x;
+        rr = result + i;
+        for (j = 0; j < xcount; ++j) {
+            carry += ((dlimb_t)(*xx++)) * word;
+            carry += *rr;
+            *rr++ = (limb_t)carry;
+            carry >>= LIMB_BITS;
+        }
+        *rr = (limb_t)carry;
+    }
+}
+
+/**
+ * \brief Reduces \a x modulo \a y using subtraction where \a y is
+ * in program memory.
+ *
+ * \param result The result of the reduction.  This can be the
+ * same as \a x.
+ * \param x The number to be reduced.
+ * \param y The base to use for the modulo reduction.  This must point
+ * into program memory.
+ * \param size The size of the values in limbs.
+ *
+ * It is assumed that \a x is less than \a y * 2 so that a single
+ * conditional subtraction will bring it down below \a y.  The reduction
+ * is performed in constant time.
+ *
+ * \sa reduceQuick()
+ */
+void BigNumberUtil::reduceQuick_P(limb_t *result, const limb_t *x,
+                                  const limb_t *y, size_t size)
+{
+    // Subtract "y" from "x" and turn the borrow into an AND mask.
+    limb_t mask = sub_P(result, x, y, size);
+    mask = (~mask) + 1;
+
+    // Add "y" back to the result if the mask is non-zero.
+    dlimb_t carry = 0;
+    while (size > 0) {
+        carry += *result;
+        carry += (pgm_read_limb(y++) & mask);
+        *result++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+        --size;
+    }
+}
+
+/**
+ * \brief Determine if a big number is zero.
+ *
+ * \param x Points to the number to test.
+ * \param size The number of limbs in \a x.
+ * \return Returns 1 if \a x is zero or 0 otherwise.
+ *
+ * This function attempts to make the determination in constant time.
+ */
+limb_t BigNumberUtil::isZero(const limb_t *x, size_t size)
+{
+    limb_t word = 0;
+    while (size > 0) {
+        word |= *x++;
+        --size;
+    }
+    return (limb_t)(((((dlimb_t)1) << LIMB_BITS) - word) >> LIMB_BITS);
+}
diff --git a/src/modules/Crypto/BigNumberUtil.h b/src/modules/Crypto/BigNumberUtil.h
new file mode 100644
index 0000000..2212cbb
--- /dev/null
+++ b/src/modules/Crypto/BigNumberUtil.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_BIGNUMBERUTIL_h
+#define CRYPTO_BIGNUMBERUTIL_h
+
+#include <inttypes.h>
+#include <stddef.h>
+
+// Define exactly one of these to 1 to set the size of the basic limb type.
+#if defined(__AVR__) || defined(ESP8266)
+// 16-bit limbs seem to give the best performance on 8-bit AVR micros.
+// They also seem to give better performance on ESP8266 as well.
+#define BIGNUMBER_LIMB_8BIT  0
+#define BIGNUMBER_LIMB_16BIT 1
+#define BIGNUMBER_LIMB_32BIT 0
+#define BIGNUMBER_LIMB_64BIT 0
+#elif defined(__GNUC__) && __WORDSIZE == 64
+// 64-bit system with 128-bit double limbs.
+#define BIGNUMBER_LIMB_8BIT  0
+#define BIGNUMBER_LIMB_16BIT 0
+#define BIGNUMBER_LIMB_32BIT 0
+#define BIGNUMBER_LIMB_64BIT 1
+#else
+// On all other platforms, assume 32-bit is best.
+#define BIGNUMBER_LIMB_8BIT  0
+#define BIGNUMBER_LIMB_16BIT 0
+#define BIGNUMBER_LIMB_32BIT 1
+#define BIGNUMBER_LIMB_64BIT 0
+#endif
+
+// Define the limb types to use on this platform.
+#if BIGNUMBER_LIMB_8BIT
+typedef uint8_t limb_t;
+typedef int8_t slimb_t;
+typedef uint16_t dlimb_t;
+#elif BIGNUMBER_LIMB_16BIT
+typedef uint16_t limb_t;
+typedef int16_t slimb_t;
+typedef uint32_t dlimb_t;
+#elif BIGNUMBER_LIMB_32BIT
+typedef uint32_t limb_t;
+typedef int32_t slimb_t;
+typedef uint64_t dlimb_t;
+#elif BIGNUMBER_LIMB_64BIT
+typedef uint64_t limb_t;
+typedef int64_t slimb_t;
+typedef unsigned __int128 dlimb_t;
+#else
+#error "limb_t must be 8, 16, 32, or 64 bits in size"
+#endif
+
+class BigNumberUtil
+{
+public:
+    static void unpackLE(limb_t *limbs, size_t count,
+                         const uint8_t *bytes, size_t len);
+    static void unpackBE(limb_t *limbs, size_t count,
+                         const uint8_t *bytes, size_t len);
+    static void packLE(uint8_t *bytes, size_t len,
+                       const limb_t *limbs, size_t count);
+    static void packBE(uint8_t *bytes, size_t len,
+                       const limb_t *limbs, size_t count);
+
+    static limb_t add(limb_t *result, const limb_t *x,
+                      const limb_t *y, size_t size);
+    static limb_t sub(limb_t *result, const limb_t *x,
+                      const limb_t *y, size_t size);
+    static void mul(limb_t *result, const limb_t *x, size_t xcount,
+                    const limb_t *y, size_t ycount);
+    static void reduceQuick(limb_t *result, const limb_t *x,
+                            const limb_t *y, size_t size);
+
+    static limb_t add_P(limb_t *result, const limb_t *x,
+                        const limb_t *y, size_t size);
+    static limb_t sub_P(limb_t *result, const limb_t *x,
+                        const limb_t *y, size_t size);
+    static void mul_P(limb_t *result, const limb_t *x, size_t xcount,
+                      const limb_t *y, size_t ycount);
+    static void reduceQuick_P(limb_t *result, const limb_t *x,
+                              const limb_t *y, size_t size);
+
+    static limb_t isZero(const limb_t *x, size_t size);
+
+private:
+    // Constructor and destructor are private - cannot instantiate this class.
+    BigNumberUtil() {}
+    ~BigNumberUtil() {}
+};
+
+#endif
diff --git a/src/modules/Crypto/BlockCipher.cpp b/src/modules/Crypto/BlockCipher.cpp
new file mode 100644
index 0000000..e98eeb6
--- /dev/null
+++ b/src/modules/Crypto/BlockCipher.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "BlockCipher.h"
+
+/**
+ * \class BlockCipher BlockCipher.h <BlockCipher.h>
+ * \brief Abstract base class for block ciphers.
+ *
+ * Block ciphers always operate in electronic codebook (ECB) mode.
+ * Higher-level classes such as CFB128 and CTR128 wrap the block cipher to
+ * create more useful classes for encryption and decryption of bulk data.
+ *
+ * References: http://en.wikipedia.org/wiki/Block_cipher,
+ * http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29
+ */
+
+/**
+ * \brief Constructs a block cipher.
+ */
+BlockCipher::BlockCipher()
+{
+}
+
+/**
+ * \brief Destroys this block cipher object.
+ *
+ * Subclasses are responsible for clearing temporary key schedules
+ * and other buffers so as to avoid leaking sensitive information.
+ *
+ * \sa clear()
+ */
+BlockCipher::~BlockCipher()
+{
+}
+
+/**
+ * \fn size_t BlockCipher::blockSize() const
+ * \brief Size of a single block processed by this cipher, in bytes.
+ *
+ * \return Returns the size of a block in bytes.
+ *
+ * \sa keySize(), encryptBlock()
+ */
+
+/**
+ * \fn size_t BlockCipher::keySize() const
+ * \brief Default size of the key for this block cipher, in bytes.
+ *
+ * This value indicates the default, or recommended, size for the key.
+ *
+ * \sa setKey(), blockSize()
+ */
+
+/**
+ * \fn bool BlockCipher::setKey(const uint8_t *key, size_t len)
+ * \brief Sets the key to use for future encryption and decryption operations.
+ *
+ * \param key The key to use.
+ * \param len The length of the key.
+ * \return Returns false if the key length is not supported, or the key
+ * is somehow "weak" and unusable by this cipher.
+ *
+ * Use clear() or the destructor to remove the key and any other sensitive
+ * data from the object once encryption or decryption is complete.
+ *
+ * \sa keySize(), clear()
+ */
+
+/**
+ * \fn void BlockCipher::encryptBlock(uint8_t *output, const uint8_t *input)
+ * \brief Encrypts a single block using this cipher.
+ *
+ * \param output The output buffer to put the ciphertext into.
+ * Must be at least blockSize() bytes in length.
+ * \param input The input buffer to read the plaintext from which is
+ * allowed to overlap with \a output.  Must be at least blockSize()
+ * bytes in length.
+ *
+ * \sa decryptBlock(), blockSize()
+ */
+
+/**
+ * \fn void BlockCipher::decryptBlock(uint8_t *output, const uint8_t *input)
+ * \brief Decrypts a single block using this cipher.
+ *
+ * \param output The output buffer to put the plaintext into.
+ * Must be at least blockSize() bytes in length.
+ * \param input The input buffer to read the ciphertext from which is
+ * allowed to overlap with \a output.  Must be at least blockSize()
+ * bytes in length.
+ *
+ * \sa encryptBlock(), blockSize()
+ */
+
+/**
+ * \fn void BlockCipher::clear()
+ * \brief Clears all security-sensitive state from this block cipher.
+ *
+ * Security-sensitive information includes key schedules and any
+ * temporary state that is used by encryptBlock() or decryptBlock()
+ * which is stored in the object itself.
+ *
+ * \sa setKey(), encryptBlock(), decryptBlock()
+ */
diff --git a/src/modules/Crypto/BlockCipher.h b/src/modules/Crypto/BlockCipher.h
new file mode 100644
index 0000000..1d374d0
--- /dev/null
+++ b/src/modules/Crypto/BlockCipher.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_BLOCKCIPHER_h
+#define CRYPTO_BLOCKCIPHER_h
+
+#include <inttypes.h>
+#include <stddef.h>
+
+class BlockCipher
+{
+public:
+    BlockCipher();
+    virtual ~BlockCipher();
+
+    virtual size_t blockSize() const = 0;
+    virtual size_t keySize() const = 0;
+
+    virtual bool setKey(const uint8_t *key, size_t len) = 0;
+
+    virtual void encryptBlock(uint8_t *output, const uint8_t *input) = 0;
+    virtual void decryptBlock(uint8_t *output, const uint8_t *input) = 0;
+
+    virtual void clear() = 0;
+};
+
+#endif
diff --git a/src/modules/Crypto/CTR.cpp b/src/modules/Crypto/CTR.cpp
new file mode 100644
index 0000000..72f4e9e
--- /dev/null
+++ b/src/modules/Crypto/CTR.cpp
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "CTR.h"
+#include "Crypto.h"
+#include <string.h>
+
+/**
+ * \class CTRCommon CTR.h <CTR.h>
+ * \brief Concrete base class to assist with implementing CTR mode for
+ * 128-bit block ciphers.
+ *
+ * Reference: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+ *
+ * \sa CTR
+ */
+
+/**
+ * \brief Constructs a new cipher in CTR mode.
+ *
+ * This constructor should be followed by a call to setBlockCipher().
+ */
+CTRCommon::CTRCommon()
+    : blockCipher(0)
+    , posn(16)
+    , counterStart(0)
+{
+}
+
+CTRCommon::~CTRCommon()
+{
+    // It is assumed that the subclass will clear sensitive
+    // information in the block cipher.
+    clean(counter);
+    clean(state);
+}
+
+size_t CTRCommon::keySize() const
+{
+    return blockCipher->keySize();
+}
+
+size_t CTRCommon::ivSize() const
+{
+    return 16;
+}
+
+/**
+ * \brief Sets the counter size for the IV.
+ *
+ * \param size The number of bytes on the end of the counter block
+ * that are relevant when incrementing, between 1 and 16.
+ * \return Returns false if the \a size value is not between 1 and 16.
+ *
+ * When the counter is incremented during encrypt(), only the last
+ * \a size bytes are considered relevant.  This can be useful
+ * to improve performance when the higher level protocol specifies that
+ * only the least significant N bytes "count".  The high level protocol
+ * should explicitly generate a new initial counter value and key long
+ * before the \a size bytes overflow and wrap around.
+ *
+ * By default, the counter size is 16 which is the same as the block size
+ * of the underlying block cipher.
+ *
+ * \sa setIV()
+ */
+bool CTRCommon::setCounterSize(size_t size)
+{
+    if (size < 1 || size > 16)
+        return false;
+    counterStart = 16 - size;
+    return true;
+}
+
+bool CTRCommon::setKey(const uint8_t *key, size_t len)
+{
+    // Verify the cipher's block size, just in case.
+    if (blockCipher->blockSize() != 16)
+        return false;
+
+    // Set the key on the underlying block cipher.
+    return blockCipher->setKey(key, len);
+}
+
+/**
+ * \brief Sets the initial counter value to use for future encryption and
+ * decryption operations.
+ *
+ * \param iv The initial counter value which must contain exactly 16 bytes.
+ * \param len The length of the counter value, which mut be 16.
+ * \return Returns false if \a len is not exactly 16.
+ *
+ * The precise method to generate the initial counter is not defined by
+ * this class.  Usually higher level protocols like SSL/TLS and SSH
+ * specify how to construct the initial counter value.  This class merely
+ * increments the counter every time a new block of keystream data is needed.
+ *
+ * \sa encrypt(), setCounterSize()
+ */
+bool CTRCommon::setIV(const uint8_t *iv, size_t len)
+{
+    if (len != 16)
+        return false;
+    memcpy(counter, iv, len);
+    posn = 16;
+    return true;
+}
+
+void CTRCommon::encrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+    while (len > 0) {
+        if (posn >= 16) {
+            // Generate a new encrypted counter block.
+            blockCipher->encryptBlock(state, counter);
+            posn = 0;
+
+            // Increment the counter, taking care not to reveal
+            // any timing information about the starting value.
+            // We iterate through the entire counter region even
+            // if we could stop earlier because a byte is non-zero.
+            uint16_t temp = 1;
+            uint8_t index = 16;
+            while (index > counterStart) {
+                --index;
+                temp += counter[index];
+                counter[index] = (uint8_t)temp;
+                temp >>= 8;
+            }
+        }
+        uint8_t templen = 16 - posn;
+        if (templen > len)
+            templen = len;
+        len -= templen;
+        while (templen > 0) {
+            *output++ = *input++ ^ state[posn++];
+            --templen;
+        }
+    }
+}
+
+void CTRCommon::decrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+    encrypt(output, input, len);
+}
+
+void CTRCommon::clear()
+{
+    blockCipher->clear();
+    clean(counter);
+    clean(state);
+    posn = 16;
+}
+
+/**
+ * \fn void CTRCommon::setBlockCipher(BlockCipher *cipher)
+ * \brief Sets the block cipher to use for this CTR object.
+ *
+ * \param cipher The block cipher to use to implement CTR mode,
+ * which must have a block size of 16 bytes (128 bits).
+ *
+ * \note This class only works with block ciphers whose block size is
+ * 16 bytes (128 bits).  If the \a cipher has a different block size,
+ * then setKey() will fail and return false.
+ */
+
+/**
+ * \class CTR CTR.h <CTR.h>
+ * \brief Implementation of the Counter (CTR) mode for 128-bit block ciphers.
+ *
+ * Counter mode converts a block cipher into a stream cipher.  The specific
+ * block cipher is passed as the template parameter T and the key is
+ * specified via the setKey() function.
+ *
+ * Keystream blocks are generated by encrypting an increasing counter value
+ * and XOR'ing it with each byte of input.  The encrypt() and decrypt()
+ * operations are identical.
+ *
+ * The template parameter T must be a concrete subclass of BlockCipher
+ * indicating the specific block cipher to use.  For example, the following
+ * creates a CTR object using AES256 as the underlying cipher:
+ *
+ * \code
+ * CTR<AES256> ctr;
+ * ctr.setKey(key, 32);
+ * ctr.setIV(iv, 16);
+ * ctr.setCounterSize(4);
+ * ctr.encrypt(output, input, len);
+ * \endcode
+ *
+ * In this example, the last 4 bytes of the IV are incremented to count
+ * blocks.  The remaining bytes are left unchanged from block to block.
+ *
+ * Reference: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
+ *
+ * \sa CFB, OFB, CBC
+ */
+
+/**
+ * \fn CTR::CTR()
+ * \brief Constructs a new CTR object for the 128-bit block cipher T.
+ */
diff --git a/src/modules/Crypto/CTR.h b/src/modules/Crypto/CTR.h
new file mode 100644
index 0000000..477166e
--- /dev/null
+++ b/src/modules/Crypto/CTR.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_CTR_h
+#define CRYPTO_CTR_h
+
+#include "Cipher.h"
+#include "BlockCipher.h"
+
+class CTRCommon : public Cipher
+{
+public:
+    virtual ~CTRCommon();
+
+    size_t keySize() const;
+    size_t ivSize() const;
+
+    bool setCounterSize(size_t size);
+
+    bool setKey(const uint8_t *key, size_t len);
+    bool setIV(const uint8_t *iv, size_t len);
+
+    void encrypt(uint8_t *output, const uint8_t *input, size_t len);
+    void decrypt(uint8_t *output, const uint8_t *input, size_t len);
+
+    void clear();
+
+protected:
+    CTRCommon();
+    void setBlockCipher(BlockCipher *cipher) { blockCipher = cipher; }
+
+private:
+    BlockCipher *blockCipher;
+    uint8_t counter[16];
+    uint8_t state[16];
+    uint8_t posn;
+    uint8_t counterStart;
+};
+
+template <typename T>
+class CTR : public CTRCommon
+{
+public:
+    CTR() { setBlockCipher(&cipher); }
+
+private:
+    T cipher;
+};
+
+#endif
diff --git a/src/modules/Crypto/ChaCha.cpp b/src/modules/Crypto/ChaCha.cpp
new file mode 100644
index 0000000..b12b1a8
--- /dev/null
+++ b/src/modules/Crypto/ChaCha.cpp
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "ChaCha.h"
+#include "Crypto.h"
+#include "utility/RotateUtil.h"
+#include "utility/EndianUtil.h"
+#include "utility/ProgMemUtil.h"
+#include <string.h>
+
+/**
+ * \class ChaCha ChaCha.h <ChaCha.h>
+ * \brief ChaCha stream cipher.
+ *
+ * ChaCha is a stream cipher that takes a key, an 8-byte nonce/IV, and a
+ * counter and hashes them to generate a keystream to XOR with the plaintext.
+ * Variations on the ChaCha cipher use 8, 12, or 20 rounds of hashing
+ * operations with either 128-bit or 256-bit keys.
+ *
+ * Reference: http://cr.yp.to/chacha.html
+ */
+
+/**
+ * \brief Constructs a new ChaCha stream cipher.
+ *
+ * \param numRounds Number of encryption rounds to use; usually 8, 12, or 20.
+ */
+ChaCha::ChaCha(uint8_t numRounds)
+    : rounds(numRounds)
+    , posn(64)
+{
+}
+
+ChaCha::~ChaCha()
+{
+    clean(block);
+    clean(stream);
+}
+
+size_t ChaCha::keySize() const
+{
+    // Default key size is 256-bit, but any key size is allowed.
+    return 32;
+}
+
+size_t ChaCha::ivSize() const
+{
+    // We return 8 but we also support 12-byte nonces in setIV().
+    return 8;
+}
+
+/**
+ * \fn uint8_t ChaCha::numRounds() const
+ * \brief Returns the number of encryption rounds; usually 8, 12, or 20.
+ *
+ * \sa setNumRounds()
+ */
+
+/**
+ * \fn void ChaCha::setNumRounds(uint8_t numRounds)
+ * \brief Sets the number of encryption rounds.
+ *
+ * \param numRounds The number of encryption rounds; usually 8, 12, or 20.
+ *
+ * \sa numRounds()
+ */
+
+bool ChaCha::setKey(const uint8_t *key, size_t len)
+{
+    static const char tag128[] PROGMEM = "expand 16-byte k";
+    static const char tag256[] PROGMEM = "expand 32-byte k";
+    if (len <= 16) {
+        memcpy_P(block, tag128, 16);
+        memcpy(block + 16, key, len);
+        memcpy(block + 32, key, len);
+        if (len < 16) {
+            memset(block + 16 + len, 0, 16 - len);
+            memset(block + 32 + len, 0, 16 - len);
+        }
+    } else {
+        if (len > 32)
+            len = 32;
+        memcpy_P(block, tag256, 16);
+        memcpy(block + 16, key, len);
+        if (len < 32)
+            memset(block + 16 + len, 0, 32 - len);
+    }
+    posn = 64;
+    return true;
+}
+
+bool ChaCha::setIV(const uint8_t *iv, size_t len)
+{
+    // From draft-nir-cfrg-chacha20-poly1305-10.txt, we can use either
+    // 64-bit or 96-bit nonces.  The 96-bit nonce consists of the high
+    // word of the counter prepended to a regular 64-bit nonce for ChaCha.
+    if (len == 8) {
+        memset(block + 48, 0, 8);
+        memcpy(block + 56, iv, len);
+        posn = 64;
+        return true;
+    } else if (len == 12) {
+        memset(block + 48, 0, 4);
+        memcpy(block + 52, iv, len);
+        posn = 64;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/**
+ * \brief Sets the starting counter for encryption.
+ *
+ * \param counter A 4-byte or 8-byte value to use for the starting counter
+ * instead of the default value of zero.
+ * \param len The length of the counter, which must be 4 or 8.
+ * \return Returns false if \a len is not 4 or 8.
+ *
+ * This function must be called after setIV() and before the first call
+ * to encrypt().  It is used to specify a different starting value than
+ * zero for the counter portion of the hash input.
+ *
+ * \sa setIV()
+ */
+bool ChaCha::setCounter(const uint8_t *counter, size_t len)
+{
+    // Normally both the IV and the counter are 8 bytes in length.
+    // However, if the IV was 12 bytes, then a 4 byte counter can be used.
+    if (len == 4 || len == 8) {
+        memcpy(block + 48, counter, len);
+        posn = 64;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void ChaCha::encrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+    while (len > 0) {
+        if (posn >= 64) {
+            // Generate a new encrypted counter block.
+            hashCore((uint32_t *)stream, (const uint32_t *)block, rounds);
+            posn = 0;
+
+            // Increment the counter, taking care not to reveal
+            // any timing information about the starting value.
+            // We iterate through the entire counter region even
+            // if we could stop earlier because a byte is non-zero.
+            uint16_t temp = 1;
+            uint8_t index = 48;
+            while (index < 56) {
+                temp += block[index];
+                block[index] = (uint8_t)temp;
+                temp >>= 8;
+                ++index;
+            }
+        }
+        uint8_t templen = 64 - posn;
+        if (templen > len)
+            templen = len;
+        len -= templen;
+        while (templen > 0) {
+            *output++ = *input++ ^ stream[posn++];
+            --templen;
+        }
+    }
+}
+
+void ChaCha::decrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+    encrypt(output, input, len);
+}
+
+/**
+ * \brief Generates a single block of output direct from the keystream.
+ *
+ * \param output The output buffer to fill with keystream bytes.
+ *
+ * Unlike encrypt(), this function does not XOR the keystream with
+ * plaintext data.  Instead it generates the keystream directly into
+ * the caller-supplied buffer.  This is useful if the caller knows
+ * that the plaintext is all-zeroes.
+ *
+ * \sa encrypt()
+ */
+void ChaCha::keystreamBlock(uint32_t *output)
+{
+    // Generate the hash output directly into the caller-supplied buffer.
+    hashCore(output, (const uint32_t *)block, rounds);
+    posn = 64;
+
+    // Increment the lowest counter byte.  We are assuming that the caller
+    // is ChaChaPoly::setKey() and that the previous counter value was zero.
+    block[48] = 1;
+}
+
+void ChaCha::clear()
+{
+    clean(block);
+    clean(stream);
+    posn = 64;
+}
+
+// Perform a ChaCha quarter round operation.
+#define quarterRound(a, b, c, d)    \
+    do { \
+        uint32_t _b = (b); \
+        uint32_t _a = (a) + _b; \
+        uint32_t _d = leftRotate((d) ^ _a, 16); \
+        uint32_t _c = (c) + _d; \
+        _b = leftRotate12(_b ^ _c); \
+        _a += _b; \
+        (d) = _d = leftRotate(_d ^ _a, 8); \
+        _c += _d; \
+        (a) = _a; \
+        (b) = leftRotate7(_b ^ _c); \
+        (c) = _c; \
+    } while (0)
+
+/**
+ * \brief Executes the ChaCha hash core on an input memory block.
+ *
+ * \param output Output memory block, must be at least 16 words in length
+ * and must not overlap with \a input.
+ * \param input Input memory block, must be at least 16 words in length.
+ * \param rounds Number of ChaCha rounds to perform; usually 8, 12, or 20.
+ *
+ * This function is provided for the convenience of applications that need
+ * access to the ChaCha hash core without the higher-level processing that
+ * turns the core into a stream cipher.
+ */
+void ChaCha::hashCore(uint32_t *output, const uint32_t *input, uint8_t rounds)
+{
+    uint8_t posn;
+
+    // Copy the input buffer to the output prior to the first round
+    // and convert from little-endian to host byte order.
+    for (posn = 0; posn < 16; ++posn)
+        output[posn] = le32toh(input[posn]);
+
+    // Perform the ChaCha rounds in sets of two.
+    for (; rounds >= 2; rounds -= 2) {
+        // Column round.
+        quarterRound(output[0], output[4], output[8],  output[12]);
+        quarterRound(output[1], output[5], output[9],  output[13]);
+        quarterRound(output[2], output[6], output[10], output[14]);
+        quarterRound(output[3], output[7], output[11], output[15]);
+
+        // Diagonal round.
+        quarterRound(output[0], output[5], output[10], output[15]);
+        quarterRound(output[1], output[6], output[11], output[12]);
+        quarterRound(output[2], output[7], output[8],  output[13]);
+        quarterRound(output[3], output[4], output[9],  output[14]);
+    }
+
+    // Add the original input to the final output, convert back to
+    // little-endian, and return the result.
+    for (posn = 0; posn < 16; ++posn)
+        output[posn] = htole32(output[posn] + le32toh(input[posn]));
+}
diff --git a/src/modules/Crypto/ChaCha.h b/src/modules/Crypto/ChaCha.h
new file mode 100644
index 0000000..8c05cd4
--- /dev/null
+++ b/src/modules/Crypto/ChaCha.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_CHACHA_h
+#define CRYPTO_CHACHA_h
+
+#include "Cipher.h"
+
+class ChaChaPoly;
+
+class ChaCha : public Cipher
+{
+public:
+    explicit ChaCha(uint8_t numRounds = 20);
+    virtual ~ChaCha();
+
+    size_t keySize() const;
+    size_t ivSize() const;
+
+    uint8_t numRounds() const { return rounds; }
+    void setNumRounds(uint8_t numRounds) { rounds = numRounds; }
+
+    bool setKey(const uint8_t *key, size_t len);
+    bool setIV(const uint8_t *iv, size_t len);
+    bool setCounter(const uint8_t *counter, size_t len);
+
+    void encrypt(uint8_t *output, const uint8_t *input, size_t len);
+    void decrypt(uint8_t *output, const uint8_t *input, size_t len);
+
+    void clear();
+
+    static void hashCore(uint32_t *output, const uint32_t *input, uint8_t rounds);
+
+private:
+    uint8_t block[64];
+    uint8_t stream[64];
+    uint8_t rounds;
+    uint8_t posn;
+
+    void keystreamBlock(uint32_t *output);
+
+    friend class ChaChaPoly;
+};
+
+#endif
diff --git a/src/modules/Crypto/ChaChaPoly.cpp b/src/modules/Crypto/ChaChaPoly.cpp
new file mode 100644
index 0000000..c6cb301
--- /dev/null
+++ b/src/modules/Crypto/ChaChaPoly.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "ChaChaPoly.h"
+#include "Crypto.h"
+#include "utility/EndianUtil.h"
+#include <string.h>
+
+/**
+ * \class ChaChaPoly ChaChaPoly.h <ChaChaPoly.h>
+ * \brief Authenticated cipher based on ChaCha and Poly1305
+ *
+ * ChaChaPoly is an authenticated cipher based on a combination of
+ * ChaCha with 20 rounds for encryption and Poly1305 for authentication.
+ * The resulting cipher has a 256-bit key, a 64-bit or 96-bit
+ * initialization vector, and a 128-bit authentication tag.
+ *
+ * Reference: https://tools.ietf.org/html/draft-irtf-cfrg-chacha20-poly1305-10
+ *
+ * \sa ChaCha, Poly1305, AuthenticatedCipher
+ */
+
+/**
+ * \brief Constructs a new ChaChaPoly authenticated cipher.
+ */
+ChaChaPoly::ChaChaPoly()
+{
+    state.authSize = 0;
+    state.dataSize = 0;
+    state.dataStarted = false;
+    state.ivSize = 8;
+}
+
+/**
+ * \brief Destroys this ChaChaPoly authenticated cipher.
+ */
+ChaChaPoly::~ChaChaPoly()
+{
+    clean(state);
+}
+
+size_t ChaChaPoly::keySize() const
+{
+    // Default key size is 256-bit, but any key size is allowed.
+    return 32;
+}
+
+size_t ChaChaPoly::ivSize() const
+{
+    // Return 8 but we also support 12-byte nonces in setIV().
+    return 8;
+}
+
+size_t ChaChaPoly::tagSize() const
+{
+    // Any tag size between 1 and 16 is supported.
+    return 16;
+}
+
+bool ChaChaPoly::setKey(const uint8_t *key, size_t len)
+{
+    return chacha.setKey(key, len);
+}
+
+bool ChaChaPoly::setIV(const uint8_t *iv, size_t len)
+{
+    // ChaCha::setIV() supports both 64-bit and 96-bit nonces.
+    if (!chacha.setIV(iv, len))
+        return false;
+
+    // Generate the key and nonce to use for Poly1305.
+    uint32_t data[16];
+    chacha.keystreamBlock(data);
+    poly1305.reset(data);
+    memcpy(state.nonce, data + 4, 16);
+    clean(data);
+
+    // Reset the size counters for the auth data and payload.
+    state.authSize = 0;
+    state.dataSize = 0;
+    state.dataStarted = false;
+    state.ivSize = len;
+    return true;
+}
+
+void ChaChaPoly::encrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+    if (!state.dataStarted) {
+        poly1305.pad();
+        state.dataStarted = true;
+    }
+    chacha.encrypt(output, input, len);
+    poly1305.update(output, len);
+    state.dataSize += len;
+}
+
+void ChaChaPoly::decrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+    if (!state.dataStarted) {
+        poly1305.pad();
+        state.dataStarted = true;
+    }
+    poly1305.update(input, len);
+    chacha.encrypt(output, input, len); // encrypt() is the same as decrypt()
+    state.dataSize += len;
+}
+
+void ChaChaPoly::addAuthData(const void *data, size_t len)
+{
+    if (!state.dataStarted) {
+        poly1305.update(data, len);
+        state.authSize += len;
+    }
+}
+
+void ChaChaPoly::computeTag(void *tag, size_t len)
+{
+    uint64_t sizes[2];
+
+    // Pad the final Poly1305 block and then hash the sizes.
+    poly1305.pad();
+    sizes[0] = htole64(state.authSize);
+    sizes[1] = htole64(state.dataSize);
+    poly1305.update(sizes, sizeof(sizes));
+
+    // Compute the tag and copy it to the return buffer.
+    poly1305.finalize(state.nonce, tag, len);
+    clean(sizes);
+}
+
+bool ChaChaPoly::checkTag(const void *tag, size_t len)
+{
+    // Can never match if the expected tag length is too long.
+    if (len > 16)
+        return false;
+
+    // Compute the tag and check it.
+    uint8_t temp[16];
+    computeTag(temp, len);
+    bool equal = secure_compare(temp, tag, len);
+    clean(temp);
+    return equal;
+}
+
+void ChaChaPoly::clear()
+{
+    chacha.clear();
+    poly1305.clear();
+    clean(state);
+    state.ivSize = 8;
+}
diff --git a/src/modules/Crypto/ChaChaPoly.h b/src/modules/Crypto/ChaChaPoly.h
new file mode 100644
index 0000000..2e7247e
--- /dev/null
+++ b/src/modules/Crypto/ChaChaPoly.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_CHACHAPOLY_H
+#define CRYPTO_CHACHAPOLY_H
+
+#include "AuthenticatedCipher.h"
+#include "ChaCha.h"
+#include "Poly1305.h"
+
+class ChaChaPoly : public AuthenticatedCipher
+{
+public:
+    ChaChaPoly();
+    virtual ~ChaChaPoly();
+
+    size_t keySize() const;
+    size_t ivSize() const;
+    size_t tagSize() const;
+
+    bool setKey(const uint8_t *key, size_t len);
+    bool setIV(const uint8_t *iv, size_t len);
+
+    void encrypt(uint8_t *output, const uint8_t *input, size_t len);
+    void decrypt(uint8_t *output, const uint8_t *input, size_t len);
+
+    void addAuthData(const void *data, size_t len);
+
+    void computeTag(void *tag, size_t len);
+    bool checkTag(const void *tag, size_t len);
+
+    void clear();
+
+private:
+    ChaCha chacha;
+    Poly1305 poly1305;
+    struct {
+        uint8_t nonce[16];
+        uint64_t authSize;
+        uint64_t dataSize;
+        bool dataStarted;
+        uint8_t ivSize;
+    } state;
+};
+
+#endif
diff --git a/src/modules/Crypto/Cipher.cpp b/src/modules/Crypto/Cipher.cpp
new file mode 100644
index 0000000..f91a14a
--- /dev/null
+++ b/src/modules/Crypto/Cipher.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "Cipher.h"
+
+/**
+ * \class Cipher Cipher.h <Cipher.h>
+ * \brief Abstract base class for stream ciphers.
+ *
+ * This class is intended for implementing ciphers that operate on arbitrary
+ * amounts of data.  In particular, stream ciphers where the number of
+ * bytes that are input to encrypt() or decrypt() is exactly the same as
+ * the number of bytes that are output.
+ *
+ * All of the stream ciphers such as ChaCha inherit directly from this class,
+ * together with block cipher modes such as CTR and CFB.
+ */
+
+/**
+ * \brief Constructs a new cipher object.
+ */
+Cipher::Cipher()
+{
+}
+
+/**
+ * \brief Destroys this cipher object.
+ *
+ * Subclasses are responsible for clearing temporary key schedules
+ * and other buffers so as to avoid leaking sensitive information.
+ *
+ * \sa clear()
+ */
+Cipher::~Cipher()
+{
+}
+
+/**
+ * \fn size_t Cipher::keySize() const
+ * \brief Default size of the key for this cipher, in bytes.
+ *
+ * If the cipher supports variable-sized keys, keySize() indicates the
+ * default or recommended key size.  The cipher may support other key sizes.
+ *
+ * \sa setKey(), ivSize()
+ */
+
+/**
+ * \fn size_t Cipher::ivSize() const
+ * \brief Size of the initialization vector for this cipher, in bytes.
+ *
+ * If the cipher does not need an initialization vector, this function
+ * will return zero.
+ */
+
+/**
+ * \fn bool Cipher::setKey(const uint8_t *key, size_t len)
+ * \brief Sets the key to use for future encryption and decryption operations.
+ *
+ * \param key The key to use.
+ * \param len The length of the key in bytes.
+ * \return Returns false if the key length is not supported, or the key
+ * is somehow "weak" and unusable by this cipher.
+ *
+ * Use clear() or the destructor to remove the key and any other sensitive
+ * data from the object once encryption or decryption is complete.
+ *
+ * Calling setKey() resets the cipher.  Any temporary data that was being
+ * retained for encrypting partial blocks will be abandoned.
+ *
+ * \sa keySize(), clear()
+ */
+
+/**
+ * \fn bool Cipher::setIV(const uint8_t *iv, size_t len)
+ * \brief Sets the initialization vector to use for future encryption and
+ * decryption operations.
+ *
+ * \param iv The initialization vector to use.
+ * \param len The length of the initialization vector in bytes.
+ * \return Returns false if the length is not supported.
+ *
+ * Initialization vectors should be set before the first call to
+ * encrypt() or decrypt() after a setKey() call.  If the initialization
+ * vector is changed after encryption or decryption begins,
+ * then the behaviour is undefined.
+ *
+ * \note The IV is not encoded into the output stream by encrypt().
+ * The caller is responsible for communicating the IV to the other party.
+ *
+ * \sa ivSize()
+ */
+
+/**
+ * \fn void Cipher::encrypt(uint8_t *output, const uint8_t *input, size_t len)
+ * \brief Encrypts an input buffer and writes the ciphertext to an
+ * output buffer.
+ *
+ * \param output The output buffer to write to, which may be the same
+ * buffer as \a input.  The \a output buffer must have at least as many
+ * bytes as the \a input buffer.
+ * \param input The input buffer to read from.
+ * \param len The number of bytes to encrypt.
+ *
+ * The encrypt() function can be called multiple times with different
+ * regions of the plaintext data.
+ *
+ * \sa decrypt()
+ */
+
+/**
+ * \fn void Cipher::decrypt(uint8_t *output, const uint8_t *input, size_t len)
+ * \brief Decrypts an input buffer and writes the plaintext to an
+ * output buffer.
+ *
+ * \param output The output buffer to write to, which may be the same
+ * buffer as \a input.  The \a output buffer must have at least as many
+ * bytes as the \a input buffer.
+ * \param input The input buffer to read from.
+ * \param len The number of bytes to decrypt.
+ *
+ * The decrypt() function can be called multiple times with different
+ * regions of the ciphertext data.
+ *
+ * \sa encrypt()
+ */
+
+/**
+ * \fn void Cipher::clear()
+ * \brief Clears all security-sensitive state from this cipher.
+ *
+ * Security-sensitive information includes key schedules, initialization
+ * vectors, and any temporary state that is used by encrypt() or decrypt()
+ * which is stored in the cipher itself.
+ */
diff --git a/src/modules/Crypto/Cipher.h b/src/modules/Crypto/Cipher.h
new file mode 100644
index 0000000..8d498d8
--- /dev/null
+++ b/src/modules/Crypto/Cipher.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_CIPHER_h
+#define CRYPTO_CIPHER_h
+
+#include <inttypes.h>
+#include <stddef.h>
+
+class Cipher
+{
+public:
+    Cipher();
+    virtual ~Cipher();
+
+    virtual size_t keySize() const = 0;
+    virtual size_t ivSize() const = 0;
+
+    virtual bool setKey(const uint8_t *key, size_t len) = 0;
+    virtual bool setIV(const uint8_t *iv, size_t len) = 0;
+
+    virtual void encrypt(uint8_t *output, const uint8_t *input, size_t len) = 0;
+    virtual void decrypt(uint8_t *output, const uint8_t *input, size_t len) = 0;
+
+    virtual void clear() = 0;
+};
+
+#endif
diff --git a/src/modules/Crypto/Crypto.cpp b/src/modules/Crypto/Crypto.cpp
new file mode 100644
index 0000000..6c537ba
--- /dev/null
+++ b/src/modules/Crypto/Crypto.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "Crypto.h"
+
+/**
+ * \brief Cleans a block of bytes.
+ *
+ * \param dest The destination block to be cleaned.
+ * \param size The size of the destination to be cleaned in bytes.
+ *
+ * Unlike memset(), this function attempts to prevent the compiler
+ * from optimizing away the clear on a memory buffer.
+ */
+void clean(void *dest, size_t size)
+{
+    // Force the use of volatile so that we actually clear the memory.
+    // Otherwise the compiler might optimise the entire contents of this
+    // function away, which will not be secure.
+    volatile uint8_t *d = (volatile uint8_t *)dest;
+    while (size > 0) {
+        *d++ = 0;
+        --size;
+    }
+}
+
+/**
+ * \fn void clean(T &var)
+ * \brief Template function that cleans a variable.
+ *
+ * \param var A reference to the variable to clean.
+ *
+ * The variable will be cleared to all-zeroes in a secure manner.
+ * Unlike memset(), this function attempts to prevent the compiler
+ * from optimizing away the variable clear.
+ */
+
+/**
+ * \brief Compares two memory blocks for equality.
+ *
+ * \param data1 Points to the first memory block.
+ * \param data2 Points to the second memory block.
+ * \param len The size of the memory blocks in bytes.
+ *
+ * Unlike memcmp(), this function attempts to compare the two memory blocks
+ * in a way that will not reveal the contents in the instruction timing.
+ * In particular, this function will not stop early if a byte is different.
+ * It will instead continue onto the end of the array.
+ */
+bool secure_compare(const void *data1, const void *data2, size_t len)
+{
+    uint8_t result = 0;
+    const uint8_t *d1 = (const uint8_t *)data1;
+    const uint8_t *d2 = (const uint8_t *)data2;
+    while (len > 0) {
+        result |= (*d1++ ^ *d2++);
+        --len;
+    }
+    return (bool)((((uint16_t)0x0100) - result) >> 8);
+}
+
+/**
+ * \brief Calculates the CRC-8 value over an array in memory.
+ *
+ * \param tag Starting tag to distinguish this calculation.
+ * \param data The data to checksum.
+ * \param size The number of bytes to checksum.
+ * \return The CRC-8 value over the data.
+ *
+ * This function does not provide any real security.  It is a simple
+ * check that seed values have been initialized within EEPROM or Flash.
+ * If the CRC-8 check fails, then it is assumed that the EEPROM/Flash
+ * contents are invalid and should be re-initialized.
+ *
+ * Reference: http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html#ch4
+ */
+uint8_t crypto_crc8(uint8_t tag, const void *data, unsigned size)
+{
+    const uint8_t *d = (const uint8_t *)data;
+    uint8_t crc = 0xFF ^ tag;
+    uint8_t bit;
+    while (size > 0) {
+        crc ^= *d++;
+        for (bit = 0; bit < 8; ++bit) {
+            // if (crc & 0x80)
+            //     crc = (crc << 1) ^ 0x1D;
+            // else
+            //     crc = (crc << 1);
+            uint8_t generator = (uint8_t)((((int8_t)crc) >> 7) & 0x1D);
+            crc = (crc << 1) ^ generator;
+        }
+        --size;
+    }
+    return crc;
+}
diff --git a/src/modules/Crypto/Crypto.h b/src/modules/Crypto/Crypto.h
new file mode 100644
index 0000000..b780f25
--- /dev/null
+++ b/src/modules/Crypto/Crypto.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_h
+#define CRYPTO_h
+
+#include <inttypes.h>
+#include <stddef.h>
+
+void clean(void *dest, size_t size);
+
+template <typename T>
+inline void clean(T &var)
+{
+    clean(&var, sizeof(T));
+}
+
+bool secure_compare(const void *data1, const void *data2, size_t len);
+
+#if defined(ESP8266)
+extern "C" void system_soft_wdt_feed(void);
+#define crypto_feed_watchdog() system_soft_wdt_feed()
+#else
+#define crypto_feed_watchdog() do { ; } while (0)
+#endif
+
+#endif
diff --git a/src/modules/Crypto/Curve25519.cpp b/src/modules/Crypto/Curve25519.cpp
new file mode 100644
index 0000000..84744f0
--- /dev/null
+++ b/src/modules/Crypto/Curve25519.cpp
@@ -0,0 +1,1610 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "Curve25519.h"
+#include "Crypto.h"
+#include "RNG.h"
+#include "utility/LimbUtil.h"
+#include <string.h>
+
+/**
+ * \class Curve25519 Curve25519.h <Curve25519.h>
+ * \brief Diffie-Hellman key agreement based on the elliptic curve
+ * modulo 2^255 - 19.
+ *
+ * \note The public functions in this class need a substantial amount of
+ * stack space to store intermediate results while the curve function is
+ * being evaluated.  About 1k of free stack space is recommended for safety.
+ *
+ * References: http://cr.yp.to/ecdh.html,
+ * <a href="http://tools.ietf.org/html/rfc7748">RFC 7748</a>
+ *
+ * \sa Ed25519
+ */
+
+// Global switch to enable/disable AVR inline assembly optimizations.
+#if defined(__AVR__)
+// Disabled for now - there are issues with newer Arduino compilers.  FIXME
+//#define CURVE25519_ASM_AVR 1
+#endif
+
+// The overhead of clean() calls in mul(), reduceQuick(), etc can
+// add up to a lot of processing time during eval().  Only do such
+// cleanups if strict mode has been enabled.  Other implementations
+// like curve25519-donna don't do any cleaning at all so the value
+// of cleaning up the stack is dubious at best anyway.
+#if defined(CURVE25519_STRICT_CLEAN)
+#define strict_clean(x)     clean(x)
+#else
+#define strict_clean(x)     do { ; } while (0)
+#endif
+
+/**
+ * \brief Evaluates the raw Curve25519 function.
+ *
+ * \param result The result of evaluating the curve function.
+ * \param s The S parameter to the curve function.
+ * \param x The X(Q) parameter to the curve function.  If this pointer is
+ * NULL then the value 9 is used for \a x.
+ *
+ * This function is provided to assist with implementating other
+ * algorithms with the curve.  Normally applications should use dh1()
+ * and dh2() directly instead.
+ *
+ * \return Returns true if the function was evaluated; false if \a x is
+ * not a proper member of the field modulo (2^255 - 19).
+ *
+ * Reference: <a href="http://tools.ietf.org/html/rfc7748">RFC 7748</a>
+ *
+ * \sa dh1(), dh2()
+ */
+bool Curve25519::eval(uint8_t result[32], const uint8_t s[32], const uint8_t x[32])
+{
+    limb_t x_1[NUM_LIMBS_256BIT];
+    limb_t x_2[NUM_LIMBS_256BIT];
+    limb_t x_3[NUM_LIMBS_256BIT];
+    limb_t z_2[NUM_LIMBS_256BIT];
+    limb_t z_3[NUM_LIMBS_256BIT];
+    limb_t A[NUM_LIMBS_256BIT];
+    limb_t B[NUM_LIMBS_256BIT];
+    limb_t C[NUM_LIMBS_256BIT];
+    limb_t D[NUM_LIMBS_256BIT];
+    limb_t E[NUM_LIMBS_256BIT];
+    limb_t AA[NUM_LIMBS_256BIT];
+    limb_t BB[NUM_LIMBS_256BIT];
+    limb_t DA[NUM_LIMBS_256BIT];
+    limb_t CB[NUM_LIMBS_256BIT];
+    uint8_t mask;
+    uint8_t sposn;
+    uint8_t select;
+    uint8_t swap;
+    bool retval;
+
+    // Unpack the "x" argument into the limb representation
+    // which also masks off the high bit.  NULL means 9.
+    if (x) {
+        // x1 = x
+        BigNumberUtil::unpackLE(x_1, NUM_LIMBS_256BIT, x, 32);
+        x_1[NUM_LIMBS_256BIT - 1] &= ((((limb_t)1) << (LIMB_BITS - 1)) - 1);
+    } else {
+        memset(x_1, 0, sizeof(x_1));    // x_1 = 9
+        x_1[0] = 9;
+    }
+
+    // Check that "x" is within the range of the modulo field.
+    // We can do this with a reduction - if there was no borrow
+    // then the value of "x" was out of range.  Timing is sensitive
+    // here so that we don't reveal anything about the value of "x".
+    // If there was a reduction, then continue executing the rest
+    // of this function with the (now) in-range "x" value and
+    // report the failure at the end.
+    retval = (bool)(reduceQuick(x_1) & 0x01);
+
+    // Initialize the other temporary variables.
+    memset(x_2, 0, sizeof(x_2));        // x_2 = 1
+    x_2[0] = 1;
+    memset(z_2, 0, sizeof(z_2));        // z_2 = 0
+    memcpy(x_3, x_1, sizeof(x_1));      // x_3 = x
+    memcpy(z_3, x_2, sizeof(x_2));      // z_3 = 1
+
+    // Iterate over all 255 bits of "s" from the highest to the lowest.
+    // We ignore the high bit of the 256-bit representation of "s".
+    mask = 0x40;
+    sposn = 31;
+    swap = 0;
+    for (uint8_t t = 255; t > 0; --t) {
+        // Conditional swaps on entry to this bit but only if we
+        // didn't swap on the previous bit.
+        select = s[sposn] & mask;
+        swap ^= select;
+        cswap(swap, x_2, x_3);
+        cswap(swap, z_2, z_3);
+
+        // Evaluate the curve.
+        add(A, x_2, z_2);               // A = x_2 + z_2
+        square(AA, A);                  // AA = A^2
+        sub(B, x_2, z_2);               // B = x_2 - z_2
+        square(BB, B);                  // BB = B^2
+        sub(E, AA, BB);                 // E = AA - BB
+        add(C, x_3, z_3);               // C = x_3 + z_3
+        sub(D, x_3, z_3);               // D = x_3 - z_3
+        mul(DA, D, A);                  // DA = D * A
+        mul(CB, C, B);                  // CB = C * B
+        add(x_3, DA, CB);               // x_3 = (DA + CB)^2
+        square(x_3, x_3);
+        sub(z_3, DA, CB);               // z_3 = x_1 * (DA - CB)^2
+        square(z_3, z_3);
+        mul(z_3, z_3, x_1);
+        mul(x_2, AA, BB);               // x_2 = AA * BB
+        mulA24(z_2, E);                 // z_2 = E * (AA + a24 * E)
+        add(z_2, z_2, AA);
+        mul(z_2, z_2, E);
+
+        // Move onto the next lower bit of "s".
+        mask >>= 1;
+        if (!mask) {
+            --sposn;
+            mask = 0x80;
+            swap = select << 7;
+        } else {
+            swap = select >> 1;
+        }
+    }
+
+    // Final conditional swaps.
+    cswap(swap, x_2, x_3);
+    cswap(swap, z_2, z_3);
+
+    // Compute x_2 * (z_2 ^ (p - 2)) where p = 2^255 - 19.
+    recip(z_3, z_2);
+    mul(x_2, x_2, z_3);
+
+    // Pack the result into the return array.
+    BigNumberUtil::packLE(result, 32, x_2, NUM_LIMBS_256BIT);
+
+    // Clean up and exit.
+    clean(x_1);
+    clean(x_2);
+    clean(x_3);
+    clean(z_2);
+    clean(z_3);
+    clean(A);
+    clean(B);
+    clean(C);
+    clean(D);
+    clean(E);
+    clean(AA);
+    clean(BB);
+    clean(DA);
+    clean(CB);
+    return retval;
+}
+
+/**
+ * \brief Performs phase 1 of a Diffie-Hellman key exchange using Curve25519.
+ *
+ * \param k The key value to send to the other party as part of the exchange.
+ * \param f The generated secret value for this party.  This must not be
+ * transmitted to any party or stored in permanent storage.  It only needs
+ * to be kept in memory until dh2() is called.
+ *
+ * The \a f value is generated with \link RNGClass::rand() RNG.rand()\endlink.
+ * It is the caller's responsibility to ensure that the global random number
+ * pool has sufficient entropy to generate the 32 bytes of \a f safely
+ * before calling this function.
+ *
+ * The following example demonstrates how to perform a full Diffie-Hellman
+ * key exchange using dh1() and dh2():
+ *
+ * \code
+ * uint8_t f[32];
+ * uint8_t k[32];
+ *
+ * // Generate the secret value "f" and the public value "k".
+ * Curve25519::dh1(k, f);
+ *
+ * // Send "k" to the other party.
+ * ...
+ *
+ * // Read the "k" value that the other party sent to us.
+ * ...
+ *
+ * // Generate the shared secret in "k" using the previous secret value "f".
+ * if (!Curve25519::dh2(k, f)) {
+ *     // The received "k" value was invalid - abort the session.
+ *     ...
+ * }
+ *
+ * // The "k" value can now be used to generate session keys for encryption.
+ * ...
+ * \endcode
+ *
+ * Reference: <a href="http://tools.ietf.org/html/rfc7748">RFC 7748</a>
+ *
+ * \sa dh2()
+ */
+void Curve25519::dh1(uint8_t k[32], uint8_t f[32])
+{
+    do {
+        // Generate a random "f" value and then adjust the value to make
+        // it valid as an "s" value for eval().  According to the specification
+        // we need to mask off the 3 right-most bits of f[0], mask off the
+        // left-most bit of f[31], and set the second to left-most bit of f[31].
+        RNG.rand(f, 32);
+        f[0] &= 0xF8;
+        f[31] = (f[31] & 0x7F) | 0x40;
+
+        // Evaluate the curve function: k = Curve25519::eval(f, 9).
+        // We pass NULL to eval() to indicate the value 9.  There is no
+        // need to check the return value from eval() because we know
+        // that 9 is a valid field element.
+        eval(k, f, 0);
+
+        // If "k" is weak for contributory behaviour then reject it,
+        // generate another "f" value, and try again.  This case is
+        // highly unlikely but we still perform the check just in case.
+    } while (isWeakPoint(k));
+}
+
+/**
+ * \brief Performs phase 2 of a Diffie-Hellman key exchange using Curve25519.
+ *
+ * \param k On entry, this is the key value that was received from the other
+ * party as part of the exchange.  On exit, this will be the shared secret.
+ * \param f The secret value for this party that was generated by dh1().
+ * The \a f value will be destroyed by this function.
+ *
+ * \return Returns true if the key exchange was successful, or false if
+ * the \a k value is invalid.
+ *
+ * Reference: <a href="http://tools.ietf.org/html/rfc7748">RFC 7748</a>
+ *
+ * \sa dh1()
+ */
+bool Curve25519::dh2(uint8_t k[32], uint8_t f[32])
+{
+    uint8_t weak;
+
+    // Evaluate the curve function: k = Curve25519::eval(f, k).
+    // If "k" is weak for contributory behaviour before or after
+    // the curve evaluation, then fail the exchange.  For safety
+    // we perform every phase of the weak checks even if we could
+    // bail out earlier so that the execution takes the same
+    // amount of time for weak and non-weak "k" values.
+    weak  = isWeakPoint(k);                     // Is "k" weak before?
+    weak |= ((eval(k, f, k) ^ 0x01) & 0x01);    // Is "k" weak during?
+    weak |= isWeakPoint(k);                     // Is "k" weak after?
+    clean(f, 32);
+    return (bool)((weak ^ 0x01) & 0x01);
+}
+
+/**
+ * \brief Determines if a Curve25519 point is weak for contributory behaviour.
+ *
+ * \param k The point to check.
+ * \return Returns 1 if \a k is weak for contributory behavior or
+ * returns zero if \a k is not weak.
+ */
+uint8_t Curve25519::isWeakPoint(const uint8_t k[32])
+{
+    // List of weak points from http://cr.yp.to/ecdh.html
+    // That page lists some others but they are variants on these
+    // of the form "point + i * (2^255 - 19)" for i = 0, 1, 2.
+    // Here we mask off the high bit and eval() catches the rest.
+    static const uint8_t points[5][32] PROGMEM = {
+        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+        {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+        {0xE0, 0xEB, 0x7A, 0x7C, 0x3B, 0x41, 0xB8, 0xAE,
+         0x16, 0x56, 0xE3, 0xFA, 0xF1, 0x9F, 0xC4, 0x6A,
+         0xDA, 0x09, 0x8D, 0xEB, 0x9C, 0x32, 0xB1, 0xFD,
+         0x86, 0x62, 0x05, 0x16, 0x5F, 0x49, 0xB8, 0x00},
+        {0x5F, 0x9C, 0x95, 0xBC, 0xA3, 0x50, 0x8C, 0x24,
+         0xB1, 0xD0, 0xB1, 0x55, 0x9C, 0x83, 0xEF, 0x5B,
+         0x04, 0x44, 0x5C, 0xC4, 0x58, 0x1C, 0x8E, 0x86,
+         0xD8, 0x22, 0x4E, 0xDD, 0xD0, 0x9F, 0x11, 0x57},
+        {0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}
+    };
+
+    // Check each of the weak points in turn.  We perform the
+    // comparisons carefully so as not to reveal the value of "k"
+    // in the instruction timing.  If "k" is indeed weak then
+    // we still check everything so as not to reveal which
+    // weak point it is.
+    uint8_t result = 0;
+    for (uint8_t posn = 0; posn < 5; ++posn) {
+        const uint8_t *point = points[posn];
+        uint8_t check = (pgm_read_byte(&(point[31])) ^ k[31]) & 0x7F;
+        for (uint8_t index = 31; index > 0; --index)
+            check |= (pgm_read_byte(&(point[index - 1])) ^ k[index - 1]);
+        result |= (uint8_t)((((uint16_t)0x0100) - check) >> 8);
+    }
+
+    // The "result" variable will be non-zero if there was a match.
+    return result;
+}
+
+/**
+ * \brief Reduces a number modulo 2^255 - 19.
+ *
+ * \param result The array that will contain the result when the
+ * function exits.  Must be NUM_LIMBS_256BIT limbs in size.
+ * \param x The number to be reduced, which must be NUM_LIMBS_512BIT
+ * limbs in size and less than or equal to square(2^255 - 19 - 1).
+ * This array will be modified by the reduction process.
+ * \param size The size of the high order half of \a x.  This indicates
+ * the size of \a x in limbs.  If it is shorter than NUM_LIMBS_256BIT
+ * then the reduction can be performed quicker.
+ */
+void Curve25519::reduce(limb_t *result, limb_t *x, uint8_t size)
+{
+    /*
+    Note: This explaination is best viewed with a UTF-8 text viewer.
+
+    To help explain what this function is doing, the following describes
+    how to efficiently compute reductions modulo a base of the form (2ⁿ - b)
+    where b is greater than zero and (b + 1)² <= 2ⁿ.
+
+    Here we are interested in reducing the result of multiplying two
+    numbers that are less than or equal to (2ⁿ - b - 1).  That is,
+    multiplying numbers that have already been reduced.
+
+    Given some x less than or equal to (2ⁿ - b - 1)², we want to find a
+    y less than (2ⁿ - b) such that:
+
+        y ≡ x mod (2ⁿ - b)
+
+    We know that for all integer values of k >= 0:
+
+        y ≡ x - k * (2ⁿ - b)
+          ≡ x - k * 2ⁿ + k * b
+
+    In our case we choose k = ⌊x / 2ⁿ⌋ and then let:
+
+        w = (x mod 2ⁿ) + ⌊x / 2ⁿ⌋ * b
+
+    The value w will either be the answer y or y can be obtained by
+    repeatedly subtracting (2ⁿ - b) from w until it is less than (2ⁿ - b).
+    At most b subtractions will be required.
+
+    In our case b is 19 which is more subtractions than we would like to do,
+    but we can handle that by performing the above reduction twice and then
+    performing a single trial subtraction:
+
+        w = (x mod 2ⁿ) + ⌊x / 2ⁿ⌋ * b
+        y = (w mod 2ⁿ) + ⌊w / 2ⁿ⌋ * b
+        if y >= (2ⁿ - b)
+            y -= (2ⁿ - b)
+
+    The value y is the answer we want for reducing x modulo (2ⁿ - b).
+    */
+
+#if !defined(CURVE25519_ASM_AVR)
+    dlimb_t carry;
+    uint8_t posn;
+
+    // Calculate (x mod 2^255) + ((x / 2^255) * 19) which will
+    // either produce the answer we want or it will produce a
+    // value of the form "answer + j * (2^255 - 19)".
+    carry = ((dlimb_t)(x[NUM_LIMBS_256BIT - 1] >> (LIMB_BITS - 1))) * 19U;
+    x[NUM_LIMBS_256BIT - 1] &= ((((limb_t)1) << (LIMB_BITS - 1)) - 1);
+    for (posn = 0; posn < size; ++posn) {
+        carry += ((dlimb_t)(x[posn + NUM_LIMBS_256BIT])) * 38U;
+        carry += x[posn];
+        x[posn] = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    if (size < NUM_LIMBS_256BIT) {
+        // The high order half of the number is short; e.g. for mulA24().
+        // Propagate the carry through the rest of the low order part.
+        for (posn = size; posn < NUM_LIMBS_256BIT; ++posn) {
+            carry += x[posn];
+            x[posn] = (limb_t)carry;
+            carry >>= LIMB_BITS;
+        }
+    }
+
+    // The "j" value may still be too large due to the final carry-out.
+    // We must repeat the reduction.  If we already have the answer,
+    // then this won't do any harm but we must still do the calculation
+    // to preserve the overall timing.
+    carry *= 38U;
+    carry += ((dlimb_t)(x[NUM_LIMBS_256BIT - 1] >> (LIMB_BITS - 1))) * 19U;
+    x[NUM_LIMBS_256BIT - 1] &= ((((limb_t)1) << (LIMB_BITS - 1)) - 1);
+    for (posn = 0; posn < NUM_LIMBS_256BIT; ++posn) {
+        carry += x[posn];
+        x[posn] = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+
+    // At this point "x" will either be the answer or it will be the
+    // answer plus (2^255 - 19).  Perform a trial subtraction which
+    // is equivalent to adding 19 and subtracting 2^255.  We put the
+    // trial answer into the top-most limbs of the original "x" array.
+    // We add 19 here; the subtraction of 2^255 occurs in the next step.
+    carry = 19U;
+    for (posn = 0; posn < NUM_LIMBS_256BIT; ++posn) {
+        carry += x[posn];
+        x[posn + NUM_LIMBS_256BIT] = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+
+    // If there was a borrow, then the bottom-most limbs of "x" are the
+    // correct answer.  If there was no borrow, then the top-most limbs
+    // of "x" are the correct answer.  Select the correct answer but do
+    // it in a way that instruction timing will not reveal which value
+    // was selected.  Borrow will occur if the high bit of the previous
+    // result is 0: turn the high bit into a selection mask.
+    limb_t mask = (limb_t)(((slimb_t)(x[NUM_LIMBS_512BIT - 1])) >> (LIMB_BITS - 1));
+    limb_t nmask = ~mask;
+    x[NUM_LIMBS_512BIT - 1] &= ((((limb_t)1) << (LIMB_BITS - 1)) - 1);
+    for (posn = 0; posn < NUM_LIMBS_256BIT; ++posn) {
+        result[posn] = (x[posn] & nmask) | (x[posn + NUM_LIMBS_256BIT] & mask);
+    }
+#else
+    __asm__ __volatile__ (
+        // Calculate (x mod 2^255) + ((x / 2^255) * 19) which will
+        // either produce the answer we want or it will produce a
+        // value of the form "answer + j * (2^255 - 19)".
+        "ldd r24,Z+31\n"                // Extract the high bit of x[31]
+        "mov r25,r24\n"                 // and mask it off
+        "andi r25,0x7F\n"
+        "std Z+31,r25\n"
+        "lsl r24\n"                     // carry = high bit * 19
+        "mov r24,__zero_reg__\n"
+        "sbc r24,__zero_reg__\n"
+        "andi r24,19\n"
+
+        "mov r25,%1\n"                  // load "size" into r25
+        "ldi r23,38\n"                  // r23 = 38
+        "mov r22,__zero_reg__\n"        // r22 = 0 (we're about to destroy r1)
+        "1:\n"
+        "ld r16,Z\n"                    // r16 = x[0]
+        "ldd r17,Z+32\n"                // r17 = x[32]
+        "mul r17,r23\n"                 // r0:r1 = r17 * 38
+        "add r0,r24\n"                  // r0:r1 += carry
+        "adc r1,r22\n"
+        "add r0,r16\n"                  // r0:r1 += r16
+        "adc r1,r22\n"
+        "st Z+,r0\n"                    // *x++ = r0
+        "mov r24,r1\n"                  // carry = r1
+        "dec r25\n"                     // if (--r25 != 0) loop
+        "brne 1b\n"
+
+        // If the size is short, then we need to continue propagating carries.
+        "ldi r25,32\n"
+        "cp %1,r25\n"
+        "breq 3f\n"
+        "sub r25,%1\n"
+        "ld __tmp_reg__,Z\n"
+        "add __tmp_reg__,r24\n"
+        "st Z+,__tmp_reg__\n"
+        "dec r25\n"
+        "2:\n"
+        "ld __tmp_reg__,Z\n"            // *x++ += carry
+        "adc __tmp_reg__,r22\n"
+        "st Z+,__tmp_reg__\n"
+        "dec r25\n"
+        "brne 2b\n"
+        "mov r24,r22\n"                 // put the carry back into r24
+        "adc r24,r22\n"
+        "3:\n"
+        "sbiw r30,32\n"                 // Point Z back to the start of "x"
+
+        // The "j" value may still be too large due to the final carry-out.
+        // We must repeat the reduction.  If we already have the answer,
+        // then this won't do any harm but we must still do the calculation
+        // to preserve the overall timing.
+        "mul r24,r23\n"                 // carry *= 38
+        "ldd r24,Z+31\n"                // Extract the high bit of x[31]
+        "mov r25,r24\n"                 // and mask it off
+        "andi r25,0x7F\n"
+        "std Z+31,r25\n"
+        "lsl r24\n"                     // carry += high bit * 19
+        "mov r24,r22\n"
+        "sbc r24,r22\n"
+        "andi r24,19\n"
+        "add r0,r24\n"
+        "adc r1,r22\n"                  // 9-bit carry is now in r0:r1
+
+        // Propagate the carry through the rest of x.
+        "ld r24,Z\n"                    // x[0]
+        "add r0,r24\n"
+        "adc r1,r22\n"
+        "st Z+,r0\n"
+        "ld r24,Z\n"                    // x[1]
+        "add r1,r24\n"
+        "st Z+,r1\n"
+        "ldi r25,30\n"                  // x[2..31]
+        "4:\n"
+        "ld r24,Z\n"
+        "adc r24,r22\n"
+        "st Z+,r24\n"
+        "dec r25\n"
+        "brne 4b\n"
+        "sbiw r30,32\n"                 // Point Z back to the start of "x"
+
+        // We destroyed __zero_reg__ (r1) above, so restore its zero value.
+        "mov __zero_reg__,r22\n"
+
+        // At this point "x" will either be the answer or it will be the
+        // answer plus (2^255 - 19).  Perform a trial subtraction which
+        // is equivalent to adding 19 and subtracting 2^255.  We put the
+        // trial answer into the top-most limbs of the original "x" array.
+        // We add 19 here; the subtraction of 2^255 occurs in the next step.
+        "ldi r24,8\n"               // Loop counter.
+        "ldi r25,19\n"              // carry = 19
+        "5:\n"
+        "ld r16,Z+\n"               // r16:r19:carry = *xx++ + carry
+        "ld r17,Z+\n"
+        "ld r18,Z+\n"
+        "ld r19,Z+\n"
+        "add r16,r25\n"             // r16:r19:carry += carry
+        "adc r17,__zero_reg__\n"
+        "adc r18,__zero_reg__\n"
+        "adc r19,__zero_reg__\n"
+        "mov r25,__zero_reg__\n"
+        "adc r25,r25\n"
+        "std Z+28,r16\n"            // *tt++ = r16:r19
+        "std Z+29,r17\n"
+        "std Z+30,r18\n"
+        "std Z+31,r19\n"
+        "dec r24\n"
+        "brne 5b\n"
+
+        // Subtract 2^255 from x[32..63] which is equivalent to extracting
+        // the top bit and then masking it off.  If the top bit is zero
+        // then a borrow has occurred and this isn't the answer we want.
+        "mov r25,r19\n"
+        "andi r19,0x7F\n"
+        "std Z+31,r19\n"
+        "lsl r25\n"
+        "mov r25,__zero_reg__\n"
+        "sbc r25,__zero_reg__\n"
+
+        // At this point, r25 is 0 if the original x[0..31] is the answer
+        // we want, or 0xFF if x[32..63] is the answer we want.  Essentially
+        // we need to do a conditional move of either x[0..31] or x[32..63]
+        // into "result".
+        "sbiw r30,32\n"             // Point Z back to x[0].
+        "ldi r24,8\n"
+        "6:\n"
+        "ldd r16,Z+32\n"
+        "ldd r17,Z+33\n"
+        "ldd r18,Z+34\n"
+        "ldd r19,Z+35\n"
+        "ld r20,Z+\n"
+        "ld r21,Z+\n"
+        "ld r22,Z+\n"
+        "ld r23,Z+\n"
+        "eor r16,r20\n"
+        "eor r17,r21\n"
+        "eor r18,r22\n"
+        "eor r19,r23\n"
+        "and r16,r25\n"
+        "and r17,r25\n"
+        "and r18,r25\n"
+        "and r19,r25\n"
+        "eor r20,r16\n"
+        "eor r21,r17\n"
+        "eor r22,r18\n"
+        "eor r23,r19\n"
+        "st X+,r20\n"
+        "st X+,r21\n"
+        "st X+,r22\n"
+        "st X+,r23\n"
+        "dec r24\n"
+        "brne 6b\n"
+
+        : : "z"(x), "r"((uint8_t)(size * sizeof(limb_t))), "x"(result)
+        : "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
+          "r24", "r25"
+    );
+#endif
+}
+
+/**
+ * \brief Quickly reduces a number modulo 2^255 - 19.
+ *
+ * \param x The number to be reduced, which must be NUM_LIMBS_256BIT
+ * limbs in size and less than or equal to 2 * (2^255 - 19 - 1).
+ * \return Zero if \a x was greater than or equal to (2^255 - 19).
+ *
+ * The answer is also put into \a x and will consist of NUM_LIMBS_256BIT limbs.
+ *
+ * This function is intended for reducing the result of additions where
+ * the caller knows that \a x is within the described range.  A single
+ * trial subtraction is all that is needed to reduce the number.
+ */
+limb_t Curve25519::reduceQuick(limb_t *x)
+{
+#if !defined(CURVE25519_ASM_AVR)
+    limb_t temp[NUM_LIMBS_256BIT];
+    dlimb_t carry;
+    uint8_t posn;
+    limb_t *xx;
+    limb_t *tt;
+    
+    // Perform a trial subtraction of (2^255 - 19) from "x" which is
+    // equivalent to adding 19 and subtracting 2^255.  We add 19 here;
+    // the subtraction of 2^255 occurs in the next step.
+    carry = 19U;
+    xx = x;
+    tt = temp;
+    for (posn = 0; posn < NUM_LIMBS_256BIT; ++posn) {
+        carry += *xx++;
+        *tt++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+
+    // If there was a borrow, then the original "x" is the correct answer.
+    // If there was no borrow, then "temp" is the correct answer.  Select the
+    // correct answer but do it in a way that instruction timing will not
+    // reveal which value was selected.  Borrow will occur if the high bit
+    // of "temp" is 0: turn the high bit into a selection mask.
+    limb_t mask = (limb_t)(((slimb_t)(temp[NUM_LIMBS_256BIT - 1])) >> (LIMB_BITS - 1));
+    limb_t nmask = ~mask;
+    temp[NUM_LIMBS_256BIT - 1] &= ((((limb_t)1) << (LIMB_BITS - 1)) - 1);
+    xx = x;
+    tt = temp;
+    for (posn = 0; posn < NUM_LIMBS_256BIT; ++posn) {
+        *xx = ((*xx) & nmask) | ((*tt++) & mask);
+        ++xx;
+    }
+
+    // Clean up "temp".
+    strict_clean(temp);
+
+    // Return a zero value if we actually subtracted (2^255 - 19) from "x".
+    return nmask;
+#else // CURVE25519_ASM_AVR
+    limb_t temp[NUM_LIMBS_256BIT];
+    uint8_t result;
+    __asm__ __volatile__ (
+        // Subtract (2^255 - 19) from "x", which is the same as adding 19
+        // and then subtracting 2^255.
+        "ldi r24,8\n"               // Loop counter.
+        "ldi r25,19\n"              // carry = 19
+        "1:\n"
+        "ld r16,Z+\n"               // r16:r19:carry = *xx++ + carry
+        "ld r17,Z+\n"
+        "ld r18,Z+\n"
+        "ld r19,Z+\n"
+        "add r16,r25\n"             // r16:r19:carry += carry
+        "adc r17,__zero_reg__\n"
+        "adc r18,__zero_reg__\n"
+        "adc r19,__zero_reg__\n"
+        "mov r25,__zero_reg__\n"
+        "adc r25,r25\n"
+        "st X+,r16\n"               // *tt++ = r16:r19
+        "st X+,r17\n"
+        "st X+,r18\n"
+        "st X+,r19\n"
+        "dec r24\n"
+        "brne 1b\n"
+
+        // Subtract 2^255 from "temp" which is equivalent to extracting
+        // the top bit and then masking it off.  If the top bit is zero
+        // then a borrow has occurred and this isn't the answer we want.
+        "mov r25,r19\n"
+        "andi r19,0x7F\n"
+        "st -X,r19\n"
+        "lsl r25\n"
+        "mov r25,__zero_reg__\n"
+        "sbc r25,__zero_reg__\n"
+
+        // At this point, r25 is 0 if the original "x" is the answer
+        // we want, or 0xFF if "temp" is the answer we want.  Essentially
+        // we need to do a conditional move of "temp" into "x".
+        "sbiw r26,31\n"             // Point X back to the start of "temp".
+        "sbiw r30,32\n"             // Point Z back to the start of "x".
+        "ldi r24,8\n"
+        "2:\n"
+        "ld r16,X+\n"
+        "ld r17,X+\n"
+        "ld r18,X+\n"
+        "ld r19,X+\n"
+        "ld r20,Z\n"
+        "ldd r21,Z+1\n"
+        "ldd r22,Z+2\n"
+        "ldd r23,Z+3\n"
+        "eor r16,r20\n"
+        "eor r17,r21\n"
+        "eor r18,r22\n"
+        "eor r19,r23\n"
+        "and r16,r25\n"
+        "and r17,r25\n"
+        "and r18,r25\n"
+        "and r19,r25\n"
+        "eor r20,r16\n"
+        "eor r21,r17\n"
+        "eor r22,r18\n"
+        "eor r23,r19\n"
+        "st Z+,r20\n"
+        "st Z+,r21\n"
+        "st Z+,r22\n"
+        "st Z+,r23\n"
+        "dec r24\n"
+        "brne 2b\n"
+        "mov %0,r25\n"
+        : "=r"(result)
+        : "x"(temp), "z"(x)
+        : "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
+          "r24", "r25"
+    );
+    strict_clean(temp);
+    return result;
+#endif // CURVE25519_ASM_AVR
+}
+
+/**
+ * \brief Multiplies two 256-bit values to produce a 512-bit result.
+ *
+ * \param result The result, which must be NUM_LIMBS_512BIT limbs in size
+ * and must not overlap with \a x or \a y.
+ * \param x The first value to multiply, which must be NUM_LIMBS_256BIT
+ * limbs in size.
+ * \param y The second value to multiply, which must be NUM_LIMBS_256BIT
+ * limbs in size.
+ *
+ * \sa mul()
+ */
+void Curve25519::mulNoReduce(limb_t *result, const limb_t *x, const limb_t *y)
+{
+#if !defined(CURVE25519_ASM_AVR)
+    uint8_t i, j;
+    dlimb_t carry;
+    limb_t word;
+    const limb_t *yy;
+    limb_t *rr;
+
+    // Multiply the lowest word of x by y.
+    carry = 0;
+    word = x[0];
+    yy = y;
+    rr = result;
+    for (i = 0; i < NUM_LIMBS_256BIT; ++i) {
+        carry += ((dlimb_t)(*yy++)) * word;
+        *rr++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    *rr = (limb_t)carry;
+
+    // Multiply and add the remaining words of x by y.
+    for (i = 1; i < NUM_LIMBS_256BIT; ++i) {
+        word = x[i];
+        carry = 0;
+        yy = y;
+        rr = result + i;
+        for (j = 0; j < NUM_LIMBS_256BIT; ++j) {
+            carry += ((dlimb_t)(*yy++)) * word;
+            carry += *rr;
+            *rr++ = (limb_t)carry;
+            carry >>= LIMB_BITS;
+        }
+        *rr = (limb_t)carry;
+    }
+#else
+    __asm__ __volatile__ (
+        // Save Y and copy the "result" pointer into it.
+        "push r28\n"
+        "push r29\n"
+        "mov r28,%A2\n"
+        "mov r29,%B2\n"
+
+        // Multiply the first byte of "x" by y[0..31].
+        "ldi r25,8\n"               // loop 8 times: 4 bytes of y each time
+        "clr r24\n"                 // carry = 0
+        "clr r22\n"                 // r22 = 0 to replace __zero_reg__
+        "ld r23,X+\n"               // r23 = *x++
+        "1:\n"
+        "ld r16,Z\n"                // r16 = y[0]
+        "mul r16,r23\n"             // r8:r9 = y[0] * r23
+        "movw r8,r0\n"
+        "ldd r16,Z+2\n"             // r16 = y[2]
+        "mul r16,r23\n"             // r10:r11 = y[2] * r23
+        "movw r10,r0\n"
+        "ldd r16,Z+1\n"             // r16 = y[1]
+        "mul r16,r23\n"             // r9:r10:r11 += y[1] * r23
+        "add r9,r0\n"
+        "adc r10,r1\n"
+        "adc r11,r22\n"
+        "ldd r16,Z+3\n"             // r16 = y[3]
+        "mul r16,r23\n"             // r11:r1 += y[3] * r23
+        "add r11,r0\n"
+        "adc r1,r22\n"
+        "add r8,r24\n"              // r8:r9:r10:r11:r1 += carry
+        "adc r9,r22\n"
+        "adc r10,r22\n"
+        "adc r11,r22\n"
+        "adc r1,r22\n"
+        "mov r24,r1\n"              // carry = r1
+        "st Y+,r8\n"                // *rr++ = r8:r9:r10:r11
+        "st Y+,r9\n"
+        "st Y+,r10\n"
+        "st Y+,r11\n"
+        "adiw r30,4\n"
+        "dec r25\n"
+        "brne 1b\n"
+        "st Y+,r24\n"               // *rr++ = carry
+        "sbiw r28,32\n"             // rr -= 32
+        "sbiw r30,32\n"             // Point Z back to the start of y
+
+        // Multiply and add the remaining bytes of "x" by y[0..31].
+        "ldi r21,31\n"              // 31 more bytes of x to go.
+        "2:\n"
+        "ldi r25,8\n"               // loop 8 times: 4 bytes of y each time
+        "clr r24\n"                 // carry = 0
+        "ld r23,X+\n"               // r23 = *x++
+        "3:\n"
+        "ld r16,Z\n"                // r16 = y[0]
+        "mul r16,r23\n"             // r8:r9 = y[0] * r23
+        "movw r8,r0\n"
+        "ldd r16,Z+2\n"             // r16 = y[2]
+        "mul r16,r23\n"             // r10:r11 = y[2] * r23
+        "movw r10,r0\n"
+        "ldd r16,Z+1\n"             // r16 = y[1]
+        "mul r16,r23\n"             // r9:r10:r11 += y[1] * r23
+        "add r9,r0\n"
+        "adc r10,r1\n"
+        "adc r11,r22\n"
+        "ldd r16,Z+3\n"             // r16 = y[3]
+        "mul r16,r23\n"             // r11:r1 += y[3] * r23
+        "add r11,r0\n"
+        "adc r1,r22\n"
+        "add r8,r24\n"              // r8:r9:r10:r11:r1 += carry
+        "adc r9,r22\n"
+        "adc r10,r22\n"
+        "adc r11,r22\n"
+        "adc r1,r22\n"
+        "ld r16,Y\n"                // r8:r9:r10:r11:r1 += rr[0..3]
+        "add r8,r16\n"
+        "ldd r16,Y+1\n"
+        "adc r9,r16\n"
+        "ldd r16,Y+2\n"
+        "adc r10,r16\n"
+        "ldd r16,Y+3\n"
+        "adc r11,r16\n"
+        "adc r1,r22\n"
+        "mov r24,r1\n"              // carry = r1
+        "st Y+,r8\n"                // *rr++ = r8:r9:r10:r11
+        "st Y+,r9\n"
+        "st Y+,r10\n"
+        "st Y+,r11\n"
+        "adiw r30,4\n"
+        "dec r25\n"
+        "brne 3b\n"
+        "st Y+,r24\n"               // *r++ = carry
+        "sbiw r28,32\n"             // rr -= 32
+        "sbiw r30,32\n"             // Point Z back to the start of y
+        "dec r21\n"
+        "brne 2b\n"
+
+        // Restore Y and __zero_reg__.
+        "pop r29\n"
+        "pop r28\n"
+        "clr __zero_reg__\n"
+        : : "x"(x), "z"(y), "r"(result)
+        : "r8", "r9", "r10", "r11", "r16", "r20", "r21", "r22",
+          "r23", "r24", "r25"
+    );
+#endif
+}
+
+/**
+ * \brief Multiplies two values and then reduces the result modulo 2^255 - 19.
+ *
+ * \param result The result, which must be NUM_LIMBS_256BIT limbs in size
+ * and can be the same array as \a x or \a y.
+ * \param x The first value to multiply, which must be NUM_LIMBS_256BIT limbs
+ * in size and less than 2^255 - 19.
+ * \param y The second value to multiply, which must be NUM_LIMBS_256BIT limbs
+ * in size and less than 2^255 - 19.  This can be the same array as \a x.
+ */
+void Curve25519::mul(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    limb_t temp[NUM_LIMBS_512BIT];
+    mulNoReduce(temp, x, y);
+    reduce(result, temp, NUM_LIMBS_256BIT);
+    strict_clean(temp);
+    crypto_feed_watchdog();
+}
+
+/**
+ * \fn void Curve25519::square(limb_t *result, const limb_t *x)
+ * \brief Squares a value and then reduces it modulo 2^255 - 19.
+ *
+ * \param result The result, which must be NUM_LIMBS_256BIT limbs in size and
+ * can be the same array as \a x.
+ * \param x The value to square, which must be NUM_LIMBS_256BIT limbs in size
+ * and less than 2^255 - 19.
+ */
+
+/**
+ * \brief Multiplies a value by the a24 constant and then reduces the result
+ * modulo 2^255 - 19.
+ *
+ * \param result The result, which must be NUM_LIMBS_256BIT limbs in size
+ * and can be the same array as \a x.
+ * \param x The value to multiply by a24, which must be NUM_LIMBS_256BIT
+ * limbs in size and less than 2^255 - 19.
+ */
+void Curve25519::mulA24(limb_t *result, const limb_t *x)
+{
+#if !defined(CURVE25519_ASM_AVR)
+    // The constant a24 = 121665 (0x1DB41) as a limb array.
+#if BIGNUMBER_LIMB_8BIT
+    static limb_t const a24[3] PROGMEM = {0x41, 0xDB, 0x01};
+#elif BIGNUMBER_LIMB_16BIT
+    static limb_t const a24[2] PROGMEM = {0xDB41, 0x0001};
+#elif BIGNUMBER_LIMB_32BIT || BIGNUMBER_LIMB_64BIT
+    static limb_t const a24[1] PROGMEM = {0x0001DB41};
+#else
+    #error "limb_t must be 8, 16, 32, or 64 bits in size"
+#endif
+    #define NUM_A24_LIMBS   (sizeof(a24) / sizeof(limb_t))
+
+    // Multiply the lowest limb of a24 by x and zero-extend into the result.
+    limb_t temp[NUM_LIMBS_512BIT];
+    uint8_t i, j;
+    dlimb_t carry = 0;
+    limb_t word = pgm_read_limb(&(a24[0]));
+    const limb_t *xx = x;
+    limb_t *tt = temp;
+    for (i = 0; i < NUM_LIMBS_256BIT; ++i) {
+        carry += ((dlimb_t)(*xx++)) * word;
+        *tt++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    *tt = (limb_t)carry;
+
+    // Multiply and add the remaining limbs of a24.
+    for (i = 1; i < NUM_A24_LIMBS; ++i) {
+        word = pgm_read_limb(&(a24[i]));
+        carry = 0;
+        xx = x;
+        tt = temp + i;
+        for (j = 0; j < NUM_LIMBS_256BIT; ++j) {
+            carry += ((dlimb_t)(*xx++)) * word;
+            carry += *tt;
+            *tt++ = (limb_t)carry;
+            carry >>= LIMB_BITS;
+        }
+        *tt = (limb_t)carry;
+    }
+#else
+    limb_t temp[NUM_LIMBS_512BIT];
+    #define NUM_A24_LIMBS   ((3 + sizeof(limb_t) - 1) / sizeof(limb_t))
+    __asm__ __volatile__ (
+        // Load the two low bytes of a24 into r16 and r17.
+        // The third byte is 0x01 which we can deal with implicitly.
+        "ldi r16,0x41\n"
+        "ldi r17,0xDB\n"
+
+        // Iterate over the bytes of "x" and multiply each with a24.
+        "ldi r25,32\n"              // 32 bytes in "x"
+        "clr r22\n"                 // r22 = 0
+        "clr r18\n"                 // r18:r19:r11 = 0 (carry)
+        "clr r19\n"
+        "clr r11\n"
+        "1:\n"
+        "ld r21,X+\n"               // r21 = *x++
+        "mul r21,r16\n"             // r8:r9 = r21 * a24[0]
+        "movw r8,r0\n"
+        "mul r21,r17\n"             // r9:r1 += r21 * a24[1]
+        "add r9,r0\n"
+        "adc r1,r21\n"              // r1:r10 += r21 * a24[2] (implicitly 1)
+        "mov r10,r22\n"
+        "adc r10,r22\n"
+        "add r8,r18\n"              // r8:r9:r1:r10 += carry
+        "adc r9,r19\n"
+        "adc r1,r11\n"
+        "adc r10,r22\n"
+        "st Z+,r8\n"                // *tt++ = r8
+        "mov r18,r9\n"              // carry = r9:r1:r10
+        "mov r19,r1\n"
+        "mov r11,r10\n"
+        "dec r25\n"
+        "brne 1b\n"
+        "st Z,r18\n"                // *tt = carry
+        "std Z+1,r19\n"
+        "std Z+2,r11\n"
+#if BIGNUMBER_LIMB_16BIT || BIGNUMBER_LIMB_32BIT
+        "std Z+3,r22\n"             // Zero pad to a limb boundary
+#endif
+
+        // Restore __zero_reg__
+        "clr __zero_reg__\n"
+
+        : : "x"(x), "z"(temp)
+        : "r8", "r9", "r10", "r11", "r16", "r17", "r18", "r19",
+          "r20", "r21", "r22", "r25"
+    );
+#endif
+
+    // Reduce the intermediate result modulo 2^255 - 19.
+    reduce(result, temp, NUM_A24_LIMBS);
+    strict_clean(temp);
+}
+
+/**
+ * \brief Multiplies two values and then reduces the result modulo 2^255 - 19,
+ * where one of the values is in program memory.
+ *
+ * \param result The result, which must be NUM_LIMBS_256BIT limbs in size
+ * and can be the same array as \a x or \a y.
+ * \param x The first value to multiply, which must be NUM_LIMBS_256BIT limbs
+ * in size and less than 2^255 - 19.
+ * \param y The second value to multiply, which must be NUM_LIMBS_256BIT limbs
+ * in size and less than 2^255 - 19.  This array must be in program memory.
+ */
+void Curve25519::mul_P(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    limb_t temp[NUM_LIMBS_512BIT];
+    uint8_t i, j;
+    dlimb_t carry;
+    limb_t word;
+    const limb_t *xx;
+    limb_t *tt;
+
+    // Multiply the lowest word of y by x.
+    carry = 0;
+    word = pgm_read_limb(&(y[0]));
+    xx = x;
+    tt = temp;
+    for (i = 0; i < NUM_LIMBS_256BIT; ++i) {
+        carry += ((dlimb_t)(*xx++)) * word;
+        *tt++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    *tt = (limb_t)carry;
+
+    // Multiply and add the remaining words of y by x.
+    for (i = 1; i < NUM_LIMBS_256BIT; ++i) {
+        word = pgm_read_limb(&(y[i]));
+        carry = 0;
+        xx = x;
+        tt = temp + i;
+        for (j = 0; j < NUM_LIMBS_256BIT; ++j) {
+            carry += ((dlimb_t)(*xx++)) * word;
+            carry += *tt;
+            *tt++ = (limb_t)carry;
+            carry >>= LIMB_BITS;
+        }
+        *tt = (limb_t)carry;
+    }
+
+    // Reduce the intermediate result modulo 2^255 - 19.
+    reduce(result, temp, NUM_LIMBS_256BIT);
+    strict_clean(temp);
+}
+
+/**
+ * \brief Adds two values and then reduces the result modulo 2^255 - 19.
+ *
+ * \param result The result, which must be NUM_LIMBS_256BIT limbs in size
+ * and can be the same array as \a x or \a y.
+ * \param x The first value to multiply, which must be NUM_LIMBS_256BIT
+ * limbs in size and less than 2^255 - 19.
+ * \param y The second value to multiply, which must be NUM_LIMBS_256BIT
+ * limbs in size and less than 2^255 - 19.
+ */
+void Curve25519::add(limb_t *result, const limb_t *x, const limb_t *y)
+{
+#if !defined(CURVE25519_ASM_AVR)
+    dlimb_t carry = 0;
+    uint8_t posn;
+    limb_t *rr = result;
+
+    // Add the two arrays to obtain the intermediate result.
+    for (posn = 0; posn < NUM_LIMBS_256BIT; ++posn) {
+        carry += *x++;
+        carry += *y++;
+        *rr++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+#else // CURVE25519_ASM_AVR
+    __asm__ __volatile__ (
+        // Save Y and copy the "result" pointer into it.
+        "push r28\n"
+        "push r29\n"
+        "mov r28,%A2\n"
+        "mov r29,%B2\n"
+
+        // Unroll the loop to operate on 4 bytes at a time (8 iterations).
+        "ldi r24,8\n"               // Loop counter.
+        "clr r25\n"                 // carry = 0
+        "1:\n"
+        "ld r16,X+\n"               // r16:r19 = *x++
+        "ld r17,X+\n"
+        "ld r18,X+\n"
+        "ld r19,X+\n"
+        "ld r20,Z+\n"               // r20:r23 = *y++
+        "ld r21,Z+\n"
+        "ld r22,Z+\n"
+        "ld r23,Z+\n"
+        "add r16,r25\n"             // r16:r19:carry += carry
+        "adc r17,__zero_reg__\n"
+        "adc r18,__zero_reg__\n"
+        "adc r19,__zero_reg__\n"
+        "mov r25,__zero_reg__\n"
+        "adc r25,r25\n"
+        "add r16,r20\n"             // r16:r19:carry += r20:r23
+        "adc r17,r21\n"
+        "adc r18,r22\n"
+        "adc r19,r23\n"
+        "adc r25,__zero_reg__\n"
+        "st Y+,r16\n"               // *rr++ = r16:r23
+        "st Y+,r17\n"
+        "st Y+,r18\n"
+        "st Y+,r19\n"
+        "dec r24\n"
+        "brne 1b\n"
+
+        // Restore Y.
+        "pop r29\n"
+        "pop r28\n"
+        : : "x"(x), "z"(y), "r"(result)
+        : "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
+          "r24", "r25"
+    );
+#endif // CURVE25519_ASM_AVR
+
+    // Reduce the result using the quick trial subtraction method.
+    reduceQuick(result);
+}
+
+/**
+ * \brief Subtracts two values and then reduces the result modulo 2^255 - 19.
+ *
+ * \param result The result, which must be NUM_LIMBS_256BIT limbs in size
+ * and can be the same array as \a x or \a y.
+ * \param x The first value to multiply, which must be NUM_LIMBS_256BIT
+ * limbs in size and less than 2^255 - 19.
+ * \param y The second value to multiply, which must be NUM_LIMBS_256BIT
+ * limbs in size and less than 2^255 - 19.
+ */
+void Curve25519::sub(limb_t *result, const limb_t *x, const limb_t *y)
+{
+#if !defined(CURVE25519_ASM_AVR)
+    dlimb_t borrow;
+    uint8_t posn;
+    limb_t *rr = result;
+
+    // Subtract y from x to generate the intermediate result.
+    borrow = 0;
+    for (posn = 0; posn < NUM_LIMBS_256BIT; ++posn) {
+        borrow = ((dlimb_t)(*x++)) - (*y++) - ((borrow >> LIMB_BITS) & 0x01);
+        *rr++ = (limb_t)borrow;
+    }
+
+    // If we had a borrow, then the result has gone negative and we
+    // have to add 2^255 - 19 to the result to make it positive again.
+    // The top bits of "borrow" will be all 1's if there is a borrow
+    // or it will be all 0's if there was no borrow.  Easiest is to
+    // conditionally subtract 19 and then mask off the high bit.
+    rr = result;
+    borrow = (borrow >> LIMB_BITS) & 19U;
+    borrow = ((dlimb_t)(*rr)) - borrow;
+    *rr++ = (limb_t)borrow;
+    for (posn = 1; posn < NUM_LIMBS_256BIT; ++posn) {
+        borrow = ((dlimb_t)(*rr)) - ((borrow >> LIMB_BITS) & 0x01);
+        *rr++ = (limb_t)borrow;
+    }
+    *(--rr) &= ((((limb_t)1) << (LIMB_BITS - 1)) - 1);
+#else // CURVE25519_ASM_AVR
+    __asm__ __volatile__ (
+        // Save Y and copy the "result" pointer into it.
+        "push r28\n"
+        "push r29\n"
+        "mov r28,%A2\n"
+        "mov r29,%B2\n"
+
+        // Unroll the sub loop to operate on 4 bytes at a time (8 iterations).
+        "ldi r24,8\n"               // Loop counter.
+        "clr r25\n"                 // borrow = 0
+        "1:\n"
+        "ld r16,X+\n"               // r16:r19 = *x++
+        "ld r17,X+\n"
+        "ld r18,X+\n"
+        "ld r19,X+\n"
+        "ld r20,Z+\n"               // r20:r23 = *y++
+        "ld r21,Z+\n"
+        "ld r22,Z+\n"
+        "ld r23,Z+\n"
+        "sub r16,r25\n"             // r16:r19:borrow -= borrow
+        "sbc r17,__zero_reg__\n"
+        "sbc r18,__zero_reg__\n"
+        "sbc r19,__zero_reg__\n"
+        "mov r25,__zero_reg__\n"
+        "sbc r25,__zero_reg__\n"
+        "sub r16,r20\n"             // r16:r19:borrow -= r20:r23
+        "sbc r17,r21\n"
+        "sbc r18,r22\n"
+        "sbc r19,r23\n"
+        "sbc r25,__zero_reg__\n"
+        "st Y+,r16\n"               // *rr++ = r16:r23
+        "st Y+,r17\n"
+        "st Y+,r18\n"
+        "st Y+,r19\n"
+        "andi r25,1\n"              // Only need the bottom bit of the borrow
+        "dec r24\n"
+        "brne 1b\n"
+
+        // If there was a borrow, then we need to add 2^255 - 19 back.
+        // We conditionally subtract 19 and then mask off the high bit.
+        "neg r25\n"                 // borrow = mask(borrow) & 19
+        "andi r25,19\n"
+        "sbiw r28,32\n"             // Point Y back to the start of "result"
+        "ldi r24,8\n"
+        "2:\n"
+        "ld r16,Y\n"                // r16:r19 = *rr
+        "ldd r17,Y+1\n"
+        "ldd r18,Y+2\n"
+        "ldd r19,Y+3\n"
+        "sub r16,r25\n"
+        "sbc r17,__zero_reg__\n"    // r16:r19:borrow -= borrow
+        "sbc r18,__zero_reg__\n"
+        "sbc r19,__zero_reg__\n"
+        "mov r25,__zero_reg__\n"
+        "sbc r25,__zero_reg__\n"
+        "andi r25,1\n"
+        "st Y+,r16\n"               // *r++ = r16:r19
+        "st Y+,r17\n"
+        "st Y+,r18\n"
+        "st Y+,r19\n"
+        "dec r24\n"
+        "brne 2b\n"
+        "andi r19,0x7F\n"           // Mask off the high bit in the last byte
+        "sbiw r28,1\n"
+        "st Y,r19\n"
+
+        // Restore Y.
+        "pop r29\n"
+        "pop r28\n"
+        : : "x"(x), "z"(y), "r"(result)
+        : "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
+          "r24", "r25"
+    );
+#endif // CURVE25519_ASM_AVR
+}
+
+/**
+ * \brief Conditionally swaps two values if a selection value is non-zero.
+ *
+ * \param select Non-zero to swap \a x and \a y, zero to leave them unchanged.
+ * \param x The first value to conditionally swap.
+ * \param y The second value to conditionally swap.
+ *
+ * The swap is performed in a way that it should take the same amount of
+ * time irrespective of the value of \a select.
+ *
+ * \sa cmove()
+ */
+void Curve25519::cswap(limb_t select, limb_t *x, limb_t *y)
+{
+#if !defined(CURVE25519_ASM_AVR)
+    uint8_t posn;
+    limb_t dummy;
+    limb_t sel;
+
+    // Turn "select" into an all-zeroes or all-ones mask.  We don't care
+    // which bit or bits is set in the original "select" value.
+    sel = (limb_t)(((((dlimb_t)1) << LIMB_BITS) - select) >> LIMB_BITS);
+    --sel;
+
+    // Swap the two values based on "select".  Algorithm from:
+    // http://tools.ietf.org/html/rfc7748
+    for (posn = 0; posn < NUM_LIMBS_256BIT; ++posn) {
+        dummy = sel & (x[posn] ^ y[posn]);
+        x[posn] ^= dummy;
+        y[posn] ^= dummy;
+    }
+#else // CURVE25519_ASM_AVR
+    __asm__ __volatile__ (
+        // Combine all bytes from "select" into one and then turn
+        // that byte into the "sel" mask in r24.
+        "clr r24\n"
+#if BIGNUMBER_LIMB_8BIT
+        "sub r24,%2\n"
+#elif BIGNUMBER_LIMB_16BIT
+        "or %A2,%B2\n"
+        "sub r24,%A2\n"
+#elif BIGNUMBER_LIMB_32BIT
+        "or %A2,%B2\n"
+        "or %A2,%C2\n"
+        "or %A2,%D2\n"
+        "sub r24,%A2\n"
+#endif
+        "mov r24,__zero_reg__\n"
+        "sbc r24,r24\n"
+
+        // Perform the conditional swap 4 bytes at a time.
+        "ldi r25,8\n"
+        "1:\n"
+        "ld r16,X+\n"           // r16:r19 = *x
+        "ld r17,X+\n"
+        "ld r18,X+\n"
+        "ld r19,X\n"
+        "ld r20,Z\n"            // r20:r23 = *y
+        "ldd r21,Z+1\n"
+        "ldd r22,Z+2\n"
+        "ldd r23,Z+3\n"
+        "mov r12,r16\n"         // r12:r15 = (r16:r19 ^ r20:r23) & sel
+        "mov r13,r17\n"
+        "mov r14,r18\n"
+        "mov r15,r19\n"
+        "eor r12,r20\n"
+        "eor r13,r21\n"
+        "eor r14,r22\n"
+        "eor r15,r23\n"
+        "and r12,r24\n"
+        "and r13,r24\n"
+        "and r14,r24\n"
+        "and r15,r24\n"
+        "eor r16,r12\n"         // r16:r19 ^= r12:r15
+        "eor r17,r13\n"
+        "eor r18,r14\n"
+        "eor r19,r15\n"
+        "eor r20,r12\n"         // r20:r23 ^= r12:r15
+        "eor r21,r13\n"
+        "eor r22,r14\n"
+        "eor r23,r15\n"
+        "st X,r19\n"            // *x++ = r16:r19
+        "st -X,r18\n"
+        "st -X,r17\n"
+        "st -X,r16\n"
+        "adiw r26,4\n"
+        "st Z+,r20\n"           // *y++ = r20:r23
+        "st Z+,r21\n"
+        "st Z+,r22\n"
+        "st Z+,r23\n"
+        "dec r25\n"
+        "brne 1b\n"
+
+        : : "x"(x), "z"(y), "r"(select)
+        : "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19",
+          "r20", "r21", "r22", "r23", "r24", "r25"
+    );
+#endif // CURVE25519_ASM_AVR
+}
+
+/**
+ * \brief Conditionally moves \a y into \a x if a selection value is non-zero.
+ *
+ * \param select Non-zero to move \a y into \a x, zero to leave \a x unchanged.
+ * \param x The destination to move into.
+ * \param y The value to conditionally move.
+ *
+ * The move is performed in a way that it should take the same amount of
+ * time irrespective of the value of \a select.
+ *
+ * \sa cswap()
+ */
+void Curve25519::cmove(limb_t select, limb_t *x, const limb_t *y)
+{
+#if !defined(CURVE25519_ASM_AVR)
+    uint8_t posn;
+    limb_t dummy;
+    limb_t sel;
+
+    // Turn "select" into an all-zeroes or all-ones mask.  We don't care
+    // which bit or bits is set in the original "select" value.
+    sel = (limb_t)(((((dlimb_t)1) << LIMB_BITS) - select) >> LIMB_BITS);
+    --sel;
+
+    // Move y into x based on "select".  Similar to conditional swap above.
+    for (posn = 0; posn < NUM_LIMBS_256BIT; ++posn) {
+        dummy = sel & (x[posn] ^ y[posn]);
+        x[posn] ^= dummy;
+    }
+#else // CURVE25519_ASM_AVR
+    __asm__ __volatile__ (
+        // Combine all bytes from "select" into one and then turn
+        // that byte into the "sel" mask in r24.
+        "clr r24\n"
+#if BIGNUMBER_LIMB_8BIT
+        "sub r24,%2\n"
+#elif BIGNUMBER_LIMB_16BIT
+        "or %A2,%B2\n"
+        "sub r24,%A2\n"
+#elif BIGNUMBER_LIMB_32BIT
+        "or %A2,%B2\n"
+        "or %A2,%C2\n"
+        "or %A2,%D2\n"
+        "sub r24,%A2\n"
+#endif
+        "mov r24,__zero_reg__\n"
+        "sbc r24,r24\n"
+
+        // Perform the conditional move 4 bytes at a time.
+        "ldi r25,8\n"
+        "1:\n"
+        "ld r16,X+\n"           // r16:r19 = *x
+        "ld r17,X+\n"
+        "ld r18,X+\n"
+        "ld r19,X\n"
+        "ld r20,Z+\n"           // r20:r23 = *y++
+        "ld r21,Z+\n"
+        "ld r22,Z+\n"
+        "ld r23,Z+\n"
+        "eor r20,r16\n"         // r20:r23 = (r16:r19 ^ r20:r23) & sel
+        "eor r21,r17\n"
+        "eor r22,r18\n"
+        "eor r23,r19\n"
+        "and r20,r24\n"
+        "and r21,r24\n"
+        "and r22,r24\n"
+        "and r23,r24\n"
+        "eor r16,r20\n"         // r16:r19 ^= r20:r23
+        "eor r17,r21\n"
+        "eor r18,r22\n"
+        "eor r19,r23\n"
+        "st X,r19\n"            // *x++ = r16:r19
+        "st -X,r18\n"
+        "st -X,r17\n"
+        "st -X,r16\n"
+        "adiw r26,4\n"
+        "dec r25\n"
+        "brne 1b\n"
+
+        : : "x"(x), "z"(y), "r"(select)
+        : "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
+          "r24", "r25"
+    );
+#endif // CURVE25519_ASM_AVR
+}
+
+/**
+ * \brief Raise x to the power of (2^250 - 1).
+ *
+ * \param result The result array, which must be NUM_LIMBS_256BIT limbs in size.
+ * \param x The value to raise.
+ */
+void Curve25519::pow250(limb_t *result, const limb_t *x)
+{
+    limb_t t1[NUM_LIMBS_256BIT];
+    uint8_t i, j;
+
+    // The big-endian hexadecimal expansion of (2^250 - 1) is:
+    // 03FFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
+    //
+    // The naive implementation needs to do 2 multiplications per 1 bit and
+    // 1 multiplication per 0 bit.  We can improve upon this by creating a
+    // pattern 0000000001 ... 0000000001.  If we square and multiply the
+    // pattern by itself we can turn the pattern into the partial results
+    // 0000000011 ... 0000000011, 0000000111 ... 0000000111, etc.
+    // This averages out to about 1.1 multiplications per 1 bit instead of 2.
+
+    // Build a pattern of 250 bits in length of repeated copies of 0000000001.
+    #define RECIP_GROUP_SIZE 10
+    #define RECIP_GROUP_BITS 250    // Must be a multiple of RECIP_GROUP_SIZE.
+    square(t1, x);
+    for (j = 0; j < (RECIP_GROUP_SIZE - 1); ++j)
+        square(t1, t1);
+    mul(result, t1, x);
+    for (i = 0; i < ((RECIP_GROUP_BITS / RECIP_GROUP_SIZE) - 2); ++i) {
+        for (j = 0; j < RECIP_GROUP_SIZE; ++j)
+            square(t1, t1);
+        mul(result, result, t1);
+    }
+
+    // Multiply bit-shifted versions of the 0000000001 pattern into
+    // the result to "fill in" the gaps in the pattern.
+    square(t1, result);
+    mul(result, result, t1);
+    for (j = 0; j < (RECIP_GROUP_SIZE - 2); ++j) {
+        square(t1, t1);
+        mul(result, result, t1);
+    }
+
+    // Clean up and exit.
+    clean(t1);
+}
+
+/**
+ * \brief Computes the reciprocal of a number modulo 2^255 - 19.
+ *
+ * \param result The result as a array of NUM_LIMBS_256BIT limbs in size.
+ * This cannot be the same array as \a x.
+ * \param x The number to compute the reciprocal for.
+ */
+void Curve25519::recip(limb_t *result, const limb_t *x)
+{
+    // The reciprocal is the same as x ^ (p - 2) where p = 2^255 - 19.
+    // The big-endian hexadecimal expansion of (p - 2) is:
+    // 7FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFEB
+    // Start with the 250 upper bits of the expansion of (p - 2).
+    pow250(result, x);
+
+    // Deal with the 5 lowest bits of (p - 2), 01011, from highest to lowest.
+    square(result, result);
+    square(result, result);
+    mul(result, result, x);
+    square(result, result);
+    square(result, result);
+    mul(result, result, x);
+    square(result, result);
+    mul(result, result, x);
+}
+
+/**
+ * \brief Computes the square root of a number modulo 2^255 - 19.
+ *
+ * \param result The result as a array of NUM_LIMBS_256BIT limbs in size.
+ * This must not overlap with \a x.
+ * \param x The number to compute the square root for.
+ *
+ * For any number \a x, there are two square roots: positive and negative.
+ * For example, both 2 and -2 are square roots of 4 because 2 * 2 = -2 * -2.
+ * This function will return one or the other.  Callers must determine which
+ * square root they are interested in and invert the result as necessary.
+ *
+ * \note This function is not constant time so it should only be used
+ * on publicly-known values.
+ */
+bool Curve25519::sqrt(limb_t *result, const limb_t *x)
+{
+    // sqrt(-1) mod (2^255 - 19).
+    static limb_t const numSqrtM1[NUM_LIMBS_256BIT] PROGMEM = {
+        LIMB_PAIR(0x4A0EA0B0, 0xC4EE1B27), LIMB_PAIR(0xAD2FE478, 0x2F431806),
+        LIMB_PAIR(0x3DFBD7A7, 0x2B4D0099), LIMB_PAIR(0x4FC1DF0B, 0x2B832480)
+    };
+    limb_t y[NUM_LIMBS_256BIT];
+
+    // Algorithm from: http://tools.ietf.org/html/rfc7748
+
+    // Compute a candidate root: result = x^((p + 3) / 8) mod p.
+    // (p + 3) / 8 = (2^252 - 2) which is 251 one bits followed by a zero:
+    // 0FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE
+    pow250(result, x);
+    square(result, result);
+    mul(result, result, x);
+    square(result, result);
+
+    // Did we get the square root immediately?
+    square(y, result);
+    if (memcmp(x, y, sizeof(y)) == 0) {
+        clean(y);
+        return true;
+    }
+
+    // Multiply the result by sqrt(-1) and check again.
+    mul_P(result, result, numSqrtM1);
+    square(y, result);
+    if (memcmp(x, y, sizeof(y)) == 0) {
+        clean(y);
+        return true;
+    }
+
+    // The number does not have a square root.
+    clean(y);
+    return false;
+}
diff --git a/src/modules/Crypto/Curve25519.h b/src/modules/Crypto/Curve25519.h
new file mode 100644
index 0000000..b95a9fa
--- /dev/null
+++ b/src/modules/Crypto/Curve25519.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_CURVE25519_h
+#define CRYPTO_CURVE25519_h
+
+#include "BigNumberUtil.h"
+
+class Ed25519;
+
+class Curve25519
+{
+public:
+    static bool eval(uint8_t result[32], const uint8_t s[32], const uint8_t x[32]);
+
+    static void dh1(uint8_t k[32], uint8_t f[32]);
+    static bool dh2(uint8_t k[32], uint8_t f[32]);
+
+#if defined(TEST_CURVE25519_FIELD_OPS)
+public:
+#else
+private:
+#endif
+    static uint8_t isWeakPoint(const uint8_t k[32]);
+
+    static void reduce(limb_t *result, limb_t *x, uint8_t size);
+    static limb_t reduceQuick(limb_t *x);
+
+    static void mulNoReduce(limb_t *result, const limb_t *x, const limb_t *y);
+
+    static void mul(limb_t *result, const limb_t *x, const limb_t *y);
+    static void square(limb_t *result, const limb_t *x)
+    {
+        mul(result, x, x);
+    }
+
+    static void mulA24(limb_t *result, const limb_t *x);
+
+    static void mul_P(limb_t *result, const limb_t *x, const limb_t *y);
+
+    static void add(limb_t *result, const limb_t *x, const limb_t *y);
+    static void sub(limb_t *result, const limb_t *x, const limb_t *y);
+
+    static void cswap(limb_t select, limb_t *x, limb_t *y);
+    static void cmove(limb_t select, limb_t *x, const limb_t *y);
+
+    static void pow250(limb_t *result, const limb_t *x);
+    static void recip(limb_t *result, const limb_t *x);
+    static bool sqrt(limb_t *result, const limb_t *x);
+
+    // Constructor and destructor are private - cannot instantiate this class.
+    Curve25519() {}
+    ~Curve25519() {}
+
+    friend class Ed25519;
+};
+
+#endif
diff --git a/src/modules/Crypto/EAX.cpp b/src/modules/Crypto/EAX.cpp
new file mode 100644
index 0000000..d5b3cff
--- /dev/null
+++ b/src/modules/Crypto/EAX.cpp
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "EAX.h"
+#include "Crypto.h"
+#include <string.h>
+
+/**
+ * \class EAXCommon EAX.h <EAX.h>
+ * \brief Concrete base class to assist with implementing EAX for
+ * 128-bit block ciphers.
+ *
+ * References: https://en.wikipedia.org/wiki/EAX_mode,
+ * http://web.cs.ucdavis.edu/~rogaway/papers/eax.html
+ *
+ * \sa EAX
+ */
+
+/**
+ * \brief Constructs a new cipher in EAX mode.
+ *
+ * This constructor must be followed by a call to setBlockCipher().
+ */
+EAXCommon::EAXCommon()
+{
+    state.encPosn = 0;
+    state.authMode = 0;
+}
+
+EAXCommon::~EAXCommon()
+{
+    clean(state);
+}
+
+size_t EAXCommon::keySize() const
+{
+    return omac.blockCipher()->keySize();
+}
+
+size_t EAXCommon::ivSize() const
+{
+    // Can use any size but 16 is recommended.
+    return 16;
+}
+
+size_t EAXCommon::tagSize() const
+{
+    // Tags can be up to 16 bytes in length.
+    return 16;
+}
+
+bool EAXCommon::setKey(const uint8_t *key, size_t len)
+{
+    return omac.blockCipher()->setKey(key, len);
+}
+
+bool EAXCommon::setIV(const uint8_t *iv, size_t len)
+{
+    // Must have at least 1 byte for the IV.
+    if (!len)
+        return false;
+
+    // Hash the IV to create the initial nonce for CTR mode.  Also creates B.
+    omac.initFirst(state.counter);
+    omac.update(state.counter, iv, len);
+    omac.finalize(state.counter);
+
+    // The tag is initially the nonce value.  Will be XOR'ed with
+    // the hash of the authenticated and encrypted data later.
+    memcpy(state.tag, state.counter, 16);
+
+    // Start the hashing context for the authenticated data.
+    omac.initNext(state.hash, 1);
+    state.encPosn = 16;
+    state.authMode = 1;
+
+    // The EAX context is ready to go.
+    return true;
+}
+
+void EAXCommon::encrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+    if (state.authMode)
+        closeAuthData();
+    encryptCTR(output, input, len);
+    omac.update(state.hash, output, len);
+}
+
+void EAXCommon::decrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+    if (state.authMode)
+        closeAuthData();
+    omac.update(state.hash, input, len);
+    encryptCTR(output, input, len);
+}
+
+void EAXCommon::addAuthData(const void *data, size_t len)
+{
+    if (state.authMode)
+        omac.update(state.hash, (const uint8_t *)data, len);
+}
+
+void EAXCommon::computeTag(void *tag, size_t len)
+{
+    closeTag();
+    if (len > 16)
+        len = 16;
+    memcpy(tag, state.tag, len);
+}
+
+bool EAXCommon::checkTag(const void *tag, size_t len)
+{
+    // Can never match if the expected tag length is too long.
+    if (len > 16)
+        return false;
+
+    // Compute the final tag and check it.
+    closeTag();
+    return secure_compare(state.tag, tag, len);
+}
+
+void EAXCommon::clear()
+{
+    clean(state);
+}
+
+/**
+ * \brief Closes the authenticated data portion of the session and
+ * starts encryption or decryption.
+ */
+void EAXCommon::closeAuthData()
+{
+    // Finalise the OMAC hash and XOR it with the final tag.
+    omac.finalize(state.hash);
+    for (uint8_t index = 0; index < 16; ++index)
+        state.tag[index] ^= state.hash[index];
+    state.authMode = 0;
+
+    // Initialise the hashing context for the ciphertext data.
+    omac.initNext(state.hash, 2);
+}
+
+/**
+ * \brief Encrypts or decrypts a region using the block cipher in CTR mode.
+ *
+ * \param output The output buffer to write to, which may be the same
+ * buffer as \a input.  The \a output buffer must have at least as many
+ * bytes as the \a input buffer.
+ * \param input The input buffer to read from.
+ * \param len The number of bytes to process.
+ */
+void EAXCommon::encryptCTR(uint8_t *output, const uint8_t *input, size_t len)
+{
+    while (len > 0) {
+        // Do we need to start a new block?
+        if (state.encPosn == 16) {
+            // Encrypt the counter to create the next keystream block.
+            omac.blockCipher()->encryptBlock(state.stream, state.counter);
+            state.encPosn = 0;
+
+            // Increment the counter, taking care not to reveal
+            // any timing information about the starting value.
+            // We iterate through the entire counter region even
+            // if we could stop earlier because a byte is non-zero.
+            uint16_t temp = 1;
+            uint8_t index = 16;
+            while (index > 0) {
+                --index;
+                temp += state.counter[index];
+                state.counter[index] = (uint8_t)temp;
+                temp >>= 8;
+            }
+        }
+
+        // Encrypt/decrypt the current input block.
+        uint8_t size = 16 - state.encPosn;
+        if (size > len)
+            size = (uint8_t)len;
+        for (uint8_t index = 0; index < size; ++index)
+            output[index] = input[index] ^ state.stream[(state.encPosn)++];
+
+        // Move onto the next block.
+        len -= size;
+        input += size;
+        output += size;
+    }
+}
+
+void EAXCommon::closeTag()
+{
+    // If we were only authenticating, then close off auth mode.
+    if (state.authMode)
+        closeAuthData();
+
+    // Finalise the hash over the ciphertext and XOR with the final tag.
+    omac.finalize(state.hash);
+    for (uint8_t index = 0; index < 16; ++index)
+        state.tag[index] ^= state.hash[index];
+}
+
+/**
+ * \fn void EAXCommon::setBlockCipher(BlockCipher *cipher)
+ * \brief Sets the block cipher to use for this EAX object.
+ *
+ * \param cipher The block cipher to use to implement EAX mode.
+ * This object must have a block size of 128 bits (16 bytes).
+ */
+
+/**
+ * \class EAX EAX.h <EAX.h>
+ * \brief Implementation of the EAX authenticated cipher.
+ *
+ * EAX mode converts a block cipher into an authenticated cipher
+ * that uses the block cipher T to encrypt and authenticate.
+ *
+ * The size of the key is determined by the underlying block cipher T.
+ * The IV is recommended to be 128 bits (16 bytes) in length, but other
+ * lengths are supported as well.  The default tagSize() is 128 bits
+ * (16 bytes) but the EAX specification does allow smaller tag sizes.
+ *
+ * The template parameter T must be a concrete subclass of BlockCipher
+ * indicating the specific block cipher to use.  The block cipher must
+ * have a block size of 128 bits.  For example, the following creates a
+ * EAX object using AES256 as the underlying cipher and then uses it
+ * to encrypt and authenticate a \c plaintext block:
+ *
+ * \code
+ * EAX<AES256> eax;
+ * eax.setKey(key, sizeof(key));
+ * eax.setIV(iv, sizeof(iv));
+ * eax.addAuthData(adata, sizeof(adata));
+ * eax.encrypt(ciphertext, plaintext, sizeof(plaintext));
+ * eax.computeTag(tag, sizeof(tag));
+ * \endcode
+ *
+ * The decryption process is almost identical to convert a \c ciphertext and
+ * \a tag back into plaintext and then check the tag:
+ *
+ * \code
+ * EAX<AES256> eax;
+ * eax.setKey(key, sizeof(key));
+ * eax.setIV(iv, sizeof(iv));
+ * eax.addAuthData(adata, sizeof(adata));
+ * eax.decrypt(ciphertext, plaintext, sizeof(plaintext));
+ * if (!eax.checkTag(tag, sizeof(tag))) {
+ *     // The data was invalid - do not use it.
+ *     ...
+ * }
+ * \endcode
+ *
+ * The EAX class can also be used to implement message authentication
+ * by omitting the plaintext:
+ *
+ * \code
+ * EAX<AES256> eax;
+ * eax.setKey(key, sizeof(key));
+ * eax.setIV(iv, sizeof(iv));
+ * eax.addAuthData(adata1, sizeof(adata1));
+ * eax.addAuthData(adata2, sizeof(adata1));
+ * ...
+ * eax.addAuthData(adataN, sizeof(adataN));
+ * eax.computeTag(tag, sizeof(tag));
+ * \endcode
+ *
+ * References: https://en.wikipedia.org/wiki/EAX_mode,
+ * http://web.cs.ucdavis.edu/~rogaway/papers/eax.html
+ *
+ * \sa EAXCommon, GCM
+ */
+
+/**
+ * \fn EAX::EAX()
+ * \brief Constructs a new EAX object for the block cipher T.
+ */
diff --git a/src/modules/Crypto/EAX.h b/src/modules/Crypto/EAX.h
new file mode 100644
index 0000000..1511a5d
--- /dev/null
+++ b/src/modules/Crypto/EAX.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_EAX_h
+#define CRYPTO_EAX_h
+
+#include "AuthenticatedCipher.h"
+#include "BlockCipher.h"
+#include "OMAC.h"
+
+class EAXCommon : public AuthenticatedCipher
+{
+public:
+    virtual ~EAXCommon();
+
+    size_t keySize() const;
+    size_t ivSize() const;
+    size_t tagSize() const;
+
+    bool setKey(const uint8_t *key, size_t len);
+    bool setIV(const uint8_t *iv, size_t len);
+
+    void encrypt(uint8_t *output, const uint8_t *input, size_t len);
+    void decrypt(uint8_t *output, const uint8_t *input, size_t len);
+
+    void addAuthData(const void *data, size_t len);
+
+    void computeTag(void *tag, size_t len);
+    bool checkTag(const void *tag, size_t len);
+
+    void clear();
+
+protected:
+    EAXCommon();
+    void setBlockCipher(BlockCipher *cipher)
+    {
+        omac.setBlockCipher(cipher);
+    }
+
+private:
+    struct {
+        uint8_t counter[16];
+        uint8_t stream[16];
+        uint8_t tag[16];
+        uint8_t hash[16];
+        uint8_t encPosn;
+        uint8_t authMode;
+    } state;
+    OMAC omac;
+
+    void closeAuthData();
+    void encryptCTR(uint8_t *output, const uint8_t *input, size_t len);
+    void closeTag();
+};
+
+template <typename T>
+class EAX : public EAXCommon
+{
+public:
+    EAX() { setBlockCipher(&cipher); }
+
+private:
+    T cipher;
+};
+
+#endif
diff --git a/src/modules/Crypto/Ed25519.cpp b/src/modules/Crypto/Ed25519.cpp
new file mode 100644
index 0000000..5732e73
--- /dev/null
+++ b/src/modules/Crypto/Ed25519.cpp
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "Ed25519.h"
+#include "Curve25519.h"
+#include "Crypto.h"
+#include "RNG.h"
+#include "utility/LimbUtil.h"
+#include <string.h>
+
+/**
+ * \class Ed25519 Ed25519.h <Ed25519.h>
+ * \brief Digital signatures based on the elliptic curve modulo 2^255 - 19.
+ *
+ * The first step in creating a digital signature with Ed25519 is to
+ * generate a key pair:
+ *
+ * \code
+ * uint8_t privateKey[32];
+ * uint8_t publicKey[32];
+ *
+ * Ed25519::generatePrivateKey(privateKey);
+ * Ed25519::derivePublicKey(publicKey, privateKey);
+ * \endcode
+ *
+ * The application can store both the private and public key for later
+ * signing operations.  Or it can store just the private key and then
+ * derive the public key at the point where signing is to occur.
+ *
+ * Message signing produces a 64-byte signature as follows:
+ *
+ * \code
+ * uint8_t message[N];
+ * uint8_t signature[64];
+ *
+ * Ed25519::sign(signature, privateKey, publicKey, message, N);
+ * \endcode
+ *
+ * And then to verify the signature:
+ *
+ * \code
+ * if (!Ed25519::verify(signature, publicKey, message, N)) {
+ *     // The signature is invalid.
+ *     ...
+ * }
+ * \endcode
+ *
+ * \note The public functions in this class need a substantial amount of
+ * stack space to store intermediate results while the curve function is
+ * being evaluated.  About 1.5k of free stack space is recommended for safety.
+ *
+ * References: https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-05
+ *
+ * \sa Curve25519
+ */
+
+/** @cond */
+
+// 37095705934669439343138083508754565189542113879843219016388785533085940283555
+static limb_t const numD[NUM_LIMBS_256BIT] PROGMEM = {
+    LIMB_PAIR(0x135978A3, 0x75EB4DCA), LIMB_PAIR(0x4141D8AB, 0x00700A4D),
+    LIMB_PAIR(0x7779E898, 0x8CC74079), LIMB_PAIR(0x2B6FFE73, 0x52036CEE)
+};
+
+// d * 2
+static limb_t const numDx2[NUM_LIMBS_256BIT] PROGMEM = {
+    LIMB_PAIR(0x26B2F159, 0xEBD69B94), LIMB_PAIR(0x8283B156, 0x00E0149A),
+    LIMB_PAIR(0xEEF3D130, 0x198E80F2), LIMB_PAIR(0x56DFFCE7, 0x2406D9DC)
+};
+
+// Extended homogenous co-ordinates for the base point.
+static limb_t const numBx[NUM_LIMBS_256BIT] PROGMEM = {
+    LIMB_PAIR(0x8F25D51A, 0xC9562D60), LIMB_PAIR(0x9525A7B2, 0x692CC760),
+    LIMB_PAIR(0xFDD6DC5C, 0xC0A4E231), LIMB_PAIR(0xCD6E53FE, 0x216936D3)
+};
+static limb_t const numBy[NUM_LIMBS_256BIT] PROGMEM = {
+    LIMB_PAIR(0x66666658, 0x66666666), LIMB_PAIR(0x66666666, 0x66666666),
+    LIMB_PAIR(0x66666666, 0x66666666), LIMB_PAIR(0x66666666, 0x66666666)
+};
+static limb_t const numBz[NUM_LIMBS_256BIT] PROGMEM = {
+    LIMB_PAIR(0x00000001, 0x00000000), LIMB_PAIR(0x00000000, 0x00000000),
+    LIMB_PAIR(0x00000000, 0x00000000), LIMB_PAIR(0x00000000, 0x00000000)
+};
+static limb_t const numBt[NUM_LIMBS_256BIT] PROGMEM = {
+    LIMB_PAIR(0xA5B7DDA3, 0x6DDE8AB3), LIMB_PAIR(0x775152F5, 0x20F09F80),
+    LIMB_PAIR(0x64ABE37D, 0x66EA4E8E), LIMB_PAIR(0xD78B7665, 0x67875F0F)
+};
+
+// 2^252 + 27742317777372353535851937790883648493
+static limb_t const numQ[NUM_LIMBS_256BIT] PROGMEM = {
+    LIMB_PAIR(0x5CF5D3ED, 0x5812631A), LIMB_PAIR(0xA2F79CD6, 0x14DEF9DE),
+    LIMB_PAIR(0x00000000, 0x00000000), LIMB_PAIR(0x00000000, 0x10000000)
+};
+
+/** @endcond */
+
+/**
+ * \brief Signs a message using a specific Ed25519 private key.
+ *
+ * \param signature The signature value.
+ * \param privateKey The private key to use to sign the message.
+ * \param publicKey The public key corresponding to \a privateKey.
+ * \param message Points to the message to be signed.
+ * \param len The length of the \a message to be signed.
+ *
+ * \sa verify(), derivePublicKey()
+ */
+void Ed25519::sign(uint8_t signature[64], const uint8_t privateKey[32],
+                   const uint8_t publicKey[32], const void *message, size_t len)
+{
+    SHA512 hash;
+    uint8_t *buf = (uint8_t *)(hash.state.w); // Reuse hash buffer to save memory.
+    limb_t a[NUM_LIMBS_256BIT];
+    limb_t r[NUM_LIMBS_256BIT];
+    limb_t k[NUM_LIMBS_256BIT];
+    limb_t t[NUM_LIMBS_512BIT + 1];
+    Point rB;
+
+    // Derive the secret scalar a and the message prefix from the private key.
+    deriveKeys(&hash, a, privateKey);
+
+    // Hash the prefix and the message to derive r.
+    hash.reset();
+    hash.update(buf + 32, 32);
+    hash.update(message, len);
+    hash.finalize(buf, 0);
+    reduceQFromBuffer(r, buf, t);
+
+    // Encode rB into the first half of the signature buffer as R.
+    mul(rB, r);
+    encodePoint(signature, rB);
+
+    // Hash R, A, and the message to get k.
+    hash.reset();
+    hash.update(signature, 32); // R
+    hash.update(publicKey, 32); // A
+    hash.update(message, len);
+    hash.finalize(buf, 0);
+    reduceQFromBuffer(k, buf, t);
+
+    // Compute s = (r + k * a) mod q.
+    Curve25519::mulNoReduce(t, k, a);
+    t[NUM_LIMBS_512BIT] = 0;
+    reduceQ(t, t);
+    BigNumberUtil::add(t, t, r, NUM_LIMBS_256BIT);
+    BigNumberUtil::reduceQuick_P(t, t, numQ, NUM_LIMBS_256BIT);
+    BigNumberUtil::packLE(signature + 32, 32, t, NUM_LIMBS_256BIT);
+
+    // Clean up.
+    clean(a);
+    clean(r);
+    clean(k);
+    clean(t);
+    clean(rB);
+}
+
+/**
+ * \brief Verifies a signature using a specific Ed25519 public key.
+ *
+ * \param signature The signature value to be verified.
+ * \param publicKey The public key to use to verify the signature.
+ * \param message The message whose signature is to be verified.
+ * \param len The length of the \a message to be verified.
+ *
+ * \return Returns true if the \a signature is valid for \a message;
+ * or false if the \a signature is not valid.
+ *
+ * \sa sign()
+ */
+bool Ed25519::verify(const uint8_t signature[64], const uint8_t publicKey[32],
+                     const void *message, size_t len)
+{
+    SHA512 hash;
+    Point A;
+    Point R;
+    Point sB;
+    Point kA;
+    uint8_t *k = (uint8_t *)(hash.state.w); // Reuse hash buffer to save memory.
+    bool result = false;
+
+    // Decode the public key and the R component of the signature.
+    if (decodePoint(A, publicKey) && decodePoint(R, signature)) {
+        // Reconstruct the k value from the signing step.
+        hash.reset();
+        hash.update(signature, 32);
+        hash.update(publicKey, 32);
+        hash.update(message, len);
+        hash.finalize(k, 0);
+
+        // Calculate s * B.  The s value is stored temporarily in kA.t.
+        BigNumberUtil::unpackLE(kA.t, NUM_LIMBS_256BIT, signature + 32, 32);
+        mul(sB, kA.t, false);
+
+        // Calculate R + k * A.  We don't need sB.t in equal() below,
+        // so we reuse that as a temporary buffer when reducing k.
+        reduceQFromBuffer(sB.t, k, kA.x);
+        mul(kA, sB.t, A, false);
+        add(R, kA);
+
+        // Compare s * B and R + k * A for equality.
+        result = equal(sB, R);
+    }
+
+    // Clean up and exit.
+    clean(A);
+    clean(R);
+    clean(sB);
+    clean(kA);
+    return result;
+}
+
+/**
+ * \brief Generates a private key for Ed25519 signing operations.
+ *
+ * \param privateKey The resulting private key.
+ *
+ * The private key is generated with \link RNGClass::rand() RNG.rand()\endlink.
+ * It is the caller's responsibility to ensure that the global random number
+ * pool has sufficient entropy to generate the 32 bytes of the key safely
+ * before calling this function.
+ *
+ * \sa derivePublicKey()
+ */
+void Ed25519::generatePrivateKey(uint8_t privateKey[32])
+{
+    RNG.rand(privateKey, 32);
+}
+
+/**
+ * \brief Derives the public key from a private key.
+ *
+ * \param publicKey The public key.
+ * \param privateKey The private key.
+ *
+ * \sa generatePrivateKey()
+ */
+void Ed25519::derivePublicKey(uint8_t publicKey[32], const uint8_t privateKey[32])
+{
+    SHA512 hash;
+    limb_t a[NUM_LIMBS_256BIT];
+    Point ptA;
+
+    // Derive the secret scalar a from the private key.
+    deriveKeys(&hash, a, privateKey);
+
+    // Compute the point A = aB and encode it.
+    mul(ptA, a);
+    encodePoint(publicKey, ptA);
+
+    // Clean up and exit.
+    clean(a);
+    clean(ptA);
+}
+
+/**
+ * \brief Reduces a number modulo q that was specified in a 512 bit buffer.
+ *
+ * \param result The result array, which must be NUM_LIMBS_256BIT limbs in size.
+ * \param buf The buffer containing the value to reduce in little-endian order.
+ * \param temp A temporary buffer of at least NUM_LIMBS_512BIT + 1 in size.
+ *
+ * \sa reduceQ()
+ */
+void Ed25519::reduceQFromBuffer(limb_t *result, const uint8_t buf[64], limb_t *temp)
+{
+    BigNumberUtil::unpackLE(temp, NUM_LIMBS_512BIT, buf, 64);
+    temp[NUM_LIMBS_512BIT] = 0;
+    reduceQ(result, temp);
+}
+
+/**
+ * \brief Reduces a number modulo q.
+ *
+ * \param result The result array, which must be NUM_LIMBS_256BIT limbs in size.
+ * \param r The value to reduce, which must be NUM_LIMBS_512BIT + 1
+ * limbs in size.
+ *
+ * The \a r array will be modified by this function as a side effect of
+ * the division.  It is allowed for \a result to be the same as \a r.
+ *
+ * \sa reduceQFromBuffer()
+ */
+void Ed25519::reduceQ(limb_t *result, limb_t *r)
+{
+    // Algorithm from: http://en.wikipedia.org/wiki/Barrett_reduction
+    //
+    // We assume that r is less than or equal to (q - 1)^2.
+    //
+    // We want to compute result = r mod q.  Find the smallest k such
+    // that 2^k > q.  In our case, k = 253.  Then set m = floor(4^k / q)
+    // and let r = r - q * floor(m * r / 4^k).  This will be the result
+    // or it will be at most one subtraction of q away from the result.
+    //
+    // Note: 4^k = 4^253 = 2^506 = 2^512/2^6.  We can more easily compute
+    // the result we want if we set m = floor(4^k * 2^6 / q) instead and
+    // then r = r - q * floor(m * r / 2^512).  Because the slight extra
+    // precision in m, r is at most two subtractions of q away from the
+    // final result.
+    static limb_t const numM[NUM_LIMBS_256BIT + 1] PROGMEM = {
+        LIMB_PAIR(0x0A2C131B, 0xED9CE5A3), LIMB_PAIR(0x086329A7, 0x2106215D),
+        LIMB_PAIR(0xFFFFFFEB, 0xFFFFFFFF), LIMB_PAIR(0xFFFFFFFF, 0xFFFFFFFF),
+        0x0F
+    };
+    limb_t temp[NUM_LIMBS_512BIT + NUM_LIMBS_256BIT + 1];
+
+    // Multiply r by m.
+    BigNumberUtil::mul_P(temp, r, NUM_LIMBS_512BIT, numM, NUM_LIMBS_256BIT + 1);
+
+    // Multiply (m * r) / 2^512 by q and subtract it from r.
+    // We can ignore the high words of the subtraction result
+    // because they will all turn into zero after the subtraction.
+    BigNumberUtil::mul_P(temp, temp + NUM_LIMBS_512BIT, NUM_LIMBS_256BIT + 1,
+                         numQ, NUM_LIMBS_256BIT);
+    BigNumberUtil::sub(r, r, temp, NUM_LIMBS_256BIT);
+
+    // Perform two subtractions of q from the result to reduce it.
+    BigNumberUtil::reduceQuick_P(result, r, numQ, NUM_LIMBS_256BIT);
+    BigNumberUtil::reduceQuick_P(result, result, numQ, NUM_LIMBS_256BIT);
+
+    // Clean up and exit.
+    clean(temp);
+}
+
+/**
+ * \brief Multiplies a value by a curve point.
+ *
+ * \param result The result of the multiplication.
+ * \param s The value, which must be NUM_LIMBS_256BIT limbs in size.
+ * \param p The curve point, which will be modified by this function.
+ * \param constTime Set to true if the evaluation must be constant-time
+ * because \a s is a secret value.
+ */
+void Ed25519::mul(Point &result, const limb_t *s, Point &p, bool constTime)
+{
+    Point q;
+    limb_t A[NUM_LIMBS_256BIT];
+    limb_t B[NUM_LIMBS_256BIT];
+    limb_t C[NUM_LIMBS_256BIT];
+    limb_t D[NUM_LIMBS_256BIT];
+    limb_t mask, select;
+    uint8_t sposn, t;
+
+    // Initialize the result to (0, 1, 1, 0).
+    memset(&result, 0, sizeof(Point));
+    result.y[0] = 1;
+    result.z[0] = 1;
+
+    // Iterate over the 255 bits of "s" to calculate "s * p".
+    mask = 1;
+    sposn = 0;
+    for (t = 255; t > 0; --t) {
+        // Add p to the result to produce q.  The specification refers
+        // to temporary variables A to H.  We can dispense with E to H
+        // by using B, D, q.z, and q.t to hold those values temporarily.
+        select = s[sposn] & mask;
+        if (constTime || select) {
+            Curve25519::sub(A, result.y, result.x);
+            Curve25519::sub(C, p.y, p.x);
+            Curve25519::mul(A, A, C);
+            Curve25519::add(B, result.y, result.x);
+            Curve25519::add(C, p.y, p.x);
+            Curve25519::mul(B, B, C);
+            Curve25519::mul(C, result.t, p.t);
+            Curve25519::mul_P(C, C, numDx2);
+            Curve25519::mul(D, result.z, p.z);
+            Curve25519::add(D, D, D);
+            Curve25519::sub(q.t, B, A);             // E = B - A
+            Curve25519::sub(q.z, D, C);             // F = D - C
+            Curve25519::add(D, D, C);               // G = D + C
+            Curve25519::add(B, B, A);               // H = B + A
+            if (constTime) {
+                // Put the intermediate value into q.
+                Curve25519::mul(q.x, q.t, q.z);         // q.x = E * F
+                Curve25519::mul(q.y, D, B);             // q.y = G * H
+                Curve25519::mul(q.z, q.z, D);           // q.z = F * G
+                Curve25519::mul(q.t, q.t, B);           // q.t = E * H
+
+                // Copy q into the result if the current bit of s is 1.
+                Curve25519::cmove(select, result.x, q.x);
+                Curve25519::cmove(select, result.y, q.y);
+                Curve25519::cmove(select, result.z, q.z);
+                Curve25519::cmove(select, result.t, q.t);
+            } else {
+                // Put the intermediate value directly into the result.
+                Curve25519::mul(result.x, q.t, q.z);     // q.x = E * F
+                Curve25519::mul(result.y, D, B);         // q.y = G * H
+                Curve25519::mul(result.z, q.z, D);       // q.z = F * G
+                Curve25519::mul(result.t, q.t, B);       // q.t = E * H
+            }
+        }
+
+        // Double p for the next iteration.
+        Curve25519::sub(A, p.y, p.x);
+        Curve25519::square(A, A);
+        Curve25519::add(B, p.y, p.x);
+        Curve25519::square(B, B);
+        Curve25519::square(C, p.t);
+        Curve25519::mul_P(C, C, numDx2);
+        Curve25519::square(D, p.z);
+        Curve25519::add(D, D, D);
+        Curve25519::sub(p.t, B, A);             // E = B - A
+        Curve25519::sub(p.z, D, C);             // F = D - C
+        Curve25519::add(D, D, C);               // G = D + C
+        Curve25519::add(B, B, A);               // H = B + A
+        Curve25519::mul(p.x, p.t, p.z);         // p.x = E * F
+        Curve25519::mul(p.y, D, B);             // p.y = G * H
+        Curve25519::mul(p.z, p.z, D);           // p.z = F * G
+        Curve25519::mul(p.t, p.t, B);           // p.t = E * H
+
+        // Move onto the next bit of s from lowest to highest.
+        if (mask != (((limb_t)1) << (LIMB_BITS - 1))) {
+            mask <<= 1;
+        } else {
+            ++sposn;
+            mask = 1;
+        }
+    }
+
+    // Clean up.
+    clean(q);
+    clean(A);
+    clean(B);
+    clean(C);
+    clean(D);
+}
+
+/**
+ * \brief Multiplies a value by the base point of the curve.
+ *
+ * \param result The result of the multiplication.
+ * \param s The value, which must be NUM_LIMBS_256BIT limbs in size.
+ * \param constTime Set to true if the evaluation must be constant-time
+ * because \a s is a secret values.
+ */
+void Ed25519::mul(Point &result, const limb_t *s, bool constTime)
+{
+    Point P;
+    memcpy_P(P.x, numBx, sizeof(P.x));
+    memcpy_P(P.y, numBy, sizeof(P.y));
+    memcpy_P(P.z, numBz, sizeof(P.z));
+    memcpy_P(P.t, numBt, sizeof(P.t));
+    mul(result, s, P, constTime);
+    clean(P);
+}
+
+/**
+ * \brief Adds two curve points.
+ *
+ * \param p The first point and the result.
+ * \param q The second point.
+ */
+void Ed25519::add(Point &p, const Point &q)
+{
+    limb_t A[NUM_LIMBS_256BIT];
+    limb_t B[NUM_LIMBS_256BIT];
+    limb_t C[NUM_LIMBS_256BIT];
+    limb_t D[NUM_LIMBS_256BIT];
+
+    Curve25519::sub(A, p.y, p.x);
+    Curve25519::sub(C, q.y, q.x);
+    Curve25519::mul(A, A, C);
+    Curve25519::add(B, p.y, p.x);
+    Curve25519::add(C, q.y, q.x);
+    Curve25519::mul(B, B, C);
+    Curve25519::mul(C, p.t, q.t);
+    Curve25519::mul_P(C, C, numDx2);
+    Curve25519::mul(D, p.z, q.z);
+    Curve25519::add(D, D, D);
+    Curve25519::sub(p.t, B, A);             // E = B - A
+    Curve25519::sub(p.z, D, C);             // F = D - C
+    Curve25519::add(D, D, C);               // G = D + C
+    Curve25519::add(B, B, A);               // H = B + A
+    Curve25519::mul(p.x, p.t, p.z);         // p.x = E * F
+    Curve25519::mul(p.y, D, B);             // p.y = G * H
+    Curve25519::mul(p.z, p.z, D);           // p.z = F * G
+    Curve25519::mul(p.t, p.t, B);           // p.t = E * H
+
+    clean(A);
+    clean(B);
+    clean(C);
+    clean(D);
+}
+
+/**
+ * \brief Determine if two curve points are equal.
+ *
+ * \param p The first curve point.
+ * \param q The second curve point.
+ *
+ * \return Returns true if \a p and \a q are equal; false otherwise.
+ */
+bool Ed25519::equal(const Point &p, const Point &q)
+{
+    limb_t a[NUM_LIMBS_256BIT];
+    limb_t b[NUM_LIMBS_256BIT];
+    bool result = true;
+
+    Curve25519::mul(a, p.x, q.z);
+    Curve25519::mul(b, q.x, p.z);
+    result &= secure_compare(a, b, sizeof(a));
+
+    Curve25519::mul(a, p.y, q.z);
+    Curve25519::mul(b, q.y, p.z);
+    result &= secure_compare(a, b, sizeof(a));
+
+    clean(a);
+    clean(b);
+    return result;
+}
+
+/**
+ * \brief Encodes a curve point into a 32-byte buffer.
+ *
+ * \param buf The buffer to encode into.
+ * \param point The curve point to encode.  This value will be modified
+ * the function and effectively destroyed.
+ *
+ * \sa decodePoint()
+ */
+void Ed25519::encodePoint(uint8_t *buf, Point &point)
+{
+    // Convert the homogeneous coordinates into plain (x, y) coordinates:
+    //      zinv = z^(-1) mod p
+    //      x = x * zinv  mod p
+    //      y = y * zinv  mod p
+    // We don't need the t coordinate, so use that to store zinv temporarily.
+    Curve25519::recip(point.t, point.z);
+    Curve25519::mul(point.x, point.x, point.t);
+    Curve25519::mul(point.y, point.y, point.t);
+
+    // Copy the lowest bit of x to the highest bit of y.
+    point.y[NUM_LIMBS_256BIT - 1] |= (point.x[0] << (LIMB_BITS - 1));
+
+    // Convert y into little-endian in the return buffer.
+    BigNumberUtil::packLE(buf, 32, point.y, NUM_LIMBS_256BIT);
+}
+
+/**
+ * \brief Decodes a curve point from a 32-byte buffer.
+ *
+ * \param point The curve point that was decoded from the buffer.
+ * \param buf The buffer to decode.
+ *
+ * \return Returns true if the point was decoded or false if the contents
+ * of the buffer do not correspond to a legitimate curve point.
+ *
+ * \note This function is not constant time so it should only be used
+ * on publicly-known values.
+ */
+bool Ed25519::decodePoint(Point &point, const uint8_t *buf)
+{
+    limb_t temp[NUM_LIMBS_256BIT];
+
+    // Convert the input buffer from little-endian into the limbs of y.
+    BigNumberUtil::unpackLE(point.y, NUM_LIMBS_256BIT, buf, 32);
+
+    // The high bit of y is the sign bit for x.
+    limb_t sign = point.y[NUM_LIMBS_256BIT - 1] >> (LIMB_BITS - 1);
+    point.y[NUM_LIMBS_256BIT - 1] &= ~(((limb_t)1) << (LIMB_BITS - 1));
+
+    // Set z to 1.
+    memcpy_P(point.z, numBz, sizeof(point.z));
+
+    // Compute t = (y * y - 1) * modinv(d * y * y + 1).
+    Curve25519::square(point.t, point.y);
+    Curve25519::sub(point.x, point.t, point.z);
+    Curve25519::mul_P(point.t, point.t, numD);
+    Curve25519::add(point.t, point.t, point.z);
+    Curve25519::recip(temp, point.t);
+    Curve25519::mul(point.t, point.x, temp);
+    clean(temp);
+
+    // Check for t = 0.
+    limb_t check = point.t[0];
+    for (uint8_t posn = 1; posn < NUM_LIMBS_256BIT; ++posn)
+        check |= point.t[posn];
+    if (!check) {
+        // If the sign bit is set, then decoding has failed.
+        // Otherwise x is zero and we're done.
+        if (sign)
+            return false;
+        memset(point.x, 0, sizeof(point.x));
+        return true;
+    }
+
+    // Recover x by taking the sqrt of t and flipping the sign if necessary.
+    if (!Curve25519::sqrt(point.x, point.t))
+        return false;
+    if (sign != (point.x[0] & ((limb_t)1))) {
+        // The signs are different so we want the other square root.
+        memset(point.t, 0, sizeof(point.t));
+        Curve25519::sub(point.x, point.t, point.x);
+    }
+
+    // Finally, t = x * y.
+    Curve25519::mul(point.t, point.x, point.y);
+    return true;
+}
+
+/**
+ * \brief Derive key material from a 32-byte private key.
+ *
+ * \param hash SHA512 hash object from the caller for use in this function.
+ * The 64-byte output buffer within this hash object will contain the
+ * hash prefix on exit.
+ * \param a The secret scalar derived from \a privateKey.  This must be
+ * NUM_LIMBS_256BIT limbs in size.
+ * \param privateKey The 32-byte private key to derive all other values from.
+ */
+void Ed25519::deriveKeys(SHA512 *hash, limb_t *a, const uint8_t privateKey[32])
+{
+    // Hash the private key to get the "a" scalar and the message prefix.
+    uint8_t *buf = (uint8_t *)(hash->state.w); // Reuse hash buffer to save memory.
+    hash->reset();
+    hash->update(privateKey, 32);
+    hash->finalize(buf, 0);
+    buf[0]  &= 0xF8;
+    buf[31] &= 0x7F;
+    buf[31] |= 0x40;
+
+    // Unpack the first half of the hash value into "a".
+    BigNumberUtil::unpackLE(a, NUM_LIMBS_256BIT, buf, 32);
+}
diff --git a/src/modules/Crypto/Ed25519.h b/src/modules/Crypto/Ed25519.h
new file mode 100644
index 0000000..bbdd9f2
--- /dev/null
+++ b/src/modules/Crypto/Ed25519.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_ED25519_h
+#define CRYPTO_ED25519_h
+
+#include "BigNumberUtil.h"
+#include "SHA512.h"
+
+class Ed25519
+{
+public:
+    static void sign(uint8_t signature[64], const uint8_t privateKey[32],
+                     const uint8_t publicKey[32], const void *message,
+                     size_t len);
+    static bool verify(const uint8_t signature[64], const uint8_t publicKey[32],
+                       const void *message, size_t len);
+
+    static void generatePrivateKey(uint8_t privateKey[32]);
+    static void derivePublicKey(uint8_t publicKey[32], const uint8_t privateKey[32]);
+
+private:
+    // Constructor and destructor are private - cannot instantiate this class.
+    Ed25519();
+    ~Ed25519();
+
+    // Curve point represented in extended homogeneous coordinates.
+    struct Point
+    {
+        limb_t x[32 / sizeof(limb_t)];
+        limb_t y[32 / sizeof(limb_t)];
+        limb_t z[32 / sizeof(limb_t)];
+        limb_t t[32 / sizeof(limb_t)];
+    };
+
+    static void reduceQFromBuffer(limb_t *result, const uint8_t buf[64], limb_t *temp);
+    static void reduceQ(limb_t *result, limb_t *r);
+
+    static void mul(Point &result, const limb_t *s, Point &p, bool constTime = true);
+    static void mul(Point &result, const limb_t *s, bool constTime = true);
+
+    static void add(Point &p, const Point &q);
+
+    static bool equal(const Point &p, const Point &q);
+
+    static void encodePoint(uint8_t *buf, Point &point);
+    static bool decodePoint(Point &point, const uint8_t *buf);
+
+    static void deriveKeys(SHA512 *hash, limb_t *a, const uint8_t privateKey[32]);
+};
+
+#endif
diff --git a/src/modules/Crypto/GCM.cpp b/src/modules/Crypto/GCM.cpp
new file mode 100644
index 0000000..3f9a45c
--- /dev/null
+++ b/src/modules/Crypto/GCM.cpp
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "GCM.h"
+#include "Crypto.h"
+#include "utility/EndianUtil.h"
+#include <string.h>
+
+/**
+ * \class GCMCommon GCM.h <GCM.h>
+ * \brief Concrete base class to assist with implementing GCM for
+ * 128-bit block ciphers.
+ *
+ * References: <a href="http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf">NIST SP 800-38D</a>,
+ * http://en.wikipedia.org/wiki/Galois/Counter_Mode
+ *
+ * \sa GCM
+ */
+
+/**
+ * \brief Constructs a new cipher in GCM mode.
+ *
+ * This constructor must be followed by a call to setBlockCipher().
+ */
+GCMCommon::GCMCommon()
+    : blockCipher(0)
+{
+    state.authSize = 0;
+    state.dataSize = 0;
+    state.dataStarted = false;
+    state.posn = 16;
+}
+
+/**
+ * \brief Destroys this cipher object after clearing sensitive information.
+ */
+GCMCommon::~GCMCommon()
+{
+    clean(state);
+}
+
+size_t GCMCommon::keySize() const
+{
+    return blockCipher->keySize();
+}
+
+size_t GCMCommon::ivSize() const
+{
+    // The GCM specification recommends an IV size of 96 bits.
+    return 12;
+}
+
+size_t GCMCommon::tagSize() const
+{
+    return 16;
+}
+
+bool GCMCommon::setKey(const uint8_t *key, size_t len)
+{
+    // Set the encryption key for the block cipher.
+    return blockCipher->setKey(key, len);
+}
+
+bool GCMCommon::setIV(const uint8_t *iv, size_t len)
+{
+    // Format the counter block from the IV.
+    if (len == 12) {
+        // IV's of exactly 96 bits are used directly as the counter block.
+        memcpy(state.counter, iv, 12);
+        state.counter[12] = 0;
+        state.counter[13] = 0;
+        state.counter[14] = 0;
+        state.counter[15] = 1;
+    } else {
+        // IV's of other sizes are hashed to produce the counter block.
+        memset(state.nonce, 0, 16);
+        blockCipher->encryptBlock(state.nonce, state.nonce);
+        ghash.reset(state.nonce);
+        ghash.update(iv, len);
+        ghash.pad();
+        uint64_t sizes[2] = {0, htobe64(((uint64_t)len) * 8)};
+        ghash.update(sizes, sizeof(sizes));
+        clean(sizes);
+        ghash.finalize(state.counter, 16);
+    }
+
+    // Reset the GCM object ready to process auth or payload data.
+    state.authSize = 0;
+    state.dataSize = 0;
+    state.dataStarted = false;
+    state.posn = 16;
+
+    // Construct the hashing key by encrypting a zero block.
+    memset(state.nonce, 0, 16);
+    blockCipher->encryptBlock(state.nonce, state.nonce);
+    ghash.reset(state.nonce);
+
+    // Replace the hash key in "nonce" with the encrypted counter.
+    // This value will be XOR'ed with the final authentication hash
+    // value in computeTag().
+    blockCipher->encryptBlock(state.nonce, state.counter);
+    return true;
+}
+
+/**
+ * \brief Increments the least significant 32 bits of the counter block.
+ *
+ * \param counter The counter block to increment.
+ */
+static inline void increment(uint8_t counter[16])
+{
+    uint16_t carry = 1;
+    carry += counter[15];
+    counter[15] = (uint8_t)carry;
+    carry = (carry >> 8) + counter[14];
+    counter[14] = (uint8_t)carry;
+    carry = (carry >> 8) + counter[13];
+    counter[13] = (uint8_t)carry;
+    carry = (carry >> 8) + counter[12];
+    counter[12] = (uint8_t)carry;
+}
+
+void GCMCommon::encrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+    // Finalize the authenticated data if necessary.
+    if (!state.dataStarted) {
+        ghash.pad();
+        state.dataStarted = true;
+    }
+
+    // Encrypt the plaintext using the block cipher in counter mode.
+    uint8_t *out = output;
+    size_t size = len;
+    while (size > 0) {
+        // Create a new keystream block if necessary.
+        if (state.posn >= 16) {
+            increment(state.counter);
+            blockCipher->encryptBlock(state.stream, state.counter);
+            state.posn = 0;
+        }
+
+        // Encrypt as many bytes as we can using the keystream block.
+        uint8_t temp = 16 - state.posn;
+        if (temp > size)
+            temp = size;
+        uint8_t *stream = state.stream + state.posn;
+        state.posn += temp;
+        size -= temp;
+        while (temp > 0) {
+            *out++ = *input++ ^ *stream++;
+            --temp;
+        }
+    }
+
+    // Feed the ciphertext into the hash.
+    ghash.update(output, len);
+    state.dataSize += len;
+}
+
+void GCMCommon::decrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+    // Finalize the authenticated data if necessary.
+    if (!state.dataStarted) {
+        ghash.pad();
+        state.dataStarted = true;
+    }
+
+    // Feed the ciphertext into the hash before we decrypt it.
+    ghash.update(input, len);
+    state.dataSize += len;
+
+    // Decrypt the plaintext using the block cipher in counter mode.
+    while (len > 0) {
+        // Create a new keystream block if necessary.
+        if (state.posn >= 16) {
+            increment(state.counter);
+            blockCipher->encryptBlock(state.stream, state.counter);
+            state.posn = 0;
+        }
+
+        // Decrypt as many bytes as we can using the keystream block.
+        uint8_t temp = 16 - state.posn;
+        if (temp > len)
+            temp = len;
+        uint8_t *stream = state.stream + state.posn;
+        state.posn += temp;
+        len -= temp;
+        while (temp > 0) {
+            *output++ = *input++ ^ *stream++;
+            --temp;
+        }
+    }
+}
+
+void GCMCommon::addAuthData(const void *data, size_t len)
+{
+    if (!state.dataStarted) {
+        ghash.update(data, len);
+        state.authSize += len;
+    }
+}
+
+void GCMCommon::computeTag(void *tag, size_t len)
+{
+    // Pad the hashed data and add the sizes.
+    ghash.pad();
+    uint64_t sizes[2] = {
+        htobe64(state.authSize * 8),
+        htobe64(state.dataSize * 8)
+    };
+    ghash.update(sizes, sizeof(sizes));
+    clean(sizes);
+
+    // Get the finalized hash, encrypt it with the nonce, and return the tag.
+    ghash.finalize(state.stream, 16);
+    for (uint8_t posn = 0; posn < 16; ++posn)
+        state.stream[posn] ^= state.nonce[posn];
+    if (len > 16)
+        len = 16;
+    memcpy(tag, state.stream, len);
+}
+
+bool GCMCommon::checkTag(const void *tag, size_t len)
+{
+    // Can never match if the expected tag length is too long.
+    if (len > 16)
+        return false;
+
+    // Compute the tag and check it.
+    computeTag(state.counter, 16);
+    return secure_compare(state.counter, tag, len);
+}
+
+void GCMCommon::clear()
+{
+    blockCipher->clear();
+    ghash.clear();
+    clean(state);
+    state.posn = 16;
+}
+
+/**
+ * \fn void GCMCommon::setBlockCipher(BlockCipher *cipher)
+ * \brief Sets the block cipher to use for this GCM object.
+ *
+ * \param cipher The block cipher to use to implement GCM mode.
+ * This object must have a block size of 128 bits (16 bytes).
+ */
+
+/**
+ * \class GCM GCM.h <GCM.h>
+ * \brief Implementation of the Galois Counter Mode (GCM).
+ *
+ * GCM mode converts a block cipher into an authenticated cipher
+ * that uses the block cipher T to encrypt and GHASH to authenticate.
+ *
+ * The size of the key is determined by the underlying block cipher T.
+ * The IV is recommended to be 96 bits (12 bytes) in length, but other
+ * lengths are supported as well.  The default tagSize() is 128 bits
+ * (16 bytes) but the GCM specification does allow other tag sizes:
+ * 32, 64, 96, 104, 112, 120, or 128 bits (4, 8, 12, 13, 14, 15, or 16 bytes).
+ *
+ * The template parameter T must be a concrete subclass of BlockCipher
+ * indicating the specific block cipher to use.  The block cipher must
+ * have a block size of 128 bits.  For example, the following creates a
+ * GCM object using AES256 as the underlying cipher and then uses it
+ * to encrypt and authenticate a \c plaintext block:
+ *
+ * \code
+ * GCM<AES256> gcm;
+ * gcm.setKey(key, sizeof(key));
+ * gcm.setIV(iv, sizeof(iv));
+ * gcm.addAuthData(adata, sizeof(adata));
+ * gcm.encrypt(ciphertext, plaintext, sizeof(plaintext));
+ * gcm.computeTag(tag, sizeof(tag));
+ * \endcode
+ *
+ * The decryption process is almost identical to convert a \c ciphertext and
+ * \a tag back into plaintext and then check the tag:
+ *
+ * \code
+ * GCM<AES256> gcm;
+ * gcm.setKey(key, sizeof(key));
+ * gcm.setIV(iv, sizeof(iv));
+ * gcm.addAuthData(adata, sizeof(adata));
+ * gcm.decrypt(ciphertext, plaintext, sizeof(plaintext));
+ * if (!gcm.checkTag(tag, sizeof(tag))) {
+ *     // The data was invalid - do not use it.
+ *     ...
+ * }
+ * \endcode
+ *
+ * The GCM class can also be used to implement GMAC message authentication
+ * by omitting the plaintext:
+ *
+ * \code
+ * GCM<AES256> gcm;
+ * gcm.setKey(key, sizeof(key));
+ * gcm.setIV(iv, sizeof(iv));
+ * gcm.addAuthData(adata1, sizeof(adata1));
+ * gcm.addAuthData(adata2, sizeof(adata1));
+ * ...
+ * gcm.addAuthData(adataN, sizeof(adataN));
+ * gcm.computeTag(tag, sizeof(tag));
+ * \endcode
+ *
+ * References: <a href="http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf">NIST SP 800-38D</a>,
+ * http://en.wikipedia.org/wiki/Galois/Counter_Mode
+ *
+ * \sa GCMCommon, GHASH
+ */
+
+/**
+ * \fn GCM::GCM()
+ * \brief Constructs a new GCM object for the block cipher T.
+ */
diff --git a/src/modules/Crypto/GCM.h b/src/modules/Crypto/GCM.h
new file mode 100644
index 0000000..6f20433
--- /dev/null
+++ b/src/modules/Crypto/GCM.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_GCM_h
+#define CRYPTO_GCM_h
+
+#include "AuthenticatedCipher.h"
+#include "BlockCipher.h"
+#include "GHASH.h"
+
+class GCMCommon : public AuthenticatedCipher
+{
+public:
+    virtual ~GCMCommon();
+
+    size_t keySize() const;
+    size_t ivSize() const;
+    size_t tagSize() const;
+
+    bool setKey(const uint8_t *key, size_t len);
+    bool setIV(const uint8_t *iv, size_t len);
+
+    void encrypt(uint8_t *output, const uint8_t *input, size_t len);
+    void decrypt(uint8_t *output, const uint8_t *input, size_t len);
+
+    void addAuthData(const void *data, size_t len);
+
+    void computeTag(void *tag, size_t len);
+    bool checkTag(const void *tag, size_t len);
+
+    void clear();
+
+protected:
+    GCMCommon();
+    void setBlockCipher(BlockCipher *cipher) { blockCipher = cipher; }
+
+private:
+    BlockCipher *blockCipher;
+    GHASH ghash;
+    struct {
+        uint8_t counter[16];
+        uint8_t stream[16];
+        uint8_t nonce[16];
+        uint64_t authSize;
+        uint64_t dataSize;
+        bool dataStarted;
+        uint8_t posn;
+    } state;
+};
+
+template <typename T>
+class GCM : public GCMCommon
+{
+public:
+    GCM() { setBlockCipher(&cipher); }
+
+private:
+    T cipher;
+};
+
+#endif
diff --git a/src/modules/Crypto/GF128.cpp b/src/modules/Crypto/GF128.cpp
new file mode 100644
index 0000000..60c1b2f
--- /dev/null
+++ b/src/modules/Crypto/GF128.cpp
@@ -0,0 +1,571 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "GF128.h"
+#include "utility/EndianUtil.h"
+#include <string.h>
+
+/**
+ * \class GF128 GF128.h <GF128.h>
+ * \brief Operations in the Galois field GF(2^128).
+ *
+ * This class contains helper functions for performing operations in
+ * the Galois field GF(2^128) which is used as the basis of GCM and GHASH.
+ * These functions are provided for use by other cryptographic protocols
+ * that make use of GF(2^128).
+ *
+ * Most of the functions in this class use the field, polynomial, and
+ * byte ordering conventions described in NIST SP 800-38D (GCM).  The one
+ * exception is dblEAX() which uses the conventions of EAX mode instead.
+ *
+ * References: <a href="http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf">NIST SP 800-38D</a>
+ *
+ * \sa GCM, GHASH
+ */
+
+/**
+ * \brief Initialize multiplication in the GF(2^128) field.
+ *
+ * \param H The hash state to be initialized.
+ * \param key Points to the 16 byte authentication key which is assumed
+ * to be in big-endian byte order.
+ *
+ * This function and the companion mul() are intended for use by other
+ * classes that need access to the raw GF(2^128) field multiplication of
+ * GHASH without the overhead of GHASH itself.
+ *
+ * \sa mul(), dbl()
+ */
+void GF128::mulInit(uint32_t H[4], const void *key)
+{
+#if defined(__AVR__)
+    // Copy the key into H but leave it in big endian order because
+    // we can correct for the byte order in mul() below.
+    memcpy(H, key, 16);
+#else
+    // Copy the key into H and convert from big endian to host order.
+    memcpy(H, key, 16);
+#if defined(CRYPTO_LITTLE_ENDIAN)
+    H[0] = be32toh(H[0]);
+    H[1] = be32toh(H[1]);
+    H[2] = be32toh(H[2]);
+    H[3] = be32toh(H[3]);
+#endif
+#endif
+}
+
+/**
+ * \brief Perform a multiplication in the GF(2^128) field.
+ *
+ * \param Y The first value to multiply, and the result.  This array is
+ * assumed to be in big-endian order on entry and exit.
+ * \param H The second value to multiply, which must have been initialized
+ * by the mulInit() function.
+ *
+ * This function and the companion mulInit() are intended for use by other
+ * classes that need access to the raw GF(2^128) field multiplication of
+ * GHASH without the overhead of GHASH itself.
+ *
+ * \sa mulInit(), dbl()
+ */
+void GF128::mul(uint32_t Y[4], const uint32_t H[4])
+{
+#if defined(__AVR__)
+    uint32_t Z[4] = {0, 0, 0, 0};   // Z = 0
+    uint32_t V0 = H[0];             // V = H
+    uint32_t V1 = H[1];
+    uint32_t V2 = H[2];
+    uint32_t V3 = H[3];
+
+    // Multiply Z by V for the set bits in Y, starting at the top.
+    // This is a very simple bit by bit version that may not be very
+    // fast but it should be resistant to cache timing attacks.
+    for (uint8_t posn = 0; posn < 16; ++posn) {
+        uint8_t value = ((const uint8_t *)Y)[posn];
+        for (uint8_t bit = 0; bit < 8; ++bit) {
+            __asm__ __volatile__ (
+                // Extract the high bit of "value" and turn it into a mask.
+                "ldd r24,%8\n"
+                "lsl r24\n"
+                "std %8,r24\n"
+                "mov __tmp_reg__,__zero_reg__\n"
+                "sbc __tmp_reg__,__zero_reg__\n"
+
+                // XOR V with Z if the bit is 1.
+                "mov r24,%D0\n"         // Z0 ^= (V0 & mask)
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%D4\n"
+                "eor r25,r24\n"
+                "std %D4,r25\n"
+                "mov r24,%C0\n"
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%C4\n"
+                "eor r25,r24\n"
+                "std %C4,r25\n"
+                "mov r24,%B0\n"
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%B4\n"
+                "eor r25,r24\n"
+                "std %B4,r25\n"
+                "mov r24,%A0\n"
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%A4\n"
+                "eor r25,r24\n"
+                "std %A4,r25\n"
+                "mov r24,%D1\n"         // Z1 ^= (V1 & mask)
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%D5\n"
+                "eor r25,r24\n"
+                "std %D5,r25\n"
+                "mov r24,%C1\n"
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%C5\n"
+                "eor r25,r24\n"
+                "std %C5,r25\n"
+                "mov r24,%B1\n"
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%B5\n"
+                "eor r25,r24\n"
+                "std %B5,r25\n"
+                "mov r24,%A1\n"
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%A5\n"
+                "eor r25,r24\n"
+                "std %A5,r25\n"
+                "mov r24,%D2\n"         // Z2 ^= (V2 & mask)
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%D6\n"
+                "eor r25,r24\n"
+                "std %D6,r25\n"
+                "mov r24,%C2\n"
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%C6\n"
+                "eor r25,r24\n"
+                "std %C6,r25\n"
+                "mov r24,%B2\n"
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%B6\n"
+                "eor r25,r24\n"
+                "std %B6,r25\n"
+                "mov r24,%A2\n"
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%A6\n"
+                "eor r25,r24\n"
+                "std %A6,r25\n"
+                "mov r24,%D3\n"         // Z3 ^= (V3 & mask)
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%D7\n"
+                "eor r25,r24\n"
+                "std %D7,r25\n"
+                "mov r24,%C3\n"
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%C7\n"
+                "eor r25,r24\n"
+                "std %C7,r25\n"
+                "mov r24,%B3\n"
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%B7\n"
+                "eor r25,r24\n"
+                "std %B7,r25\n"
+                "mov r24,%A3\n"
+                "and r24,__tmp_reg__\n"
+                "ldd r25,%A7\n"
+                "eor r25,r24\n"
+                "std %A7,r25\n"
+
+                // Rotate V right by 1 bit.
+                "lsr %A0\n"
+                "ror %B0\n"
+                "ror %C0\n"
+                "ror %D0\n"
+                "ror %A1\n"
+                "ror %B1\n"
+                "ror %C1\n"
+                "ror %D1\n"
+                "ror %A2\n"
+                "ror %B2\n"
+                "ror %C2\n"
+                "ror %D2\n"
+                "ror %A3\n"
+                "ror %B3\n"
+                "ror %C3\n"
+                "ror %D3\n"
+                "mov r24,__zero_reg__\n"
+                "sbc r24,__zero_reg__\n"
+                "andi r24,0xE1\n"
+                "eor %A0,r24\n"
+                : "+r"(V0), "+r"(V1), "+r"(V2), "+r"(V3)
+                : "Q"(Z[0]), "Q"(Z[1]), "Q"(Z[2]), "Q"(Z[3]), "Q"(value)
+                : "r24", "r25"
+            );
+        }
+    }
+
+    // We have finished the block so copy Z into Y and byte-swap.
+    __asm__ __volatile__ (
+        "ldd __tmp_reg__,%A0\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%B0\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%C0\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%D0\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%A1\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%B1\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%C1\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%D1\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%A2\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%B2\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%C2\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%D2\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%A3\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%B3\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%C3\n"
+        "st X+,__tmp_reg__\n"
+        "ldd __tmp_reg__,%D3\n"
+        "st X,__tmp_reg__\n"
+        : : "Q"(Z[0]), "Q"(Z[1]), "Q"(Z[2]), "Q"(Z[3]), "x"(Y)
+    );
+#else // !__AVR__
+    uint32_t Z0 = 0;        // Z = 0
+    uint32_t Z1 = 0;
+    uint32_t Z2 = 0;
+    uint32_t Z3 = 0;
+    uint32_t V0 = H[0];     // V = H
+    uint32_t V1 = H[1];
+    uint32_t V2 = H[2];
+    uint32_t V3 = H[3];
+
+    // Multiply Z by V for the set bits in Y, starting at the top.
+    // This is a very simple bit by bit version that may not be very
+    // fast but it should be resistant to cache timing attacks.
+    for (uint8_t posn = 0; posn < 16; ++posn) {
+        uint8_t value = ((const uint8_t *)Y)[posn];
+        for (uint8_t bit = 0; bit < 8; ++bit, value <<= 1) {
+            // Extract the high bit of "value" and turn it into a mask.
+            uint32_t mask = (~((uint32_t)(value >> 7))) + 1;
+
+            // XOR V with Z if the bit is 1.
+            Z0 ^= (V0 & mask);
+            Z1 ^= (V1 & mask);
+            Z2 ^= (V2 & mask);
+            Z3 ^= (V3 & mask);
+
+            // Rotate V right by 1 bit.
+            mask = ((~(V3 & 0x01)) + 1) & 0xE1000000;
+            V3 = (V3 >> 1) | (V2 << 31);
+            V2 = (V2 >> 1) | (V1 << 31);
+            V1 = (V1 >> 1) | (V0 << 31);
+            V0 = (V0 >> 1) ^ mask;
+        }
+    }
+
+    // We have finished the block so copy Z into Y and byte-swap.
+    Y[0] = htobe32(Z0);
+    Y[1] = htobe32(Z1);
+    Y[2] = htobe32(Z2);
+    Y[3] = htobe32(Z3);
+#endif // !__AVR__
+}
+
+/**
+ * \brief Doubles a value in the GF(2^128) field.
+ *
+ * \param V The value to double, and the result.  This array is
+ * assumed to be in big-endian order on entry and exit.
+ *
+ * Block cipher modes such as <a href="https://en.wikipedia.org/wiki/Disk_encryption_theory#Xor-encrypt-xor_.28XEX.29">XEX</a>
+ * are similar to CTR mode but instead of incrementing the nonce every
+ * block, the modes multiply the nonce by 2 in the GF(2^128) field every
+ * block.  This function is provided to help with implementing such modes.
+ *
+ * \sa dblEAX(), dblXTS(), mul()
+ */
+void GF128::dbl(uint32_t V[4])
+{
+#if defined(__AVR__)
+    __asm__ __volatile__ (
+        "ld r16,Z\n"
+        "ldd r17,Z+1\n"
+        "ldd r18,Z+2\n"
+        "ldd r19,Z+3\n"
+        "lsr r16\n"
+        "ror r17\n"
+        "ror r18\n"
+        "ror r19\n"
+        "std Z+1,r17\n"
+        "std Z+2,r18\n"
+        "std Z+3,r19\n"
+        "ldd r17,Z+4\n"
+        "ldd r18,Z+5\n"
+        "ldd r19,Z+6\n"
+        "ldd r20,Z+7\n"
+        "ror r17\n"
+        "ror r18\n"
+        "ror r19\n"
+        "ror r20\n"
+        "std Z+4,r17\n"
+        "std Z+5,r18\n"
+        "std Z+6,r19\n"
+        "std Z+7,r20\n"
+        "ldd r17,Z+8\n"
+        "ldd r18,Z+9\n"
+        "ldd r19,Z+10\n"
+        "ldd r20,Z+11\n"
+        "ror r17\n"
+        "ror r18\n"
+        "ror r19\n"
+        "ror r20\n"
+        "std Z+8,r17\n"
+        "std Z+9,r18\n"
+        "std Z+10,r19\n"
+        "std Z+11,r20\n"
+        "ldd r17,Z+12\n"
+        "ldd r18,Z+13\n"
+        "ldd r19,Z+14\n"
+        "ldd r20,Z+15\n"
+        "ror r17\n"
+        "ror r18\n"
+        "ror r19\n"
+        "ror r20\n"
+        "std Z+12,r17\n"
+        "std Z+13,r18\n"
+        "std Z+14,r19\n"
+        "std Z+15,r20\n"
+        "mov r17,__zero_reg__\n"
+        "sbc r17,__zero_reg__\n"
+        "andi r17,0xE1\n"
+        "eor r16,r17\n"
+        "st Z,r16\n"
+        : : "z"(V)
+        : "r16", "r17", "r18", "r19", "r20"
+    );
+#else
+    uint32_t V0 = be32toh(V[0]);
+    uint32_t V1 = be32toh(V[1]);
+    uint32_t V2 = be32toh(V[2]);
+    uint32_t V3 = be32toh(V[3]);
+    uint32_t mask = ((~(V3 & 0x01)) + 1) & 0xE1000000;
+    V3 = (V3 >> 1) | (V2 << 31);
+    V2 = (V2 >> 1) | (V1 << 31);
+    V1 = (V1 >> 1) | (V0 << 31);
+    V0 = (V0 >> 1) ^ mask;
+    V[0] = htobe32(V0);
+    V[1] = htobe32(V1);
+    V[2] = htobe32(V2);
+    V[3] = htobe32(V3);
+#endif
+}
+
+/**
+ * \brief Doubles a value in the GF(2^128) field using EAX conventions.
+ *
+ * \param V The value to double, and the result.  This array is
+ * assumed to be in big-endian order on entry and exit.
+ *
+ * This function differs from dbl() that it uses the conventions of EAX mode
+ * instead of those of NIST SP 800-38D (GCM).  The two operations have
+ * equivalent security but the bits are ordered differently with the
+ * value shifted left instead of right.
+ *
+ * References: https://en.wikipedia.org/wiki/EAX_mode,
+ * http://web.cs.ucdavis.edu/~rogaway/papers/eax.html
+ *
+ * \sa dbl(), dblXTS(), mul()
+ */
+void GF128::dblEAX(uint32_t V[4])
+{
+#if defined(__AVR__)
+    __asm__ __volatile__ (
+        "ldd r16,Z+15\n"
+        "ldd r17,Z+14\n"
+        "ldd r18,Z+13\n"
+        "ldd r19,Z+12\n"
+        "lsl r16\n"
+        "rol r17\n"
+        "rol r18\n"
+        "rol r19\n"
+        "std Z+14,r17\n"
+        "std Z+13,r18\n"
+        "std Z+12,r19\n"
+        "ldd r17,Z+11\n"
+        "ldd r18,Z+10\n"
+        "ldd r19,Z+9\n"
+        "ldd r20,Z+8\n"
+        "rol r17\n"
+        "rol r18\n"
+        "rol r19\n"
+        "rol r20\n"
+        "std Z+11,r17\n"
+        "std Z+10,r18\n"
+        "std Z+9,r19\n"
+        "std Z+8,r20\n"
+        "ldd r17,Z+7\n"
+        "ldd r18,Z+6\n"
+        "ldd r19,Z+5\n"
+        "ldd r20,Z+4\n"
+        "rol r17\n"
+        "rol r18\n"
+        "rol r19\n"
+        "rol r20\n"
+        "std Z+7,r17\n"
+        "std Z+6,r18\n"
+        "std Z+5,r19\n"
+        "std Z+4,r20\n"
+        "ldd r17,Z+3\n"
+        "ldd r18,Z+2\n"
+        "ldd r19,Z+1\n"
+        "ld r20,Z\n"
+        "rol r17\n"
+        "rol r18\n"
+        "rol r19\n"
+        "rol r20\n"
+        "std Z+3,r17\n"
+        "std Z+2,r18\n"
+        "std Z+1,r19\n"
+        "st Z,r20\n"
+        "mov r17,__zero_reg__\n"
+        "sbc r17,__zero_reg__\n"
+        "andi r17,0x87\n"
+        "eor r16,r17\n"
+        "std Z+15,r16\n"
+        : : "z"(V)
+        : "r16", "r17", "r18", "r19", "r20"
+    );
+#else
+    uint32_t V0 = be32toh(V[0]);
+    uint32_t V1 = be32toh(V[1]);
+    uint32_t V2 = be32toh(V[2]);
+    uint32_t V3 = be32toh(V[3]);
+    uint32_t mask = ((~(V0 >> 31)) + 1) & 0x00000087;
+    V0 = (V0 << 1) | (V1 >> 31);
+    V1 = (V1 << 1) | (V2 >> 31);
+    V2 = (V2 << 1) | (V3 >> 31);
+    V3 = (V3 << 1) ^ mask;
+    V[0] = htobe32(V0);
+    V[1] = htobe32(V1);
+    V[2] = htobe32(V2);
+    V[3] = htobe32(V3);
+#endif
+}
+
+/**
+ * \brief Doubles a value in the GF(2^128) field using XTS conventions.
+ *
+ * \param V The value to double, and the result.  This array is
+ * assumed to be in littlen-endian order on entry and exit.
+ *
+ * This function differs from dbl() that it uses the conventions of XTS mode
+ * instead of those of NIST SP 800-38D (GCM).  The two operations have
+ * equivalent security but the bits are ordered differently with the
+ * value shifted left instead of right.
+ *
+ * References: <a href="http://libeccio.di.unisa.it/Crypto14/Lab/p1619.pdf">IEEE Std. 1619-2007, XTS-AES</a>
+ *
+ * \sa dbl(), dblEAX(), mul()
+ */
+void GF128::dblXTS(uint32_t V[4])
+{
+#if defined(__AVR__)
+    __asm__ __volatile__ (
+        "ld r16,Z\n"
+        "ldd r17,Z+1\n"
+        "ldd r18,Z+2\n"
+        "ldd r19,Z+3\n"
+        "lsl r16\n"
+        "rol r17\n"
+        "rol r18\n"
+        "rol r19\n"
+        "std Z+1,r17\n"
+        "std Z+2,r18\n"
+        "std Z+3,r19\n"
+        "ldd r17,Z+4\n"
+        "ldd r18,Z+5\n"
+        "ldd r19,Z+6\n"
+        "ldd r20,Z+7\n"
+        "rol r17\n"
+        "rol r18\n"
+        "rol r19\n"
+        "rol r20\n"
+        "std Z+4,r17\n"
+        "std Z+5,r18\n"
+        "std Z+6,r19\n"
+        "std Z+7,r20\n"
+        "ldd r17,Z+8\n"
+        "ldd r18,Z+9\n"
+        "ldd r19,Z+10\n"
+        "ldd r20,Z+11\n"
+        "rol r17\n"
+        "rol r18\n"
+        "rol r19\n"
+        "rol r20\n"
+        "std Z+8,r17\n"
+        "std Z+9,r18\n"
+        "std Z+10,r19\n"
+        "std Z+11,r20\n"
+        "ldd r17,Z+12\n"
+        "ldd r18,Z+13\n"
+        "ldd r19,Z+14\n"
+        "ldd r20,Z+15\n"
+        "rol r17\n"
+        "rol r18\n"
+        "rol r19\n"
+        "rol r20\n"
+        "std Z+12,r17\n"
+        "std Z+13,r18\n"
+        "std Z+14,r19\n"
+        "std Z+15,r20\n"
+        "mov r17,__zero_reg__\n"
+        "sbc r17,__zero_reg__\n"
+        "andi r17,0x87\n"
+        "eor r16,r17\n"
+        "st Z,r16\n"
+        : : "z"(V)
+        : "r16", "r17", "r18", "r19", "r20"
+    );
+#else
+    uint32_t V0 = le32toh(V[0]);
+    uint32_t V1 = le32toh(V[1]);
+    uint32_t V2 = le32toh(V[2]);
+    uint32_t V3 = le32toh(V[3]);
+    uint32_t mask = ((~(V3 >> 31)) + 1) & 0x00000087;
+    V3 = (V3 << 1) | (V2 >> 31);
+    V2 = (V2 << 1) | (V1 >> 31);
+    V1 = (V1 << 1) | (V0 >> 31);
+    V0 = (V0 << 1) ^ mask;
+    V[0] = htole32(V0);
+    V[1] = htole32(V1);
+    V[2] = htole32(V2);
+    V[3] = htole32(V3);
+#endif
+}
diff --git a/src/modules/Crypto/GF128.h b/src/modules/Crypto/GF128.h
new file mode 100644
index 0000000..96d63df
--- /dev/null
+++ b/src/modules/Crypto/GF128.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_GF128_h
+#define CRYPTO_GF128_h
+
+#include <inttypes.h>
+
+class GF128
+{
+private:
+    GF128() {}
+    ~GF128() {}
+
+public:
+    static void mulInit(uint32_t H[4], const void *key);
+    static void mul(uint32_t Y[4], const uint32_t H[4]);
+    static void dbl(uint32_t V[4]);
+    static void dblEAX(uint32_t V[4]);
+    static void dblXTS(uint32_t V[4]);
+};
+
+#endif
diff --git a/src/modules/Crypto/GHASH.cpp b/src/modules/Crypto/GHASH.cpp
new file mode 100644
index 0000000..6853f08
--- /dev/null
+++ b/src/modules/Crypto/GHASH.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "GHASH.h"
+#include "GF128.h"
+#include "Crypto.h"
+#include <string.h>
+
+/**
+ * \class GHASH GHASH.h <GHASH.h>
+ * \brief Implementation of the GHASH message authenticator.
+ *
+ * GHASH is the message authentication part of Galois Counter Mode (GCM).
+ *
+ * \note GHASH is not the same as GMAC.  GHASH implements the low level
+ * hashing primitive that is used by both GCM and GMAC.  GMAC can be
+ * simulated using GCM and an empty plaintext/ciphertext.
+ *
+ * References: <a href="http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf">NIST SP 800-38D</a>,
+ * http://en.wikipedia.org/wiki/Galois/Counter_Mode
+ *
+ * \sa GCM
+ */
+
+/**
+ * \brief Constructs a new GHASH message authenticator.
+ */
+GHASH::GHASH()
+{
+    state.posn = 0;
+}
+
+/**
+ * \brief Destroys this GHASH message authenticator.
+ */
+GHASH::~GHASH()
+{
+    clean(state);
+}
+
+/**
+ * \brief Resets the GHASH message authenticator for a new session.
+ *
+ * \param key Points to the 16 byte authentication key.
+ *
+ * \sa update(), finalize()
+ */
+void GHASH::reset(const void *key)
+{
+    GF128::mulInit(state.H, key);
+    memset(state.Y, 0, sizeof(state.Y));
+    state.posn = 0;
+}
+
+/**
+ * \brief Updates the message authenticator with more data.
+ *
+ * \param data Data to be hashed.
+ * \param len Number of bytes of data to be hashed.
+ *
+ * If finalize() has already been called, then the behavior of update() will
+ * be undefined.  Call reset() first to start a new authentication process.
+ *
+ * \sa pad(), reset(), finalize()
+ */
+void GHASH::update(const void *data, size_t len)
+{
+    // XOR the input with state.Y in 128-bit chunks and process them.
+    const uint8_t *d = (const uint8_t *)data;
+    while (len > 0) {
+        uint8_t size = 16 - state.posn;
+        if (size > len)
+            size = len;
+        uint8_t *y = ((uint8_t *)state.Y) + state.posn;
+        for (uint8_t i = 0; i < size; ++i)
+            y[i] ^= d[i];
+        state.posn += size;
+        len -= size;
+        d += size;
+        if (state.posn == 16) {
+            GF128::mul(state.Y, state.H);
+            state.posn = 0;
+        }
+    }
+}
+
+/**
+ * \brief Finalizes the authentication process and returns the token.
+ *
+ * \param token The buffer to return the token value in.
+ * \param len The length of the \a token buffer between 0 and 16.
+ *
+ * If \a len is less than 16, then the token value will be truncated to
+ * the first \a len bytes.  If \a len is greater than 16, then the remaining
+ * bytes will left unchanged.
+ *
+ * If finalize() is called again, then the returned \a token value is
+ * undefined.  Call reset() first to start a new authentication process.
+ *
+ * \sa reset(), update()
+ */
+void GHASH::finalize(void *token, size_t len)
+{
+    // Pad with zeroes to a multiple of 16 bytes.
+    pad();
+
+    // The token is the current value of Y.
+    if (len > 16)
+        len = 16;
+    memcpy(token, state.Y, len);
+}
+
+/**
+ * \brief Pads the input stream with zero bytes to a multiple of 16.
+ *
+ * \sa update()
+ */
+void GHASH::pad()
+{
+    if (state.posn != 0) {
+        // Padding involves XOR'ing the rest of state.Y with zeroes,
+        // which does nothing.  Immediately process the next chunk.
+        GF128::mul(state.Y, state.H);
+        state.posn = 0;
+    }
+}
+
+/**
+ * \brief Clears the authenticator's state, removing all sensitive data.
+ */
+void GHASH::clear()
+{
+    clean(state);
+}
diff --git a/src/modules/Crypto/GHASH.h b/src/modules/Crypto/GHASH.h
new file mode 100644
index 0000000..2067768
--- /dev/null
+++ b/src/modules/Crypto/GHASH.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_GHASH_h
+#define CRYPTO_GHASH_h
+
+#include <inttypes.h>
+#include <stddef.h>
+
+class GHASH
+{
+public:
+    GHASH();
+    ~GHASH();
+
+    void reset(const void *key);
+    void update(const void *data, size_t len);
+    void finalize(void *token, size_t len);
+
+    void pad();
+
+    void clear();
+
+private:
+    struct {
+        uint32_t H[4];
+        uint32_t Y[4];
+        uint8_t posn;
+    } state;
+};
+
+#endif
diff --git a/src/modules/Crypto/Hash.cpp b/src/modules/Crypto/Hash.cpp
new file mode 100644
index 0000000..ef13257
--- /dev/null
+++ b/src/modules/Crypto/Hash.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "Hash.h"
+#include <string.h>
+
+/**
+ * \class Hash Hash.h <Hash.h>
+ * \brief Abstract base class for cryptographic hash algorithms.
+ *
+ * \sa SHA256, SHA3_256, BLAKE2s
+ */
+
+/**
+ * \brief Constructs a new hash object.
+ */
+Hash::Hash()
+{
+}
+
+/**
+ * \brief Destroys this hash object.
+ *
+ * \note Subclasses are responsible for clearing any sensitive data
+ * that remains in the hash object when it is destroyed.
+ *
+ * \sa clear()
+ */
+Hash::~Hash()
+{
+}
+
+/**
+ * \fn size_t Hash::hashSize() const
+ * \brief Size of the hash result from finalize().
+ *
+ * \sa finalize(), blockSize()
+ */
+
+/**
+ * \fn size_t Hash::blockSize() const
+ * \brief Size of the internal block used by the hash algorithm.
+ *
+ * \sa update(), hashSize()
+ */
+
+/**
+ * \fn void Hash::reset()
+ * \brief Resets the hash ready for a new hashing process.
+ *
+ * \sa update(), finalize(), resetHMAC()
+ */
+
+/**
+ * \fn void Hash::update(const void *data, size_t len)
+ * \brief Updates the hash with more data.
+ *
+ * \param data Data to be hashed.
+ * \param len Number of bytes of data to be hashed.
+ *
+ * If finalize() has already been called, then the behavior of update() will
+ * be undefined.  Call reset() first to start a new hashing process.
+ *
+ * \sa reset(), finalize()
+ */
+
+/**
+ * \fn void Hash::finalize(void *hash, size_t len)
+ * \brief Finalizes the hashing process and returns the hash.
+ *
+ * \param hash The buffer to return the hash value in.
+ * \param len The length of the \a hash buffer, normally hashSize().
+ *
+ * If \a len is less than hashSize(), then the hash value will be
+ * truncated to the first \a len bytes.  If \a len is greater than
+ * hashSize(), then the remaining bytes will left unchanged.
+ *
+ * If finalize() is called again, then the returned \a hash value is
+ * undefined.  Call reset() first to start a new hashing process.
+ *
+ * \sa reset(), update(), finalizeHMAC()
+ */
+
+/**
+ * \fn void Hash::clear()
+ * \brief Clears the hash state, removing all sensitive data, and then
+ * resets the hash ready for a new hashing process.
+ *
+ * \sa reset()
+ */
+
+/**
+ * \fn void Hash::resetHMAC(const void *key, size_t keyLen)
+ * \brief Resets the hash ready for a new HMAC hashing process.
+ *
+ * \param key Points to the HMAC key for the hashing process.
+ * \param keyLen Size of the HMAC \a key in bytes.
+ *
+ * The following example computes a HMAC over a series of data blocks
+ * with a specific key:
+ *
+ * \code
+ * hash.resetHMAC(key, sizeof(key));
+ * hash.update(data1, sizeof(data1));
+ * hash.update(data2, sizeof(data2));
+ * ...
+ * hash.update(dataN, sizeof(dataN));
+ * hash.finalizeHMAC(key, sizeof(key), hmac, sizeof(hmac));
+ * \endcode
+ *
+ * The same key must be passed to both resetHMAC() and finalizeHMAC().
+ *
+ * \sa finalizeHMAC(), reset()
+ */
+
+/**
+ * \fn void Hash::finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen)
+ * \brief Finalizes the HMAC hashing process and returns the hash.
+ *
+ * \param key Points to the HMAC key for the hashing process.  The contents
+ * of this array must be identical to the value passed to resetHMAC().
+ * \param keyLen Size of the HMAC \a key in bytes.
+ * \param hash The buffer to return the hash value in.
+ * \param hashLen The length of the \a hash buffer, normally hashSize().
+ *
+ * \sa resetHMAC(), finalize()
+ */
+
+/**
+ * \brief Formats a HMAC key into a block.
+ *
+ * \param block The block to format the key into.  Must be at least
+ * blockSize() bytes in length.
+ * \param key Points to the HMAC key for the hashing process.
+ * \param len Length of the HMAC \a key in bytes.
+ * \param pad Inner (0x36) or outer (0x5C) padding value to XOR with
+ * the formatted HMAC key.
+ *
+ * This function is intended to help subclasses implement resetHMAC() and
+ * finalizeHMAC() by directly formatting the HMAC key into the subclass's
+ * internal block buffer and resetting the hash.
+ */
+void Hash::formatHMACKey(void *block, const void *key, size_t len, uint8_t pad)
+{
+    size_t size = blockSize();
+    reset();
+    if (len <= size) {
+        memcpy(block, key, len);
+    } else {
+        update(key, len);
+        len = hashSize();
+        finalize(block, len);
+        reset();
+    }
+    uint8_t *b = (uint8_t *)block;
+    memset(b + len, pad, size - len);
+    while (len > 0) {
+        *b++ ^= pad;
+        --len;
+    }
+}
diff --git a/src/modules/Crypto/Hash.h b/src/modules/Crypto/Hash.h
new file mode 100644
index 0000000..2f7db16
--- /dev/null
+++ b/src/modules/Crypto/Hash.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_HASH_h
+#define CRYPTO_HASH_h
+
+#include <inttypes.h>
+#include <stddef.h>
+
+class Hash
+{
+public:
+    Hash();
+    virtual ~Hash();
+
+    virtual size_t hashSize() const = 0;
+    virtual size_t blockSize() const = 0;
+
+    virtual void reset() = 0;
+    virtual void update(const void *data, size_t len) = 0;
+    virtual void finalize(void *hash, size_t len) = 0;
+
+    virtual void clear() = 0;
+
+    virtual void resetHMAC(const void *key, size_t keyLen) = 0;
+    virtual void finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen) = 0;
+
+protected:
+    void formatHMACKey(void *block, const void *key, size_t len, uint8_t pad);
+};
+
+#endif
diff --git a/src/modules/Crypto/KeccakCore.cpp b/src/modules/Crypto/KeccakCore.cpp
new file mode 100644
index 0000000..4cc108a
--- /dev/null
+++ b/src/modules/Crypto/KeccakCore.cpp
@@ -0,0 +1,2013 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "KeccakCore.h"
+#include "Crypto.h"
+#include "utility/EndianUtil.h"
+#include "utility/RotateUtil.h"
+#include "utility/ProgMemUtil.h"
+#include <string.h>
+
+/**
+ * \class KeccakCore KeccakCore.h <KeccakCore.h>
+ * \brief Keccak core sponge function.
+ *
+ * KeccakCore provides the core sponge function for different capacities.
+ * It is used to implement algorithms such as SHA3 and SHAKE.
+ *
+ * References: http://en.wikipedia.org/wiki/SHA-3
+ *
+ * \sa SHA3_256, SHAKE256
+ */
+
+#if !defined(CRYPTO_LITTLE_ENDIAN)
+// All of the Arduino platforms we care about are little-endian.
+#error "KeccakCore is not supported on big-endian platforms yet - todo"
+#endif
+
+/**
+ * \brief Constructs a new Keccak sponge function.
+ *
+ * The capacity() will initially be set to 1536, which normally won't be
+ * of much use to the caller.  The constructor should be followed by a
+ * call to setCapacity() to select the capacity of interest.
+ */
+KeccakCore::KeccakCore()
+    : _blockSize(8)
+{
+    memset(state.A, 0, sizeof(state.A));
+    state.inputSize = 0;
+    state.outputSize = 0;
+}
+
+/**
+ * \brief Destroys this Keccak sponge function after clearing all
+ * sensitive information.
+ */
+KeccakCore::~KeccakCore()
+{
+    clean(state);
+}
+
+/**
+ * \brief Returns the capacity of the sponge function in bits.
+ *
+ * \sa setCapacity(), blockSize()
+ */
+size_t KeccakCore::capacity() const
+{
+    return 1600 - ((size_t)_blockSize) * 8;
+}
+
+/**
+ * \brief Sets the capacity of the Keccak sponge function in bits.
+ *
+ * \param capacity The capacity of the Keccak sponge function in bits which
+ * should be a multiple of 64 and between 64 and 1536.
+ *
+ * \note It is possible to create a sponge function with this constructor that
+ * doesn't strictly conform with the capacity and hash size constraints
+ * defined in the relevant standards.  It is the responsibility of callers
+ * to only use standard parameter combinations.
+ *
+ * \sa capacity(), blockSize()
+ */
+void KeccakCore::setCapacity(size_t capacity)
+{
+    _blockSize = (1600 - capacity) / 8;
+    reset();
+}
+
+/**
+ * \fn size_t KeccakCore::blockSize() const
+ * \brief Returns the input block size for the sponge function in bytes.
+ *
+ * The block size is (1600 - capacity()) / 8.
+ *
+ * \sa capacity()
+ */
+
+/**
+ * \brief Resets the Keccak sponge function ready for a new session.
+ *
+ * \sa update(), extract()
+ */
+void KeccakCore::reset()
+{
+    memset(state.A, 0, sizeof(state.A));
+    state.inputSize = 0;
+    state.outputSize = 0;
+}
+
+/**
+ * \brief Updates the Keccak sponge function with more input data.
+ *
+ * \param data The extra input data to incorporate.
+ * \param size The size of the new data to incorporate.
+ *
+ * This function will invoke the sponge function whenever a full blockSize()
+ * bytes of input data have been accumulated.  Call pad() after the last
+ * block to finalize the input before calling extract().
+ *
+ * \sa pad(), extract(), reset()
+ */
+void KeccakCore::update(const void *data, size_t size)
+{
+    // Stop generating output while we incorporate the new data.
+    state.outputSize = 0;
+
+    // Break the input up into chunks and process each in turn.
+    const uint8_t *d = (const uint8_t *)data;
+    while (size > 0) {
+        uint8_t len = _blockSize - state.inputSize;
+        if (len > size)
+            len = size;
+        uint8_t *Abytes = ((uint8_t *)state.A) + state.inputSize;
+        for (uint8_t posn = 0; posn < len; ++posn)
+            Abytes[posn] ^= d[posn];
+        state.inputSize += len;
+        size -= len;
+        d += len;
+        if (state.inputSize == _blockSize) {
+            keccakp();
+            state.inputSize = 0;
+        }
+    }
+}
+
+/**
+ * \brief Pads the last block of input data to blockSize().
+ *
+ * \param tag The tag byte to add to the padding to identify SHA3 (0x06),
+ * SHAKE (0x1F), or the plain pre-standardized version of Keccak (0x01).
+ *
+ * The sponge function will be invoked to process the completed padding block.
+ *
+ * \sa update(), extract()
+ */
+void KeccakCore::pad(uint8_t tag)
+{
+    // Padding for SHA3-NNN variants according to FIPS 202 appends "01",
+    // then another "1", then many zero bits, followed by a final "1".
+    // SHAKE appends "1111" first instead of "01".  Note that SHA-3 numbers
+    // bits from the least significant, so appending "01" is equivalent
+    // to 0x02 for byte-aligned data, not 0x40.
+    uint8_t size = state.inputSize;
+    uint64_t *Awords = &(state.A[0][0]);
+    Awords[size / 8] ^= (((uint64_t)tag) << ((size % 8) * 8));
+    Awords[(_blockSize - 1) / 8] ^= 0x8000000000000000ULL;
+    keccakp();
+    state.inputSize = 0;
+    state.outputSize = 0;
+}
+
+/**
+ * \brief Extracts data from the Keccak sponge function.
+ *
+ * \param data The data buffer to fill with extracted data.
+ * \param size The number number of bytes of extracted data that are required.
+ *
+ * If more than blockSize() bytes are required, the sponge function will
+ * be invoked to generate additional data.
+ *
+ * \sa update(), reset(), encrypt()
+ */
+void KeccakCore::extract(void *data, size_t size)
+{
+    // Stop accepting input while we are generating output.
+    state.inputSize = 0;
+
+    // Copy the output data into the caller's return buffer.
+    uint8_t *d = (uint8_t *)data;
+    uint8_t tempSize;
+    while (size > 0) {
+        // Generate another output block if the current one has been exhausted.
+        if (state.outputSize >= _blockSize) {
+            keccakp();
+            state.outputSize = 0;
+        }
+
+        // How many bytes can we copy this time around?
+        tempSize = _blockSize - state.outputSize;
+        if (tempSize > size)
+            tempSize = size;
+
+        // Copy the partial output data into the caller's return buffer.
+        memcpy(d, ((uint8_t *)(state.A)) + state.outputSize, tempSize);
+        state.outputSize += tempSize;
+        size -= tempSize;
+        d += tempSize;
+    }
+}
+
+/**
+ * \brief Extracts data from the Keccak sponge function and uses it to
+ * encrypt a buffer.
+ *
+ * \param output The output buffer to write to, which may be the same
+ * buffer as \a input.  The \a output buffer must have at least as many
+ * bytes as the \a input buffer.
+ * \param input The input buffer to read from.
+ * \param size The number of bytes to encrypt.
+ *
+ * This function extracts data from the sponge function and then XOR's
+ * it with \a input to generate the \a output.
+ *
+ * If more than blockSize() bytes are required, the sponge function will
+ * be invoked to generate additional data.
+ *
+ * \sa update(), reset(), extract()
+ */
+void KeccakCore::encrypt(void *output, const void *input, size_t size)
+{
+    // Stop accepting input while we are generating output.
+    state.inputSize = 0;
+
+    // Copy the output data into the caller's return buffer.
+    uint8_t *out = (uint8_t *)output;
+    const uint8_t *in = (const uint8_t *)input;
+    uint8_t tempSize;
+    while (size > 0) {
+        // Generate another output block if the current one has been exhausted.
+        if (state.outputSize >= _blockSize) {
+            keccakp();
+            state.outputSize = 0;
+        }
+
+        // How many bytes can we extract this time around?
+        tempSize = _blockSize - state.outputSize;
+        if (tempSize > size)
+            tempSize = size;
+
+        // XOR the partial output data into the caller's return buffer.
+        const uint8_t *d = ((const uint8_t *)(state.A)) + state.outputSize;
+        for (uint8_t index = 0; index < tempSize; ++index)
+            out[index] = in[index] ^ d[index];
+        state.outputSize += tempSize;
+        size -= tempSize;
+        out += tempSize;
+        in += tempSize;
+    }
+}
+
+/**
+ * \brief Clears all sensitive data from this object.
+ */
+void KeccakCore::clear()
+{
+    clean(state);
+}
+
+/**
+ * \brief Sets a HMAC key for a Keccak-based hash algorithm.
+ *
+ * \param key Points to the HMAC key for the hashing process.
+ * \param len Length of the HMAC \a key in bytes.
+ * \param pad Inner (0x36) or outer (0x5C) padding value to XOR with
+ * the formatted HMAC key.
+ * \param hashSize The size of the output from the hash algorithm.
+ *
+ * This function is intended to help classes implement Hash::resetHMAC() and
+ * Hash::finalizeHMAC() by directly formatting the HMAC key into the
+ * internal block buffer and resetting the hash.
+ */
+void KeccakCore::setHMACKey(const void *key, size_t len, uint8_t pad, size_t hashSize)
+{
+    uint8_t *Abytes = (uint8_t *)state.A;
+    size_t size = blockSize();
+    reset();
+    if (len <= size) {
+        // Because the state has just been reset, state.A is set to
+        // all-zeroes.  We can copy the key directly into the state
+        // and then XOR the block with the pad value.
+        memcpy(Abytes, key, len);
+    } else {
+        // The key is larger than the block size.  Hash it down.
+        // Afterwards, state.A will contain the first block of data
+        // to be extracted.  We truncate it to the first "hashSize"
+        // bytes and XOR with the padding.
+        update(key, len);
+        this->pad(0x06);
+        memset(Abytes + hashSize, pad, size - hashSize);
+        memset(Abytes + size, 0, sizeof(state.A) - size);
+        size = hashSize;
+    }
+    while (size > 0) {
+        *Abytes++ ^= pad;
+        --size;
+    }
+    keccakp();
+}
+
+/**
+ * \brief Transform the state with the KECCAK-p sponge function with b = 1600.
+ */
+void KeccakCore::keccakp()
+{
+    uint64_t B[5][5];
+#if defined(__AVR__)
+    // This assembly code was generated by the "genkeccak.c" program.
+    // Do not modify this code directly.  Instead modify "genkeccak.c"
+    // and then re-generate the code here.
+    for (uint8_t round = 0; round < 24; ++round) {
+    __asm__ __volatile__ (
+        "push r29\n"
+        "push r28\n"
+        "mov r28,r26\n"
+        "mov r29,r27\n"
+
+        // Step mapping theta.  Compute C.
+        "ldi r20,5\n"
+        "100:\n"
+        "ld r8,Z\n"
+        "ldd r9,Z+1\n"
+        "ldd r10,Z+2\n"
+        "ldd r11,Z+3\n"
+        "ldd r12,Z+4\n"
+        "ldd r13,Z+5\n"
+        "ldd r14,Z+6\n"
+        "ldd r15,Z+7\n"
+        "ldi r19,4\n"
+        "101:\n"
+        "adiw r30,40\n"
+        "ld __tmp_reg__,Z\n"
+        "eor r8,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+1\n"
+        "eor r9,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+2\n"
+        "eor r10,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+3\n"
+        "eor r11,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+4\n"
+        "eor r12,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+5\n"
+        "eor r13,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+6\n"
+        "eor r14,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+7\n"
+        "eor r15,__tmp_reg__\n"
+        "dec r19\n"
+        "brne 101b\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "subi r30,152\n"
+        "sbc r31,__zero_reg__\n"
+        "dec r20\n"
+        "brne 100b\n"
+        "sbiw r30,40\n"
+        "sbiw r26,40\n"
+
+        // Step mapping theta.  Compute D and XOR with A.
+        "ldd r8,Y+8\n"
+        "ldd r9,Y+9\n"
+        "ldd r10,Y+10\n"
+        "ldd r11,Y+11\n"
+        "ldd r12,Y+12\n"
+        "ldd r13,Y+13\n"
+        "ldd r14,Y+14\n"
+        "ldd r15,Y+15\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "ldd __tmp_reg__,Y+32\n"
+        "eor r8,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+33\n"
+        "eor r9,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+34\n"
+        "eor r10,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+35\n"
+        "eor r11,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+36\n"
+        "eor r12,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+37\n"
+        "eor r13,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+38\n"
+        "eor r14,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+39\n"
+        "eor r15,__tmp_reg__\n"
+        "ldi r19,5\n"
+        "103:\n"
+        "ld __tmp_reg__,Z\n"
+        "eor __tmp_reg__,r8\n"
+        "st Z,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+1\n"
+        "eor __tmp_reg__,r9\n"
+        "std Z+1,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+2\n"
+        "eor __tmp_reg__,r10\n"
+        "std Z+2,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+3\n"
+        "eor __tmp_reg__,r11\n"
+        "std Z+3,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+4\n"
+        "eor __tmp_reg__,r12\n"
+        "std Z+4,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+5\n"
+        "eor __tmp_reg__,r13\n"
+        "std Z+5,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+6\n"
+        "eor __tmp_reg__,r14\n"
+        "std Z+6,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+7\n"
+        "eor __tmp_reg__,r15\n"
+        "std Z+7,__tmp_reg__\n"
+        "adiw r30,40\n"
+        "dec r19\n"
+        "brne 103b\n"
+        "subi r30,192\n"
+        "sbc r31,__zero_reg__\n"
+        "ldd r8,Y+16\n"
+        "ldd r9,Y+17\n"
+        "ldd r10,Y+18\n"
+        "ldd r11,Y+19\n"
+        "ldd r12,Y+20\n"
+        "ldd r13,Y+21\n"
+        "ldd r14,Y+22\n"
+        "ldd r15,Y+23\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "ld __tmp_reg__,Y\n"
+        "eor r8,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+1\n"
+        "eor r9,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+2\n"
+        "eor r10,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+3\n"
+        "eor r11,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+4\n"
+        "eor r12,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+5\n"
+        "eor r13,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+6\n"
+        "eor r14,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+7\n"
+        "eor r15,__tmp_reg__\n"
+        "ldi r19,5\n"
+        "104:\n"
+        "ld __tmp_reg__,Z\n"
+        "eor __tmp_reg__,r8\n"
+        "st Z,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+1\n"
+        "eor __tmp_reg__,r9\n"
+        "std Z+1,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+2\n"
+        "eor __tmp_reg__,r10\n"
+        "std Z+2,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+3\n"
+        "eor __tmp_reg__,r11\n"
+        "std Z+3,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+4\n"
+        "eor __tmp_reg__,r12\n"
+        "std Z+4,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+5\n"
+        "eor __tmp_reg__,r13\n"
+        "std Z+5,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+6\n"
+        "eor __tmp_reg__,r14\n"
+        "std Z+6,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+7\n"
+        "eor __tmp_reg__,r15\n"
+        "std Z+7,__tmp_reg__\n"
+        "adiw r30,40\n"
+        "dec r19\n"
+        "brne 104b\n"
+        "subi r30,192\n"
+        "sbc r31,__zero_reg__\n"
+        "ldd r8,Y+24\n"
+        "ldd r9,Y+25\n"
+        "ldd r10,Y+26\n"
+        "ldd r11,Y+27\n"
+        "ldd r12,Y+28\n"
+        "ldd r13,Y+29\n"
+        "ldd r14,Y+30\n"
+        "ldd r15,Y+31\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "ldd __tmp_reg__,Y+8\n"
+        "eor r8,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+9\n"
+        "eor r9,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+10\n"
+        "eor r10,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+11\n"
+        "eor r11,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+12\n"
+        "eor r12,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+13\n"
+        "eor r13,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+14\n"
+        "eor r14,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+15\n"
+        "eor r15,__tmp_reg__\n"
+        "ldi r19,5\n"
+        "105:\n"
+        "ld __tmp_reg__,Z\n"
+        "eor __tmp_reg__,r8\n"
+        "st Z,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+1\n"
+        "eor __tmp_reg__,r9\n"
+        "std Z+1,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+2\n"
+        "eor __tmp_reg__,r10\n"
+        "std Z+2,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+3\n"
+        "eor __tmp_reg__,r11\n"
+        "std Z+3,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+4\n"
+        "eor __tmp_reg__,r12\n"
+        "std Z+4,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+5\n"
+        "eor __tmp_reg__,r13\n"
+        "std Z+5,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+6\n"
+        "eor __tmp_reg__,r14\n"
+        "std Z+6,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+7\n"
+        "eor __tmp_reg__,r15\n"
+        "std Z+7,__tmp_reg__\n"
+        "adiw r30,40\n"
+        "dec r19\n"
+        "brne 105b\n"
+        "subi r30,192\n"
+        "sbc r31,__zero_reg__\n"
+        "ldd r8,Y+32\n"
+        "ldd r9,Y+33\n"
+        "ldd r10,Y+34\n"
+        "ldd r11,Y+35\n"
+        "ldd r12,Y+36\n"
+        "ldd r13,Y+37\n"
+        "ldd r14,Y+38\n"
+        "ldd r15,Y+39\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "ldd __tmp_reg__,Y+16\n"
+        "eor r8,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+17\n"
+        "eor r9,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+18\n"
+        "eor r10,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+19\n"
+        "eor r11,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+20\n"
+        "eor r12,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+21\n"
+        "eor r13,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+22\n"
+        "eor r14,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+23\n"
+        "eor r15,__tmp_reg__\n"
+        "ldi r19,5\n"
+        "106:\n"
+        "ld __tmp_reg__,Z\n"
+        "eor __tmp_reg__,r8\n"
+        "st Z,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+1\n"
+        "eor __tmp_reg__,r9\n"
+        "std Z+1,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+2\n"
+        "eor __tmp_reg__,r10\n"
+        "std Z+2,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+3\n"
+        "eor __tmp_reg__,r11\n"
+        "std Z+3,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+4\n"
+        "eor __tmp_reg__,r12\n"
+        "std Z+4,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+5\n"
+        "eor __tmp_reg__,r13\n"
+        "std Z+5,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+6\n"
+        "eor __tmp_reg__,r14\n"
+        "std Z+6,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+7\n"
+        "eor __tmp_reg__,r15\n"
+        "std Z+7,__tmp_reg__\n"
+        "adiw r30,40\n"
+        "dec r19\n"
+        "brne 106b\n"
+        "subi r30,192\n"
+        "sbc r31,__zero_reg__\n"
+        "ld r8,Y\n"
+        "ldd r9,Y+1\n"
+        "ldd r10,Y+2\n"
+        "ldd r11,Y+3\n"
+        "ldd r12,Y+4\n"
+        "ldd r13,Y+5\n"
+        "ldd r14,Y+6\n"
+        "ldd r15,Y+7\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "ldd __tmp_reg__,Y+24\n"
+        "eor r8,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+25\n"
+        "eor r9,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+26\n"
+        "eor r10,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+27\n"
+        "eor r11,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+28\n"
+        "eor r12,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+29\n"
+        "eor r13,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+30\n"
+        "eor r14,__tmp_reg__\n"
+        "ldd __tmp_reg__,Y+31\n"
+        "eor r15,__tmp_reg__\n"
+        "ldi r19,5\n"
+        "107:\n"
+        "ld __tmp_reg__,Z\n"
+        "eor __tmp_reg__,r8\n"
+        "st Z,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+1\n"
+        "eor __tmp_reg__,r9\n"
+        "std Z+1,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+2\n"
+        "eor __tmp_reg__,r10\n"
+        "std Z+2,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+3\n"
+        "eor __tmp_reg__,r11\n"
+        "std Z+3,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+4\n"
+        "eor __tmp_reg__,r12\n"
+        "std Z+4,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+5\n"
+        "eor __tmp_reg__,r13\n"
+        "std Z+5,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+6\n"
+        "eor __tmp_reg__,r14\n"
+        "std Z+6,__tmp_reg__\n"
+        "ldd __tmp_reg__,Z+7\n"
+        "eor __tmp_reg__,r15\n"
+        "std Z+7,__tmp_reg__\n"
+        "adiw r30,40\n"
+        "dec r19\n"
+        "brne 107b\n"
+        "subi r30,232\n"
+        "sbc r31,__zero_reg__\n"
+
+        // Step mappings rho and pi combined into one step.
+
+        // B[0][0] = A[0][0]
+        "ld r8,Z\n"
+        "ldd r9,Z+1\n"
+        "ldd r10,Z+2\n"
+        "ldd r11,Z+3\n"
+        "ldd r12,Z+4\n"
+        "ldd r13,Z+5\n"
+        "ldd r14,Z+6\n"
+        "ldd r15,Z+7\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+
+        // B[1][0] = leftRotate28_64(A[0][3])
+        "adiw r26,32\n"
+        "ldd r8,Z+24\n"
+        "ldd r9,Z+25\n"
+        "ldd r10,Z+26\n"
+        "ldd r11,Z+27\n"
+        "ldd r12,Z+28\n"
+        "ldd r13,Z+29\n"
+        "ldd r14,Z+30\n"
+        "ldd r15,Z+31\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+
+        // B[2][0] = leftRotate1_64(A[0][1])
+        "adiw r26,32\n"
+        "ldd r8,Z+8\n"
+        "ldd r9,Z+9\n"
+        "ldd r10,Z+10\n"
+        "ldd r11,Z+11\n"
+        "ldd r12,Z+12\n"
+        "ldd r13,Z+13\n"
+        "ldd r14,Z+14\n"
+        "ldd r15,Z+15\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+
+        // B[3][0] = leftRotate27_64(A[0][4])
+        "adiw r26,32\n"
+        "ldd r8,Z+32\n"
+        "ldd r9,Z+33\n"
+        "ldd r10,Z+34\n"
+        "ldd r11,Z+35\n"
+        "ldd r12,Z+36\n"
+        "ldd r13,Z+37\n"
+        "ldd r14,Z+38\n"
+        "ldd r15,Z+39\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+
+        // B[4][0] = leftRotate62_64(A[0][2])
+        "adiw r26,32\n"
+        "ldd r8,Z+16\n"
+        "ldd r9,Z+17\n"
+        "ldd r10,Z+18\n"
+        "ldd r11,Z+19\n"
+        "ldd r12,Z+20\n"
+        "ldd r13,Z+21\n"
+        "ldd r14,Z+22\n"
+        "ldd r15,Z+23\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+
+        // B[0][1] = leftRotate44_64(A[1][1])
+        "subi r26,160\n"
+        "sbc r27,__zero_reg__\n"
+        "adiw r30,40\n"
+        "ldd r8,Z+8\n"
+        "ldd r9,Z+9\n"
+        "ldd r10,Z+10\n"
+        "ldd r11,Z+11\n"
+        "ldd r12,Z+12\n"
+        "ldd r13,Z+13\n"
+        "ldd r14,Z+14\n"
+        "ldd r15,Z+15\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+
+        // B[1][1] = leftRotate20_64(A[1][4])
+        "adiw r26,32\n"
+        "ldd r8,Z+32\n"
+        "ldd r9,Z+33\n"
+        "ldd r10,Z+34\n"
+        "ldd r11,Z+35\n"
+        "ldd r12,Z+36\n"
+        "ldd r13,Z+37\n"
+        "ldd r14,Z+38\n"
+        "ldd r15,Z+39\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+
+        // B[2][1] = leftRotate6_64(A[1][2])
+        "adiw r26,32\n"
+        "ldd r8,Z+16\n"
+        "ldd r9,Z+17\n"
+        "ldd r10,Z+18\n"
+        "ldd r11,Z+19\n"
+        "ldd r12,Z+20\n"
+        "ldd r13,Z+21\n"
+        "ldd r14,Z+22\n"
+        "ldd r15,Z+23\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+
+        // B[3][1] = leftRotate36_64(A[1][0])
+        "adiw r26,32\n"
+        "ld r8,Z\n"
+        "ldd r9,Z+1\n"
+        "ldd r10,Z+2\n"
+        "ldd r11,Z+3\n"
+        "ldd r12,Z+4\n"
+        "ldd r13,Z+5\n"
+        "ldd r14,Z+6\n"
+        "ldd r15,Z+7\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+
+        // B[4][1] = leftRotate55_64(A[1][3])
+        "adiw r26,32\n"
+        "ldd r8,Z+24\n"
+        "ldd r9,Z+25\n"
+        "ldd r10,Z+26\n"
+        "ldd r11,Z+27\n"
+        "ldd r12,Z+28\n"
+        "ldd r13,Z+29\n"
+        "ldd r14,Z+30\n"
+        "ldd r15,Z+31\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+
+        // B[0][2] = leftRotate43_64(A[2][2])
+        "subi r26,160\n"
+        "sbc r27,__zero_reg__\n"
+        "adiw r30,40\n"
+        "ldd r8,Z+16\n"
+        "ldd r9,Z+17\n"
+        "ldd r10,Z+18\n"
+        "ldd r11,Z+19\n"
+        "ldd r12,Z+20\n"
+        "ldd r13,Z+21\n"
+        "ldd r14,Z+22\n"
+        "ldd r15,Z+23\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+
+        // B[1][2] = leftRotate3_64(A[2][0])
+        "adiw r26,32\n"
+        "ld r8,Z\n"
+        "ldd r9,Z+1\n"
+        "ldd r10,Z+2\n"
+        "ldd r11,Z+3\n"
+        "ldd r12,Z+4\n"
+        "ldd r13,Z+5\n"
+        "ldd r14,Z+6\n"
+        "ldd r15,Z+7\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+
+        // B[2][2] = leftRotate25_64(A[2][3])
+        "adiw r26,32\n"
+        "ldd r8,Z+24\n"
+        "ldd r9,Z+25\n"
+        "ldd r10,Z+26\n"
+        "ldd r11,Z+27\n"
+        "ldd r12,Z+28\n"
+        "ldd r13,Z+29\n"
+        "ldd r14,Z+30\n"
+        "ldd r15,Z+31\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+
+        // B[3][2] = leftRotate10_64(A[2][1])
+        "adiw r26,32\n"
+        "ldd r8,Z+8\n"
+        "ldd r9,Z+9\n"
+        "ldd r10,Z+10\n"
+        "ldd r11,Z+11\n"
+        "ldd r12,Z+12\n"
+        "ldd r13,Z+13\n"
+        "ldd r14,Z+14\n"
+        "ldd r15,Z+15\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+
+        // B[4][2] = leftRotate39_64(A[2][4])
+        "adiw r26,32\n"
+        "ldd r8,Z+32\n"
+        "ldd r9,Z+33\n"
+        "ldd r10,Z+34\n"
+        "ldd r11,Z+35\n"
+        "ldd r12,Z+36\n"
+        "ldd r13,Z+37\n"
+        "ldd r14,Z+38\n"
+        "ldd r15,Z+39\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+
+        // B[0][3] = leftRotate21_64(A[3][3])
+        "subi r26,160\n"
+        "sbc r27,__zero_reg__\n"
+        "adiw r30,40\n"
+        "ldd r8,Z+24\n"
+        "ldd r9,Z+25\n"
+        "ldd r10,Z+26\n"
+        "ldd r11,Z+27\n"
+        "ldd r12,Z+28\n"
+        "ldd r13,Z+29\n"
+        "ldd r14,Z+30\n"
+        "ldd r15,Z+31\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+
+        // B[1][3] = leftRotate45_64(A[3][1])
+        "adiw r26,32\n"
+        "ldd r8,Z+8\n"
+        "ldd r9,Z+9\n"
+        "ldd r10,Z+10\n"
+        "ldd r11,Z+11\n"
+        "ldd r12,Z+12\n"
+        "ldd r13,Z+13\n"
+        "ldd r14,Z+14\n"
+        "ldd r15,Z+15\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+
+        // B[2][3] = leftRotate8_64(A[3][4])
+        "adiw r26,32\n"
+        "ldd r8,Z+32\n"
+        "ldd r9,Z+33\n"
+        "ldd r10,Z+34\n"
+        "ldd r11,Z+35\n"
+        "ldd r12,Z+36\n"
+        "ldd r13,Z+37\n"
+        "ldd r14,Z+38\n"
+        "ldd r15,Z+39\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+
+        // B[3][3] = leftRotate15_64(A[3][2])
+        "adiw r26,32\n"
+        "ldd r8,Z+16\n"
+        "ldd r9,Z+17\n"
+        "ldd r10,Z+18\n"
+        "ldd r11,Z+19\n"
+        "ldd r12,Z+20\n"
+        "ldd r13,Z+21\n"
+        "ldd r14,Z+22\n"
+        "ldd r15,Z+23\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+
+        // B[4][3] = leftRotate41_64(A[3][0])
+        "adiw r26,32\n"
+        "ld r8,Z\n"
+        "ldd r9,Z+1\n"
+        "ldd r10,Z+2\n"
+        "ldd r11,Z+3\n"
+        "ldd r12,Z+4\n"
+        "ldd r13,Z+5\n"
+        "ldd r14,Z+6\n"
+        "ldd r15,Z+7\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+
+        // B[0][4] = leftRotate14_64(A[4][4])
+        "subi r26,160\n"
+        "sbc r27,__zero_reg__\n"
+        "adiw r30,40\n"
+        "ldd r8,Z+32\n"
+        "ldd r9,Z+33\n"
+        "ldd r10,Z+34\n"
+        "ldd r11,Z+35\n"
+        "ldd r12,Z+36\n"
+        "ldd r13,Z+37\n"
+        "ldd r14,Z+38\n"
+        "ldd r15,Z+39\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+
+        // B[1][4] = leftRotate61_64(A[4][2])
+        "adiw r26,32\n"
+        "ldd r8,Z+16\n"
+        "ldd r9,Z+17\n"
+        "ldd r10,Z+18\n"
+        "ldd r11,Z+19\n"
+        "ldd r12,Z+20\n"
+        "ldd r13,Z+21\n"
+        "ldd r14,Z+22\n"
+        "ldd r15,Z+23\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "bst r8,0\n"
+        "ror r15\n"
+        "ror r14\n"
+        "ror r13\n"
+        "ror r12\n"
+        "ror r11\n"
+        "ror r10\n"
+        "ror r9\n"
+        "ror r8\n"
+        "bld r15,7\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+
+        // B[2][4] = leftRotate18_64(A[4][0])
+        "adiw r26,32\n"
+        "ld r8,Z\n"
+        "ldd r9,Z+1\n"
+        "ldd r10,Z+2\n"
+        "ldd r11,Z+3\n"
+        "ldd r12,Z+4\n"
+        "ldd r13,Z+5\n"
+        "ldd r14,Z+6\n"
+        "ldd r15,Z+7\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+
+        // B[3][4] = leftRotate56_64(A[4][3])
+        "adiw r26,32\n"
+        "ldd r8,Z+24\n"
+        "ldd r9,Z+25\n"
+        "ldd r10,Z+26\n"
+        "ldd r11,Z+27\n"
+        "ldd r12,Z+28\n"
+        "ldd r13,Z+29\n"
+        "ldd r14,Z+30\n"
+        "ldd r15,Z+31\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "st X+,r8\n"
+
+        // B[4][4] = leftRotate2_64(A[4][1])
+        "adiw r26,32\n"
+        "ldd r8,Z+8\n"
+        "ldd r9,Z+9\n"
+        "ldd r10,Z+10\n"
+        "ldd r11,Z+11\n"
+        "ldd r12,Z+12\n"
+        "ldd r13,Z+13\n"
+        "ldd r14,Z+14\n"
+        "ldd r15,Z+15\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "lsl r8\n"
+        "rol r9\n"
+        "rol r10\n"
+        "rol r11\n"
+        "rol r12\n"
+        "rol r13\n"
+        "rol r14\n"
+        "rol r15\n"
+        "adc r8, __zero_reg__\n"
+        "st X+,r8\n"
+        "st X+,r9\n"
+        "st X+,r10\n"
+        "st X+,r11\n"
+        "st X+,r12\n"
+        "st X+,r13\n"
+        "st X+,r14\n"
+        "st X+,r15\n"
+        "subi r26,200\n"
+        "sbc r27,__zero_reg__\n"
+        "subi r30,160\n"
+        "sbc r31,__zero_reg__\n"
+
+        // Step mapping chi.
+        "ldi r20,5\n"
+        "50:\n"
+        "ld r8,Y\n"
+        "ldd r9,Y+8\n"
+        "ldd r10,Y+16\n"
+        "ldd r11,Y+24\n"
+        "ldd r12,Y+32\n"
+        "mov r13,r9\n"
+        "com r13\n"
+        "and r13,r10\n"
+        "eor r13,r8\n"
+        "mov r14,r10\n"
+        "com r14\n"
+        "and r14,r11\n"
+        "eor r14,r9\n"
+        "mov r15,r11\n"
+        "com r15\n"
+        "and r15,r12\n"
+        "eor r15,r10\n"
+        "mov r17,r12\n"
+        "com r17\n"
+        "and r17,r8\n"
+        "eor r17,r11\n"
+        "mov r16,r8\n"
+        "com r16\n"
+        "and r16,r9\n"
+        "eor r16,r12\n"
+        "st Z,r13\n"
+        "std Z+8,r14\n"
+        "std Z+16,r15\n"
+        "std Z+24,r17\n"
+        "std Z+32,r16\n"
+        "ldd r8,Y+1\n"
+        "ldd r9,Y+9\n"
+        "ldd r10,Y+17\n"
+        "ldd r11,Y+25\n"
+        "ldd r12,Y+33\n"
+        "mov r13,r9\n"
+        "com r13\n"
+        "and r13,r10\n"
+        "eor r13,r8\n"
+        "mov r14,r10\n"
+        "com r14\n"
+        "and r14,r11\n"
+        "eor r14,r9\n"
+        "mov r15,r11\n"
+        "com r15\n"
+        "and r15,r12\n"
+        "eor r15,r10\n"
+        "mov r17,r12\n"
+        "com r17\n"
+        "and r17,r8\n"
+        "eor r17,r11\n"
+        "mov r16,r8\n"
+        "com r16\n"
+        "and r16,r9\n"
+        "eor r16,r12\n"
+        "std Z+1,r13\n"
+        "std Z+9,r14\n"
+        "std Z+17,r15\n"
+        "std Z+25,r17\n"
+        "std Z+33,r16\n"
+        "ldd r8,Y+2\n"
+        "ldd r9,Y+10\n"
+        "ldd r10,Y+18\n"
+        "ldd r11,Y+26\n"
+        "ldd r12,Y+34\n"
+        "mov r13,r9\n"
+        "com r13\n"
+        "and r13,r10\n"
+        "eor r13,r8\n"
+        "mov r14,r10\n"
+        "com r14\n"
+        "and r14,r11\n"
+        "eor r14,r9\n"
+        "mov r15,r11\n"
+        "com r15\n"
+        "and r15,r12\n"
+        "eor r15,r10\n"
+        "mov r17,r12\n"
+        "com r17\n"
+        "and r17,r8\n"
+        "eor r17,r11\n"
+        "mov r16,r8\n"
+        "com r16\n"
+        "and r16,r9\n"
+        "eor r16,r12\n"
+        "std Z+2,r13\n"
+        "std Z+10,r14\n"
+        "std Z+18,r15\n"
+        "std Z+26,r17\n"
+        "std Z+34,r16\n"
+        "ldd r8,Y+3\n"
+        "ldd r9,Y+11\n"
+        "ldd r10,Y+19\n"
+        "ldd r11,Y+27\n"
+        "ldd r12,Y+35\n"
+        "mov r13,r9\n"
+        "com r13\n"
+        "and r13,r10\n"
+        "eor r13,r8\n"
+        "mov r14,r10\n"
+        "com r14\n"
+        "and r14,r11\n"
+        "eor r14,r9\n"
+        "mov r15,r11\n"
+        "com r15\n"
+        "and r15,r12\n"
+        "eor r15,r10\n"
+        "mov r17,r12\n"
+        "com r17\n"
+        "and r17,r8\n"
+        "eor r17,r11\n"
+        "mov r16,r8\n"
+        "com r16\n"
+        "and r16,r9\n"
+        "eor r16,r12\n"
+        "std Z+3,r13\n"
+        "std Z+11,r14\n"
+        "std Z+19,r15\n"
+        "std Z+27,r17\n"
+        "std Z+35,r16\n"
+        "ldd r8,Y+4\n"
+        "ldd r9,Y+12\n"
+        "ldd r10,Y+20\n"
+        "ldd r11,Y+28\n"
+        "ldd r12,Y+36\n"
+        "mov r13,r9\n"
+        "com r13\n"
+        "and r13,r10\n"
+        "eor r13,r8\n"
+        "mov r14,r10\n"
+        "com r14\n"
+        "and r14,r11\n"
+        "eor r14,r9\n"
+        "mov r15,r11\n"
+        "com r15\n"
+        "and r15,r12\n"
+        "eor r15,r10\n"
+        "mov r17,r12\n"
+        "com r17\n"
+        "and r17,r8\n"
+        "eor r17,r11\n"
+        "mov r16,r8\n"
+        "com r16\n"
+        "and r16,r9\n"
+        "eor r16,r12\n"
+        "std Z+4,r13\n"
+        "std Z+12,r14\n"
+        "std Z+20,r15\n"
+        "std Z+28,r17\n"
+        "std Z+36,r16\n"
+        "ldd r8,Y+5\n"
+        "ldd r9,Y+13\n"
+        "ldd r10,Y+21\n"
+        "ldd r11,Y+29\n"
+        "ldd r12,Y+37\n"
+        "mov r13,r9\n"
+        "com r13\n"
+        "and r13,r10\n"
+        "eor r13,r8\n"
+        "mov r14,r10\n"
+        "com r14\n"
+        "and r14,r11\n"
+        "eor r14,r9\n"
+        "mov r15,r11\n"
+        "com r15\n"
+        "and r15,r12\n"
+        "eor r15,r10\n"
+        "mov r17,r12\n"
+        "com r17\n"
+        "and r17,r8\n"
+        "eor r17,r11\n"
+        "mov r16,r8\n"
+        "com r16\n"
+        "and r16,r9\n"
+        "eor r16,r12\n"
+        "std Z+5,r13\n"
+        "std Z+13,r14\n"
+        "std Z+21,r15\n"
+        "std Z+29,r17\n"
+        "std Z+37,r16\n"
+        "ldd r8,Y+6\n"
+        "ldd r9,Y+14\n"
+        "ldd r10,Y+22\n"
+        "ldd r11,Y+30\n"
+        "ldd r12,Y+38\n"
+        "mov r13,r9\n"
+        "com r13\n"
+        "and r13,r10\n"
+        "eor r13,r8\n"
+        "mov r14,r10\n"
+        "com r14\n"
+        "and r14,r11\n"
+        "eor r14,r9\n"
+        "mov r15,r11\n"
+        "com r15\n"
+        "and r15,r12\n"
+        "eor r15,r10\n"
+        "mov r17,r12\n"
+        "com r17\n"
+        "and r17,r8\n"
+        "eor r17,r11\n"
+        "mov r16,r8\n"
+        "com r16\n"
+        "and r16,r9\n"
+        "eor r16,r12\n"
+        "std Z+6,r13\n"
+        "std Z+14,r14\n"
+        "std Z+22,r15\n"
+        "std Z+30,r17\n"
+        "std Z+38,r16\n"
+        "ldd r8,Y+7\n"
+        "ldd r9,Y+15\n"
+        "ldd r10,Y+23\n"
+        "ldd r11,Y+31\n"
+        "ldd r12,Y+39\n"
+        "mov r13,r9\n"
+        "com r13\n"
+        "and r13,r10\n"
+        "eor r13,r8\n"
+        "mov r14,r10\n"
+        "com r14\n"
+        "and r14,r11\n"
+        "eor r14,r9\n"
+        "mov r15,r11\n"
+        "com r15\n"
+        "and r15,r12\n"
+        "eor r15,r10\n"
+        "mov r17,r12\n"
+        "com r17\n"
+        "and r17,r8\n"
+        "eor r17,r11\n"
+        "mov r16,r8\n"
+        "com r16\n"
+        "and r16,r9\n"
+        "eor r16,r12\n"
+        "std Z+7,r13\n"
+        "std Z+15,r14\n"
+        "std Z+23,r15\n"
+        "std Z+31,r17\n"
+        "std Z+39,r16\n"
+        "adiw r30,40\n"
+        "adiw r28,40\n"
+        "dec r20\n"
+        "breq 51f\n"
+        "rjmp 50b\n"
+        "51:\n"
+        "pop r28\n"
+        "pop r29\n"
+
+        // Done
+        : : "x"(B), "z"(state.A)
+        : "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
+          "r16", "r17", "r18", "r19", "r20", "r21", "memory"
+    );
+#else
+    static const uint8_t addMod5Table[9] PROGMEM = {
+        0, 1, 2, 3, 4, 0, 1, 2, 3
+    };
+    #define addMod5(x, y) (pgm_read_byte(&(addMod5Table[(x) + (y)])))
+    uint64_t D;
+    uint8_t index, index2;
+    for (uint8_t round = 0; round < 24; ++round) {
+        // Step mapping theta.  The specification mentions two temporary
+        // arrays of size 5 called C and D.  To save a bit of memory,
+        // we use the first row of B to store C and compute D on the fly.
+        for (index = 0; index < 5; ++index) {
+            B[0][index] = state.A[0][index] ^ state.A[1][index] ^
+                          state.A[2][index] ^ state.A[3][index] ^
+                          state.A[4][index];
+        }
+        for (index = 0; index < 5; ++index) {
+            D = B[0][addMod5(index, 4)] ^
+                leftRotate1_64(B[0][addMod5(index, 1)]);
+            for (index2 = 0; index2 < 5; ++index2)
+                state.A[index2][index] ^= D;
+        }
+
+        // Step mapping rho and pi combined into a single step.
+        // Rotate all lanes by a specific offset and rearrange.
+        B[0][0] = state.A[0][0];
+        B[1][0] = leftRotate28_64(state.A[0][3]);
+        B[2][0] = leftRotate1_64 (state.A[0][1]);
+        B[3][0] = leftRotate27_64(state.A[0][4]);
+        B[4][0] = leftRotate62_64(state.A[0][2]);
+        B[0][1] = leftRotate44_64(state.A[1][1]);
+        B[1][1] = leftRotate20_64(state.A[1][4]);
+        B[2][1] = leftRotate6_64 (state.A[1][2]);
+        B[3][1] = leftRotate36_64(state.A[1][0]);
+        B[4][1] = leftRotate55_64(state.A[1][3]);
+        B[0][2] = leftRotate43_64(state.A[2][2]);
+        B[1][2] = leftRotate3_64 (state.A[2][0]);
+        B[2][2] = leftRotate25_64(state.A[2][3]);
+        B[3][2] = leftRotate10_64(state.A[2][1]);
+        B[4][2] = leftRotate39_64(state.A[2][4]);
+        B[0][3] = leftRotate21_64(state.A[3][3]);
+        B[1][3] = leftRotate45_64(state.A[3][1]);
+        B[2][3] = leftRotate8_64 (state.A[3][4]);
+        B[3][3] = leftRotate15_64(state.A[3][2]);
+        B[4][3] = leftRotate41_64(state.A[3][0]);
+        B[0][4] = leftRotate14_64(state.A[4][4]);
+        B[1][4] = leftRotate61_64(state.A[4][2]);
+        B[2][4] = leftRotate18_64(state.A[4][0]);
+        B[3][4] = leftRotate56_64(state.A[4][3]);
+        B[4][4] = leftRotate2_64 (state.A[4][1]);
+
+        // Step mapping chi.  Combine each lane with two other lanes in its row.
+        for (index = 0; index < 5; ++index) {
+            for (index2 = 0; index2 < 5; ++index2) {
+                state.A[index2][index] =
+                    B[index2][index] ^
+                    ((~B[index2][addMod5(index, 1)]) &
+                     B[index2][addMod5(index, 2)]);
+            }
+        }
+#endif
+
+        // Step mapping iota.  XOR A[0][0] with the round constant.
+        static uint64_t const RC[24] PROGMEM = {
+            0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808AULL,
+            0x8000000080008000ULL, 0x000000000000808BULL, 0x0000000080000001ULL,
+            0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008AULL,
+            0x0000000000000088ULL, 0x0000000080008009ULL, 0x000000008000000AULL,
+            0x000000008000808BULL, 0x800000000000008BULL, 0x8000000000008089ULL,
+            0x8000000000008003ULL, 0x8000000000008002ULL, 0x8000000000000080ULL,
+            0x000000000000800AULL, 0x800000008000000AULL, 0x8000000080008081ULL,
+            0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL
+        };
+        state.A[0][0] ^= pgm_read_qword(RC + round);
+    }
+}
diff --git a/src/modules/Crypto/KeccakCore.h b/src/modules/Crypto/KeccakCore.h
new file mode 100644
index 0000000..ca2447e
--- /dev/null
+++ b/src/modules/Crypto/KeccakCore.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_KECCAKCORE_H
+#define CRYPTO_KECCAKCORE_H
+
+#include <inttypes.h>
+#include <stddef.h>
+
+class KeccakCore
+{
+public:
+    KeccakCore();
+    ~KeccakCore();
+
+    size_t capacity() const;
+    void setCapacity(size_t capacity);
+
+    size_t blockSize() const { return _blockSize; }
+
+    void reset();
+
+    void update(const void *data, size_t size);
+    void pad(uint8_t tag);
+
+    void extract(void *data, size_t size);
+    void encrypt(void *output, const void *input, size_t size);
+
+    void clear();
+
+    void setHMACKey(const void *key, size_t len, uint8_t pad, size_t hashSize);
+
+private:
+    struct {
+        uint64_t A[5][5];
+        uint8_t inputSize;
+        uint8_t outputSize;
+    } state;
+    uint8_t _blockSize;
+
+    void keccakp();
+};
+
+#endif
diff --git a/src/modules/Crypto/NoiseSource.cpp b/src/modules/Crypto/NoiseSource.cpp
new file mode 100644
index 0000000..588e1ab
--- /dev/null
+++ b/src/modules/Crypto/NoiseSource.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "NoiseSource.h"
+#include "RNG.h"
+
+/**
+ * \class NoiseSource NoiseSource.h <NoiseSource.h>
+ * \brief Abstract base class for random noise sources.
+ *
+ * \sa \link RNGClass RNG\endlink, TransistorNoiseSource
+ */
+
+/**
+ * \brief Constructs a new random noise source.
+ */
+NoiseSource::NoiseSource()
+{
+}
+
+/**
+ * \brief Destroys this random noise source.
+ */
+NoiseSource::~NoiseSource()
+{
+}
+
+/**
+ * \fn bool NoiseSource::calibrating() const
+ * \brief Determine if the noise source is still calibrating itself.
+ *
+ * \return Returns true if calibration is in progress; false if the noise
+ * source is generating valid random data.
+ *
+ * Noise sources that require calibration start doing so at system startup
+ * and then switch over to random data generation once calibration is complete.
+ * Since no random data is being generated during calibration, the output
+ * from \link RNGClass::rand() RNG.rand()\endlink may be predictable.
+ * Use \link RNGClass::available() RNG.available()\endlink to determine
+ * when sufficient entropy is available to generate good random values.
+ *
+ * It is possible that the noise source never exits calibration.  This can
+ * happen if the input voltage is insufficient to trigger noise or if the
+ * noise source is not connected.  Noise sources may also periodically
+ * recalibrate themselves.
+ *
+ * \sa stir()
+ */
+
+/**
+ * \fn void NoiseSource::stir()
+ * \brief Stirs entropy from this noise source into the global random
+ * number pool.
+ *
+ * This function should call output() to add the entropy from this noise
+ * source to the global random number pool.
+ *
+ * The noise source should batch up the entropy data, providing between
+ * 16 and 48 bytes of data each time.  If the noise source does not have
+ * sufficient entropy data at the moment, it should return without stiring
+ * the current data in.
+ *
+ * \sa calibrating(), output()
+ */
+
+/**
+ * \brief Called when the noise source is added to RNG with
+ * \link RNGClass::addNoiseSource() RNG.addNoiseSource()\endlink.
+ *
+ * This function is intended for noise source initialization tasks that
+ * must be performed after \link RNGClass::begin() RNG.begin()\endlink
+ * has been called to initialize the global random number pool.
+ * For example, if the noise source has a unique identifier or serial
+ * number then this function can stir it into the pool at startup time.
+ */
+void NoiseSource::added()
+{
+    // Nothing to do here.
+}
+
+/**
+ * \brief Called from subclasses to output noise to the global random
+ * number pool.
+ *
+ * \param data Points to the noise data.
+ * \param len Number of bytes of noise data.
+ * \param credit The number of bits of entropy to credit for the data.
+ * Note that this is bits, not bytes.
+ *
+ * The default implementation of this function calls
+ * \link RNGClass::stir() RNG.stir()\endlink to add the entropy from
+ * this noise source to the global random number pool.
+ *
+ * This function may be overridden by subclasses to capture the raw
+ * output from the noise source before it is mixed into the pool to
+ * allow the raw data to be analyzed for randomness.
+ */
+void NoiseSource::output(const uint8_t *data, size_t len, unsigned int credit)
+{
+    RNG.stir(data, len, credit);
+}
diff --git a/src/modules/Crypto/NoiseSource.h b/src/modules/Crypto/NoiseSource.h
new file mode 100644
index 0000000..7baf243
--- /dev/null
+++ b/src/modules/Crypto/NoiseSource.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_NOISESOURCE_H
+#define CRYPTO_NOISESOURCE_H
+
+#include <inttypes.h>
+#include <stddef.h>
+
+class NoiseSource
+{
+public:
+    NoiseSource();
+    virtual ~NoiseSource();
+
+    virtual bool calibrating() const = 0;
+    virtual void stir() = 0;
+
+    virtual void added();
+
+protected:
+    virtual void output(const uint8_t *data, size_t len, unsigned int credit);
+};
+
+#endif
diff --git a/src/modules/Crypto/OMAC.cpp b/src/modules/Crypto/OMAC.cpp
new file mode 100644
index 0000000..70cf083
--- /dev/null
+++ b/src/modules/Crypto/OMAC.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "OMAC.h"
+#include "GF128.h"
+#include "Crypto.h"
+#include <string.h>
+
+/**
+ * \class OMAC OMAC.h <OMAC.h>
+ * \brief Implementation of the OMAC message authenticator.
+ *
+ * OMAC is the message authentication part of EAX mode.  It is provided
+ * as a separate class for the convenience of applications that need
+ * message authentication separate from encryption.
+ *
+ * References: https://en.wikipedia.org/wiki/EAX_mode,
+ * http://web.cs.ucdavis.edu/~rogaway/papers/eax.html
+ *
+ * \sa EAX
+ */
+
+/**
+ * \brief Constructs a new OMAC object.
+ *
+ * This constructor must be followed by a call to setBlockCipher()
+ * to specify the block cipher to use.
+ */
+OMAC::OMAC()
+    : _blockCipher(0)
+    , posn(0)
+{
+}
+
+/**
+ * \brief Destroys this OMAC object.
+ *
+ * \sa clear()
+ */
+OMAC::~OMAC()
+{
+    clean(b);
+}
+
+/**
+ * \fn BlockCipher *OMAC::blockCipher() const
+ * \brief Gets the block cipher that is in use for this OMAC object.
+ *
+ * \sa setBlockCipher()
+ */
+
+/**
+ * \fn void OMAC::setBlockCipher(BlockCipher *cipher)
+ * \brief Sets the block cipher to use for this OMAC object.
+ *
+ * \param cipher The block cipher to use to implement OMAC.
+ * This object must have a block size of 128 bits (16 bytes).
+ *
+ * \sa blockCipher()
+ */
+
+/**
+ * \brief Initialises the first OMAC hashing context and creates the B value.
+ *
+ * \param omac The OMAC hashing context.
+ *
+ * This function must be called first before initNext(), update(), or
+ * finalize() to create the B value from the OMAC algorithm which is
+ * used to finalize later hashes.  It is assumed that setBlockCipher()
+ * has already been called.
+ *
+ * The tag value for the context is implicitly set to zero, which means
+ * that the context can be used for ordinary hashing as long as the
+ * data that follows is non-zero in length.  Alternatively, initNext()
+ * can be called to restart the context with a specific tag.
+ *
+ * This function must be called again whenever the block cipher or the
+ * key changes.
+ *
+ * \sa initNext(), update(), finalize()
+ */
+void OMAC::initFirst(uint8_t omac[16])
+{
+    // Start the OMAC context.  We assume that the data that follows
+    // will be at least 1 byte in length so that we can encrypt the
+    // zeroes now to derive the B value.
+    memset(omac, 0, 16);
+    _blockCipher->encryptBlock(omac, omac);
+    posn = 0;
+
+    // Generate the B value from the encrypted block of zeroes.
+    // We will need this later when finalising the OMAC hashes.
+    memcpy(b, omac, 16);
+    GF128::dblEAX(b);
+}
+
+/**
+ * \brief Initialises or restarts an OMAC hashing context.
+ *
+ * \param omac The OMAC hashing context.
+ * \param tag The tag value indicating which OMAC calculation we are doing.
+ *
+ * It is assumed that initFirst() was called previously to create the B
+ * value for the context.
+ *
+ * \sa initFirst(), update(), finalize()
+ */
+void OMAC::initNext(uint8_t omac[16], uint8_t tag)
+{
+    memset(omac, 0, 15);
+    omac[15] = tag;
+    posn = 16;
+}
+
+/**
+ * \brief Updates an OMAC hashing context with more data.
+ *
+ * \param omac The OMAC hashing context.
+ * \param data Points to the data to be hashed.
+ * \param size The number of bytes to be hashed.
+ *
+ * \sa initFirst(), initNext(), finalize()
+ */
+void OMAC::update(uint8_t omac[16], const uint8_t *data, size_t size)
+{
+    while (size > 0) {
+        // Encrypt the current block if it is already full.
+        if (posn == 16) {
+            _blockCipher->encryptBlock(omac, omac);
+            posn = 0;
+        }
+
+        // XOR the incoming data with the current block.
+        uint8_t len = 16 - posn;
+        if (len > size)
+            len = (uint8_t)size;
+        for (uint8_t index = 0; index < len; ++index)
+            omac[posn++] ^= data[index];
+
+        // Move onto the next block.
+        size -= len;
+        data += len;
+    }
+}
+
+/**
+ * \brief Finalises an OMAC hashing context.
+ *
+ * \param omac The OMAC hashing context on entry, the final OMAC value on exit.
+ *
+ * \sa initFirst(), initNext(), update()
+ */
+void OMAC::finalize(uint8_t omac[16])
+{
+    // Apply padding if necessary.
+    if (posn != 16) {
+        // Need padding: XOR with P = 2 * B.
+        uint32_t p[4];
+        memcpy(p, b, 16);
+        GF128::dblEAX(p);
+        omac[posn] ^= 0x80;
+        for (uint8_t index = 0; index < 16; ++index)
+            omac[index] ^= ((const uint8_t *)p)[index];
+        clean(p);
+    } else {
+        // No padding necessary: XOR with B.
+        for (uint8_t index = 0; index < 16; ++index)
+            omac[index] ^= ((const uint8_t *)b)[index];
+    }
+
+    // Encrypt the hash to get the final OMAC value.
+    _blockCipher->encryptBlock(omac, omac);
+}
+
+/**
+ * \brief Clears all security-sensitive state from this object.
+ */
+void OMAC::clear()
+{
+    clean(b);
+}
diff --git a/src/modules/Crypto/OMAC.h b/src/modules/Crypto/OMAC.h
new file mode 100644
index 0000000..37e0fa7
--- /dev/null
+++ b/src/modules/Crypto/OMAC.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_OMAC_H
+#define CRYPTO_OMAC_H
+
+#include "BlockCipher.h"
+
+class OMAC
+{
+public:
+    OMAC();
+    ~OMAC();
+
+    BlockCipher *blockCipher() const { return _blockCipher; }
+    void setBlockCipher(BlockCipher *cipher) { _blockCipher = cipher; }
+
+    void initFirst(uint8_t omac[16]);
+    void initNext(uint8_t omac[16], uint8_t tag);
+    void update(uint8_t omac[16], const uint8_t *data, size_t size);
+    void finalize(uint8_t omac[16]);
+
+    void clear();
+
+private:
+    BlockCipher *_blockCipher;
+    uint32_t b[4];
+    uint8_t posn;
+};
+
+#endif
diff --git a/src/modules/Crypto/P521.cpp b/src/modules/Crypto/P521.cpp
new file mode 100644
index 0000000..f46df3c
--- /dev/null
+++ b/src/modules/Crypto/P521.cpp
@@ -0,0 +1,1642 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "P521.h"
+#include "Crypto.h"
+#include "RNG.h"
+#include "SHA512.h"
+#include "utility/LimbUtil.h"
+#include <string.h>
+
+/**
+ * \class P521 P521.h <P521.h>
+ * \brief Elliptic curve operations with the NIST P-521 curve.
+ *
+ * This class supports both ECDH key exchange and ECDSA signatures.
+ *
+ * \note The public functions in this class need a substantial amount of
+ * stack space to store intermediate results while the curve function is
+ * being evaluated.  About 2k of free stack space is recommended for safety.
+ *
+ * References: NIST FIPS 186-4,
+ * <a href="http://tools.ietf.org/html/rfc6090">RFC 6090</a>,
+ * <a href="http://tools.ietf.org/html/rfc6979">RFC 6979</a>,
+ * <a href="http://tools.ietf.org/html/rfc6090">RFC 5903</a>
+ *
+ * \sa Curve25519
+ */
+
+// Number of limbs that are needed to represent a 521-bit number.
+#define NUM_LIMBS_521BIT    NUM_LIMBS_BITS(521)
+
+// Number of limbs that are needed to represent a 1042-bit number.
+// To simply things we also require that this be twice the size of
+// NUM_LIMB_521BIT which involves a little wastage at the high end
+// of one extra limb for 8-bit and 32-bit limbs.  There is no
+// wastage for 16-bit limbs.
+#define NUM_LIMBS_1042BIT   (NUM_LIMBS_BITS(521) * 2)
+
+// The overhead of clean() calls in mul(), etc can add up to a lot of
+// processing time.  Only do such cleanups if strict mode has been enabled.
+#if defined(P521_STRICT_CLEAN)
+#define strict_clean(x)     clean(x)
+#else
+#define strict_clean(x)     do { ; } while (0)
+#endif
+
+// Expand the partial 9-bit left over limb at the top of a 521-bit number.
+#if BIGNUMBER_LIMB_8BIT
+#define LIMB_PARTIAL(value) ((uint8_t)(value)), \
+                            ((uint8_t)((value) >> 8))
+#else
+#define LIMB_PARTIAL(value) (value)
+#endif
+
+/** @cond */
+
+// The group order "q" value from RFC 4754 and RFC 5903.  This is the
+// same as the "n" value from Appendix D.1.2.5 of NIST FIPS 186-4.
+static limb_t const P521_q[NUM_LIMBS_521BIT] PROGMEM = {
+    LIMB_PAIR(0x91386409, 0xbb6fb71e), LIMB_PAIR(0x899c47ae, 0x3bb5c9b8),
+    LIMB_PAIR(0xf709a5d0, 0x7fcc0148), LIMB_PAIR(0xbf2f966b, 0x51868783),
+    LIMB_PAIR(0xfffffffa, 0xffffffff), LIMB_PAIR(0xffffffff, 0xffffffff),
+    LIMB_PAIR(0xffffffff, 0xffffffff), LIMB_PAIR(0xffffffff, 0xffffffff),
+    LIMB_PARTIAL(0x1ff)
+};
+
+// The "b" value from Appendix D.1.2.5 of NIST FIPS 186-4.
+static limb_t const P521_b[NUM_LIMBS_521BIT] PROGMEM = {
+    LIMB_PAIR(0x6b503f00, 0xef451fd4), LIMB_PAIR(0x3d2c34f1, 0x3573df88),
+    LIMB_PAIR(0x3bb1bf07, 0x1652c0bd), LIMB_PAIR(0xec7e937b, 0x56193951),
+    LIMB_PAIR(0x8ef109e1, 0xb8b48991), LIMB_PAIR(0x99b315f3, 0xa2da725b),
+    LIMB_PAIR(0xb68540ee, 0x929a21a0), LIMB_PAIR(0x8e1c9a1f, 0x953eb961),
+    LIMB_PARTIAL(0x051)
+};
+
+// The "Gx" value from Appendix D.1.2.5 of NIST FIPS 186-4.
+static limb_t const P521_Gx[NUM_LIMBS_521BIT] PROGMEM = {
+    LIMB_PAIR(0xc2e5bd66, 0xf97e7e31), LIMB_PAIR(0x856a429b, 0x3348b3c1),
+    LIMB_PAIR(0xa2ffa8de, 0xfe1dc127), LIMB_PAIR(0xefe75928, 0xa14b5e77),
+    LIMB_PAIR(0x6b4d3dba, 0xf828af60), LIMB_PAIR(0x053fb521, 0x9c648139),
+    LIMB_PAIR(0x2395b442, 0x9e3ecb66), LIMB_PAIR(0x0404e9cd, 0x858e06b7),
+    LIMB_PARTIAL(0x0c6)
+};
+
+// The "Gy" value from Appendix D.1.2.5 of NIST FIPS 186-4.
+static limb_t const P521_Gy[NUM_LIMBS_521BIT] PROGMEM = {
+    LIMB_PAIR(0x9fd16650, 0x88be9476), LIMB_PAIR(0xa272c240, 0x353c7086),
+    LIMB_PAIR(0x3fad0761, 0xc550b901), LIMB_PAIR(0x5ef42640, 0x97ee7299),
+    LIMB_PAIR(0x273e662c, 0x17afbd17), LIMB_PAIR(0x579b4468, 0x98f54449),
+    LIMB_PAIR(0x2c7d1bd9, 0x5c8a5fb4), LIMB_PAIR(0x9a3bc004, 0x39296a78),
+    LIMB_PARTIAL(0x118)
+};
+
+/** @endcond */
+
+/**
+ * \brief Evaluates the curve function.
+ *
+ * \param result The result of applying the curve function, which consists
+ * of the x and y values of the result point encoded in big-endian order.
+ * \param f The scalar value to multiply by \a point to create the \a result.
+ * This is assumed to be be a 521-bit number in big-endian order.
+ * \param point The curve point to multiply consisting of the x and y
+ * values encoded in big-endian order.  If \a point is NULL, then the
+ * generator Gx and Gy values for the curve will be used instead.
+ *
+ * \return Returns true if \a f * \a point could be evaluated, or false if
+ * \a point is not a point on the curve.
+ *
+ * This function provides access to the raw curve operation for testing
+ * purposes.  Normally an application would use a higher-level function
+ * like dh1(), dh2(), sign(), or verify().
+ *
+ * \sa dh1(), sign()
+ */
+bool P521::eval(uint8_t result[132], const uint8_t f[66], const uint8_t point[132])
+{
+    limb_t x[NUM_LIMBS_521BIT];
+    limb_t y[NUM_LIMBS_521BIT];
+    bool ok;
+
+    // Unpack the curve point from the parameters and validate it.
+    if (point) {
+        BigNumberUtil::unpackBE(x, NUM_LIMBS_521BIT, point, 66);
+        BigNumberUtil::unpackBE(y, NUM_LIMBS_521BIT, point + 66, 66);
+        ok = validate(x, y);
+    } else {
+        memcpy_P(x, P521_Gx, sizeof(x));
+        memcpy_P(y, P521_Gy, sizeof(y));
+        ok = true;
+    }
+
+    // Evaluate the curve function.
+    evaluate(x, y, f);
+
+    // Pack the answer into the result array.
+    BigNumberUtil::packBE(result, 66, x, NUM_LIMBS_521BIT);
+    BigNumberUtil::packBE(result + 66, 66, y, NUM_LIMBS_521BIT);
+
+    // Clean up.
+    clean(x);
+    clean(y);
+    return ok;
+}
+
+/**
+ * \brief Performs phase 1 of an ECDH key exchange using P-521.
+ *
+ * \param k The key value to send to the other party as part of the exchange.
+ * \param f The generated secret value for this party.  This must not be
+ * transmitted to any party or stored in permanent storage.  It only needs
+ * to be kept in memory until dh2() is called.
+ *
+ * The \a f value is generated with \link RNGClass::rand() RNG.rand()\endlink.
+ * It is the caller's responsibility to ensure that the global random number
+ * pool has sufficient entropy to generate the 66 bytes of \a f safely
+ * before calling this function.
+ *
+ * The following example demonstrates how to perform a full ECDH
+ * key exchange using dh1() and dh2():
+ *
+ * \code
+ * uint8_t f[66];
+ * uint8_t k[132];
+ *
+ * // Generate the secret value "f" and the public value "k".
+ * P521::dh1(k, f);
+ *
+ * // Send "k" to the other party.
+ * ...
+ *
+ * // Read the "k" value that the other party sent to us.
+ * ...
+ *
+ * // Generate the shared secret in "f".
+ * if (!P521::dh2(k, f)) {
+ *     // The received "k" value was invalid - abort the session.
+ *     ...
+ * }
+ *
+ * // The "f" value can now be used to generate session keys for encryption.
+ * ...
+ * \endcode
+ *
+ * Reference: <a href="http://tools.ietf.org/html/rfc6090">RFC 6090</a>
+ *
+ * \sa dh2()
+ */
+void P521::dh1(uint8_t k[132], uint8_t f[66])
+{
+    generatePrivateKey(f);
+    derivePublicKey(k, f);
+}
+
+/**
+ * \brief Performs phase 2 of an ECDH key exchange using P-521.
+ *
+ * \param k The public key value that was received from the other
+ * party as part of the exchange.
+ * \param f On entry, this is the secret value for this party that was
+ * generated by dh1().  On exit, this will be the shared secret.
+ *
+ * \return Returns true if the key exchange was successful, or false if
+ * the \a k value is invalid.
+ *
+ * Reference: <a href="http://tools.ietf.org/html/rfc6090">RFC 6090</a>
+ *
+ * \sa dh1()
+ */
+bool P521::dh2(const uint8_t k[132], uint8_t f[66])
+{
+    // Unpack the (x, y) point from k.
+    limb_t x[NUM_LIMBS_521BIT];
+    limb_t y[NUM_LIMBS_521BIT];
+    BigNumberUtil::unpackBE(x, NUM_LIMBS_521BIT, k, 66);
+    BigNumberUtil::unpackBE(y, NUM_LIMBS_521BIT, k + 66, 66);
+
+    // Validate the curve point.  We keep going to preserve the timing.
+    bool ok = validate(x, y);
+
+    // Evaluate the curve function.
+    evaluate(x, y, f);
+
+    // The secret key is the x component of the final value.
+    BigNumberUtil::packBE(f, 66, x, NUM_LIMBS_521BIT);
+
+    // Clean up.
+    clean(x);
+    clean(y);
+    return ok;
+}
+
+/**
+ * \brief Signs a message using a specific P-521 private key.
+ *
+ * \param signature The signature value.
+ * \param privateKey The private key to use to sign the message.
+ * \param message Points to the message to be signed.
+ * \param len The length of the \a message to be signed.
+ * \param hash The hash algorithm to use to hash the \a message before signing.
+ * If \a hash is NULL, then the \a message is assumed to already be a hash
+ * value from some previous process.
+ *
+ * This function generates deterministic ECDSA signatures according to
+ * RFC 6979.  The \a hash function is used to generate the k value for
+ * the signature.  If \a hash is NULL, then SHA512 is used.
+ * The \a hash object must be capable of HMAC mode.
+ *
+ * The length of the hashed message must be less than or equal to 64
+ * bytes in size.  Longer messages will be truncated to 64 bytes.
+ *
+ * References: <a href="http://tools.ietf.org/html/rfc6090">RFC 6090</a>,
+ * <a href="http://tools.ietf.org/html/rfc6979">RFC 6979</a>
+ *
+ * \sa verify(), generatePrivateKey()
+ */
+void P521::sign(uint8_t signature[132], const uint8_t privateKey[66],
+                const void *message, size_t len, Hash *hash)
+{
+    uint8_t hm[66];
+    uint8_t k[66];
+    limb_t x[NUM_LIMBS_521BIT];
+    limb_t y[NUM_LIMBS_521BIT];
+    limb_t t[NUM_LIMBS_521BIT];
+    uint64_t count = 0;
+
+    // Format the incoming message, hashing it if necessary.
+    if (hash) {
+        // Hash the message.
+        hash->reset();
+        hash->update(message, len);
+        len = hash->hashSize();
+        if (len > 64)
+            len = 64;
+        memset(hm, 0, 66 - len);
+        hash->finalize(hm + 66 - len, len);
+    } else {
+        // The message is the hash.
+        if (len > 64)
+            len = 64;
+        memset(hm, 0, 66 - len);
+        memcpy(hm + 66 - len, message, len);
+    }
+
+    // Keep generating k values until both r and s are non-zero.
+    for (;;) {
+        // Generate the k value deterministically according to RFC 6979.
+        if (hash)
+            generateK(k, hm, privateKey, hash, count);
+        else
+            generateK(k, hm, privateKey, count);
+
+        // Generate r = kG.x mod q.
+        memcpy_P(x, P521_Gx, sizeof(x));
+        memcpy_P(y, P521_Gy, sizeof(y));
+        evaluate(x, y, k);
+        BigNumberUtil::reduceQuick_P(x, x, P521_q, NUM_LIMBS_521BIT);
+        BigNumberUtil::packBE(signature, 66, x, NUM_LIMBS_521BIT);
+
+        // If r is zero, then we need to generate a new k value.
+        // This is utterly improbable, but let's be safe anyway.
+        if (BigNumberUtil::isZero(x, NUM_LIMBS_521BIT)) {
+            ++count;
+            continue;
+        }
+
+        // Generate s = (privateKey * r + hm) / k mod q.
+        BigNumberUtil::unpackBE(y, NUM_LIMBS_521BIT, privateKey, 66);
+        mulQ(y, y, x);
+        BigNumberUtil::unpackBE(x, NUM_LIMBS_521BIT, hm, 66);
+        BigNumberUtil::add(x, x, y, NUM_LIMBS_521BIT);
+        BigNumberUtil::reduceQuick_P(x, x, P521_q, NUM_LIMBS_521BIT);
+        BigNumberUtil::unpackBE(y, NUM_LIMBS_521BIT, k, 66);
+        recipQ(t, y);
+        mulQ(x, x, t);
+        BigNumberUtil::packBE(signature + 66, 66, x, NUM_LIMBS_521BIT);
+
+        // Exit the loop if s is non-zero.
+        if (!BigNumberUtil::isZero(x, NUM_LIMBS_521BIT))
+            break;
+
+        // We need to generate a new k value according to RFC 6979.
+        // This is utterly improbable, but let's be safe anyway.
+        ++count;
+    }
+
+    // Clean up.
+    clean(hm);
+    clean(k);
+    clean(x);
+    clean(y);
+    clean(t);
+}
+
+/**
+ * \brief Verifies a signature using a specific P-521 public key.
+ *
+ * \param signature The signature value to be verified.
+ * \param publicKey The public key to use to verify the signature.
+ * \param message The message whose signature is to be verified.
+ * \param len The length of the \a message to be verified.
+ * \param hash The hash algorithm to use to hash the \a message before
+ * verification.  If \a hash is NULL, then the \a message is assumed to
+ * already be a hash value from some previous process.
+ *
+ * The length of the hashed message must be less than or equal to 64
+ * bytes in size.  Longer messages will be truncated to 64 bytes.
+ *
+ * \return Returns true if the \a signature is valid for \a message;
+ * or false if the \a publicKey or \a signature is not valid.
+ *
+ * \sa sign()
+ */
+bool P521::verify(const uint8_t signature[132],
+                  const uint8_t publicKey[132],
+                  const void *message, size_t len, Hash *hash)
+{
+    limb_t x[NUM_LIMBS_521BIT];
+    limb_t y[NUM_LIMBS_521BIT];
+    limb_t r[NUM_LIMBS_521BIT];
+    limb_t s[NUM_LIMBS_521BIT];
+    limb_t u1[NUM_LIMBS_521BIT];
+    limb_t u2[NUM_LIMBS_521BIT];
+    uint8_t t[66];
+    bool ok = false;
+
+    // Because we are operating on public values, we don't need to
+    // be as strict about constant time.  Bail out early if there
+    // is a problem with the parameters.
+
+    // Unpack the signature.  The values must be between 1 and q - 1.
+    BigNumberUtil::unpackBE(r, NUM_LIMBS_521BIT, signature, 66);
+    BigNumberUtil::unpackBE(s, NUM_LIMBS_521BIT, signature + 66, 66);
+    if (BigNumberUtil::isZero(r, NUM_LIMBS_521BIT) ||
+            BigNumberUtil::isZero(s, NUM_LIMBS_521BIT) ||
+            !BigNumberUtil::sub_P(x, r, P521_q, NUM_LIMBS_521BIT) ||
+            !BigNumberUtil::sub_P(x, s, P521_q, NUM_LIMBS_521BIT)) {
+        goto failed;
+    }
+
+    // Unpack the public key and check that it is a valid curve point.
+    BigNumberUtil::unpackBE(x, NUM_LIMBS_521BIT, publicKey, 66);
+    BigNumberUtil::unpackBE(y, NUM_LIMBS_521BIT, publicKey + 66, 66);
+    if (!validate(x, y)) {
+        goto failed;
+    }
+
+    // Hash the message to generate hm, which we store into u1.
+    if (hash) {
+        // Hash the message.
+        hash->reset();
+        hash->update(message, len);
+        len = hash->hashSize();
+        if (len > 64)
+            len = 64;
+        hash->finalize(u2, len);
+        BigNumberUtil::unpackBE(u1, NUM_LIMBS_521BIT, (uint8_t *)u2, len);
+    } else {
+        // The message is the hash.
+        if (len > 64)
+            len = 64;
+        BigNumberUtil::unpackBE(u1, NUM_LIMBS_521BIT, (uint8_t *)message, len);
+    }
+
+    // Compute u1 = hm * s^-1 mod q and u2 = r * s^-1 mod q.
+    recipQ(u2, s);
+    mulQ(u1, u1, u2);
+    mulQ(u2, r, u2);
+
+    // Compute the curve point R = u2 * publicKey + u1 * G.
+    BigNumberUtil::packBE(t, 66, u2, NUM_LIMBS_521BIT);
+    evaluate(x, y, t);
+    memcpy_P(u2, P521_Gx, sizeof(x));
+    memcpy_P(s, P521_Gy, sizeof(y));
+    BigNumberUtil::packBE(t, 66, u1, NUM_LIMBS_521BIT);
+    evaluate(u2, s, t);
+    addAffine(u2, s, x, y);
+
+    // If R.x = r mod q, then the signature is valid.
+    BigNumberUtil::reduceQuick_P(u1, u2, P521_q, NUM_LIMBS_521BIT);
+    ok = secure_compare(u1, r, NUM_LIMBS_521BIT * sizeof(limb_t));
+
+    // Clean up and exit.
+failed:
+    clean(x);
+    clean(y);
+    clean(r);
+    clean(s);
+    clean(u1);
+    clean(u2);
+    clean(t);
+    return ok;
+}
+
+/**
+ * \brief Generates a private key for P-521 signing operations.
+ *
+ * \param privateKey The resulting private key.
+ *
+ * The private key is generated with \link RNGClass::rand() RNG.rand()\endlink.
+ * It is the caller's responsibility to ensure that the global random number
+ * pool has sufficient entropy to generate the 521 bits of the key safely
+ * before calling this function.
+ *
+ * \sa derivePublicKey(), sign()
+ */
+void P521::generatePrivateKey(uint8_t privateKey[66])
+{
+    // Generate a random 521-bit value for the private key.  The value
+    // must be generated uniformly at random between 1 and q - 1 where q
+    // is the group order (RFC 6090).  We use the recommended algorithm
+    // from Appendix B of RFC 6090: generate a random 521-bit value
+    // and discard it if it is not within the range 1 to q - 1.
+    limb_t x[NUM_LIMBS_521BIT];
+    do {
+        RNG.rand((uint8_t *)x, sizeof(x));
+#if BIGNUMBER_LIMB_8BIT
+        x[NUM_LIMBS_521BIT - 1] &= 0x01;
+#else
+        x[NUM_LIMBS_521BIT - 1] &= 0x1FF;
+#endif
+        BigNumberUtil::packBE(privateKey, 66, x, NUM_LIMBS_521BIT);
+    } while (BigNumberUtil::isZero(x, NUM_LIMBS_521BIT) ||
+             !BigNumberUtil::sub_P(x, x, P521_q, NUM_LIMBS_521BIT));
+    clean(x);
+}
+
+/**
+ * \brief Derives the public key from a private key for P-521
+ * signing operations.
+ *
+ * \param publicKey The public key.
+ * \param privateKey The private key, which is assumed to have been
+ * created by generatePrivateKey().
+ *
+ * \sa generatePrivateKey(), verify()
+ */
+void P521::derivePublicKey(uint8_t publicKey[132], const uint8_t privateKey[66])
+{
+    // Evaluate the curve function starting with the generator.
+    limb_t x[NUM_LIMBS_521BIT];
+    limb_t y[NUM_LIMBS_521BIT];
+    memcpy_P(x, P521_Gx, sizeof(x));
+    memcpy_P(y, P521_Gy, sizeof(y));
+    evaluate(x, y, privateKey);
+
+    // Pack the (x, y) point into the public key.
+    BigNumberUtil::packBE(publicKey, 66, x, NUM_LIMBS_521BIT);
+    BigNumberUtil::packBE(publicKey + 66, 66, y, NUM_LIMBS_521BIT);
+
+    // Clean up.
+    clean(x);
+    clean(y);
+}
+
+/**
+ * \brief Validates a private key value to ensure that it is
+ * between 1 and q - 1.
+ *
+ * \param privateKey The private key value to validate.
+ * \return Returns true if \a privateKey is valid, false if not.
+ *
+ * \sa isValidPublicKey()
+ */
+bool P521::isValidPrivateKey(const uint8_t privateKey[66])
+{
+    // The value "q" as a byte array from most to least significant.
+    static uint8_t const P521_q_bytes[66] PROGMEM = {
+        0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+        0xFF, 0xFA, 0x51, 0x86, 0x87, 0x83, 0xBF, 0x2F,
+        0x96, 0x6B, 0x7F, 0xCC, 0x01, 0x48, 0xF7, 0x09,
+        0xA5, 0xD0, 0x3B, 0xB5, 0xC9, 0xB8, 0x89, 0x9C,
+        0x47, 0xAE, 0xBB, 0x6F, 0xB7, 0x1E, 0x91, 0x38,
+        0x64, 0x09
+    };
+    uint8_t zeroTest = 0;
+    uint8_t posn = 66;
+    uint16_t borrow = 0;
+    while (posn > 0) {
+        --posn;
+
+        // Check for zero.
+        zeroTest |= privateKey[posn];
+
+        // Subtract P521_q_bytes from the key.  If there is no borrow,
+        // then the key value was greater than or equal to q.
+        borrow = ((uint16_t)(privateKey[posn])) -
+                 pgm_read_byte(&(P521_q_bytes[posn])) -
+                 ((borrow >> 8) & 0x01);
+    }
+    return zeroTest != 0 && borrow != 0;
+}
+
+/**
+ * \brief Validates a public key to ensure that it is a valid curve point.
+ *
+ * \param publicKey The public key value to validate.
+ * \return Returns true if \a publicKey is valid, false if not.
+ *
+ * \sa isValidPrivateKey()
+ */
+bool P521::isValidPublicKey(const uint8_t publicKey[132])
+{
+    limb_t x[NUM_LIMBS_521BIT];
+    limb_t y[NUM_LIMBS_521BIT];
+    BigNumberUtil::unpackBE(x, NUM_LIMBS_521BIT, publicKey, 66);
+    BigNumberUtil::unpackBE(y, NUM_LIMBS_521BIT, publicKey + 66, 66);
+    bool ok = validate(x, y);
+    clean(x);
+    clean(y);
+    return ok;
+}
+
+/**
+ * \fn bool P521::isValidCurvePoint(const uint8_t point[132])
+ * \brief Validates a point to ensure that it is on the curve.
+ *
+ * \param point The point to validate.
+ * \return Returns true if \a point is valid and on the curve, false if not.
+ *
+ * This is a convenience function that calls isValidPublicKey() as the
+ * two operations are equivalent.
+ */
+
+/**
+ * \brief Evaluates the curve function by multiplying (x, y) by f.
+ *
+ * \param x The X co-ordinate of the curve point.  Replaced with the X
+ * co-ordinate of the result on exit.
+ * \param y The Y co-ordinate of the curve point.  Replaced with the Y
+ * co-ordinate of the result on exit.
+ * \param f The 521-bit scalar to multiply (x, y) by, most significant
+ * bit first.
+ */
+void P521::evaluate(limb_t *x, limb_t *y, const uint8_t f[66])
+{
+    limb_t x1[NUM_LIMBS_521BIT];
+    limb_t y1[NUM_LIMBS_521BIT];
+    limb_t z1[NUM_LIMBS_521BIT];
+    limb_t x2[NUM_LIMBS_521BIT];
+    limb_t y2[NUM_LIMBS_521BIT];
+    limb_t z2[NUM_LIMBS_521BIT];
+
+    // We want the input in Jacobian co-ordinates.  The point (x, y, z)
+    // corresponds to the affine point (x / z^2, y / z^3), so if we set z
+    // to 1 we end up with Jacobian co-ordinates.  Remember that z is 1
+    // and continue on.
+
+    // Set the answer to the point-at-infinity initially (z = 0).
+    memset(x1, 0, sizeof(x1));
+    memset(y1, 0, sizeof(y1));
+    memset(z1, 0, sizeof(z1));
+
+    // Special handling for the highest bit.  We can skip dblPoint()/addPoint()
+    // and simply conditionally move (x, y, z) into (x1, y1, z1).
+    uint8_t select = (f[0] & 0x01);
+    cmove(select, x1, x);
+    cmove(select, y1, y);
+    cmove1(select, z1); // z = 1
+
+    // Iterate over the remaining 520 bits of f from highest to lowest.
+    uint8_t mask = 0x80;
+    uint8_t fposn = 1;
+    for (uint16_t t = 520; t > 0; --t) {
+        // Double the answer.
+        dblPoint(x1, y1, z1, x1, y1, z1);
+
+        // Add (x, y, z) to (x1, y1, z1) for the next 1 bit.
+        // We must always do this to preserve the overall timing.
+        // The z value is always 1 so we can omit that argument.
+        addPoint(x2, y2, z2, x1, y1, z1, x, y/*, z*/);
+
+        // If the bit was 1, then move (x2, y2, z2) into (x1, y1, z1).
+        select = (f[fposn] & mask);
+        cmove(select, x1, x2);
+        cmove(select, y1, y2);
+        cmove(select, z1, z2);
+
+        // Move onto the next bit.
+        mask >>= 1;
+        if (!mask) {
+            ++fposn;
+            mask = 0x80;
+        }
+    }
+
+    // Convert from Jacobian co-ordinates back into affine co-ordinates.
+    // x = x1 * (z1^2)^-1, y = y1 * (z1^3)^-1.
+    recip(x2, z1);
+    square(y2, x2);
+    mul(x, x1, y2);
+    mul(y2, y2, x2);
+    mul(y, y1, y2);
+
+    // Clean up.
+    clean(x1);
+    clean(y1);
+    clean(z1);
+    clean(x2);
+    clean(y2);
+    clean(z2);
+}
+
+/**
+ * \brief Adds two affine points.
+ *
+ * \param x1 The X value for the first point to add, and the result.
+ * \param y1 The Y value for the first point to add, and the result.
+ * \param x2 The X value for the second point to add.
+ * \param y2 The Y value for the second point to add.
+ *
+ * The Z values for the two points are assumed to be 1.
+ */
+void P521::addAffine(limb_t *x1, limb_t *y1, const limb_t *x2, const limb_t *y2)
+{
+    limb_t xout[NUM_LIMBS_521BIT];
+    limb_t yout[NUM_LIMBS_521BIT];
+    limb_t zout[NUM_LIMBS_521BIT];
+    limb_t z1[NUM_LIMBS_521BIT];
+
+    // z1 = 1
+    z1[0] = 1;
+    memset(z1 + 1, 0, (NUM_LIMBS_521BIT - 1) * sizeof(limb_t));
+
+    // Add the two points.
+    addPoint(xout, yout, zout, x1, y1, z1, x2, y2/*, z2*/);
+
+    // Convert from Jacobian co-ordinates back into affine co-ordinates.
+    // x1 = xout * (zout^2)^-1, y1 = yout * (zout^3)^-1.
+    recip(z1, zout);
+    square(zout, z1);
+    mul(x1, xout, zout);
+    mul(zout, zout, z1);
+    mul(y1, yout, zout);
+
+    // Clean up.
+    clean(xout);
+    clean(yout);
+    clean(zout);
+    clean(z1);
+}
+
+/**
+ * \brief Validates that (x, y) is actually a point on the curve.
+ *
+ * \param x The X co-ordinate of the point to test.
+ * \param y The Y co-ordinate of the point to test.
+ * \return Returns true if (x, y) is on the curve, or false if not.
+ *
+ * \sa inRange()
+ */
+bool P521::validate(const limb_t *x, const limb_t *y)
+{
+    bool result;
+
+    // If x or y is greater than or equal to 2^521 - 1, then the
+    // point is definitely not on the curve.  Preserve timing by
+    // delaying the reporting of the result until later.
+    result = inRange(x);
+    result &= inRange(y);
+
+    // We need to check that y^2 = x^3 - 3 * x + b mod 2^521 - 1.
+    limb_t t1[NUM_LIMBS_521BIT];
+    limb_t t2[NUM_LIMBS_521BIT];
+    square(t1, x);
+    mul(t1, t1, x);
+    mulLiteral(t2, x, 3);
+    sub(t1, t1, t2);
+    memcpy_P(t2, P521_b, sizeof(t2));
+    add(t1, t1, t2);
+    square(t2, y);
+    result &= secure_compare(t1, t2, sizeof(t1));
+    clean(t1);
+    clean(t2);
+    return result;
+}
+
+/**
+ * \brief Determines if a value is between 0 and 2^521 - 2.
+ *
+ * \param x The value to test.
+ * \return Returns true if \a x is in range, false if not.
+ *
+ * \sa validate()
+ */
+bool P521::inRange(const limb_t *x)
+{
+    // Do a trial subtraction of 2^521 - 1 from x, which is equivalent
+    // to adding 1 and subtracting 2^521.  We only need the carry.
+    dlimb_t carry = 1;
+    limb_t word = 0;
+    for (uint8_t index = 0; index < NUM_LIMBS_521BIT; ++index) {
+        carry += *x++;
+        word = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+
+    // Determine the carry out from the low 521 bits.
+#if BIGNUMBER_LIMB_8BIT
+    carry = (carry << 7) + (word >> 1);
+#else
+    carry = (carry << (LIMB_BITS - 9)) + (word >> 9);
+#endif
+
+    // If the carry is zero, then x was in range.  Otherwise it is out
+    // of range.  Check for zero in a way that preserves constant timing.
+    word = (limb_t)(carry | (carry >> LIMB_BITS));
+    word = (limb_t)(((((dlimb_t)1) << LIMB_BITS) - word) >> LIMB_BITS);
+    return (bool)word;
+}
+
+/**
+ * \brief Reduces a number modulo 2^521 - 1.
+ *
+ * \param result The array that will contain the result when the
+ * function exits.  Must be NUM_LIMBS_521BIT limbs in size.
+ * \param x The number to be reduced, which must be NUM_LIMBS_1042BIT
+ * limbs in size and less than square(2^521 - 1).  This array can be
+ * the same as \a result.
+ */
+void P521::reduce(limb_t *result, const limb_t *x)
+{
+#if BIGNUMBER_LIMB_16BIT || BIGNUMBER_LIMB_32BIT || BIGNUMBER_LIMB_64BIT
+    // According to NIST FIPS 186-4, we add the high 521 bits to the
+    // low 521 bits and then do a trial subtraction of 2^521 - 1.
+    // We do both in a single step.  Subtracting 2^521 - 1 is equivalent
+    // to adding 1 and subtracting 2^521.
+    uint8_t index;
+    const limb_t *xl = x;
+    const limb_t *xh = x + NUM_LIMBS_521BIT;
+    limb_t *rr = result;
+    dlimb_t carry;
+    limb_t word = x[NUM_LIMBS_521BIT - 1];
+    carry = (word >> 9) + 1;
+    word &= 0x1FF;
+    for (index = 0; index < (NUM_LIMBS_521BIT - 1); ++index) {
+        carry += *xl++;
+        carry += ((dlimb_t)(*xh++)) << (LIMB_BITS - 9);
+        *rr++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    carry += word;
+    carry += ((dlimb_t)(x[NUM_LIMBS_1042BIT - 1])) << (LIMB_BITS - 9);
+    word = (limb_t)carry;
+    *rr = word;
+
+    // If the carry out was 1, then mask it off and we have the answer.
+    // If the carry out was 0, then we need to add 2^521 - 1 back again.
+    // To preserve the timing we perform a conditional subtract of 1 and
+    // then mask off the high bits.
+    carry = ((word >> 9) ^ 0x01) & 0x01;
+    rr = result;
+    for (index = 0; index < NUM_LIMBS_521BIT; ++index) {
+        carry = ((dlimb_t)(*rr)) - carry;
+        *rr++ = (limb_t)carry;
+        carry = (carry >> LIMB_BITS) & 0x01;
+    }
+    *(--rr) &= 0x1FF;
+#elif BIGNUMBER_LIMB_8BIT
+    // Same as above, but for 8-bit limbs.
+    uint8_t index;
+    const limb_t *xl = x;
+    const limb_t *xh = x + NUM_LIMBS_521BIT;
+    limb_t *rr = result;
+    dlimb_t carry;
+    limb_t word = x[NUM_LIMBS_521BIT - 1];
+    carry = (word >> 1) + 1;
+    word &= 0x01;
+    for (index = 0; index < (NUM_LIMBS_521BIT - 1); ++index) {
+        carry += *xl++;
+        carry += ((dlimb_t)(*xh++)) << 7;
+        *rr++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    carry += word;
+    carry += ((dlimb_t)(x[NUM_LIMBS_1042BIT - 1])) << 1;
+    word = (limb_t)carry;
+    *rr = word;
+    carry = ((word >> 1) ^ 0x01) & 0x01;
+    rr = result;
+    for (index = 0; index < NUM_LIMBS_521BIT; ++index) {
+        carry = ((dlimb_t)(*rr)) - carry;
+        *rr++ = (limb_t)carry;
+        carry = (carry >> LIMB_BITS) & 0x01;
+    }
+    *(--rr) &= 0x01;
+#else
+    #error "Don't know how to reduce values mod 2^521 - 1"
+#endif
+}
+
+/**
+ * \brief Quickly reduces a number modulo 2^521 - 1.
+ *
+ * \param x The number to be reduced, which must be NUM_LIMBS_521BIT
+ * limbs in size and less than or equal to 2 * (2^521 - 2).
+ *
+ * The answer is also put into \a x and will consist of NUM_LIMBS_521BIT limbs.
+ *
+ * This function is intended for reducing the result of additions where
+ * the caller knows that \a x is within the described range.  A single
+ * trial subtraction is all that is needed to reduce the number.
+ */
+void P521::reduceQuick(limb_t *x)
+{
+    // Perform a trial subtraction of 2^521 - 1 from x.  This is
+    // equivalent to adding 1 and subtracting 2^521 - 1.
+    uint8_t index;
+    limb_t *xx = x;
+    dlimb_t carry = 1;
+    for (index = 0; index < NUM_LIMBS_521BIT; ++index) {
+        carry += *xx;
+        *xx++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+
+    // If the carry out was 1, then mask it off and we have the answer.
+    // If the carry out was 0, then we need to add 2^521 - 1 back again.
+    // To preserve the timing we perform a conditional subtract of 1 and
+    // then mask off the high bits.
+#if BIGNUMBER_LIMB_16BIT || BIGNUMBER_LIMB_32BIT || BIGNUMBER_LIMB_64BIT
+    carry = ((x[NUM_LIMBS_521BIT - 1] >> 9) ^ 0x01) & 0x01;
+    xx = x;
+    for (index = 0; index < NUM_LIMBS_521BIT; ++index) {
+        carry = ((dlimb_t)(*xx)) - carry;
+        *xx++ = (limb_t)carry;
+        carry = (carry >> LIMB_BITS) & 0x01;
+    }
+    *(--xx) &= 0x1FF;
+#elif BIGNUMBER_LIMB_8BIT
+    carry = ((x[NUM_LIMBS_521BIT - 1] >> 1) ^ 0x01) & 0x01;
+    xx = x;
+    for (index = 0; index < NUM_LIMBS_521BIT; ++index) {
+        carry = ((dlimb_t)(*xx)) - carry;
+        *xx++ = (limb_t)carry;
+        carry = (carry >> LIMB_BITS) & 0x01;
+    }
+    *(--xx) &= 0x01;
+#endif
+}
+
+/**
+ * \brief Multiplies two 521-bit values to produce a 1042-bit result.
+ *
+ * \param result The result, which must be NUM_LIMBS_1042BIT limbs in size
+ * and must not overlap with \a x or \a y.
+ * \param x The first value to multiply, which must be NUM_LIMBS_521BIT
+ * limbs in size.
+ * \param y The second value to multiply, which must be NUM_LIMBS_521BIT
+ * limbs in size.
+ *
+ * \sa mul()
+ */
+void P521::mulNoReduce(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    uint8_t i, j;
+    dlimb_t carry;
+    limb_t word;
+    const limb_t *yy;
+    limb_t *rr;
+
+    // Multiply the lowest word of x by y.
+    carry = 0;
+    word = x[0];
+    yy = y;
+    rr = result;
+    for (i = 0; i < NUM_LIMBS_521BIT; ++i) {
+        carry += ((dlimb_t)(*yy++)) * word;
+        *rr++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    *rr = (limb_t)carry;
+
+    // Multiply and add the remaining words of x by y.
+    for (i = 1; i < NUM_LIMBS_521BIT; ++i) {
+        word = x[i];
+        carry = 0;
+        yy = y;
+        rr = result + i;
+        for (j = 0; j < NUM_LIMBS_521BIT; ++j) {
+            carry += ((dlimb_t)(*yy++)) * word;
+            carry += *rr;
+            *rr++ = (limb_t)carry;
+            carry >>= LIMB_BITS;
+        }
+        *rr = (limb_t)carry;
+    }
+}
+
+/**
+ * \brief Multiplies two values and then reduces the result modulo 2^521 - 1.
+ *
+ * \param result The result, which must be NUM_LIMBS_521BIT limbs in size
+ * and can be the same array as \a x or \a y.
+ * \param x The first value to multiply, which must be NUM_LIMBS_521BIT limbs
+ * in size and less than 2^521 - 1.
+ * \param y The second value to multiply, which must be NUM_LIMBS_521BIT limbs
+ * in size and less than 2^521 - 1.  This can be the same array as \a x.
+ */
+void P521::mul(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    limb_t temp[NUM_LIMBS_1042BIT];
+    mulNoReduce(temp, x, y);
+    reduce(result, temp);
+    strict_clean(temp);
+    crypto_feed_watchdog();
+}
+
+/**
+ * \fn void P521::square(limb_t *result, const limb_t *x)
+ * \brief Squares a value and then reduces it modulo 2^521 - 1.
+ *
+ * \param result The result, which must be NUM_LIMBS_521BIT limbs in size and
+ * can be the same array as \a x.
+ * \param x The value to square, which must be NUM_LIMBS_521BIT limbs in size
+ * and less than 2^521 - 1.
+ */
+
+/**
+ * \brief Multiply a value by a single-limb literal modulo 2^521 - 1.
+ *
+ * \param result The result, which must be NUM_LIMBS_521BIT limbs in size and
+ * can be the same array as \a x.
+ * \param x The first value to multiply, which must be NUM_LIMBS_521BIT limbs
+ * in size and less than 2^521 - 1.
+ * \param y The second value to multiply, which must be less than 128.
+ */
+void P521::mulLiteral(limb_t *result, const limb_t *x, limb_t y)
+{
+    uint8_t index;
+    dlimb_t carry = 0;
+    const limb_t *xx = x;
+    limb_t *rr = result;
+
+    // Multiply x by the literal and put it into the result array.
+    // We assume that y is small enough that overflow from the
+    // highest limb will not occur during this process.
+    for (index = 0; index < NUM_LIMBS_521BIT; ++index) {
+        carry += ((dlimb_t)(*xx++)) * y;
+        *rr++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+
+    // Reduce the value modulo 2^521 - 1.  The high half is only a
+    // single limb, so we can short-cut some of reduce() here.
+#if BIGNUMBER_LIMB_16BIT || BIGNUMBER_LIMB_32BIT || BIGNUMBER_LIMB_64BIT
+    limb_t word = result[NUM_LIMBS_521BIT - 1];
+    carry = (word >> 9) + 1;
+    word &= 0x1FF;
+    rr = result;
+    for (index = 0; index < (NUM_LIMBS_521BIT - 1); ++index) {
+        carry += *rr;
+        *rr++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    carry += word;
+    word = (limb_t)carry;
+    *rr = word;
+
+    // If the carry out was 1, then mask it off and we have the answer.
+    // If the carry out was 0, then we need to add 2^521 - 1 back again.
+    // To preserve the timing we perform a conditional subtract of 1 and
+    // then mask off the high bits.
+    carry = ((word >> 9) ^ 0x01) & 0x01;
+    rr = result;
+    for (index = 0; index < NUM_LIMBS_521BIT; ++index) {
+        carry = ((dlimb_t)(*rr)) - carry;
+        *rr++ = (limb_t)carry;
+        carry = (carry >> LIMB_BITS) & 0x01;
+    }
+    *(--rr) &= 0x1FF;
+#elif BIGNUMBER_LIMB_8BIT
+    // Same as above, but for 8-bit limbs.
+    limb_t word = result[NUM_LIMBS_521BIT - 1];
+    carry = (word >> 1) + 1;
+    word &= 0x01;
+    rr = result;
+    for (index = 0; index < (NUM_LIMBS_521BIT - 1); ++index) {
+        carry += *rr;
+        *rr++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    carry += word;
+    word = (limb_t)carry;
+    *rr = word;
+    carry = ((word >> 1) ^ 0x01) & 0x01;
+    rr = result;
+    for (index = 0; index < NUM_LIMBS_521BIT; ++index) {
+        carry = ((dlimb_t)(*rr)) - carry;
+        *rr++ = (limb_t)carry;
+        carry = (carry >> LIMB_BITS) & 0x01;
+    }
+    *(--rr) &= 0x01;
+#endif
+}
+
+/**
+ * \brief Adds two values and then reduces the result modulo 2^521 - 1.
+ *
+ * \param result The result, which must be NUM_LIMBS_521BIT limbs in size
+ * and can be the same array as \a x or \a y.
+ * \param x The first value to multiply, which must be NUM_LIMBS_521BIT
+ * limbs in size and less than 2^521 - 1.
+ * \param y The second value to multiply, which must be NUM_LIMBS_521BIT
+ * limbs in size and less than 2^521 - 1.
+ */
+void P521::add(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    dlimb_t carry = 0;
+    limb_t *rr = result;
+    for (uint8_t posn = 0; posn < NUM_LIMBS_521BIT; ++posn) {
+        carry += *x++;
+        carry += *y++;
+        *rr++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    reduceQuick(result);
+}
+
+/**
+ * \brief Subtracts two values and then reduces the result modulo 2^521 - 1.
+ *
+ * \param result The result, which must be NUM_LIMBS_521BIT limbs in size
+ * and can be the same array as \a x or \a y.
+ * \param x The first value to multiply, which must be NUM_LIMBS_521BIT
+ * limbs in size and less than 2^521 - 1.
+ * \param y The second value to multiply, which must be NUM_LIMBS_521BIT
+ * limbs in size and less than 2^521 - 1.
+ */
+void P521::sub(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    dlimb_t borrow;
+    uint8_t posn;
+    limb_t *rr = result;
+
+    // Subtract y from x to generate the intermediate result.
+    borrow = 0;
+    for (posn = 0; posn < NUM_LIMBS_521BIT; ++posn) {
+        borrow = ((dlimb_t)(*x++)) - (*y++) - ((borrow >> LIMB_BITS) & 0x01);
+        *rr++ = (limb_t)borrow;
+    }
+
+    // If we had a borrow, then the result has gone negative and we
+    // have to add 2^521 - 1 to the result to make it positive again.
+    // The top bits of "borrow" will be all 1's if there is a borrow
+    // or it will be all 0's if there was no borrow.  Easiest is to
+    // conditionally subtract 1 and then mask off the high bits.
+    rr = result;
+    borrow = (borrow >> LIMB_BITS) & 1U;
+    borrow = ((dlimb_t)(*rr)) - borrow;
+    *rr++ = (limb_t)borrow;
+    for (posn = 1; posn < NUM_LIMBS_521BIT; ++posn) {
+        borrow = ((dlimb_t)(*rr)) - ((borrow >> LIMB_BITS) & 0x01);
+        *rr++ = (limb_t)borrow;
+    }
+#if BIGNUMBER_LIMB_8BIT
+    *(--rr) &= 0x01;
+#else
+    *(--rr) &= 0x1FF;
+#endif
+}
+
+/**
+ * \brief Doubles a point represented in Jacobian co-ordinates.
+ *
+ * \param xout The X value for the result.
+ * \param yout The Y value for the result.
+ * \param zout The Z value for the result.
+ * \param xin The X value for the point to be doubled.
+ * \param yin The Y value for the point to be doubled.
+ * \param zin The Z value for the point to be doubled.
+ *
+ * The output parameters can be the same as the input parameters
+ * to double in-place.
+ *
+ * Reference: http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b
+ */
+void P521::dblPoint(limb_t *xout, limb_t *yout, limb_t *zout,
+                    const limb_t *xin, const limb_t *yin,
+                    const limb_t *zin)
+{
+    limb_t alpha[NUM_LIMBS_521BIT];
+    limb_t beta[NUM_LIMBS_521BIT];
+    limb_t gamma[NUM_LIMBS_521BIT];
+    limb_t delta[NUM_LIMBS_521BIT];
+    limb_t tmp[NUM_LIMBS_521BIT];
+
+    // Double the point.  If it is the point at infinity (z = 0),
+    // then zout will still be zero at the end of this process so
+    // we don't need any special handling for that case.
+    square(delta, zin);       // delta = z^2
+    square(gamma, yin);       // gamma = y^2
+    mul(beta, xin, gamma);    // beta = x * gamma
+    sub(tmp, xin, delta);     // alpha = 3 * (x - delta) * (x + delta)
+    mulLiteral(alpha, tmp, 3);
+    add(tmp, xin, delta);
+    mul(alpha, alpha, tmp);
+    square(xout, alpha);      // xout = alpha^2 - 8 * beta
+    mulLiteral(tmp, beta, 8);
+    sub(xout, xout, tmp);
+    add(zout, yin, zin);      // zout = (y + z)^2 - gamma - delta
+    square(zout, zout);
+    sub(zout, zout, gamma);
+    sub(zout, zout, delta);
+    mulLiteral(yout, beta, 4);// yout = alpha * (4 * beta - xout) - 8 * gamma^2
+    sub(yout, yout, xout);
+    mul(yout, alpha, yout);
+    square(gamma, gamma);
+    mulLiteral(gamma, gamma, 8);
+    sub(yout, yout, gamma);
+
+    // Clean up.
+    strict_clean(alpha);
+    strict_clean(beta);
+    strict_clean(gamma);
+    strict_clean(delta);
+    strict_clean(tmp);
+}
+
+/**
+ * \brief Adds two curve points, one represented in Jacobian co-ordinates,
+ * and the other represented in affine co-ordinates.
+ *
+ * \param xout The X value for the result.
+ * \param yout The Y value for the result.
+ * \param zout The Z value for the result.
+ * \param x1 The X value for the first point to add.
+ * \param y1 The Y value for the first point to add.
+ * \param z1 The Z value for the first point to add.
+ * \param x2 The X value for the second point to add.
+ * \param y2 The Y value for the second point to add.
+ *
+ * The output parameters must not overlap with either of the inputs.
+ *
+ * The Z value of the second point is implicitly assumed to be 1.
+ *
+ * Reference: http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl
+ */
+void P521::addPoint(limb_t *xout, limb_t *yout, limb_t *zout,
+                    const limb_t *x1, const limb_t *y1,
+                    const limb_t *z1, const limb_t *x2,
+                    const limb_t *y2)
+{
+    limb_t z1z1[NUM_LIMBS_521BIT];
+    limb_t u2[NUM_LIMBS_521BIT];
+    limb_t s2[NUM_LIMBS_521BIT];
+    limb_t h[NUM_LIMBS_521BIT];
+    limb_t i[NUM_LIMBS_521BIT];
+    limb_t j[NUM_LIMBS_521BIT];
+    limb_t r[NUM_LIMBS_521BIT];
+    limb_t v[NUM_LIMBS_521BIT];
+
+    // Determine if the first value is the point-at-infinity identity element.
+    // The second z value is always 1 so it cannot be the point-at-infinity.
+    limb_t p1IsIdentity = BigNumberUtil::isZero(z1, NUM_LIMBS_521BIT);
+
+    // Multiply the points, assuming that z2 = 1.
+    square(z1z1, z1);               // z1z1 = z1^2
+    mul(u2, x2, z1z1);              // u2 = x2 * z1z1
+    mul(s2, y2, z1);                // s2 = y2 * z1 * z1z1
+    mul(s2, s2, z1z1);
+    sub(h, u2, x1);                 // h = u2 - x1
+    mulLiteral(i, h, 2);            // i = (2 * h)^2
+    square(i, i);
+    sub(r, s2, y1);                 // r = 2 * (s2 - y1)
+    add(r, r, r);
+    mul(j, h, i);                   // j = h * i
+    mul(v, x1, i);                  // v = x1 * i
+    square(xout, r);                // xout = r^2 - j - 2 * v
+    sub(xout, xout, j);
+    sub(xout, xout, v);
+    sub(xout, xout, v);
+    sub(yout, v, xout);             // yout = r * (v - xout) - 2 * y1 * j
+    mul(yout, r, yout);
+    mul(j, y1, j);
+    sub(yout, yout, j);
+    sub(yout, yout, j);
+    mul(zout, z1, h);               // zout = 2 * z1 * h
+    add(zout, zout, zout);
+
+    // Select the answer to return.  If (x1, y1, z1) was the identity,
+    // then the answer is (x2, y2, z2).  Otherwise it is (xout, yout, zout).
+    // Conditionally move the second argument over the output if necessary.
+    cmove(p1IsIdentity, xout, x2);
+    cmove(p1IsIdentity, yout, y2);
+    cmove1(p1IsIdentity, zout); // z2 = 1
+
+    // Clean up.
+    strict_clean(z1z1);
+    strict_clean(u2);
+    strict_clean(s2);
+    strict_clean(h);
+    strict_clean(i);
+    strict_clean(j);
+    strict_clean(r);
+    strict_clean(v);
+}
+
+/**
+ * \brief Conditionally moves \a y into \a x if a selection value is non-zero.
+ *
+ * \param select Non-zero to move \a y into \a x, zero to leave \a x unchanged.
+ * \param x The destination to move into.
+ * \param y The value to conditionally move.
+ *
+ * The move is performed in a way that it should take the same amount of
+ * time irrespective of the value of \a select.
+ *
+ * \sa cmove1()
+ */
+void P521::cmove(limb_t select, limb_t *x, const limb_t *y)
+{
+    uint8_t posn;
+    limb_t dummy;
+    limb_t sel;
+
+    // Turn "select" into an all-zeroes or all-ones mask.  We don't care
+    // which bit or bits is set in the original "select" value.
+    sel = (limb_t)(((((dlimb_t)1) << LIMB_BITS) - select) >> LIMB_BITS);
+    --sel;
+
+    // Move y into x based on "select".
+    for (posn = 0; posn < NUM_LIMBS_521BIT; ++posn) {
+        dummy = sel & (*x ^ *y++);
+        *x++ ^= dummy;
+    }
+}
+
+/**
+ * \brief Conditionally moves 1 into \a x if a selection value is non-zero.
+ *
+ * \param select Non-zero to move 1 into \a x, zero to leave \a x unchanged.
+ * \param x The destination to move into.
+ *
+ * The move is performed in a way that it should take the same amount of
+ * time irrespective of the value of \a select.
+ *
+ * \sa cmove()
+ */
+void P521::cmove1(limb_t select, limb_t *x)
+{
+    uint8_t posn;
+    limb_t dummy;
+    limb_t sel;
+
+    // Turn "select" into an all-zeroes or all-ones mask.  We don't care
+    // which bit or bits is set in the original "select" value.
+    sel = (limb_t)(((((dlimb_t)1) << LIMB_BITS) - select) >> LIMB_BITS);
+    --sel;
+
+    // Move 1 into x based on "select".
+    dummy = sel & (*x ^ 1);
+    *x++ ^= dummy;
+    for (posn = 1; posn < NUM_LIMBS_521BIT; ++posn) {
+        dummy = sel & *x;
+        *x++ ^= dummy;
+    }
+}
+
+/**
+ * \brief Computes the reciprocal of a number modulo 2^521 - 1.
+ *
+ * \param result The result as a array of NUM_LIMBS_521BIT limbs in size.
+ * This cannot be the same array as \a x.
+ * \param x The number to compute the reciprocal for, also NUM_LIMBS_521BIT
+ * limbs in size.
+ */
+void P521::recip(limb_t *result, const limb_t *x)
+{
+    limb_t t1[NUM_LIMBS_521BIT];
+
+    // The reciprocal is the same as x ^ (p - 2) where p = 2^521 - 1.
+    // The big-endian hexadecimal expansion of (p - 2) is:
+    // 01FF FFFFFFF FFFFFFFF ... FFFFFFFF FFFFFFFD
+    //
+    // The naive implementation needs to do 2 multiplications per 1 bit and
+    // 1 multiplication per 0 bit.  We can improve upon this by creating a
+    // pattern 1111 and then shifting and multiplying to create 11111111,
+    // and then 1111111111111111, and so on for the top 512-bits.
+
+    // Build a 4-bit pattern 1111 in the result.
+    square(result, x);
+    mul(result, result, x);
+    square(result, result);
+    mul(result, result, x);
+    square(result, result);
+    mul(result, result, x);
+
+    // Shift and multiply by increasing powers of two.  This turns
+    // 1111 into 11111111, and then 1111111111111111, and so on.
+    for (size_t power = 4; power <= 256; power <<= 1) {
+        square(t1, result);
+        for (size_t temp = 1; temp < power; ++temp)
+            square(t1, t1);
+        mul(result, result, t1);
+    }
+
+    // Handle the 9 lowest bits of (p - 2), 111111101, from highest to lowest.
+    for (uint8_t index = 0; index < 7; ++index) {
+        square(result, result);
+        mul(result, result, x);
+    }
+    square(result, result);
+    square(result, result);
+    mul(result, result, x);
+
+    // Clean up.
+    clean(t1);
+}
+
+/**
+ * \brief Reduces a number modulo q.
+ *
+ * \param result The result array, which must be NUM_LIMBS_521BIT limbs in size.
+ * \param r The value to reduce, which must be NUM_LIMBS_1042BIT limbs in size.
+ *
+ * It is allowed for \a result to be the same as \a r.
+ */
+void P521::reduceQ(limb_t *result, const limb_t *r)
+{
+    // Algorithm from: http://en.wikipedia.org/wiki/Barrett_reduction
+    //
+    // We assume that r is less than or equal to (q - 1)^2.
+    //
+    // We want to compute result = r mod q.  Find the smallest k such
+    // that 2^k > q.  In our case, k = 521.  Then set m = floor(4^k / q)
+    // and let r = r - q * floor(m * r / 4^k).  This will be the result
+    // or it will be at most one subtraction of q away from the result.
+    //
+    // Note: m is a 522-bit number, which fits in the same number of limbs
+    // as a 521-bit number assuming that limbs are 8 bits or more in size.
+    static limb_t const numM[NUM_LIMBS_521BIT] PROGMEM = {
+        LIMB_PAIR(0x6EC79BF7, 0x449048E1), LIMB_PAIR(0x7663B851, 0xC44A3647),
+        LIMB_PAIR(0x08F65A2F, 0x8033FEB7), LIMB_PAIR(0x40D06994, 0xAE79787C),
+        LIMB_PAIR(0x00000005, 0x00000000), LIMB_PAIR(0x00000000, 0x00000000),
+        LIMB_PAIR(0x00000000, 0x00000000), LIMB_PAIR(0x00000000, 0x00000000),
+        LIMB_PARTIAL(0x200)
+    };
+    limb_t temp[NUM_LIMBS_1042BIT + NUM_LIMBS_521BIT];
+    limb_t temp2[NUM_LIMBS_521BIT];
+
+    // Multiply r by m.
+    BigNumberUtil::mul_P(temp, r, NUM_LIMBS_1042BIT, numM, NUM_LIMBS_521BIT);
+
+    // Compute (m * r / 4^521) = (m * r / 2^1042).
+#if BIGNUMBER_LIMB_8BIT || BIGNUMBER_LIMB_16BIT
+    dlimb_t carry = temp[NUM_LIMBS_BITS(1040)] >> 2;
+    for (uint8_t index = 0; index < NUM_LIMBS_521BIT; ++index) {
+        carry += ((dlimb_t)(temp[NUM_LIMBS_BITS(1040) + index + 1])) << (LIMB_BITS - 2);
+        temp2[index] = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+#elif BIGNUMBER_LIMB_32BIT || BIGNUMBER_LIMB_64BIT
+    dlimb_t carry = temp[NUM_LIMBS_BITS(1024)] >> 18;
+    for (uint8_t index = 0; index < NUM_LIMBS_521BIT; ++index) {
+        carry += ((dlimb_t)(temp[NUM_LIMBS_BITS(1024) + index + 1])) << (LIMB_BITS - 18);
+        temp2[index] = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+#endif
+
+    // Multiply (m * r) / 2^1042 by q and subtract it from r.
+    // We can ignore the high words of the subtraction result
+    // because they will all turn into zero after the subtraction.
+    BigNumberUtil::mul_P(temp, temp2, NUM_LIMBS_521BIT,
+                         P521_q, NUM_LIMBS_521BIT);
+    BigNumberUtil::sub(result, r, temp, NUM_LIMBS_521BIT);
+
+    // Perform a trial subtraction of q from the result to reduce it.
+    BigNumberUtil::reduceQuick_P(result, result, P521_q, NUM_LIMBS_521BIT);
+
+    // Clean up and exit.
+    clean(temp);
+    clean(temp2);
+}
+
+/**
+ * \brief Multiplies two values and then reduces the result modulo q.
+ *
+ * \param result The result, which must be NUM_LIMBS_521BIT limbs in size
+ * and can be the same array as \a x or \a y.
+ * \param x The first value to multiply, which must be NUM_LIMBS_521BIT limbs
+ * in size and less than q.
+ * \param y The second value to multiply, which must be NUM_LIMBS_521BIT limbs
+ * in size and less than q.  This can be the same array as \a x.
+ */
+void P521::mulQ(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    limb_t temp[NUM_LIMBS_1042BIT];
+    mulNoReduce(temp, x, y);
+    reduceQ(result, temp);
+    strict_clean(temp);
+}
+
+/**
+ * \brief Computes the reciprocal of a number modulo q.
+ *
+ * \param result The result as a array of NUM_LIMBS_521BIT limbs in size.
+ * This cannot be the same array as \a x.
+ * \param x The number to compute the reciprocal for, also NUM_LIMBS_521BIT
+ * limbs in size.
+ */
+void P521::recipQ(limb_t *result, const limb_t *x)
+{
+    // Bottom 265 bits of q - 2.  The top 256 bits are all-1's.
+    static limb_t const P521_q_m2[] PROGMEM = {
+        LIMB_PAIR(0x91386407, 0xbb6fb71e), LIMB_PAIR(0x899c47ae, 0x3bb5c9b8),
+        LIMB_PAIR(0xf709a5d0, 0x7fcc0148), LIMB_PAIR(0xbf2f966b, 0x51868783),
+        LIMB_PARTIAL(0x1fa)
+    };
+
+    // Raise x to the power of q - 2, mod q.  We start with the top
+    // 256 bits which are all-1's, using a similar technique to recip().
+    limb_t t1[NUM_LIMBS_521BIT];
+    mulQ(result, x, x);
+    mulQ(result, result, x);
+    mulQ(result, result, result);
+    mulQ(result, result, x);
+    mulQ(result, result, result);
+    mulQ(result, result, x);
+    for (size_t power = 4; power <= 128; power <<= 1) {
+        mulQ(t1, result, result);
+        for (size_t temp = 1; temp < power; ++temp)
+            mulQ(t1, t1, t1);
+        mulQ(result, result, t1);
+    }
+    clean(t1);
+
+    // Deal with the bottom 265 bits from highest to lowest.  Square for
+    // each bit and multiply in x whenever there is a 1 bit.  The timing
+    // is based on the publicly-known constant q - 2, not on the value of x.
+    size_t bit = 265;
+    while (bit > 0) {
+        --bit;
+        mulQ(result, result, result);
+        if (pgm_read_limb(&(P521_q_m2[bit / LIMB_BITS])) &
+                (((limb_t)1) << (bit % LIMB_BITS))) {
+            mulQ(result, result, x);
+        }
+    }
+}
+
+/**
+ * \brief Generates a k value using the algorithm from RFC 6979.
+ *
+ * \param k The value to generate.
+ * \param hm The hashed message formatted ready to be signed.
+ * \param x The private key to sign with.
+ * \param hash The hash algorithm to use.
+ * \param count Iteration counter for generating new values of k when the
+ * previous one is rejected.
+ */
+void P521::generateK(uint8_t k[66], const uint8_t hm[66],
+                     const uint8_t x[66], Hash *hash, uint64_t count)
+{
+    size_t hlen = hash->hashSize();
+    uint8_t V[64];
+    uint8_t K[64];
+    uint8_t marker;
+
+    // If for some reason a hash function was supplied with more than
+    // 512 bits of output, truncate hash values to the first 512 bits.
+    // We cannot support more than this yet.
+    if (hlen > 64)
+        hlen = 64;
+
+    // RFC 6979, Section 3.2, Step a.  Hash the message, reduce modulo q,
+    // and produce an octet string the same length as q, bits2octets(H(m)).
+    // We support hashes up to 512 bits and q is a 521-bit number, so "hm"
+    // is already the bits2octets(H(m)) value that we need.
+
+    // Steps b and c.  Set V to all-ones and K to all-zeroes.
+    memset(V, 0x01, hlen);
+    memset(K, 0x00, hlen);
+
+    // Step d.  K = HMAC_K(V || 0x00 || x || hm).  We make a small
+    // modification here to append the count value if it is non-zero.
+    // We use this to generate a new k if we have to re-enter this
+    // function because the previous one was rejected by sign().
+    // This is slightly different to RFC 6979 which says that the
+    // loop in step h below should be continued.  That code path is
+    // difficult to access, so instead modify K and V in steps d and f.
+    // This alternative construction is compatible with the second
+    // variant described in section 3.6 of RFC 6979.
+    hash->resetHMAC(K, hlen);
+    hash->update(V, hlen);
+    marker = 0x00;
+    hash->update(&marker, 1);
+    hash->update(x, 66);
+    hash->update(hm, 66);
+    if (count)
+        hash->update(&count, sizeof(count));
+    hash->finalizeHMAC(K, hlen, K, hlen);
+
+    // Step e.  V = HMAC_K(V)
+    hash->resetHMAC(K, hlen);
+    hash->update(V, hlen);
+    hash->finalizeHMAC(K, hlen, V, hlen);
+
+    // Step f.  K = HMAC_K(V || 0x01 || x || hm)
+    hash->resetHMAC(K, hlen);
+    hash->update(V, hlen);
+    marker = 0x01;
+    hash->update(&marker, 1);
+    hash->update(x, 66);
+    hash->update(hm, 66);
+    if (count)
+        hash->update(&count, sizeof(count));
+    hash->finalizeHMAC(K, hlen, K, hlen);
+
+    // Step g.  V = HMAC_K(V)
+    hash->resetHMAC(K, hlen);
+    hash->update(V, hlen);
+    hash->finalizeHMAC(K, hlen, V, hlen);
+
+    // Step h.  Generate candidate k values until we find what we want.
+    for (;;) {
+        // Step h.1 and h.2.  Generate a string of 66 bytes in length.
+        //      T = empty
+        //      while (len(T) < 66)
+        //          V = HMAC_K(V)
+        //          T = T || V
+        size_t posn = 0;
+        while (posn < 66) {
+            size_t temp = 66 - posn;
+            if (temp > hlen)
+                temp = hlen;
+            hash->resetHMAC(K, hlen);
+            hash->update(V, hlen);
+            hash->finalizeHMAC(K, hlen, V, hlen);
+            memcpy(k + posn, V, temp);
+            posn += temp;
+        }
+
+        // Step h.3.  k = bits2int(T) and exit the loop if k is not in
+        // the range 1 to q - 1.  Note: We have to extract the 521 most
+        // significant bits of T, which means shifting it right by seven
+        // bits to put it into the correct form.
+        for (posn = 65; posn > 0; --posn)
+            k[posn] = (k[posn - 1] << 1) | (k[posn] >> 7);
+        k[0] >>= 7;
+        if (isValidPrivateKey(k))
+            break;
+
+        // Generate new K and V values and try again.
+        //      K = HMAC_K(V || 0x00)
+        //      V = HMAC_K(V)
+        hash->resetHMAC(K, hlen);
+        hash->update(V, hlen);
+        marker = 0x00;
+        hash->update(&marker, 1);
+        hash->finalizeHMAC(K, hlen, K, hlen);
+        hash->resetHMAC(K, hlen);
+        hash->update(V, hlen);
+        hash->finalizeHMAC(K, hlen, V, hlen);
+    }
+
+    // Clean up.
+    clean(V);
+    clean(K);
+}
+
+/**
+ * \brief Generates a k value using the algorithm from RFC 6979.
+ *
+ * \param k The value to generate.
+ * \param hm The hashed message formatted ready to be signed.
+ * \param x The private key to sign with.
+ * \param count Iteration counter for generating new values of k when the
+ * previous one is rejected.
+ *
+ * This override uses SHA512 to generate k values.  It is used when
+ * sign() was not passed an explicit hash object by the application.
+ */
+void P521::generateK(uint8_t k[66], const uint8_t hm[66],
+                     const uint8_t x[66], uint64_t count)
+{
+    SHA512 hash;
+    generateK(k, hm, x, &hash, count);
+}
diff --git a/src/modules/Crypto/P521.h b/src/modules/Crypto/P521.h
new file mode 100644
index 0000000..09ac02f
--- /dev/null
+++ b/src/modules/Crypto/P521.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_P521_h
+#define CRYPTO_P521_h
+
+#include "BigNumberUtil.h"
+
+class Hash;
+
+class P521
+{
+public:
+
+    static bool eval(uint8_t result[132], const uint8_t f[66], const uint8_t point[132]);
+
+    static void dh1(uint8_t k[132], uint8_t f[66]);
+    static bool dh2(const uint8_t k[132], uint8_t f[66]);
+
+    static void sign(uint8_t signature[132], const uint8_t privateKey[66],
+                     const void *message, size_t len, Hash *hash = 0);
+    static bool verify(const uint8_t signature[132],
+                       const uint8_t publicKey[132],
+                       const void *message, size_t len, Hash *hash = 0);
+
+    static void generatePrivateKey(uint8_t privateKey[66]);
+    static void derivePublicKey(uint8_t publicKey[132], const uint8_t privateKey[66]);
+
+    static bool isValidPrivateKey(const uint8_t privateKey[66]);
+    static bool isValidPublicKey(const uint8_t publicKey[132]);
+
+    static bool isValidCurvePoint(const uint8_t point[132])
+    {
+        return isValidPublicKey(point);
+    }
+
+#if defined(TEST_P521_FIELD_OPS)
+public:
+#else
+private:
+#endif
+    static void evaluate(limb_t *x, limb_t *y, const uint8_t f[66]);
+
+    static void addAffine(limb_t *x1, limb_t *y1,
+                          const limb_t *x2, const limb_t *y2);
+
+    static bool validate(const limb_t *x, const limb_t *y);
+    static bool inRange(const limb_t *x);
+
+    static void reduce(limb_t *result, const limb_t *x);
+    static void reduceQuick(limb_t *x);
+
+    static void mulNoReduce(limb_t *result, const limb_t *x, const limb_t *y);
+
+    static void mul(limb_t *result, const limb_t *x, const limb_t *y);
+    static void square(limb_t *result, const limb_t *x)
+    {
+        mul(result, x, x);
+    }
+
+    static void mulLiteral(limb_t *result, const limb_t *x, limb_t y);
+
+    static void add(limb_t *result, const limb_t *x, const limb_t *y);
+    static void sub(limb_t *result, const limb_t *x, const limb_t *y);
+
+    static void dblPoint(limb_t *xout, limb_t *yout, limb_t *zout,
+                         const limb_t *xin, const limb_t *yin,
+                         const limb_t *zin);
+    static void addPoint(limb_t *xout, limb_t *yout, limb_t *zout,
+                         const limb_t *x1, const limb_t *y1,
+                         const limb_t *z1, const limb_t *x2,
+                         const limb_t *y2);
+
+    static void cmove(limb_t select, limb_t *x, const limb_t *y);
+    static void cmove1(limb_t select, limb_t *x);
+
+    static void recip(limb_t *result, const limb_t *x);
+
+    static void reduceQ(limb_t *result, const limb_t *r);
+    static void mulQ(limb_t *result, const limb_t *x, const limb_t *y);
+    static void recipQ(limb_t *result, const limb_t *x);
+
+    static void generateK(uint8_t k[66], const uint8_t hm[66],
+                          const uint8_t x[66], Hash *hash, uint64_t count);
+    static void generateK(uint8_t k[66], const uint8_t hm[66],
+                          const uint8_t x[66], uint64_t count);
+
+    // Constructor and destructor are private - cannot instantiate this class.
+    P521() {}
+    ~P521() {}
+};
+
+#endif
diff --git a/src/modules/Crypto/Poly1305.cpp b/src/modules/Crypto/Poly1305.cpp
new file mode 100644
index 0000000..c9e2851
--- /dev/null
+++ b/src/modules/Crypto/Poly1305.cpp
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "Poly1305.h"
+#include "Crypto.h"
+#include "utility/EndianUtil.h"
+#include "utility/LimbUtil.h"
+#include <string.h>
+
+/**
+ * \class Poly1305 Poly1305.h <Poly1305.h>
+ * \brief Poly1305 message authenticator
+ *
+ * Poly1305 is a message authenticator designed by Daniel J. Bernstein.
+ * An arbitrary-length message is broken up into 16-byte chunks and fed
+ * into a polynomial mod 2<sup>130</sup> - 5 based on the 16-byte
+ * authentication key.  The final polynomial value is then combined with a
+ * 16-byte nonce to create the authentication token.
+ *
+ * The following example demonstrates how to compute an authentication token
+ * for a message made up of several blocks under a specific key and nonce:
+ *
+ * \code
+ * Poly1305 poly1305;
+ * uint8_t token[16];
+ * poly1305.reset(key);
+ * poly1305.update(block1, sizeof(block1));
+ * poly1305.update(block2, sizeof(block2));
+ * ...
+ * poly1305.update(blockN, sizeof(blockN));
+ * poly1305.finalize(nonce, token, sizeof(token));
+ * \endcode
+ *
+ * In the original Poly1305 specification, the nonce was encrypted with AES
+ * and a second 16-byte key.  Since then, common practice has been for the
+ * caller to encrypt the nonce which gives the caller more flexibility as
+ * to how to derive and/or encrypt the nonce.
+ *
+ * References: http://en.wikipedia.org/wiki/Poly1305-AES,
+ * http://cr.yp.to/mac.html
+ */
+
+// Limb array with enough space for 130 bits.
+#define NUM_LIMBS_130BIT    (NUM_LIMBS_128BIT + 1)
+
+// Endian helper macros for limbs and arrays of limbs.
+#if BIGNUMBER_LIMB_8BIT
+#define lelimbtoh(x)        (x)
+#define htolelimb(x)        (x)
+#elif BIGNUMBER_LIMB_16BIT
+#define lelimbtoh(x)        (le16toh((x)))
+#define htolelimb(x)        (htole16((x)))
+#elif BIGNUMBER_LIMB_32BIT
+#define lelimbtoh(x)        (le32toh((x)))
+#define htolelimb(x)        (htole32((x)))
+#elif BIGNUMBER_LIMB_64BIT
+#define lelimbtoh(x)        (le64toh((x)))
+#define htolelimb(x)        (htole64((x)))
+#endif
+#if defined(CRYPTO_LITTLE_ENDIAN)
+#define littleToHost(r,size)    do { ; } while (0)
+#else
+#define littleToHost(r,size)   \
+    do { \
+        for (uint8_t i = 0; i < (size); ++i) \
+            (r)[i] = lelimbtoh((r)[i]); \
+    } while (0)
+#endif
+
+/**
+ * \brief Constructs a new Poly1305 message authenticator.
+ */
+Poly1305::Poly1305()
+{
+    state.chunkSize = 0;
+}
+
+/**
+ * \brief Destroys this Poly1305 message authenticator after clearing all
+ * sensitive information.
+ */
+Poly1305::~Poly1305()
+{
+    clean(state);
+}
+
+/**
+ * \brief Resets the Poly1305 message authenticator for a new session.
+ *
+ * \param key Points to the 16 byte authentication key.
+ *
+ * \sa update(), finalize()
+ */
+void Poly1305::reset(const void *key)
+{
+    // Copy the key into place and clear the bits we don't need.
+    uint8_t *r = (uint8_t *)state.r;
+    memcpy(r, key, 16);
+    r[3] &= 0x0F;
+    r[4] &= 0xFC;
+    r[7] &= 0x0F;
+    r[8] &= 0xFC;
+    r[11] &= 0x0F;
+    r[12] &= 0xFC;
+    r[15] &= 0x0F;
+
+    // Convert into little-endian if necessary.
+    littleToHost(state.r, NUM_LIMBS_128BIT);
+
+    // Reset the hashing process.
+    state.chunkSize = 0;
+    memset(state.h, 0, sizeof(state.h));
+}
+
+/**
+ * \brief Updates the message authenticator with more data.
+ *
+ * \param data Data to be hashed.
+ * \param len Number of bytes of data to be hashed.
+ *
+ * If finalize() has already been called, then the behavior of update() will
+ * be undefined.  Call reset() first to start a new authentication process.
+ *
+ * \sa pad(), reset(), finalize()
+ */
+void Poly1305::update(const void *data, size_t len)
+{
+    // Break the input up into 128-bit chunks and process each in turn.
+    const uint8_t *d = (const uint8_t *)data;
+    while (len > 0) {
+        uint8_t size = 16 - state.chunkSize;
+        if (size > len)
+            size = len;
+        memcpy(((uint8_t *)state.c) + state.chunkSize, d, size);
+        state.chunkSize += size;
+        len -= size;
+        d += size;
+        if (state.chunkSize == 16) {
+            littleToHost(state.c, NUM_LIMBS_128BIT);
+            state.c[NUM_LIMBS_128BIT] = 1;
+            processChunk();
+            state.chunkSize = 0;
+        }
+    }
+}
+
+/**
+ * \brief Finalizes the authentication process and returns the token.
+ *
+ * \param nonce Points to the 16-bit nonce to combine with the token.
+ * \param token The buffer to return the token value in.
+ * \param len The length of the \a token buffer between 0 and 16.
+ *
+ * If \a len is less than 16, then the token value will be truncated to
+ * the first \a len bytes.  If \a len is greater than 16, then the remaining
+ * bytes will left unchanged.
+ *
+ * If finalize() is called again, then the returned \a token value is
+ * undefined.  Call reset() first to start a new authentication process.
+ *
+ * \sa reset(), update()
+ */
+void Poly1305::finalize(const void *nonce, void *token, size_t len)
+{
+    dlimb_t carry;
+    uint8_t i;
+    limb_t t[NUM_LIMBS_256BIT + 1];
+
+    // Pad and flush the final chunk.
+    if (state.chunkSize > 0) {
+        uint8_t *c = (uint8_t *)state.c;
+        c[state.chunkSize] = 1;
+        memset(c + state.chunkSize + 1, 0, 16 - state.chunkSize - 1);
+        littleToHost(state.c, NUM_LIMBS_128BIT);
+        state.c[NUM_LIMBS_128BIT] = 0;
+        processChunk();
+    }
+
+    // At this point, processChunk() has left h as a partially reduced
+    // result that is less than (2^130 - 5) * 6.  Perform one more
+    // reduction and a trial subtraction to produce the final result.
+
+    // Multiply the high bits of h by 5 and add them to the 130 low bits.
+    carry = (dlimb_t)((state.h[NUM_LIMBS_128BIT] >> 2) +
+                      (state.h[NUM_LIMBS_128BIT] & ~((limb_t)3)));
+    state.h[NUM_LIMBS_128BIT] &= 0x0003;
+    for (i = 0; i < NUM_LIMBS_128BIT; ++i) {
+        carry += state.h[i];
+        state.h[i] = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    state.h[i] += (limb_t)carry;
+
+    // Subtract (2^130 - 5) from h by computing t = h + 5 - 2^130.
+    // The "minus 2^130" step is implicit.
+    carry = 5;
+    for (i = 0; i < NUM_LIMBS_130BIT; ++i) {
+        carry += state.h[i];
+        t[i] = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+
+    // Borrow occurs if bit 2^130 of the previous t result is zero.
+    // Carefully turn this into a selection mask so we can select either
+    // h or t as the final result.  We don't care about the highest word
+    // of the result because we are about to drop it in the next step.
+    // We have to do it this way to avoid giving away any information
+    // about the value of h in the instruction timing.
+    limb_t mask = (~((t[NUM_LIMBS_128BIT] >> 2) & 1)) + 1;
+    limb_t nmask = ~mask;
+    for (i = 0; i < NUM_LIMBS_128BIT; ++i) {
+        state.h[i] = (state.h[i] & nmask) | (t[i] & mask);
+    }
+
+    // Add the encrypted nonce and format the final hash.
+    memcpy(state.c, nonce, 16);
+    littleToHost(state.c, NUM_LIMBS_128BIT);
+    carry = 0;
+    for (i = 0; i < NUM_LIMBS_128BIT; ++i) {
+        carry += state.h[i];
+        carry += state.c[i];
+        state.h[i] = htolelimb((limb_t)carry);
+        carry >>= LIMB_BITS;
+    }
+    if (len > 16)
+        len = 16;
+    memcpy(token, state.h, len);
+}
+
+/**
+ * \brief Pads the input stream with zero bytes to a multiple of 16.
+ *
+ * \sa update()
+ */
+void Poly1305::pad()
+{
+    if (state.chunkSize != 0) {
+        memset(((uint8_t *)state.c) + state.chunkSize, 0, 16 - state.chunkSize);
+        littleToHost(state.c, NUM_LIMBS_128BIT);
+        state.c[NUM_LIMBS_128BIT] = 1;
+        processChunk();
+        state.chunkSize = 0;
+    }
+}
+
+/**
+ * \brief Clears the authenticator's state, removing all sensitive data.
+ */
+void Poly1305::clear()
+{
+    clean(state);
+}
+
+/**
+ * \brief Processes a single 128-bit chunk of input data.
+ */
+void Poly1305::processChunk()
+{
+    limb_t t[NUM_LIMBS_256BIT + 1];
+
+    // Compute h = ((h + c) * r) mod (2^130 - 5).
+
+    // Start with h += c.  We assume that h is less than (2^130 - 5) * 6
+    // and that c is less than 2^129, so the result will be less than 2^133.
+    dlimb_t carry = 0;
+    uint8_t i, j;
+    for (i = 0; i < NUM_LIMBS_130BIT; ++i) {
+        carry += state.h[i];
+        carry += state.c[i];
+        state.h[i] = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+
+    // Multiply h by r.  We know that r is less than 2^124 because the
+    // top 4 bits were AND-ed off by reset().  That makes h * r less
+    // than 2^257.  Which is less than the (2^130 - 6)^2 we want for
+    // the modulo reduction step that follows.
+    carry = 0;
+    limb_t word = state.r[0];
+    for (i = 0; i < NUM_LIMBS_130BIT; ++i) {
+        carry += ((dlimb_t)(state.h[i])) * word;
+        t[i] = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    t[NUM_LIMBS_130BIT] = (limb_t)carry;
+    for (i = 1; i < NUM_LIMBS_128BIT; ++i) {
+        word = state.r[i];
+        carry = 0;
+        for (j = 0; j < NUM_LIMBS_130BIT; ++j) {
+            carry += ((dlimb_t)(state.h[j])) * word;
+            carry += t[i + j];
+            t[i + j] = (limb_t)carry;
+            carry >>= LIMB_BITS;
+        }
+        t[i + NUM_LIMBS_130BIT] = (limb_t)carry;
+    }
+
+    // Reduce h * r modulo (2^130 - 5) by multiplying the high 130 bits by 5
+    // and adding them to the low 130 bits.  See the explaination in the
+    // comments for Curve25519::reduce() for a description of how this works.
+    carry = ((dlimb_t)(t[NUM_LIMBS_128BIT] >> 2)) +
+                      (t[NUM_LIMBS_128BIT] & ~((limb_t)3));
+    t[NUM_LIMBS_128BIT] &= 0x0003;
+    for (i = 0; i < NUM_LIMBS_128BIT; ++i) {
+        // Shift the next word of t up by (LIMB_BITS - 2) bits and then
+        // multiply it by 5.  Breaking it down, we can add the results
+        // of shifting up by LIMB_BITS and shifting up by (LIMB_BITS - 2).
+        // The main wrinkle here is that this can result in an intermediate
+        // carry that is (LIMB_BITS * 2 + 1) bits in size which doesn't
+        // fit within a dlimb_t variable.  However, we can defer adding
+        // (word << LIMB_BITS) until after the "carry >>= LIMB_BITS" step
+        // because it won't affect the low bits of the carry.
+        word = t[i + NUM_LIMBS_130BIT];
+        carry += ((dlimb_t)word) << (LIMB_BITS - 2);
+        carry += t[i];
+        state.h[i] = (limb_t)carry;
+        carry >>= LIMB_BITS;
+        carry += word;
+    }
+    state.h[i] = (limb_t)(carry + t[NUM_LIMBS_128BIT]);
+
+    // At this point, h is either the answer of reducing modulo (2^130 - 5)
+    // or it is at most 5 subtractions away from the answer we want.
+    // Leave it as-is for now with h less than (2^130 - 5) * 6.  It is
+    // still within a range where the next h * r step will not overflow.
+}
diff --git a/src/modules/Crypto/Poly1305.h b/src/modules/Crypto/Poly1305.h
new file mode 100644
index 0000000..ae1e453
--- /dev/null
+++ b/src/modules/Crypto/Poly1305.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_POLY1305_h
+#define CRYPTO_POLY1305_h
+
+#include "BigNumberUtil.h"
+#include <stddef.h>
+
+class Poly1305
+{
+public:
+    Poly1305();
+    ~Poly1305();
+
+    void reset(const void *key);
+    void update(const void *data, size_t len);
+    void finalize(const void *nonce, void *token, size_t len);
+
+    void pad();
+
+    void clear();
+
+private:
+    struct {
+        limb_t h[(16 / sizeof(limb_t)) + 1];
+        limb_t c[(16 / sizeof(limb_t)) + 1];
+        limb_t r[(16 / sizeof(limb_t))];
+        uint8_t chunkSize;
+    } state;
+
+    void processChunk();
+};
+
+#endif
diff --git a/src/modules/Crypto/RNG.cpp b/src/modules/Crypto/RNG.cpp
new file mode 100644
index 0000000..11db363
--- /dev/null
+++ b/src/modules/Crypto/RNG.cpp
@@ -0,0 +1,1004 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "RNG.h"
+#include "NoiseSource.h"
+#include "ChaCha.h"
+#include "Crypto.h"
+#include "utility/ProgMemUtil.h"
+#include <Arduino.h>
+#if defined (__arm__) && defined (__SAM3X8E__)
+// The Arduino Due does not have any EEPROM natively on the main chip.
+// However, it does have a TRNG and flash memory.
+#define RNG_DUE_TRNG 1
+#elif defined(__AVR__)
+#define RNG_EEPROM 1        // Use EEPROM to save the seed.
+#define RNG_WATCHDOG 1      // Harvest entropy from watchdog jitter.
+#include <avr/eeprom.h>
+#include <avr/wdt.h>
+#include <avr/io.h>
+#define RNG_EEPROM_ADDRESS (E2END + 1 - RNGClass::SEED_SIZE)
+#elif defined(ESP8266)
+// ESP8266 does not have EEPROM but it does have SPI flash memory.
+// It also has a TRNG register for generating "true" random numbers.
+// For now we use the TRNG but don't save the seed in flash memory.
+#define RNG_WORD_TRNG 1
+#define RNG_WORD_TRNG_GET() (ESP8266_DREG(0x20E44))
+#elif defined(ESP32)
+// ESP32 has a word-based TRNG and an API for Non-Volatile Storage (NVS).
+#define RNG_WORD_TRNG 1
+#define RNG_WORD_TRNG_GET() (esp_random())
+#define RNG_ESP_NVS 1
+#include <nvs.h>
+#endif
+#include <string.h>
+
+// Throw a warning if there is no built-in hardware random number source.
+// If this happens, then you need to do one of two things:
+//    1. Edit RNG.cpp to add your platform's hardware TRNG.
+//    2. Provide a proper noise source like TransistorNoiseSource
+//       in your sketch and then comment out the #warning line below.
+#if !defined(RNG_DUE_TRNG) && \
+    !defined(RNG_WATCHDOG) && \
+    !defined(RNG_WORD_TRNG)
+#warning "no hardware random number source detected for this platform"
+#endif
+
+/**
+ * \class RNGClass RNG.h <RNG.h>
+ * \brief Pseudo random number generator suitable for cryptography.
+ *
+ * Random number generators must be seeded properly before they can
+ * be used or an adversary may be able to predict the random output.
+ * Seed data may be:
+ *
+ * \li Device-specific, for example serial numbers or MAC addresses.
+ * \li Application-specific, unique to the application.  The tag that is
+ * passed to begin() is an example of an application-specific value.
+ * \li Noise-based, generated by a hardware random number generator
+ * that provides unpredictable values from a noise source.
+ *
+ * The following example demonstrates how to initialise the random
+ * number generator:
+ *
+ * \code
+ * #include <SPI.h>
+ * #include <Ethernet.h>
+ * #include <Crypto.h>
+ * #include <RNG.h>
+ * #include <TransistorNoiseSource.h>
+ *
+ * // Noise source to seed the random number generator.
+ * TransistorNoiseSource noise(A1);
+ *
+ * // MAC address for Ethernet communication.
+ * byte mac_address[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
+ *
+ * void setup() {
+ *     // Initialize the Ethernet shield.
+ *     Ethernet.begin(mac_address);
+ *
+ *     // Initialize the random number generator with the application tag
+ *     // "MyApp 1.0" and load the previous seed from EEPROM.
+ *     RNG.begin("MyApp 1.0");
+ *
+ *     // Stir in the Ethernet MAC address.
+ *     RNG.stir(mac_address, sizeof(mac_address));
+ *
+ *     // Add the noise source to the list of sources known to RNG.
+ *     RNG.addNoiseSource(noise);
+ *
+ *     // ...
+ * }
+ * \endcode
+ *
+ * The application should regularly call loop() to stir in new data
+ * from the registered noise sources and to periodically save the seed:
+ *
+ * \code
+ * void loop() {
+ *     // ...
+ *
+ *     // Perform regular housekeeping on the random number generator.
+ *     RNG.loop();
+ *
+ *     // ...
+ * }
+ * \endcode
+ *
+ * The loop() function will automatically save the random number seed on a
+ * regular basis to the last SEED_SIZE bytes of EEPROM memory.  By default
+ * the seed is saved every hour but this can be changed using setAutoSaveTime().
+ *
+ * Keep in mind that saving too often may cause the EEPROM to wear out quicker.
+ * It is wise to limit saving to once an hour or once a day depending
+ * upon how long you intend to field the device before replacing it.
+ * For example, an EEPROM rated for 100k erase/write cycles will last about
+ * 69 days saving once a minute or 11 years saving once an hour.
+ *
+ * The application can still elect to call save() at any time if wants.
+ * For example, if the application can detect power loss or shutdown
+ * conditions programmatically, then it may make sense to force a save()
+ * of the seed upon shutdown.
+ *
+ * The Arduino Due does not have EEPROM so RNG saves the seed into
+ * the last page of system flash memory instead.  The RNG class will also
+ * mix in data from the CPU's built-in True Random Number Generator (TRNG).
+ * Assuming that the CPU's TRNG is trustworthy, this should be sufficient
+ * to properly seed the random number generator.  It is recommended to
+ * also mix in data from other noise sources just in case the CPU's TRNG
+ * is not trustworthy.
+ *
+ * \sa NoiseSource
+ */
+
+/**
+ * \brief Global random number generator instance.
+ *
+ * \sa RNGClass
+ */
+RNGClass RNG;
+
+/**
+ * \var RNGClass::SEED_SIZE
+ * \brief Size of a saved random number seed in EEPROM space.
+ *
+ * The seed is saved into the last SEED_SIZE bytes of EEPROM memory.
+ * The address is dependent upon the size of EEPROM fitted in the device.
+ */
+
+// Number of ChaCha hash rounds to use for random number generation.
+#define RNG_ROUNDS          20
+
+// Force a rekey after this many blocks of random data.
+#define RNG_REKEY_BLOCKS    16
+
+// Maximum entropy credit that can be contained in the pool.
+#define RNG_MAX_CREDITS     384u
+
+/** @cond */
+
+// Imported from Crypto.cpp.
+extern uint8_t crypto_crc8(uint8_t tag, const void *data, unsigned size);
+
+// Tag for 256-bit ChaCha20 keys.  This will always appear in the
+// first 16 bytes of the block.  The remaining 48 bytes are the seed.
+static const char tagRNG[16] PROGMEM = {
+    'e', 'x', 'p', 'a', 'n', 'd', ' ', '3',
+    '2', '-', 'b', 'y', 't', 'e', ' ', 'k'
+};
+
+// Initialization seed.  This is the ChaCha20 output of hashing
+// "expand 32-byte k" followed by 48 bytes set to the numbers 1 to 48.
+// The ChaCha20 output block is then truncated to the first 48 bytes.
+//
+// This value is intended to start the RNG in a semi-chaotic state if
+// we don't have a previously saved seed in EEPROM.
+static const uint8_t initRNG[48] PROGMEM = {
+    0xB0, 0x2A, 0xAE, 0x7D, 0xEE, 0xCB, 0xBB, 0xB1,
+    0xFC, 0x03, 0x6F, 0xDD, 0xDC, 0x7D, 0x76, 0x67,
+    0x0C, 0xE8, 0x1F, 0x0D, 0xA3, 0xA0, 0xAA, 0x1E,
+    0xB0, 0xBD, 0x72, 0x6B, 0x2B, 0x4C, 0x8A, 0x7E,
+    0x34, 0xFC, 0x37, 0x60, 0xF4, 0x1E, 0x22, 0xA0,
+    0x0B, 0xFB, 0x18, 0x84, 0x60, 0xA5, 0x77, 0x72
+};
+
+#if defined(RNG_WATCHDOG)
+
+// Use jitter between the watchdog timer and the main CPU clock to
+// harvest some entropy on AVR-based systems.  This technique comes from:
+//
+// https://sites.google.com/site/astudyofentropy/project-definition/timer-jitter-entropy-sources/entropy-library
+//
+// The watchdog generates entropy very slowly - it can take around 32 seconds
+// to generate 256 bits of entropy credit.  This is a "better than nothing"
+// entropy source but a real noise source is definitely recommended.
+
+// Helper macros for specific 32-bit shift counts.
+#define leftShift3(value)   ((value) << 3)
+#define leftShift10(value)  ((value) << 10)
+#define leftShift15(value)  ((value) << 15)
+#define rightShift6(value)  ((value) >> 6)
+#define rightShift11(value) ((value) >> 11)
+
+static uint32_t volatile hash = 0;
+static uint8_t volatile outBits = 0;
+
+// Watchdog interrupt handler.  This fires off every 16ms.  We collect
+// 32 bits and then pass them off onto RNGClass::loop().
+ISR(WDT_vect)
+{
+    // Read the low byte of Timer 1.  We assume that the timer was
+    // initialized by the Arduino startup code for PWM use or that the
+    // application is free-running Timer 1 for its own purposes.
+    // Timer 0 is used on systems that don't have a Timer 1.
+#if defined(TCNT1L)
+    unsigned char value = TCNT1L;
+#elif defined(TCNT0L)
+    unsigned char value = TCNT0L;
+#else
+    unsigned char value = TCNT0;
+#endif
+    // Use Jenkin's one-at-a-time hash function to scatter the entropy a bit.
+    // https://en.wikipedia.org/wiki/Jenkins_hash_function
+    hash += value;
+    hash += leftShift10(hash);
+    hash ^= rightShift6(hash);
+    ++outBits;
+}
+
+#endif // RNG_WATCHDOG
+
+/** @endcond */
+
+/**
+ * \brief Constructs a new random number generator instance.
+ *
+ * This constructor must be followed by a call to begin() to
+ * properly initialize the random number generator.
+ *
+ * \sa begin()
+ */
+RNGClass::RNGClass()
+    : credits(0)
+    , firstSave(1)
+    , initialized(0)
+    , trngPending(0)
+    , timer(0)
+    , timeout(3600000UL)    // 1 hour in milliseconds
+    , count(0)
+    , trngPosn(0)
+{
+}
+
+/**
+ * \brief Destroys this random number generator instance.
+ */
+RNGClass::~RNGClass()
+{
+#if defined(RNG_DUE_TRNG)
+    // Disable the TRNG in the Arduino Due.
+    REG_TRNG_CR = TRNG_CR_KEY(0x524E47);
+#endif
+#if defined(RNG_WATCHDOG)
+    // Disable interrupts and reset the watchdog.
+    cli();
+    wdt_reset();
+
+    // Clear the "reset due to watchdog" flag.
+    MCUSR &= ~(1 << WDRF);
+
+    // Disable the watchdog.
+    _WD_CONTROL_REG |= (1 << _WD_CHANGE_BIT) | (1 << WDE);
+    _WD_CONTROL_REG = 0;
+
+    // Re-enable interrupts.  The watchdog should be stopped.
+    sei();
+#endif
+    clean(block);
+    clean(stream);
+}
+
+#if defined(RNG_DUE_TRNG)
+
+// Find the flash memory of interest.  Allow for the possibility
+// of other SAM-based Arduino variants in the future.
+#if defined(IFLASH1_ADDR)
+#define RNG_FLASH_ADDR      IFLASH1_ADDR
+#define RNG_FLASH_SIZE      IFLASH1_SIZE
+#define RNG_FLASH_PAGE_SIZE IFLASH1_PAGE_SIZE
+#define RNG_EFC             EFC1
+#elif defined(IFLASH0_ADDR)
+#define RNG_FLASH_ADDR      IFLASH0_ADDR
+#define RNG_FLASH_SIZE      IFLASH0_SIZE
+#define RNG_FLASH_PAGE_SIZE IFLASH0_PAGE_SIZE
+#define RNG_EFC             EFC0
+#else
+#define RNG_FLASH_ADDR      IFLASH_ADDR
+#define RNG_FLASH_SIZE      IFLASH_SIZE
+#define RNG_FLASH_PAGE_SIZE IFLASH_PAGE_SIZE
+#define RNG_EFC             EFC
+#endif
+
+// Address of the flash page to use for saving the seed on the Due.
+// All SAM variants have a page size of 256 bytes or greater so there is
+// plenty of room for the 48 byte seed in the last page of flash memory.
+#define RNG_SEED_ADDR (RNG_FLASH_ADDR + RNG_FLASH_SIZE - RNG_FLASH_PAGE_SIZE)
+#define RNG_SEED_PAGE ((RNG_FLASH_SIZE / RNG_FLASH_PAGE_SIZE) - 1)
+
+// Stir in the unique identifier for the Arduino Due's CPU.
+// This function must be in RAM because programs running out of
+// flash memory are not allowed to access the unique identifier.
+// Info from: http://forum.arduino.cc/index.php?topic=289190.0
+__attribute__((section(".ramfunc")))
+static void stirUniqueIdentifier(void)
+{
+    uint32_t id[4];
+
+    // Start Read Unique Identifier.
+    RNG_EFC->EEFC_FCR = (0x5A << 24) | EFC_FCMD_STUI;
+    while ((RNG_EFC->EEFC_FSR & EEFC_FSR_FRDY) != 0)
+        ;   // do nothing until FRDY falls.
+
+    // Read the identifier.
+    id[0] = *((const uint32_t *)RNG_FLASH_ADDR);
+    id[1] = *((const uint32_t *)(RNG_FLASH_ADDR + 4));
+    id[2] = *((const uint32_t *)(RNG_FLASH_ADDR + 8));
+    id[3] = *((const uint32_t *)(RNG_FLASH_ADDR + 12));
+
+    // Stop Read Unique Identifier.
+    RNG_EFC->EEFC_FCR = (0x5A << 24) | EFC_FCMD_SPUI;
+    while ((RNG_EFC->EEFC_FSR & EEFC_FSR_FRDY) == 0)
+        ;   // do nothing until FRDY rises.
+
+    // Stir the unique identifier into the entropy pool.
+    RNG.stir((uint8_t *)id, sizeof(id));
+}
+
+// Erases the flash page containing the seed and then writes the new seed.
+// It is assumed the seed has already been loaded into the latch registers.
+__attribute__((section(".ramfunc")))
+static void eraseAndWriteSeed()
+{
+    // Execute the "Erase and Write Page" command.
+    RNG_EFC->EEFC_FCR = (0x5A << 24) | (RNG_SEED_PAGE << 8) | EFC_FCMD_EWP;
+
+    // Wait for the FRDY bit to be raised.
+    while ((RNG_EFC->EEFC_FSR & EEFC_FSR_FRDY) == 0)
+        ;   // do nothing until FRDY rises.
+}
+
+#endif
+
+/**
+ * \brief Initializes the random number generator.
+ *
+ * \param tag A string that is stirred into the random pool at startup;
+ * usually this should be a value that is unique to the application and
+ * version such as "MyApp 1.0" so that different applications do not
+ * generate the same sequence of values upon first boot.
+ *
+ * This function should be followed by calls to addNoiseSource() to
+ * register the application's noise sources.
+ *
+ * \sa addNoiseSource(), stir(), save()
+ */
+void RNGClass::begin(const char *tag)
+{
+    // Bail out if we have already done this.
+    if (initialized)
+        return;
+
+    // Initialize the ChaCha20 input block from the saved seed.
+    memcpy_P(block, tagRNG, sizeof(tagRNG));
+    memcpy_P(block + 4, initRNG, sizeof(initRNG));
+#if defined(RNG_EEPROM)
+    int address = RNG_EEPROM_ADDRESS;
+    eeprom_read_block(stream, (const void *)address, SEED_SIZE);
+    if (crypto_crc8('S', stream, SEED_SIZE - 1) ==
+            ((const uint8_t *)stream)[SEED_SIZE - 1]) {
+        // We have a saved seed: XOR it with the initialization block.
+        // Note: the CRC-8 value is included.  No point throwing it away.
+        for (int posn = 0; posn < 12; ++posn)
+            block[posn + 4] ^= stream[posn];
+    }
+#elif defined(RNG_DUE_TRNG)
+    // Do we have a seed saved in the last page of flash memory on the Due?
+    if (crypto_crc8('S', ((const uint32_t *)RNG_SEED_ADDR) + 1, SEED_SIZE)
+            == ((const uint32_t *)RNG_SEED_ADDR)[0]) {
+        // XOR the saved seed with the initialization block.
+        for (int posn = 0; posn < 12; ++posn)
+            block[posn + 4] ^= ((const uint32_t *)RNG_SEED_ADDR)[posn + 1];
+    }
+
+    // If the device has just been reprogrammed, there will be no saved seed.
+    // XOR the initialization block with some output from the CPU's TRNG
+    // to permute the state in a first boot situation after reprogramming.
+    pmc_enable_periph_clk(ID_TRNG);
+    REG_TRNG_CR = TRNG_CR_KEY(0x524E47) | TRNG_CR_ENABLE;
+    REG_TRNG_IDR = TRNG_IDR_DATRDY; // Disable interrupts - we will poll.
+    mixTRNG();
+#endif
+#if defined(RNG_ESP_NVS)
+    // Do we have a seed saved in ESP non-volatile storage (NVS)?
+    nvs_handle handle = 0;
+    if (nvs_open("rng", NVS_READONLY, &handle) == 0) {
+        size_t len = 0;
+        if (nvs_get_blob(handle, "seed", NULL, &len) == 0 && len == SEED_SIZE) {
+            uint32_t seed[12];
+            if (nvs_get_blob(handle, "seed", seed, &len) == 0) {
+                for (int posn = 0; posn < 12; ++posn)
+                    block[posn + 4] ^= seed[posn];
+            }
+            clean(seed);
+        }
+        nvs_close(handle);
+    }
+#endif
+#if defined(RNG_WORD_TRNG)
+    // Mix in some output from a word-based TRNG to initialize the state.
+    mixTRNG();
+#endif
+
+    // No entropy credits for the saved seed.
+    credits = 0;
+
+    // Trigger an automatic save once the entropy credits max out.
+    firstSave = 1;
+
+    // Rekey the random number generator immediately.
+    rekey();
+
+    // Stir in the supplied tag data but don't credit any entropy to it.
+    if (tag)
+        stir((const uint8_t *)tag, strlen(tag));
+
+#if defined(RNG_DUE_TRNG)
+    // Stir in the unique identifier for the CPU so that different
+    // devices will give different outputs even without seeding.
+    stirUniqueIdentifier();
+#elif defined(ESP8266)
+    // ESP8266's have a 32-bit CPU chip ID and 32-bit flash chip ID
+    // that we can use as a device unique identifier.
+    uint32_t ids[2];
+    ids[0] = ESP.getChipId();
+    ids[1] = ESP.getFlashChipId();
+    stir((const uint8_t *)ids, sizeof(ids));
+#elif defined(ESP32)
+    // ESP32's have a MAC address that can be used as a device identifier.
+    uint64_t mac = ESP.getEfuseMac();
+    stir((const uint8_t *)&mac, sizeof(mac));
+#else
+    // AVR devices don't have anything like a serial number so it is
+    // difficult to make every device unique.  Use the compilation
+    // time and date to provide a little randomness across applications
+    // if not across devices running the same pre-compiled application.
+    tag = __TIME__ __DATE__;
+    stir((const uint8_t *)tag, strlen(tag));
+#endif
+
+#if defined(RNG_WATCHDOG)
+    // Disable interrupts and reset the watchdog.
+    cli();
+    wdt_reset();
+
+    // Clear the "reset due to watchdog" flag.
+    MCUSR &= ~(1 << WDRF);
+
+    // Enable the watchdog with the smallest duration (16ms)
+    // and interrupt-only mode.
+    _WD_CONTROL_REG |= (1 << _WD_CHANGE_BIT) | (1 << WDE);
+    _WD_CONTROL_REG = (1 << WDIE);
+
+    // Re-enable interrupts.  The watchdog should be running.
+    sei();
+#endif
+
+    // Re-save the seed to obliterate the previous value and to ensure
+    // that if the system is reset without a call to save() that we won't
+    // accidentally generate the same sequence of random data again.
+    save();
+
+    // The RNG has now been initialized.
+    initialized = 1;
+}
+
+/**
+ * \brief Adds a noise source to the random number generator.
+ *
+ * \param source The noise source to add, which will be polled regularly
+ * by loop() to accumulate noise-based entropy from the source.
+ *
+ * RNG supports a maximum of four noise sources.  If the application needs
+ * more than that then the application must poll the noise sources itself by
+ * calling NoiseSource::stir() directly.
+ *
+ * \sa loop(), begin()
+ */
+void RNGClass::addNoiseSource(NoiseSource &source)
+{
+    #define MAX_NOISE_SOURCES (sizeof(noiseSources) / sizeof(noiseSources[0]))
+    if (count < MAX_NOISE_SOURCES) {
+        noiseSources[count++] = &source;
+        source.added();
+    }
+}
+
+/**
+ * \brief Sets the amount of time between automatic seed saves.
+ *
+ * \param minutes The number of minutes between automatic seed saves.
+ *
+ * The default time between automatic seed saves is 1 hour.
+ *
+ * This function is intended to help with EEPROM wear by slowing down how
+ * often seed data is saved as noise is stirred into the random pool.
+ * The exact period to use depends upon how long you intend to field
+ * the device before replacing it.  For example, an EEPROM rated for
+ * 100k erase/write cycles will last about 69 days saving once a minute
+ * or 11 years saving once an hour.
+ *
+ * \sa save(), stir()
+ */
+void RNGClass::setAutoSaveTime(uint16_t minutes)
+{
+    if (!minutes)
+        minutes = 1; // Just in case.
+    timeout = ((uint32_t)minutes) * 60000U;
+}
+
+/**
+ * \brief Generates random bytes into a caller-supplied buffer.
+ *
+ * \param data Points to the buffer to fill with random bytes.
+ * \param len Number of bytes to generate.
+ *
+ * Calling this function will decrease the amount of entropy in the
+ * random number pool by \a len * 8 bits.  If there isn't enough
+ * entropy, then this function will still return \a len bytes of
+ * random data generated from what entropy it does have.
+ *
+ * If the application requires a specific amount of entropy before
+ * generating important values, the available() function can be
+ * polled to determine when sufficient entropy is available.
+ *
+ * \sa available(), stir()
+ */
+void RNGClass::rand(uint8_t *data, size_t len)
+{
+    // Make sure that the RNG is initialized in case the application
+    // forgot to call RNG.begin() at startup time.
+    if (!initialized)
+        begin(0);
+
+    // Decrease the amount of entropy in the pool.
+    if (len > (credits / 8u))
+        credits = 0;
+    else
+        credits -= len * 8;
+
+    // If we have pending TRNG data from the loop() function,
+    // then force a stir on the state.  Otherwise mix in some
+    // fresh data from the TRNG because it is possible that
+    // the application forgot to call RNG.loop().
+    if (trngPending) {
+        stir(0, 0, 0);
+        trngPending = 0;
+        trngPosn = 0;
+    } else {
+        mixTRNG();
+    }
+
+    // Generate the random data.
+    uint8_t count = 0;
+    while (len > 0) {
+        // Force a rekey if we have generated too many blocks in this request.
+        if (count >= RNG_REKEY_BLOCKS) {
+            rekey();
+            count = 1;
+        } else {
+            ++count;
+        }
+
+        // Increment the low counter word and generate a new keystream block.
+        ++(block[12]);
+        ChaCha::hashCore(stream, block, RNG_ROUNDS);
+
+        // Copy the data to the return buffer.
+        if (len < 64) {
+            memcpy(data, stream, len);
+            break;
+        } else {
+            memcpy(data, stream, 64);
+            data += 64;
+            len -= 64;
+        }
+    }
+
+    // Force a rekey after every request.
+    rekey();
+}
+
+/**
+ * \brief Determine if there is sufficient entropy available for a
+ * specific request size.
+ *
+ * \param len The number of bytes of random data that will be requested
+ * via a call to rand().
+ * \return Returns true if there is at least \a len * 8 bits of entropy
+ * in the random number pool, or false if not.
+ *
+ * This function can be used by the application to wait for sufficient
+ * entropy to become available from the system's noise sources before
+ * generating important values.  For example:
+ *
+ * \code
+ * bool haveKey = false;
+ * byte key[32];
+ *
+ * void loop() {
+ *     ...
+ *
+ *     if (!haveKey && RNG.available(sizeof(key))) {
+ *         RNG.rand(key, sizeof(key));
+ *         haveKey = true;
+ *     }
+ *
+ *     ...
+ * }
+ * \endcode
+ *
+ * If \a len is larger than the maximum number of entropy credits supported
+ * by the random number pool (384 bits, 48 bytes), then the maximum will be
+ * used instead.  For example, asking if 512 bits (64 bytes) are available
+ * will return true if in reality only 384 bits are available.  If this is a
+ * problem for the application's security requirements, then large requests
+ * for random data should be broken up into smaller chunks with the
+ * application waiting for the entropy pool to refill between chunks.
+ *
+ * \sa rand()
+ */
+bool RNGClass::available(size_t len) const
+{
+    if (len >= (RNG_MAX_CREDITS / 8))
+        return credits >= RNG_MAX_CREDITS;
+    else
+        return len <= (credits / 8u);
+}
+
+/**
+ * \brief Stirs additional entropy data into the random pool.
+ *
+ * \param data Points to the additional data to be stirred in.
+ * \param len Number of bytes to be stirred in.
+ * \param credit The number of bits of entropy to credit for the
+ * data that is stirred in.  Note that this is bits, not bytes.
+ *
+ * The maximum credit allowed is \a len * 8 bits, indicating that
+ * every bit in the input \a data is good and random.  Practical noise
+ * sources are rarely that good, so \a credit will usually be smaller.
+ * For example, to credit 2 bits of entropy per byte, the function
+ * would be called as follows:
+ *
+ * \code
+ * RNG.stir(noise_data, noise_bytes, noise_bytes * 2);
+ * \endcode
+ *
+ * If \a credit is zero, then the \a data will be stirred in but no
+ * entropy credit is given.  This is useful for static values like
+ * serial numbers and MAC addresses that are different between
+ * devices but highly predictable.
+ *
+ * \sa loop()
+ */
+void RNGClass::stir(const uint8_t *data, size_t len, unsigned int credit)
+{
+    // Increase the entropy credit.
+    if ((credit / 8) >= len && len)
+        credit = len * 8;
+    if ((RNG_MAX_CREDITS - credits) > credit)
+        credits += credit;
+    else
+        credits = RNG_MAX_CREDITS;
+
+    // Process the supplied input data.
+    if (len > 0) {
+        // XOR the data with the ChaCha input block in 48 byte
+        // chunks and rekey the ChaCha cipher for each chunk to mix
+        // the data in.  This should scatter any "true entropy" in
+        // the input across the entire block.
+        while (len > 0) {
+            size_t templen = len;
+            if (templen > 48)
+                templen = 48;
+            uint8_t *output = ((uint8_t *)block) + 16;
+            len -= templen;
+            while (templen > 0) {
+                *output++ ^= *data++;
+                --templen;
+            }
+            rekey();
+        }
+    } else {
+        // There was no input data, so just force a rekey so we
+        // get some mixing of the state even without new data.
+        rekey();
+    }
+
+    // Save if this is the first time we have reached max entropy.
+    // This provides some protection if the system is powered off before
+    // the first auto-save timeout occurs.
+    if (firstSave && credits >= RNG_MAX_CREDITS) {
+        firstSave = 0;
+        save();
+    }
+}
+
+/**
+ * \brief Saves the random seed to EEPROM.
+ *
+ * During system startup, noise sources typically won't have accumulated
+ * much entropy.  But startup is usually the time when the system most
+ * needs to generate random data for session keys, IV's, and the like.
+ *
+ * The purpose of this function is to pass some of the accumulated entropy
+ * from one session to the next after a loss of power.  Thus, once the system
+ * has been running for a while it will get progressively better at generating
+ * random values and the accumulated entropy will not be completely lost.
+ *
+ * Normally it isn't necessary to call save() directly.  The loop() function
+ * will automatically save the seed on a periodic basis (default of 1 hour).
+ *
+ * The seed that is saved is generated in such a way that it cannot be used
+ * to predict random values that were generated previously or subsequently
+ * in the current session.  So a compromise of the EEPROM contents of a
+ * captured device should not result in compromise of random values
+ * that have already been generated.  However, if power is lost and the
+ * system restarted, then there will be a short period of time where the
+ * random state will be predictable from the seed.  For this reason it is
+ * very important to stir() in new noise data at startup.
+ *
+ * \sa loop(), stir()
+ */
+void RNGClass::save()
+{
+    // Generate random data from the current state and save
+    // that as the seed.  Then force a rekey.
+    ++(block[12]);
+    ChaCha::hashCore(stream, block, RNG_ROUNDS);
+#if defined(RNG_EEPROM)
+    // We shorten the seed from 48 bytes to 47 to leave room for
+    // the CRC-8 value.  We do this to align the data on an 8-byte
+    // boundary in EERPOM.
+    int address = RNG_EEPROM_ADDRESS;
+    eeprom_write_block(stream, (void *)address, SEED_SIZE - 1);
+    eeprom_write_byte((uint8_t *)(address + SEED_SIZE - 1),
+                      crypto_crc8('S', stream, SEED_SIZE - 1));
+#elif defined(RNG_DUE_TRNG)
+    unsigned posn;
+    ((uint32_t *)(RNG_SEED_ADDR))[0] = crypto_crc8('S', stream, SEED_SIZE);
+    for (posn = 0; posn < 12; ++posn)
+        ((uint32_t *)(RNG_SEED_ADDR))[posn + 1] = stream[posn];
+    for (posn = 13; posn < (RNG_FLASH_PAGE_SIZE / 4); ++posn)
+        ((uint32_t *)(RNG_SEED_ADDR))[posn + 13] = 0xFFFFFFFF;
+    eraseAndWriteSeed();
+#elif defined(RNG_ESP_NVS)
+    // Save the seed into ESP non-volatile storage (NVS).
+    nvs_handle handle = 0;
+    if (nvs_open("rng", NVS_READWRITE, &handle) == 0) {
+        nvs_erase_all(handle);
+        nvs_set_blob(handle, "seed", stream, SEED_SIZE);
+        nvs_commit(handle);
+        nvs_close(handle);
+    }
+#endif
+    rekey();
+    timer = millis();
+}
+
+/**
+ * \brief Run periodic housekeeping tasks on the random number generator.
+ *
+ * This function must be called on a regular basis from the application's
+ * main "loop()" function.
+ */
+void RNGClass::loop()
+{
+    // Stir in the entropy from all registered noise sources.
+    for (uint8_t posn = 0; posn < count; ++posn)
+        noiseSources[posn]->stir();
+
+#if defined(RNG_DUE_TRNG)
+    // If there is data available from the Arudino Due's TRNG, then XOR
+    // it with the state block and increase the entropy credit.  We don't
+    // call stir() yet because that will seriously slow down the system
+    // given how fast the TRNG is.  Instead we save up the XOR'ed TRNG
+    // data until the next rand() call and then hash it to generate the
+    // desired output.
+    //
+    // The CPU documentation claims that the TRNG output is very good so
+    // this should only make the pool more and more random as time goes on.
+    // However there is a risk that the CPU manufacturer was pressured by
+    // government or intelligence agencies to insert a back door that
+    // generates predictable output.  Or the manufacturer was overly
+    // optimistic about their TRNG design and it is actually flawed in a
+    // way they don't realise.
+    //
+    // If you are concerned about such threats, then make sure to mix in
+    // data from other noise sources.  By hashing together the TRNG with
+    // the other noise data, rand() should produce unpredictable data even
+    // if one of the sources is actually predictable.
+    if ((REG_TRNG_ISR & TRNG_ISR_DATRDY) != 0) {
+        block[4 + trngPosn] ^= REG_TRNG_ODATA;
+        if (++trngPosn >= 12)
+            trngPosn = 0;
+        if (credits < RNG_MAX_CREDITS) {
+            // Credit 1 bit of entropy for the word.  The TRNG should be
+            // better than this but it is so fast that we want to collect
+            // up more data before passing it to the application.
+            ++credits;
+        }
+        trngPending = 1;
+    }
+#elif defined(RNG_WORD_TRNG)
+    // Read a word from the TRNG and XOR it into the state.
+    block[4 + trngPosn] ^= RNG_WORD_TRNG_GET();
+    if (++trngPosn >= 12)
+        trngPosn = 0;
+    if (credits < RNG_MAX_CREDITS) {
+        // Credit 1 bit of entropy for the word.  The TRNG should be
+        // better than this but it is so fast that we want to collect
+        // up more data before passing it to the application.
+        ++credits;
+    }
+    trngPending = 1;
+#elif defined(RNG_WATCHDOG)
+    // Read the 32 bit buffer from the WDT interrupt.
+    cli();
+    if (outBits >= 32) {
+        uint32_t value = hash;
+        hash = 0;
+        outBits = 0;
+        sei();
+
+        // Final steps of the Jenkin's one-at-a-time hash function.
+        // https://en.wikipedia.org/wiki/Jenkins_hash_function
+        value += leftShift3(value);
+        value ^= rightShift11(value);
+        value += leftShift15(value);
+
+        // Credit 1 bit of entropy for each byte of input.  It can take
+        // between 30 and 40 seconds to accumulate 256 bits of credit.
+        credits += 4;
+        if (credits > RNG_MAX_CREDITS)
+            credits = RNG_MAX_CREDITS;
+
+        // XOR the word with the state.  Stir once we accumulate 48 bytes,
+        // which happens about once every 6.4 seconds.
+        block[4 + trngPosn] ^= value;
+        if (++trngPosn >= 12) {
+            trngPosn = 0;
+            trngPending = 0;
+            stir(0, 0, 0);
+        } else {
+            trngPending = 1;
+        }
+    } else {
+        sei();
+    }
+#endif
+
+    // Save the seed if the auto-save timer has expired.
+    if ((millis() - timer) >= timeout)
+        save();
+}
+
+/**
+ * \brief Destroys the data in the random number pool and the saved seed
+ * in EEPROM.
+ *
+ * This function attempts to throw away any data that could theoretically be
+ * used to predict previous and future outputs of the random number generator
+ * if the device is captured, sold, or otherwise compromised.
+ *
+ * After this function is called, begin() must be called again to
+ * re-initialize the random number generator.
+ *
+ * \note The rand() and save() functions take some care to manage the
+ * random number pool in a way that makes prediction of past outputs from a
+ * captured state very difficult.  Future outputs may be predictable if
+ * noise or other high-entropy data is not mixed in with stir() on a
+ * regular basis.
+ *
+ * \sa begin()
+ */
+void RNGClass::destroy()
+{
+    clean(block);
+    clean(stream);
+#if defined(RNG_EEPROM)
+    int address = RNG_EEPROM_ADDRESS;
+    for (int posn = 0; posn < SEED_SIZE; ++posn)
+        eeprom_write_byte((uint8_t *)(address + posn), 0xFF);
+#elif defined(RNG_DUE_TRNG)
+    for (unsigned posn = 0; posn < (RNG_FLASH_PAGE_SIZE / 4); ++posn)
+        ((uint32_t *)(RNG_SEED_ADDR))[posn] = 0xFFFFFFFF;
+    eraseAndWriteSeed();
+#elif defined(RNG_ESP_NVS)
+    nvs_handle handle = 0;
+    if (nvs_open("rng", NVS_READWRITE, &handle) == 0) {
+        nvs_erase_all(handle);
+        nvs_commit(handle);
+        nvs_close(handle);
+    }
+#endif
+    initialized = 0;
+}
+
+/**
+ * \brief Rekeys the random number generator.
+ */
+void RNGClass::rekey()
+{
+    // Rekey the cipher for the next request by generating a new block.
+    // This is intended to make it difficult to wind the random number
+    // backwards if the state is captured later.  The first 16 bytes of
+    // "block" remain set to "tagRNG".
+    ++(block[12]);
+    ChaCha::hashCore(stream, block, RNG_ROUNDS);
+    memcpy(block + 4, stream, 48);
+
+    // Permute the high word of the counter using the system microsecond
+    // counter to introduce a little bit of non-stir randomness for each
+    // request.  Note: If random data is requested on a predictable schedule
+    // then this may not help very much.  It is still necessary to stir in
+    // high quality entropy data on a regular basis using stir().
+    block[13] ^= micros();
+}
+
+/**
+ * \brief Mix in fresh data from the TRNG when rand() is called.
+ */
+void RNGClass::mixTRNG()
+{
+#if defined(RNG_DUE_TRNG)
+    // Mix in 12 words from the Due's TRNG.
+    for (int posn = 0; posn < 12; ++posn) {
+        // According to the documentation the TRNG should produce a new
+        // 32-bit random value every 84 clock cycles.  If it still hasn't
+        // produced a value after 200 iterations, then assume that the
+        // TRNG is not producing output and stop.
+        int counter;
+        for (counter = 0; counter < 200; ++counter) {
+            if ((REG_TRNG_ISR & TRNG_ISR_DATRDY) != 0)
+                break;
+        }
+        if (counter >= 200)
+            break;
+        block[posn + 4] ^= REG_TRNG_ODATA;
+    }
+#elif defined(RNG_WORD_TRNG)
+    // Read 12 words from the TRNG and XOR them into the state.
+    for (uint8_t index = 4; index < 16; ++index)
+        block[index] ^= RNG_WORD_TRNG_GET();
+#elif defined(RNG_WATCHDOG)
+    // Read the pending 32 bit buffer from the WDT interrupt and mix it in.
+    cli();
+    if (outBits >= 32) {
+        uint32_t value = hash;
+        hash = 0;
+        outBits = 0;
+        sei();
+
+        // Final steps of the Jenkin's one-at-a-time hash function.
+        // https://en.wikipedia.org/wiki/Jenkins_hash_function
+        value += leftShift3(value);
+        value ^= rightShift11(value);
+        value += leftShift15(value);
+
+        // XOR the word with the state.
+        block[4] ^= value;
+    } else {
+        sei();
+    }
+#endif
+}
diff --git a/src/modules/Crypto/RNG.h b/src/modules/Crypto/RNG.h
new file mode 100644
index 0000000..307c058
--- /dev/null
+++ b/src/modules/Crypto/RNG.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_RNG_h
+#define CRYPTO_RNG_h
+
+#include <inttypes.h>
+#include <stddef.h>
+
+class NoiseSource;
+
+class RNGClass
+{
+public:
+    RNGClass();
+    ~RNGClass();
+
+    void begin(const char *tag);
+    void addNoiseSource(NoiseSource &source);
+
+    void setAutoSaveTime(uint16_t minutes);
+
+    void rand(uint8_t *data, size_t len);
+    bool available(size_t len) const;
+
+    void stir(const uint8_t *data, size_t len, unsigned int credit = 0);
+
+    void save();
+
+    void loop();
+
+    void destroy();
+
+    static const int SEED_SIZE = 48;
+
+private:
+    uint32_t block[16];
+    uint32_t stream[16];
+    uint16_t credits : 13;
+    uint16_t firstSave : 1;
+    uint16_t initialized : 1;
+    uint16_t trngPending : 1;
+    unsigned long timer;
+    unsigned long timeout;
+    NoiseSource *noiseSources[4];
+    uint8_t count;
+    uint8_t trngPosn;
+
+    void rekey();
+    void mixTRNG();
+};
+
+extern RNGClass RNG;
+
+#endif
diff --git a/src/modules/Crypto/SHA256.cpp b/src/modules/Crypto/SHA256.cpp
new file mode 100644
index 0000000..098b2e7
--- /dev/null
+++ b/src/modules/Crypto/SHA256.cpp
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "SHA256.h"
+#include "Crypto.h"
+#include "utility/RotateUtil.h"
+#include "utility/EndianUtil.h"
+#include "utility/ProgMemUtil.h"
+#include <string.h>
+
+/**
+ * \class SHA256 SHA256.h <SHA256.h>
+ * \brief SHA-256 hash algorithm.
+ *
+ * Reference: http://en.wikipedia.org/wiki/SHA-2
+ *
+ * \sa SHA512, SHA3_256, BLAKE2s
+ */
+
+/**
+ * \brief Constructs a SHA-256 hash object.
+ */
+SHA256::SHA256()
+{
+    reset();
+}
+
+/**
+ * \brief Destroys this SHA-256 hash object after clearing
+ * sensitive information.
+ */
+SHA256::~SHA256()
+{
+    clean(state);
+}
+
+size_t SHA256::hashSize() const
+{
+    return 32;
+}
+
+size_t SHA256::blockSize() const
+{
+    return 64;
+}
+
+void SHA256::reset()
+{
+    state.h[0] = 0x6a09e667;
+    state.h[1] = 0xbb67ae85;
+    state.h[2] = 0x3c6ef372;
+    state.h[3] = 0xa54ff53a,
+    state.h[4] = 0x510e527f;
+    state.h[5] = 0x9b05688c;
+    state.h[6] = 0x1f83d9ab;
+    state.h[7] = 0x5be0cd19;
+    state.chunkSize = 0;
+    state.length = 0;
+}
+
+void SHA256::update(const void *data, size_t len)
+{
+    // Update the total length (in bits, not bytes).
+    state.length += ((uint64_t)len) << 3;
+
+    // Break the input up into 512-bit chunks and process each in turn.
+    const uint8_t *d = (const uint8_t *)data;
+    while (len > 0) {
+        uint8_t size = 64 - state.chunkSize;
+        if (size > len)
+            size = len;
+        memcpy(((uint8_t *)state.w) + state.chunkSize, d, size);
+        state.chunkSize += size;
+        len -= size;
+        d += size;
+        if (state.chunkSize == 64) {
+            processChunk();
+            state.chunkSize = 0;
+        }
+    }
+}
+
+void SHA256::finalize(void *hash, size_t len)
+{
+    // Pad the last chunk.  We may need two padding chunks if there
+    // isn't enough room in the first for the padding and length.
+    uint8_t *wbytes = (uint8_t *)state.w;
+    if (state.chunkSize <= (64 - 9)) {
+        wbytes[state.chunkSize] = 0x80;
+        memset(wbytes + state.chunkSize + 1, 0x00, 64 - 8 - (state.chunkSize + 1));
+        state.w[14] = htobe32((uint32_t)(state.length >> 32));
+        state.w[15] = htobe32((uint32_t)state.length);
+        processChunk();
+    } else {
+        wbytes[state.chunkSize] = 0x80;
+        memset(wbytes + state.chunkSize + 1, 0x00, 64 - (state.chunkSize + 1));
+        processChunk();
+        memset(wbytes, 0x00, 64 - 8);
+        state.w[14] = htobe32((uint32_t)(state.length >> 32));
+        state.w[15] = htobe32((uint32_t)state.length);
+        processChunk();
+    }
+
+    // Convert the result into big endian and return it.
+    for (uint8_t posn = 0; posn < 8; ++posn)
+        state.w[posn] = htobe32(state.h[posn]);
+
+    // Copy the hash to the caller's return buffer.
+    if (len > 32)
+        len = 32;
+    memcpy(hash, state.w, len);
+}
+
+void SHA256::clear()
+{
+    clean(state);
+    reset();
+}
+
+void SHA256::resetHMAC(const void *key, size_t keyLen)
+{
+    formatHMACKey(state.w, key, keyLen, 0x36);
+    state.length += 64 * 8;
+    processChunk();
+}
+
+void SHA256::finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen)
+{
+    uint8_t temp[32];
+    finalize(temp, sizeof(temp));
+    formatHMACKey(state.w, key, keyLen, 0x5C);
+    state.length += 64 * 8;
+    processChunk();
+    update(temp, sizeof(temp));
+    finalize(hash, hashLen);
+    clean(temp);
+}
+
+/**
+ * \brief Processes a single 512-bit chunk with the core SHA-256 algorithm.
+ *
+ * Reference: http://en.wikipedia.org/wiki/SHA-2
+ */
+void SHA256::processChunk()
+{
+    // Round constants for SHA-256.
+    static uint32_t const k[64] PROGMEM = {
+        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+        0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+        0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+        0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+        0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+        0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+        0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+        0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+        0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+    };
+
+    // Convert the first 16 words from big endian to host byte order.
+    uint8_t index;
+    for (index = 0; index < 16; ++index)
+        state.w[index] = be32toh(state.w[index]);
+
+    // Initialise working variables to the current hash value.
+    uint32_t a = state.h[0];
+    uint32_t b = state.h[1];
+    uint32_t c = state.h[2];
+    uint32_t d = state.h[3];
+    uint32_t e = state.h[4];
+    uint32_t f = state.h[5];
+    uint32_t g = state.h[6];
+    uint32_t h = state.h[7];
+
+    // Perform the first 16 rounds of the compression function main loop.
+    uint32_t temp1, temp2;
+    for (index = 0; index < 16; ++index) {
+        temp1 = h + pgm_read_dword(k + index) + state.w[index] +
+                (rightRotate6(e) ^ rightRotate11(e) ^ rightRotate25(e)) +
+                ((e & f) ^ ((~e) & g));
+        temp2 = (rightRotate2(a) ^ rightRotate13(a) ^ rightRotate22(a)) +
+                ((a & b) ^ (a & c) ^ (b & c));
+        h = g;
+        g = f;
+        f = e;
+        e = d + temp1;
+        d = c;
+        c = b;
+        b = a;
+        a = temp1 + temp2;
+    }
+
+    // Perform the 48 remaining rounds.  We expand the first 16 words to
+    // 64 in-place in the "w" array.  This saves 192 bytes of memory
+    // that would have otherwise need to be allocated to the "w" array.
+    for (; index < 64; ++index) {
+        // Expand the next word.
+        temp1 = state.w[(index - 15) & 0x0F];
+        temp2 = state.w[(index - 2) & 0x0F];
+        temp1 = state.w[index & 0x0F] =
+            state.w[(index - 16) & 0x0F] + state.w[(index - 7) & 0x0F] +
+                (rightRotate7(temp1) ^ rightRotate18(temp1) ^ (temp1 >> 3)) +
+                (rightRotate17(temp2) ^ rightRotate19(temp2) ^ (temp2 >> 10));
+
+        // Perform the round.
+        temp1 = h + pgm_read_dword(k + index) + temp1 +
+                (rightRotate6(e) ^ rightRotate11(e) ^ rightRotate25(e)) +
+                ((e & f) ^ ((~e) & g));
+        temp2 = (rightRotate2(a) ^ rightRotate13(a) ^ rightRotate22(a)) +
+                ((a & b) ^ (a & c) ^ (b & c));
+        h = g;
+        g = f;
+        f = e;
+        e = d + temp1;
+        d = c;
+        c = b;
+        b = a;
+        a = temp1 + temp2;
+    }
+
+    // Add the compressed chunk to the current hash value.
+    state.h[0] += a;
+    state.h[1] += b;
+    state.h[2] += c;
+    state.h[3] += d;
+    state.h[4] += e;
+    state.h[5] += f;
+    state.h[6] += g;
+    state.h[7] += h;
+
+    // Attempt to clean up the stack.
+    a = b = c = d = e = f = g = h = temp1 = temp2 = 0;
+}
diff --git a/src/modules/Crypto/SHA256.h b/src/modules/Crypto/SHA256.h
new file mode 100644
index 0000000..246ea58
--- /dev/null
+++ b/src/modules/Crypto/SHA256.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_SHA256_h
+#define CRYPTO_SHA256_h
+
+#include "Hash.h"
+
+class SHA256 : public Hash
+{
+public:
+    SHA256();
+    virtual ~SHA256();
+
+    size_t hashSize() const;
+    size_t blockSize() const;
+
+    void reset();
+    void update(const void *data, size_t len);
+    void finalize(void *hash, size_t len);
+
+    void clear();
+
+    void resetHMAC(const void *key, size_t keyLen);
+    void finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen);
+
+private:
+    struct {
+        uint32_t h[8];
+        uint32_t w[16];
+        uint64_t length;
+        uint8_t chunkSize;
+    } state;
+
+    void processChunk();
+};
+
+#endif
diff --git a/src/modules/Crypto/SHA3.cpp b/src/modules/Crypto/SHA3.cpp
new file mode 100644
index 0000000..74353c4
--- /dev/null
+++ b/src/modules/Crypto/SHA3.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "SHA3.h"
+#include "Crypto.h"
+
+/**
+ * \class SHA3_256 SHA3.h <SHA3.h>
+ * \brief SHA3-256 hash algorithm.
+ *
+ * Reference: http://en.wikipedia.org/wiki/SHA-3
+ *
+ * \sa SHA3_512
+ */
+
+/**
+ * \brief Constructs a new SHA3-256 hash object.
+ */
+SHA3_256::SHA3_256()
+{
+    core.setCapacity(512);
+}
+
+/**
+ * \brief Destroys this hash object after clearing sensitive information.
+ */
+SHA3_256::~SHA3_256()
+{
+    // The destructor for the KeccakCore object will do most of the work.
+}
+
+size_t SHA3_256::hashSize() const
+{
+    return 32;
+}
+
+size_t SHA3_256::blockSize() const
+{
+    return core.blockSize();
+}
+
+void SHA3_256::reset()
+{
+    core.reset();
+}
+
+void SHA3_256::update(const void *data, size_t len)
+{
+    core.update(data, len);
+}
+
+void SHA3_256::finalize(void *hash, size_t len)
+{
+    // Pad the final block and then extract the hash value.
+    core.pad(0x06);
+    core.extract(hash, len);
+}
+
+void SHA3_256::clear()
+{
+    core.clear();
+}
+
+void SHA3_256::resetHMAC(const void *key, size_t keyLen)
+{
+    core.setHMACKey(key, keyLen, 0x36, 32);
+}
+
+void SHA3_256::finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen)
+{
+    uint8_t temp[32];
+    finalize(temp, sizeof(temp));
+    core.setHMACKey(key, keyLen, 0x5C, 32);
+    core.update(temp, sizeof(temp));
+    finalize(hash, hashLen);
+    clean(temp);
+}
+
+/**
+ * \class SHA3_512 SHA3.h <SHA3.h>
+ * \brief SHA3-512 hash algorithm.
+ *
+ * Reference: http://en.wikipedia.org/wiki/SHA-3
+ *
+ * \sa SHA3_256
+ */
+
+/**
+ * \brief Constructs a new SHA3-512 hash object.
+ */
+SHA3_512::SHA3_512()
+{
+    core.setCapacity(1024);
+}
+
+/**
+ * \brief Destroys this hash object after clearing sensitive information.
+ */
+SHA3_512::~SHA3_512()
+{
+    // The destructor for the KeccakCore object will do most of the work.
+}
+
+size_t SHA3_512::hashSize() const
+{
+    return 64;
+}
+
+size_t SHA3_512::blockSize() const
+{
+    return core.blockSize();
+}
+
+void SHA3_512::reset()
+{
+    core.reset();
+}
+
+void SHA3_512::update(const void *data, size_t len)
+{
+    core.update(data, len);
+}
+
+void SHA3_512::finalize(void *hash, size_t len)
+{
+    // Pad the final block and then extract the hash value.
+    core.pad(0x06);
+    core.extract(hash, len);
+}
+
+void SHA3_512::clear()
+{
+    core.clear();
+}
+
+void SHA3_512::resetHMAC(const void *key, size_t keyLen)
+{
+    core.setHMACKey(key, keyLen, 0x36, 64);
+}
+
+void SHA3_512::finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen)
+{
+    uint8_t temp[64];
+    finalize(temp, sizeof(temp));
+    core.setHMACKey(key, keyLen, 0x5C, 64);
+    core.update(temp, sizeof(temp));
+    finalize(hash, hashLen);
+    clean(temp);
+}
diff --git a/src/modules/Crypto/SHA3.h b/src/modules/Crypto/SHA3.h
new file mode 100644
index 0000000..76bb031
--- /dev/null
+++ b/src/modules/Crypto/SHA3.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_SHA3_h
+#define CRYPTO_SHA3_h
+
+#include "KeccakCore.h"
+#include "Hash.h"
+
+class SHA3_256 : public Hash
+{
+public:
+    SHA3_256();
+    virtual ~SHA3_256();
+
+    size_t hashSize() const;
+    size_t blockSize() const;
+
+    void reset();
+    void update(const void *data, size_t len);
+    void finalize(void *hash, size_t len);
+
+    void clear();
+
+    void resetHMAC(const void *key, size_t keyLen);
+    void finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen);
+
+private:
+    KeccakCore core;
+};
+
+class SHA3_512 : public Hash
+{
+public:
+    SHA3_512();
+    virtual ~SHA3_512();
+
+    size_t hashSize() const;
+    size_t blockSize() const;
+
+    void reset();
+    void update(const void *data, size_t len);
+    void finalize(void *hash, size_t len);
+
+    void clear();
+
+    void resetHMAC(const void *key, size_t keyLen);
+    void finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen);
+
+private:
+    KeccakCore core;
+};
+
+#endif
diff --git a/src/modules/Crypto/SHA512.cpp b/src/modules/Crypto/SHA512.cpp
new file mode 100644
index 0000000..71cf019
--- /dev/null
+++ b/src/modules/Crypto/SHA512.cpp
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "SHA512.h"
+#include "Crypto.h"
+#include "utility/RotateUtil.h"
+#include "utility/EndianUtil.h"
+#include "utility/ProgMemUtil.h"
+#include <string.h>
+
+/**
+ * \class SHA512 SHA512.h <SHA512.h>
+ * \brief SHA-512 hash algorithm.
+ *
+ * Reference: http://en.wikipedia.org/wiki/SHA-2
+ *
+ * \sa SHA256, SHA3_512, BLAKE2b
+ */
+
+/**
+ * \brief Constructs a SHA-512 hash object.
+ */
+SHA512::SHA512()
+{
+    reset();
+}
+
+/**
+ * \brief Destroys this SHA-512 hash object after clearing
+ * sensitive information.
+ */
+SHA512::~SHA512()
+{
+    clean(state);
+}
+
+size_t SHA512::hashSize() const
+{
+    return 64;
+}
+
+size_t SHA512::blockSize() const
+{
+    return 128;
+}
+
+void SHA512::reset()
+{
+    static uint64_t const hashStart[8] PROGMEM = {
+        0x6A09E667F3BCC908ULL, 0xBB67AE8584CAA73BULL, 0x3C6EF372FE94F82BULL,
+        0xA54FF53A5F1D36F1ULL, 0x510E527FADE682D1ULL, 0x9B05688C2B3E6C1FULL,
+        0x1F83D9ABFB41BD6BULL, 0x5BE0CD19137E2179ULL
+    };
+    memcpy_P(state.h, hashStart, sizeof(hashStart));
+    state.chunkSize = 0;
+    state.lengthLow = 0;
+    state.lengthHigh = 0;
+}
+
+void SHA512::update(const void *data, size_t len)
+{
+    // Update the total length in bits, not bytes.
+    uint64_t temp = state.lengthLow;
+    state.lengthLow += (((uint64_t)len) << 3);
+    state.lengthHigh += (((uint64_t)len) >> 61);
+    if (state.lengthLow < temp)
+        ++state.lengthHigh;
+
+    // Break the input up into 1024-bit chunks and process each in turn.
+    const uint8_t *d = (const uint8_t *)data;
+    while (len > 0) {
+        uint8_t size = 128 - state.chunkSize;
+        if (size > len)
+            size = len;
+        memcpy(((uint8_t *)state.w) + state.chunkSize, d, size);
+        state.chunkSize += size;
+        len -= size;
+        d += size;
+        if (state.chunkSize == 128) {
+            processChunk();
+            state.chunkSize = 0;
+        }
+    }
+}
+
+void SHA512::finalize(void *hash, size_t len)
+{
+    // Pad the last chunk.  We may need two padding chunks if there
+    // isn't enough room in the first for the padding and length.
+    uint8_t *wbytes = (uint8_t *)state.w;
+    if (state.chunkSize <= (128 - 17)) {
+        wbytes[state.chunkSize] = 0x80;
+        memset(wbytes + state.chunkSize + 1, 0x00, 128 - 16 - (state.chunkSize + 1));
+        state.w[14] = htobe64(state.lengthHigh);
+        state.w[15] = htobe64(state.lengthLow);
+        processChunk();
+    } else {
+        wbytes[state.chunkSize] = 0x80;
+        memset(wbytes + state.chunkSize + 1, 0x00, 128 - (state.chunkSize + 1));
+        processChunk();
+        memset(wbytes, 0x00, 128 - 16);
+        state.w[14] = htobe64(state.lengthHigh);
+        state.w[15] = htobe64(state.lengthLow);
+        processChunk();
+    }
+
+    // Convert the result into big endian and return it.
+    for (uint8_t posn = 0; posn < 8; ++posn)
+        state.w[posn] = htobe64(state.h[posn]);
+
+    // Copy the hash to the caller's return buffer.
+    if (len > 64)
+        len = 64;
+    memcpy(hash, state.w, len);
+}
+
+void SHA512::clear()
+{
+    clean(state);
+    reset();
+}
+
+void SHA512::resetHMAC(const void *key, size_t keyLen)
+{
+    formatHMACKey(state.w, key, keyLen, 0x36);
+    state.lengthLow += 128 * 8;
+    processChunk();
+}
+
+void SHA512::finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen)
+{
+    uint8_t temp[64];
+    finalize(temp, sizeof(temp));
+    formatHMACKey(state.w, key, keyLen, 0x5C);
+    state.lengthLow += 128 * 8;
+    processChunk();
+    update(temp, sizeof(temp));
+    finalize(hash, hashLen);
+    clean(temp);
+}
+
+/**
+ * \brief Processes a single 1024-bit chunk with the core SHA-512 algorithm.
+ *
+ * Reference: http://en.wikipedia.org/wiki/SHA-2
+ */
+void SHA512::processChunk()
+{
+    // Round constants for SHA-512.
+    static uint64_t const k[80] PROGMEM = {
+        0x428A2F98D728AE22ULL, 0x7137449123EF65CDULL, 0xB5C0FBCFEC4D3B2FULL,
+        0xE9B5DBA58189DBBCULL, 0x3956C25BF348B538ULL, 0x59F111F1B605D019ULL,
+        0x923F82A4AF194F9BULL, 0xAB1C5ED5DA6D8118ULL, 0xD807AA98A3030242ULL,
+        0x12835B0145706FBEULL, 0x243185BE4EE4B28CULL, 0x550C7DC3D5FFB4E2ULL,
+        0x72BE5D74F27B896FULL, 0x80DEB1FE3B1696B1ULL, 0x9BDC06A725C71235ULL,
+        0xC19BF174CF692694ULL, 0xE49B69C19EF14AD2ULL, 0xEFBE4786384F25E3ULL,
+        0x0FC19DC68B8CD5B5ULL, 0x240CA1CC77AC9C65ULL, 0x2DE92C6F592B0275ULL,
+        0x4A7484AA6EA6E483ULL, 0x5CB0A9DCBD41FBD4ULL, 0x76F988DA831153B5ULL,
+        0x983E5152EE66DFABULL, 0xA831C66D2DB43210ULL, 0xB00327C898FB213FULL,
+        0xBF597FC7BEEF0EE4ULL, 0xC6E00BF33DA88FC2ULL, 0xD5A79147930AA725ULL,
+        0x06CA6351E003826FULL, 0x142929670A0E6E70ULL, 0x27B70A8546D22FFCULL,
+        0x2E1B21385C26C926ULL, 0x4D2C6DFC5AC42AEDULL, 0x53380D139D95B3DFULL,
+        0x650A73548BAF63DEULL, 0x766A0ABB3C77B2A8ULL, 0x81C2C92E47EDAEE6ULL,
+        0x92722C851482353BULL, 0xA2BFE8A14CF10364ULL, 0xA81A664BBC423001ULL,
+        0xC24B8B70D0F89791ULL, 0xC76C51A30654BE30ULL, 0xD192E819D6EF5218ULL,
+        0xD69906245565A910ULL, 0xF40E35855771202AULL, 0x106AA07032BBD1B8ULL,
+        0x19A4C116B8D2D0C8ULL, 0x1E376C085141AB53ULL, 0x2748774CDF8EEB99ULL,
+        0x34B0BCB5E19B48A8ULL, 0x391C0CB3C5C95A63ULL, 0x4ED8AA4AE3418ACBULL,
+        0x5B9CCA4F7763E373ULL, 0x682E6FF3D6B2B8A3ULL, 0x748F82EE5DEFB2FCULL,
+        0x78A5636F43172F60ULL, 0x84C87814A1F0AB72ULL, 0x8CC702081A6439ECULL,
+        0x90BEFFFA23631E28ULL, 0xA4506CEBDE82BDE9ULL, 0xBEF9A3F7B2C67915ULL,
+        0xC67178F2E372532BULL, 0xCA273ECEEA26619CULL, 0xD186B8C721C0C207ULL,
+        0xEADA7DD6CDE0EB1EULL, 0xF57D4F7FEE6ED178ULL, 0x06F067AA72176FBAULL,
+        0x0A637DC5A2C898A6ULL, 0x113F9804BEF90DAEULL, 0x1B710B35131C471BULL,
+        0x28DB77F523047D84ULL, 0x32CAAB7B40C72493ULL, 0x3C9EBE0A15C9BEBCULL,
+        0x431D67C49C100D4CULL, 0x4CC5D4BECB3E42B6ULL, 0x597F299CFC657E2AULL,
+        0x5FCB6FAB3AD6FAECULL, 0x6C44198C4A475817ULL
+    };
+
+    // Convert the first 16 words from big endian to host byte order.
+    uint8_t index;
+    for (index = 0; index < 16; ++index)
+        state.w[index] = be64toh(state.w[index]);
+
+    // Initialise working variables to the current hash value.
+    uint64_t a = state.h[0];
+    uint64_t b = state.h[1];
+    uint64_t c = state.h[2];
+    uint64_t d = state.h[3];
+    uint64_t e = state.h[4];
+    uint64_t f = state.h[5];
+    uint64_t g = state.h[6];
+    uint64_t h = state.h[7];
+
+    // Perform the first 16 rounds of the compression function main loop.
+    uint64_t temp1, temp2;
+    for (index = 0; index < 16; ++index) {
+        temp1 = h + pgm_read_qword(k + index) + state.w[index] +
+                (rightRotate14_64(e) ^ rightRotate18_64(e) ^
+                 rightRotate41_64(e)) + ((e & f) ^ ((~e) & g));
+        temp2 = (rightRotate28_64(a) ^ rightRotate34_64(a) ^
+                 rightRotate39_64(a)) + ((a & b) ^ (a & c) ^ (b & c));
+        h = g;
+        g = f;
+        f = e;
+        e = d + temp1;
+        d = c;
+        c = b;
+        b = a;
+        a = temp1 + temp2;
+    }
+
+    // Perform the 64 remaining rounds.  We expand the first 16 words to
+    // 80 in-place in the "w" array.  This saves 512 bytes of memory
+    // that would have otherwise need to be allocated to the "w" array.
+    for (; index < 80; ++index) {
+        // Expand the next word.
+        temp1 = state.w[(index - 15) & 0x0F];
+        temp2 = state.w[(index - 2) & 0x0F];
+        temp1 = state.w[index & 0x0F] =
+            state.w[(index - 16) & 0x0F] + state.w[(index - 7) & 0x0F] +
+                (rightRotate1_64(temp1) ^ rightRotate8_64(temp1) ^
+                 (temp1 >> 7)) +
+                (rightRotate19_64(temp2) ^ rightRotate61_64(temp2) ^
+                 (temp2 >> 6));
+
+        // Perform the round.
+        temp1 = h + pgm_read_qword(k + index) + temp1 +
+                (rightRotate14_64(e) ^ rightRotate18_64(e) ^
+                 rightRotate41_64(e)) + ((e & f) ^ ((~e) & g));
+        temp2 = (rightRotate28_64(a) ^ rightRotate34_64(a) ^
+                 rightRotate39_64(a)) + ((a & b) ^ (a & c) ^ (b & c));
+        h = g;
+        g = f;
+        f = e;
+        e = d + temp1;
+        d = c;
+        c = b;
+        b = a;
+        a = temp1 + temp2;
+    }
+
+    // Add the compressed chunk to the current hash value.
+    state.h[0] += a;
+    state.h[1] += b;
+    state.h[2] += c;
+    state.h[3] += d;
+    state.h[4] += e;
+    state.h[5] += f;
+    state.h[6] += g;
+    state.h[7] += h;
+
+    // Attempt to clean up the stack.
+    a = b = c = d = e = f = g = h = temp1 = temp2 = 0;
+}
diff --git a/src/modules/Crypto/SHA512.h b/src/modules/Crypto/SHA512.h
new file mode 100644
index 0000000..d1a069f
--- /dev/null
+++ b/src/modules/Crypto/SHA512.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_SHA512_h
+#define CRYPTO_SHA512_h
+
+#include "Hash.h"
+
+class Ed25519;
+
+class SHA512 : public Hash
+{
+public:
+    SHA512();
+    virtual ~SHA512();
+
+    size_t hashSize() const;
+    size_t blockSize() const;
+
+    void reset();
+    void update(const void *data, size_t len);
+    void finalize(void *hash, size_t len);
+
+    void clear();
+
+    void resetHMAC(const void *key, size_t keyLen);
+    void finalizeHMAC(const void *key, size_t keyLen, void *hash, size_t hashLen);
+
+private:
+    struct {
+        uint64_t h[8];
+        uint64_t w[16];
+        uint64_t lengthLow;
+        uint64_t lengthHigh;
+        uint8_t chunkSize;
+    } state;
+
+    void processChunk();
+
+    friend class Ed25519;
+};
+
+#endif
diff --git a/src/modules/Crypto/SHAKE.cpp b/src/modules/Crypto/SHAKE.cpp
new file mode 100644
index 0000000..b813675
--- /dev/null
+++ b/src/modules/Crypto/SHAKE.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "SHAKE.h"
+
+/**
+ * \class SHAKE SHAKE.h <SHAKE.h>
+ * \brief Abstract base class for the SHAKE Extendable-Output Functions (XOFs).
+ *
+ * Reference: http://en.wikipedia.org/wiki/SHA-3
+ *
+ * \sa SHAKE256, SHAKE128, SHA3_256
+ */
+
+/**
+ * \brief Constructs a SHAKE object.
+ *
+ * \param capacity The capacity of the Keccak sponge function in bits which
+ * should be a multiple of 64 and between 64 and 1536.
+ */
+SHAKE::SHAKE(size_t capacity)
+    : finalized(false)
+{
+    core.setCapacity(capacity);
+}
+
+/**
+ * \brief Destroys this SHAKE object after clearing all sensitive information.
+ */
+SHAKE::~SHAKE()
+{
+}
+
+size_t SHAKE::blockSize() const
+{
+    return core.blockSize();
+}
+
+void SHAKE::reset()
+{
+    core.reset();
+    finalized = false;
+}
+
+void SHAKE::update(const void *data, size_t len)
+{
+    if (finalized)
+        reset();
+    core.update(data, len);
+}
+
+void SHAKE::extend(uint8_t *data, size_t len)
+{
+    if (!finalized) {
+        core.pad(0x1F);
+        finalized = true;
+    }
+    core.extract(data, len);
+}
+
+void SHAKE::encrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+    if (!finalized) {
+        core.pad(0x1F);
+        finalized = true;
+    }
+    core.encrypt(output, input, len);
+}
+
+void SHAKE::clear()
+{
+    core.clear();
+    finalized = false;
+}
+
+/**
+ * \class SHAKE128 SHAKE.h <SHAKE.h>
+ * \brief SHAKE Extendable-Output Function (XOF) with 128-bit security.
+ *
+ * Reference: http://en.wikipedia.org/wiki/SHA-3
+ *
+ * \sa SHAKE256, SHAKE, SHA3_256
+ */
+
+/**
+ * \fn SHAKE128::SHAKE128()
+ * \brief Constructs a SHAKE object with 128-bit security.
+ */
+
+/**
+ * \brief Destroys this SHAKE128 object after clearing all sensitive
+ * information.
+ */
+SHAKE128::~SHAKE128()
+{
+}
+
+/**
+ * \class SHAKE256 SHAKE.h <SHAKE.h>
+ * \brief SHAKE Extendable-Output Function (XOF) with 256-bit security.
+ *
+ * Reference: http://en.wikipedia.org/wiki/SHA-3
+ *
+ * \sa SHAKE128, SHAKE, SHA3_256
+ */
+
+/**
+ * \fn SHAKE256::SHAKE256()
+ * \brief Constructs a SHAKE object with 256-bit security.
+ */
+
+/**
+ * \brief Destroys this SHAKE256 object after clearing all sensitive
+ * information.
+ */
+SHAKE256::~SHAKE256()
+{
+}
diff --git a/src/modules/Crypto/SHAKE.h b/src/modules/Crypto/SHAKE.h
new file mode 100644
index 0000000..83826e6
--- /dev/null
+++ b/src/modules/Crypto/SHAKE.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_SHAKE_h
+#define CRYPTO_SHAKE_h
+
+#include "XOF.h"
+#include "KeccakCore.h"
+
+class SHAKE : public XOF
+{
+public:
+    virtual ~SHAKE();
+
+    size_t blockSize() const;
+
+    void reset();
+    void update(const void *data, size_t len);
+
+    void extend(uint8_t *data, size_t len);
+    void encrypt(uint8_t *output, const uint8_t *input, size_t len);
+
+    void clear();
+
+protected:
+    SHAKE(size_t capacity);
+
+private:
+    KeccakCore core;
+    bool finalized;
+};
+
+class SHAKE128 : public SHAKE
+{
+public:
+    SHAKE128() : SHAKE(256) {}
+    virtual ~SHAKE128();
+};
+
+class SHAKE256 : public SHAKE
+{
+public:
+    SHAKE256() : SHAKE(512) {}
+    virtual ~SHAKE256();
+};
+
+#endif
diff --git a/src/modules/Crypto/XOF.cpp b/src/modules/Crypto/XOF.cpp
new file mode 100644
index 0000000..2462a81
--- /dev/null
+++ b/src/modules/Crypto/XOF.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "XOF.h"
+
+/**
+ * \class XOF XOF.h <XOF.h>
+ * \brief Abstract base class for Extendable-Output Functions (XOFs).
+ *
+ * Extendable-Output Functions, or XOFs, are a new class of cryptographic
+ * primitive that was defined by NIST during the SHA-3 standardization
+ * process.  Essentially an XOF is a hash algorithm that has an
+ * arbitrary-length output instead of a fixed-length digest.
+ *
+ * XOFs can be used for a variety of cryptographic tasks:
+ *
+ * \li Mask generation functions for RSA OAEP style padding.
+ * \li Key derivation functions for expanding key seed material into
+ * arbitrary amounts of keying material for a secure session.
+ * \li Stream ciphers based on a key and IV.
+ *
+ * To use an XOF, it is first reset() and then data is added via multiple
+ * calls to update():
+ *
+ * \code
+ * SHAKE256 xof;
+ * xof.reset();
+ * xof.update(data1, sizeof(data1));
+ * xof.update(data2, sizeof(data2));
+ * ...
+ * \endcode
+ *
+ * Once all input data has been added, the XOF switches into extend mode
+ * to generate the arbitrary-length output data:
+ *
+ * \code
+ * xof.extend(output1, sizeof(output1));
+ * xof.extend(output2, sizeof(output2));
+ * ...
+ * \endcode
+ *
+ * Mask generation and key derivation is achieved as follows, where the
+ * key is unique for each invocation:
+ *
+ * \code
+ * SHAKE256 xof;
+ * xof.reset();
+ * xof.update(key, sizeof(key));
+ * xof.extend(output, sizeof(output));
+ * \endcode
+ *
+ * Stream ciphers can be constructed as follows, using the special
+ * encrypt() function that XOR's the output of extend() with the
+ * input plaintext to generate the output ciphertext (or alternatively
+ * XOR's the output of extend() with the ciphertext to recover the
+ * plaintext):
+ *
+ * \code
+ * SHAKE256 xof;
+ * xof.reset();
+ * xof.update(key, sizeof(key));
+ * xof.update(iv, sizeof(iv));
+ * xof.encrypt(output1, input1, sizeof(input1));
+ * xof.encrypt(output2, input2, sizeof(input2));
+ * ...
+ * \endcode
+ *
+ * If the key is reused, then the IV must be different for each session
+ * or the encryption scheme can be easily broken.  It is better to
+ * generate a new key and IV combination for every session.
+ *
+ * It may also be a good idea to include some tag information with the input
+ * data to distinguish different uses of the XOF.  For example:
+ *
+ * \code
+ * SHAKE256 xof;
+ * xof.reset();
+ * xof.update(key, sizeof(key));
+ * xof.update(iv, sizeof(iv));
+ * xof.update("MyCrypt", 7);
+ * xof.encrypt(output, input, sizeof(input));
+ * \endcode
+ *
+ * If the same key and IV was used with a different package, then it would
+ * not generate the same output as "MyCrypt".
+ *
+ * NIST warns that XOFs should not be used in place of hash functions.
+ * This is because of related outputs: if the same input is provided to
+ * an XOF with different output lengths, then the shorter output will
+ * be a prefix of the larger.  This breaks the expected collision-resistance
+ * of regular hash functions.  There is typically no need to use an XOF
+ * for hashing because NIST has already defined SHA3_256 and SHA3_512
+ * for that purpose.
+ *
+ * Reference: http://en.wikipedia.org/wiki/SHA-3
+ *
+ * \sa SHAKE256, SHAKE128, SHA3_256
+ */
+
+/**
+ * \brief Constructs a new XOF object.
+ */
+XOF::XOF()
+{
+}
+
+/**
+ * \brief Destroys this XOF object.
+ *
+ * \note Subclasses are responsible for clearing any sensitive data
+ * that remains in the XOF object when it is destroyed.
+ *
+ * \sa clear()
+ */
+XOF::~XOF()
+{
+}
+
+/**
+ * \fn size_t XOF::blockSize() const
+ * \brief Size of the internal block used by the XOF algorithm, in bytes.
+ *
+ * \sa update()
+ */
+
+/**
+ * \fn void XOF::reset()
+ * \brief Resets the XOF ready for a new session.
+ *
+ * \sa update(), extend(), encrypt()
+ */
+
+/**
+ * \fn void XOF::update(const void *data, size_t len)
+ * \brief Updates the XOF with more data.
+ *
+ * \param data Data to be hashed.
+ * \param len Number of bytes of data to be added to the XOF.
+ *
+ * If extend() or encrypt() has already been called, then the behavior of
+ * update() will be undefined.  Call reset() first to start a new session.
+ *
+ * \sa reset(), extend(), encrypt()
+ */
+
+/**
+ * \fn void XOF::extend(uint8_t *data, size_t len)
+ * \brief Generates extendable output from this XOF.
+ *
+ * \param data The data buffer to be filled.
+ * \param len The number of bytes to write to \a data.
+ *
+ * \sa reset(), update(), encrypt()
+ */
+
+/**
+ * \fn void XOF::encrypt(uint8_t *output, const uint8_t *input, size_t len)
+ * \brief Encrypts an input buffer with extendable output from this XOF.
+ *
+ * \param output The output buffer to write to, which may be the same
+ * buffer as \a input.  The \a output buffer must have at least as many
+ * bytes as the \a input buffer.
+ * \param input The input buffer to read from.
+ * \param len The number of bytes to encrypt.
+ *
+ * This function is a convenience that generates data with extend() and
+ * then XOR's it with the contents of \a input to generate the \a output.
+ * This function can also be used to decrypt.
+ *
+ * The encrypt() function can be called multiple times with different
+ * regions of the plaintext data.
+ *
+ * \sa reset(), update(), extend(), decrypt()
+ */
+
+/**
+ * \fn void XOF::decrypt(uint8_t *output, const uint8_t *input, size_t len)
+ * \brief Decrypts an input buffer with extendable output from this XOF.
+ *
+ * \param output The output buffer to write to, which may be the same
+ * buffer as \a input.  The \a output buffer must have at least as many
+ * bytes as the \a input buffer.
+ * \param input The input buffer to read from.
+ * \param len The number of bytes to encrypt.
+ *
+ * This is a convenience function that merely calls encrypt().
+ *
+ * \sa reset(), update(), extend(), encrypt()
+ */
+
+/**
+ * \fn void XOF::clear()
+ * \brief Clears the hash state, removing all sensitive data, and then
+ * resets the XOF ready for a new session.
+ *
+ * \sa reset()
+ */
diff --git a/src/modules/Crypto/XOF.h b/src/modules/Crypto/XOF.h
new file mode 100644
index 0000000..fc72856
--- /dev/null
+++ b/src/modules/Crypto/XOF.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_XOF_h
+#define CRYPTO_XOF_h
+
+#include <inttypes.h>
+#include <stddef.h>
+
+class XOF
+{
+public:
+    XOF();
+    virtual ~XOF();
+
+    virtual size_t blockSize() const = 0;
+
+    virtual void reset() = 0;
+    virtual void update(const void *data, size_t len) = 0;
+
+    virtual void extend(uint8_t *data, size_t len) = 0;
+    virtual void encrypt(uint8_t *output, const uint8_t *input, size_t len) = 0;
+
+    inline void decrypt(uint8_t *output, const uint8_t *input, size_t len)
+    {
+        encrypt(output, input, len);
+    }
+
+    virtual void clear() = 0;
+};
+
+#endif
diff --git a/src/modules/Crypto/XTS.cpp b/src/modules/Crypto/XTS.cpp
new file mode 100644
index 0000000..87c4044
--- /dev/null
+++ b/src/modules/Crypto/XTS.cpp
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "XTS.h"
+#include "Crypto.h"
+#include "GF128.h"
+#include <string.h>
+
+/**
+ * \class XTSCommon XTS.h <XTS.h>
+ * \brief Concrete base class to assist with implementing XTS mode for
+ * 128-bit block ciphers.
+ *
+ * References: <a href="http://libeccio.di.unisa.it/Crypto14/Lab/p1619.pdf">IEEE Std. 1619-2007</a>, <a href="http://csrc.nist.gov/publications/nistpubs/800-38E/nist-sp-800-38E.pdf">NIST SP 800-38E</a>, a href="http://web.cs.ucdavis.edu/~rogaway/papers/offsets.pdf">XEX</a>.
+ *
+ * \sa XTS, XTSSingleKey
+ */
+
+/**
+ * \brief Constructs an XTS object with a default sector size of 512 bytes.
+ */
+XTSCommon::XTSCommon()
+    : sectSize(512)
+{
+}
+
+/**
+ * \brief Clears all sensitive information and destroys this object.
+ */
+XTSCommon::~XTSCommon()
+{
+    clean(twk);
+}
+
+/**
+ * \brief Gets the size of the key for XTS mode.
+ *
+ * The key size for XTS mode is twice the size of the underlying
+ * block cipher key size.
+ *
+ * \sa setKey(), tweakSize()
+ */
+size_t XTSCommon::keySize() const
+{
+    return blockCipher1->keySize() * 2;
+}
+
+/**
+ * \brief Gets the maximum supported size for the tweak.
+ *
+ * This function returns 16, which indicates that any tweak up to 16 bytes
+ * in size can be specified via setTweak().
+ */
+size_t XTSCommon::tweakSize() const
+{
+    return 16;
+}
+
+/**
+ * \fn size_t XTSCommon::sectorSize() const
+ * \brief Gets the size of sectors encrypted or decrypted by this class.
+ *
+ * The default value is 512 bytes.
+ *
+ * \sa setSectorSize()
+ */
+
+/**
+ * \brief Sets the size of sectors encrypted or decrypted by this class.
+ *
+ * \param size The sector size in bytes, which must be greater than or
+ * equal to 16.
+ *
+ * \return Returns false if \a size is less than 16.
+ *
+ * \sa sectorSize(), encryptSector()
+ */
+bool XTSCommon::setSectorSize(size_t size)
+{
+    if (size < 16)
+        return false;
+    sectSize = size;
+    return true;
+}
+
+/**
+ * \brief Sets the key to use for XTS mode.
+ *
+ * \param key Points to the key.
+ * \param len The size of the key in bytes which must be twice the
+ * size of the underlying block cipher's key size.
+ *
+ * \return Returns true if the key was set or false if \a len was incorrect.
+ *
+ * This function should be followed by a call to setTweak() to specify
+ * the sector-specific tweak.
+ *
+ * \sa keySize(), setTweak(), encryptSector()
+ */
+bool XTSCommon::setKey(const uint8_t *key, size_t len)
+{
+    if (!blockCipher1->setKey(key, len / 2))
+        return false;
+    return blockCipher2->setKey(key + len / 2, len - (len / 2));
+}
+
+/**
+ * \brief Sets the tweak value for the current sector to encrypt or decrypt.
+ *
+ * \param tweak Points to the tweak.
+ * \param len The length of the tweak which must be less than or equal to 16.
+ *
+ * \return Returns true if the tweak was set or false if \a len was incorrect.
+ *
+ * If \a len is less than 16, then the \a tweak will be zero-padded to
+ * 16 bytes.
+ *
+ * The \a tweak is encrypted with the second half of the XTS key to generate
+ * the actual tweak value for the sector.
+ *
+ * \sa tweakSize(), setKey(), encryptSector()
+ */
+bool XTSCommon::setTweak(const uint8_t *tweak, size_t len)
+{
+    if (len > 16)
+        return false;
+    memcpy(twk, tweak, len);
+    memset(((uint8_t *)twk) + len, 0, 16 - len);
+    blockCipher2->encryptBlock((uint8_t *)twk, (uint8_t *)twk);
+    return true;
+}
+
+#define xorTweak(output, input, tweak) \
+    do { \
+        for (uint8_t i = 0; i < 16; ++i) \
+            (output)[i] = (input)[i] ^ ((const uint8_t *)(tweak))[i]; \
+    } while (0)
+
+/**
+ * \brief Encrypts an entire sector of data.
+ *
+ * \param output The output buffer to write the ciphertext to, which can
+ * be the same as \a input.
+ * \param input The input buffer to read the plaintext from.
+ *
+ * The \a input and \a output buffers must be at least sectorSize()
+ * bytes in length.
+ *
+ * \sa decryptSector(), setKey(), setTweak()
+ */
+void XTSCommon::encryptSector(uint8_t *output, const uint8_t *input)
+{
+    size_t sectLast = sectSize & ~15;
+    size_t posn = 0;
+    uint32_t t[4];
+    memcpy(t, twk, sizeof(t));
+    while (posn < sectLast) {
+        // Process all complete 16-byte blocks.
+        xorTweak(output, input, t);
+        blockCipher1->encryptBlock(output, output);
+        xorTweak(output, output, t);
+        GF128::dblXTS(t);
+        input += 16;
+        output += 16;
+        posn += 16;
+    }
+    if (posn < sectSize) {
+        // Perform ciphertext stealing on the final partial block.
+        uint8_t leftOver = sectSize - posn;
+        output -= 16;
+        while (leftOver > 0) {
+            // Swap the left-over bytes in the last two blocks.
+            --leftOver;
+            uint8_t temp = input[leftOver];
+            output[leftOver + 16] = output[leftOver];
+            output[leftOver] = temp;
+        }
+        xorTweak(output, output, t);
+        blockCipher1->encryptBlock(output, output);
+        xorTweak(output, output, t);
+    }
+}
+
+/**
+ * \brief Decrypts an entire sector of data.
+ *
+ * \param output The output buffer to write the plaintext to, which can
+ * be the same as \a input.
+ * \param input The input buffer to read the ciphertext from.
+ *
+ * The \a input and \a output buffers must be at least sectorSize()
+ * bytes in length.
+ *
+ * \sa encryptSector(), setKey(), setTweak()
+ */
+void XTSCommon::decryptSector(uint8_t *output, const uint8_t *input)
+{
+    size_t sectLast = sectSize & ~15;
+    size_t posn = 0;
+    uint32_t t[4];
+    memcpy(t, twk, sizeof(t));
+    if (sectLast != sectSize)
+        sectLast -= 16;
+    while (posn < sectLast) {
+        // Process all complete 16-byte blocks.
+        xorTweak(output, input, t);
+        blockCipher1->decryptBlock(output, output);
+        xorTweak(output, output, t);
+        GF128::dblXTS(t);
+        input += 16;
+        output += 16;
+        posn += 16;
+    }
+    if (posn < sectSize) {
+        // Perform ciphertext stealing on the final two blocks.
+        uint8_t leftOver = sectSize - 16 - posn;
+        uint32_t u[4];
+
+        // Decrypt the second-last block of ciphertext to recover
+        // the last partial block of plaintext.  We need to use
+        // dblXTS(t) as the tweak for this block.  Save the current
+        // tweak in "u" for use later.
+        memcpy(u, t, sizeof(t));
+        GF128::dblXTS(t);
+        xorTweak(output, input, t);
+        blockCipher1->decryptBlock(output, output);
+        xorTweak(output, output, t);
+
+        // Swap the left-over bytes in the last two blocks.
+        while (leftOver > 0) {
+            --leftOver;
+            uint8_t temp = input[leftOver + 16];
+            output[leftOver + 16] = output[leftOver];
+            output[leftOver] = temp;
+        }
+
+        // Decrypt the second-last block using the second-last tweak.
+        xorTweak(output, output, u);
+        blockCipher1->decryptBlock(output, output);
+        xorTweak(output, output, u);
+    }
+}
+
+/**
+ * \brief Clears all security-sensitive state from this XTS object.
+ */
+void XTSCommon::clear()
+{
+    clean(twk);
+    blockCipher1->clear();
+    blockCipher2->clear();
+}
+
+/**
+ * \fn void XTSCommon::setBlockCiphers(BlockCipher *cipher1, BlockCipher *cipher2)
+ * \brief Sets the two block ciphers to use for XTS mode.
+ *
+ * \param cipher1 Points to the first block cipher object, which must be
+ * capable of both encryption and decryption.
+ * \param cipher2 Points to the second block cipher object, which must be
+ * capable of both encryption but does not need to be capable of decryption.
+ *
+ * Both block ciphers must have a 128-bit block size.
+ */
+
+/**
+ * \class XTSSingleKeyCommon XTS.h <XTS.h>
+ * \brief Concrete base class to assist with implementing single-key XTS
+ * mode for 128-bit block ciphers.
+ *
+ * References: <a href="http://libeccio.di.unisa.it/Crypto14/Lab/p1619.pdf">IEEE Std. 1619-2007</a>, <a href="http://csrc.nist.gov/publications/nistpubs/800-38E/nist-sp-800-38E.pdf">NIST SP 800-38E</a>, a href="http://web.cs.ucdavis.edu/~rogaway/papers/offsets.pdf">XEX</a>.
+ *
+ * \sa XTSSingleKey, XTSCommon
+ */
+
+/**
+ * \fn XTSSingleKeyCommon::XTSSingleKeyCommon()
+ * \brief Constructs an XTS object with a default sector size of 512 bytes.
+ */
+
+/**
+ * \brief Clears all sensitive information and destroys this object.
+ */
+XTSSingleKeyCommon::~XTSSingleKeyCommon()
+{
+}
+
+/**
+ * \brief Gets the size of the key for single-pkey XTS mode.
+ *
+ * The key size for single-key XTS mode is the same as the key size
+ * for the underlying block cipher.
+ *
+ * \sa setKey(), tweakSize()
+ */
+size_t XTSSingleKeyCommon::keySize() const
+{
+    return blockCipher1->keySize();
+}
+
+/**
+ * \brief Sets the key to use for single-keyh XTS mode.
+ *
+ * \param key Points to the key.
+ * \param len The size of the key in bytes which must be same as the
+ * size of the underlying block cipher.
+ *
+ * \return Returns true if the key was set or false if \a len was incorrect.
+ *
+ * This function should be followed by a call to setTweak() to specify
+ * the sector-specific tweak.
+ *
+ * \sa keySize(), setTweak(), encryptSector()
+ */
+bool XTSSingleKeyCommon::setKey(const uint8_t *key, size_t len)
+{
+    return blockCipher1->setKey(key, len);
+}
+
+/**
+ * \class XTS XTS.h <XTS.h>
+ * \brief Implementation of the XTS mode for 128-bit block ciphers.
+ *
+ * XTS mode implements the XEX tweakable block cipher mode with ciphertext
+ * stealing for data that isn't a multiple of the 128-bit block size.
+ *
+ * XTS was designed for use in disk encryption where a large number of
+ * equal-sized "sectors" need to be encrypted in a way that information
+ * from one sector cannot be used to decrypt the other sectors.  The mode
+ * combines the key with a sector-specific "tweak" which is usually
+ * based on the sector number.
+ *
+ * Some Arduino systems have SD cards, but typically embedded systems
+ * do not have disk drives.  However, XTS can still be useful on
+ * Arduino systems with lots of EEPROM or flash memory.  If the application
+ * needs to store critical security parameters like private keys then
+ * XTS can be used to encrypt non-volatile memory to protect the parameters.
+ *
+ * The following example encrypts a sector using XTS mode:
+ *
+ * \code
+ * XTS<AES256> xts;
+ * xts.setSectorSize(520);
+ * xts.setKey(key, 64); // Twice the AES256 key size.
+ * xts.setTweak(sectorNumber, sizeof(sectorNumber));
+ * xts.encryptSector(output, input);
+ * \endcode
+ *
+ * XTS keys are twice the size of the underlying block cipher
+ * (AES256 in the above example).  The XTS key is divided into two halves.
+ * The first half is used to encrypt the plaintext and the second half
+ * is used to encrypt the sector-specific tweak.  The same key can be
+ * used for both, in which case XTS is equivalent to the original
+ * XEX design upon which XTS was based.  The companion XTSSingleKey class
+ * can be used for single-key scenarios.
+ *
+ * The template parameter must be a concrete subclass of BlockCipher
+ * indicating the specific block cipher to use.  The example above uses
+ * AES256 as the underlying cipher.
+ *
+ * It is also possible to specify two different block ciphers, as long as
+ * they have the same key size.  Because the second half of the key is only
+ * used to encrypt tweaks and never decrypt, a reduced block cipher
+ * implementation like SpeckTiny that only supports encryption can be
+ * used for the second block cipher:
+ *
+ * \code
+ * XTS<SpeckSmall, SpeckTiny> xts;
+ * \endcode
+ *
+ * This might save some memory that would otherwise be needed for the
+ * decryption key schedule of the second block cipher.  XTSSingleKey provides
+ * another method to save memory.
+ *
+ * References: <a href="http://libeccio.di.unisa.it/Crypto14/Lab/p1619.pdf">IEEE Std. 1619-2007</a>, <a href="http://csrc.nist.gov/publications/nistpubs/800-38E/nist-sp-800-38E.pdf">NIST SP 800-38E</a>, a href="http://web.cs.ucdavis.edu/~rogaway/papers/offsets.pdf">XEX</a>.
+ *
+ * \sa XTSSingleKey, XTSCommon
+ */
+
+/**
+ * \fn XTS::XTS()
+ * \brief Constructs an object for encrypting sectors in XTS mode.
+ *
+ * This constructor should be followed by a call to setSectorSize().
+ * The default sector size is 512 bytes.
+ */
+
+/**
+ * \fn XTS::~XTS()
+ * \brief Clears all sensitive information and destroys this object.
+ */
+
+/**
+ * \class XTSSingleKey XTS.h <XTS.h>
+ * \brief Implementation of the single-key XTS mode for 128-bit block ciphers.
+ *
+ * XTS mode normally uses two keys to encrypt plaintext and the
+ * sector-specific tweak values.  This class uses the same key for
+ * both purposes, which can help save memory.
+ *
+ * References: <a href="http://libeccio.di.unisa.it/Crypto14/Lab/p1619.pdf">IEEE Std. 1619-2007</a>, <a href="http://csrc.nist.gov/publications/nistpubs/800-38E/nist-sp-800-38E.pdf">NIST SP 800-38E</a>, a href="http://web.cs.ucdavis.edu/~rogaway/papers/offsets.pdf">XEX</a>.
+ *
+ * \sa XTS, XTSSingleKeyCommon
+ */
+
+/**
+ * \fn XTSSingleKey::XTSSingleKey()
+ * \brief Constructs an object for encrypting sectors in XTS mode
+ * with a single key instead of two split keys.
+ *
+ * This constructor should be followed by a call to setSectorSize().
+ * The default sector size is 512 bytes.
+ */
+
+/**
+ * \fn XTSSingleKey::~XTSSingleKey()
+ * \brief Clears all sensitive information and destroys this object.
+ */
diff --git a/src/modules/Crypto/XTS.h b/src/modules/Crypto/XTS.h
new file mode 100644
index 0000000..8b745ad
--- /dev/null
+++ b/src/modules/Crypto/XTS.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_XTS_h
+#define CRYPTO_XTS_h
+
+#include "BlockCipher.h"
+
+class XTSSingleKeyCommon;
+
+class XTSCommon
+{
+public:
+    virtual ~XTSCommon();
+
+    virtual size_t keySize() const;
+    size_t tweakSize() const;
+
+    size_t sectorSize() const { return sectSize; }
+    bool setSectorSize(size_t size);
+
+    virtual bool setKey(const uint8_t *key, size_t len);
+    bool setTweak(const uint8_t *tweak, size_t len);
+
+    void encryptSector(uint8_t *output, const uint8_t *input);
+    void decryptSector(uint8_t *output, const uint8_t *input);
+
+    void clear();
+
+protected:
+    XTSCommon();
+    void setBlockCiphers(BlockCipher *cipher1, BlockCipher *cipher2)
+    {
+        blockCipher1 = cipher1;
+        blockCipher2 = cipher2;
+    }
+
+private:
+    BlockCipher *blockCipher1;
+    BlockCipher *blockCipher2;
+    uint32_t twk[4];
+    size_t sectSize;
+
+    friend class XTSSingleKeyCommon;
+};
+
+class XTSSingleKeyCommon : public XTSCommon
+{
+public:
+    virtual ~XTSSingleKeyCommon();
+
+    size_t keySize() const;
+    bool setKey(const uint8_t *key, size_t len);
+
+protected:
+    XTSSingleKeyCommon() : XTSCommon() {}
+};
+
+template <typename T1, typename T2 = T1>
+class XTS : public XTSCommon
+{
+public:
+    XTS() { setBlockCiphers(&cipher1, &cipher2); }
+    ~XTS() {}
+
+private:
+    T1 cipher1;
+    T2 cipher2;
+};
+
+template <typename T>
+class XTSSingleKey : public XTSSingleKeyCommon
+{
+public:
+    XTSSingleKey() { setBlockCiphers(&cipher, &cipher); }
+    ~XTSSingleKey() {}
+
+private:
+    T cipher;
+};
+
+#endif
diff --git a/src/modules/Crypto/examples/TestAES/TestAES.ino b/src/modules/Crypto/examples/TestAES/TestAES.ino
new file mode 100644
index 0000000..c63e331
--- /dev/null
+++ b/src/modules/Crypto/examples/TestAES/TestAES.ino
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the AES implementation to verify correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <AES.h>
+#include <string.h>
+
+struct TestVector
+{
+    const char *name;
+    byte key[32];
+    byte plaintext[16];
+    byte ciphertext[16];
+};
+
+// Define the ECB test vectors from the FIPS specification.
+static TestVector const testVectorAES128 = {
+    .name        = "AES-128-ECB",
+    .key         = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
+    .plaintext   = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+                    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+    .ciphertext  = {0x69, 0xC4, 0xE0, 0xD8, 0x6A, 0x7B, 0x04, 0x30,
+                    0xD8, 0xCD, 0xB7, 0x80, 0x70, 0xB4, 0xC5, 0x5A}
+};
+static TestVector const testVectorAES192 = {
+    .name        = "AES-192-ECB",
+    .key         = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+                    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17},
+    .plaintext   = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+                    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+    .ciphertext  = {0xDD, 0xA9, 0x7C, 0xA4, 0x86, 0x4C, 0xDF, 0xE0,
+                    0x6E, 0xAF, 0x70, 0xA0, 0xEC, 0x0D, 0x71, 0x91}
+};
+static TestVector const testVectorAES256 = {
+    .name        = "AES-256-ECB",
+    .key         = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+                    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+                    0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F},
+    .plaintext   = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+                    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+    .ciphertext  = {0x8E, 0xA2, 0xB7, 0xCA, 0x51, 0x67, 0x45, 0xBF,
+                    0xEA, 0xFC, 0x49, 0x90, 0x4B, 0x49, 0x60, 0x89}
+};
+
+AES128 aes128;
+AES192 aes192;
+AES256 aes256;
+
+byte buffer[16];
+
+void testCipher(BlockCipher *cipher, const struct TestVector *test)
+{
+    crypto_feed_watchdog();
+    Serial.print(test->name);
+    Serial.print(" Encryption ... ");
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->encryptBlock(buffer, test->plaintext);
+    if (memcmp(buffer, test->ciphertext, 16) == 0)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+
+    Serial.print(test->name);
+    Serial.print(" Decryption ... ");
+    cipher->decryptBlock(buffer, test->ciphertext);
+    if (memcmp(buffer, test->plaintext, 16) == 0)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfCipher(BlockCipher *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    Serial.print(test->name);
+    Serial.print(" Set Key ... ");
+    start = micros();
+    for (count = 0; count < 10000; ++count) {
+        cipher->setKey(test->key, cipher->keySize());
+    }
+    elapsed = micros() - start;
+    Serial.print(elapsed / 10000.0);
+    Serial.print("us per operation, ");
+    Serial.print((10000.0 * 1000000.0) / elapsed);
+    Serial.println(" per second");
+
+    Serial.print(test->name);
+    Serial.print(" Encrypt ... ");
+    start = micros();
+    for (count = 0; count < 5000; ++count) {
+        cipher->encryptBlock(buffer, buffer);
+    }
+    elapsed = micros() - start;
+    Serial.print(elapsed / (5000.0 * 16.0));
+    Serial.print("us per byte, ");
+    Serial.print((16.0 * 5000.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+
+    Serial.print(test->name);
+    Serial.print(" Decrypt ... ");
+    start = micros();
+    for (count = 0; count < 5000; ++count) {
+        cipher->decryptBlock(buffer, buffer);
+    }
+    elapsed = micros() - start;
+    Serial.print(elapsed / (5000.0 * 16.0));
+    Serial.print("us per byte, ");
+    Serial.print((16.0 * 5000.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+
+    Serial.println();
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.println("State Sizes:");
+    Serial.print("AES128 ... ");
+    Serial.println(sizeof(AES128));
+    Serial.print("AES192 ... ");
+    Serial.println(sizeof(AES192));
+    Serial.print("AES256 ... ");
+    Serial.println(sizeof(AES256));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testCipher(&aes128, &testVectorAES128);
+    testCipher(&aes192, &testVectorAES192);
+    testCipher(&aes256, &testVectorAES256);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfCipher(&aes128, &testVectorAES128);
+    perfCipher(&aes192, &testVectorAES192);
+    perfCipher(&aes256, &testVectorAES256);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestAESSmall/TestAESSmall.ino b/src/modules/Crypto/examples/TestAESSmall/TestAESSmall.ino
new file mode 100644
index 0000000..88b1db9
--- /dev/null
+++ b/src/modules/Crypto/examples/TestAESSmall/TestAESSmall.ino
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the small AES implementation to verify behaviour.
+*/
+
+#include <Crypto.h>
+#include <AES.h>
+#include <string.h>
+
+struct TestVector
+{
+    const char *name;
+    byte key[32];
+    byte plaintext[16];
+    byte ciphertext[16];
+};
+
+// Define the ECB test vectors from the FIPS specification.
+static TestVector const testVectorAES128 = {
+    .name        = "AES-128-ECB",
+    .key         = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
+    .plaintext   = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+                    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+    .ciphertext  = {0x69, 0xC4, 0xE0, 0xD8, 0x6A, 0x7B, 0x04, 0x30,
+                    0xD8, 0xCD, 0xB7, 0x80, 0x70, 0xB4, 0xC5, 0x5A}
+};
+static TestVector const testVectorAES192 = {
+    .name        = "AES-192-ECB",
+    .key         = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+                    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17},
+    .plaintext   = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+                    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+    .ciphertext  = {0xDD, 0xA9, 0x7C, 0xA4, 0x86, 0x4C, 0xDF, 0xE0,
+                    0x6E, 0xAF, 0x70, 0xA0, 0xEC, 0x0D, 0x71, 0x91}
+};
+static TestVector const testVectorAES256 = {
+    .name        = "AES-256-ECB",
+    .key         = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+                    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+                    0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F},
+    .plaintext   = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+                    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+    .ciphertext  = {0x8E, 0xA2, 0xB7, 0xCA, 0x51, 0x67, 0x45, 0xBF,
+                    0xEA, 0xFC, 0x49, 0x90, 0x4B, 0x49, 0x60, 0x89}
+};
+
+AESSmall128 aes128;
+AESSmall256 aes256;
+
+byte buffer[16];
+
+void testCipher(BlockCipher *cipher, const struct TestVector *test)
+{
+    crypto_feed_watchdog();
+    Serial.print(test->name);
+    Serial.print(" Encryption ... ");
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->encryptBlock(buffer, test->plaintext);
+    if (memcmp(buffer, test->ciphertext, 16) == 0)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+
+    Serial.print(test->name);
+    Serial.print(" Decryption ... ");
+    cipher->decryptBlock(buffer, test->ciphertext);
+    if (memcmp(buffer, test->plaintext, 16) == 0)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfCipher(BlockCipher *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    Serial.print(test->name);
+    Serial.print(" Set Key ... ");
+    start = micros();
+    for (count = 0; count < 10000; ++count) {
+        cipher->setKey(test->key, cipher->keySize());
+    }
+    elapsed = micros() - start;
+    Serial.print(elapsed / 10000.0);
+    Serial.print("us per operation, ");
+    Serial.print((10000.0 * 1000000.0) / elapsed);
+    Serial.println(" per second");
+
+    Serial.print(test->name);
+    Serial.print(" Encrypt ... ");
+    start = micros();
+    for (count = 0; count < 5000; ++count) {
+        cipher->encryptBlock(buffer, buffer);
+    }
+    elapsed = micros() - start;
+    Serial.print(elapsed / (5000.0 * 16.0));
+    Serial.print("us per byte, ");
+    Serial.print((16.0 * 5000.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+
+    Serial.print(test->name);
+    Serial.print(" Decrypt ... ");
+    start = micros();
+    for (count = 0; count < 5000; ++count) {
+        cipher->decryptBlock(buffer, buffer);
+    }
+    elapsed = micros() - start;
+    Serial.print(elapsed / (5000.0 * 16.0));
+    Serial.print("us per byte, ");
+    Serial.print((16.0 * 5000.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+
+    Serial.println();
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.println("State Sizes:");
+    Serial.print("AESSmall128 ... ");
+    Serial.println(sizeof(AESSmall128));
+    Serial.print("AESSmall256 ... ");
+    Serial.println(sizeof(AESSmall256));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testCipher(&aes128, &testVectorAES128);
+    testCipher(&aes256, &testVectorAES256);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfCipher(&aes128, &testVectorAES128);
+    perfCipher(&aes256, &testVectorAES256);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestAESTiny/TestAESTiny.ino b/src/modules/Crypto/examples/TestAESTiny/TestAESTiny.ino
new file mode 100644
index 0000000..cc32fa2
--- /dev/null
+++ b/src/modules/Crypto/examples/TestAESTiny/TestAESTiny.ino
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the tiny AES implementation to verify behaviour.
+*/
+
+#include <Crypto.h>
+#include <AES.h>
+#include <string.h>
+
+struct TestVector
+{
+    const char *name;
+    byte key[32];
+    byte plaintext[16];
+    byte ciphertext[16];
+};
+
+// Define the ECB test vectors from the FIPS specification.
+static TestVector const testVectorAES128 = {
+    .name        = "AES-128-ECB",
+    .key         = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
+    .plaintext   = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+                    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+    .ciphertext  = {0x69, 0xC4, 0xE0, 0xD8, 0x6A, 0x7B, 0x04, 0x30,
+                    0xD8, 0xCD, 0xB7, 0x80, 0x70, 0xB4, 0xC5, 0x5A}
+};
+static TestVector const testVectorAES192 = {
+    .name        = "AES-192-ECB",
+    .key         = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+                    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17},
+    .plaintext   = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+                    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+    .ciphertext  = {0xDD, 0xA9, 0x7C, 0xA4, 0x86, 0x4C, 0xDF, 0xE0,
+                    0x6E, 0xAF, 0x70, 0xA0, 0xEC, 0x0D, 0x71, 0x91}
+};
+static TestVector const testVectorAES256 = {
+    .name        = "AES-256-ECB",
+    .key         = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+                    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+                    0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F},
+    .plaintext   = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+                    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+    .ciphertext  = {0x8E, 0xA2, 0xB7, 0xCA, 0x51, 0x67, 0x45, 0xBF,
+                    0xEA, 0xFC, 0x49, 0x90, 0x4B, 0x49, 0x60, 0x89}
+};
+
+AESTiny128 aes128;
+AESTiny256 aes256;
+
+byte buffer[16];
+
+void testCipher(BlockCipher *cipher, const struct TestVector *test)
+{
+    crypto_feed_watchdog();
+    Serial.print(test->name);
+    Serial.print(" Encryption ... ");
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->encryptBlock(buffer, test->plaintext);
+    if (memcmp(buffer, test->ciphertext, 16) == 0)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfCipher(BlockCipher *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    Serial.print(test->name);
+    Serial.print(" Set Key ... ");
+    start = micros();
+    for (count = 0; count < 10000; ++count) {
+        cipher->setKey(test->key, cipher->keySize());
+    }
+    elapsed = micros() - start;
+    Serial.print(elapsed / 10000.0);
+    Serial.print("us per operation, ");
+    Serial.print((10000.0 * 1000000.0) / elapsed);
+    Serial.println(" per second");
+
+    Serial.print(test->name);
+    Serial.print(" Encrypt ... ");
+    start = micros();
+    for (count = 0; count < 5000; ++count) {
+        cipher->encryptBlock(buffer, buffer);
+    }
+    elapsed = micros() - start;
+    Serial.print(elapsed / (5000.0 * 16.0));
+    Serial.print("us per byte, ");
+    Serial.print((16.0 * 5000.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+
+    Serial.println();
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.println("State Sizes:");
+    Serial.print("AESTiny128 ... ");
+    Serial.println(sizeof(AESTiny128));
+    Serial.print("AESTiny256 ... ");
+    Serial.println(sizeof(AESTiny256));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testCipher(&aes128, &testVectorAES128);
+    testCipher(&aes256, &testVectorAES256);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfCipher(&aes128, &testVectorAES128);
+    perfCipher(&aes256, &testVectorAES256);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestBLAKE2b/TestBLAKE2b.ino b/src/modules/Crypto/examples/TestBLAKE2b/TestBLAKE2b.ino
new file mode 100644
index 0000000..28daa00
--- /dev/null
+++ b/src/modules/Crypto/examples/TestBLAKE2b/TestBLAKE2b.ino
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the BLAKE2b implementation to verify correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <BLAKE2b.h>
+#include <string.h>
+#if defined(ESP8266) || defined(ESP32)
+#include <pgmspace.h>
+#else
+#include <avr/pgmspace.h>
+#endif
+
+#define HASH_SIZE 64
+#define BLOCK_SIZE 128
+
+struct TestHashVector
+{
+    const char *name;
+    const char *data;
+    uint8_t hash[HASH_SIZE];
+};
+
+// Test vectors generated with the reference implementation of BLAKE2b.
+static TestHashVector const testVectorBLAKE2b_1 PROGMEM = {
+    "BLAKE2b #1",
+    "",
+    {0x78, 0x6a, 0x02, 0xf7, 0x42, 0x01, 0x59, 0x03,
+     0xc6, 0xc6, 0xfd, 0x85, 0x25, 0x52, 0xd2, 0x72,
+     0x91, 0x2f, 0x47, 0x40, 0xe1, 0x58, 0x47, 0x61,
+     0x8a, 0x86, 0xe2, 0x17, 0xf7, 0x1f, 0x54, 0x19,
+     0xd2, 0x5e, 0x10, 0x31, 0xaf, 0xee, 0x58, 0x53,
+     0x13, 0x89, 0x64, 0x44, 0x93, 0x4e, 0xb0, 0x4b,
+     0x90, 0x3a, 0x68, 0x5b, 0x14, 0x48, 0xb7, 0x55,
+     0xd5, 0x6f, 0x70, 0x1a, 0xfe, 0x9b, 0xe2, 0xce}
+};
+static TestHashVector const testVectorBLAKE2b_2 PROGMEM = {
+    "BLAKE2b #2",
+    "abc",
+    {0xba, 0x80, 0xa5, 0x3f, 0x98, 0x1c, 0x4d, 0x0d,
+     0x6a, 0x27, 0x97, 0xb6, 0x9f, 0x12, 0xf6, 0xe9,
+     0x4c, 0x21, 0x2f, 0x14, 0x68, 0x5a, 0xc4, 0xb7,
+     0x4b, 0x12, 0xbb, 0x6f, 0xdb, 0xff, 0xa2, 0xd1,
+     0x7d, 0x87, 0xc5, 0x39, 0x2a, 0xab, 0x79, 0x2d,
+     0xc2, 0x52, 0xd5, 0xde, 0x45, 0x33, 0xcc, 0x95,
+     0x18, 0xd3, 0x8a, 0xa8, 0xdb, 0xf1, 0x92, 0x5a,
+     0xb9, 0x23, 0x86, 0xed, 0xd4, 0x00, 0x99, 0x23}
+};
+static TestHashVector const testVectorBLAKE2b_3 PROGMEM = {
+    "BLAKE2b #3",
+    "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+    {0x72, 0x85, 0xff, 0x3e, 0x8b, 0xd7, 0x68, 0xd6,
+     0x9b, 0xe6, 0x2b, 0x3b, 0xf1, 0x87, 0x65, 0xa3,
+     0x25, 0x91, 0x7f, 0xa9, 0x74, 0x4a, 0xc2, 0xf5,
+     0x82, 0xa2, 0x08, 0x50, 0xbc, 0x2b, 0x11, 0x41,
+     0xed, 0x1b, 0x3e, 0x45, 0x28, 0x59, 0x5a, 0xcc,
+     0x90, 0x77, 0x2b, 0xdf, 0x2d, 0x37, 0xdc, 0x8a,
+     0x47, 0x13, 0x0b, 0x44, 0xf3, 0x3a, 0x02, 0xe8,
+     0x73, 0x0e, 0x5a, 0xd8, 0xe1, 0x66, 0xe8, 0x88}
+};
+static TestHashVector const testVectorBLAKE2b_4 PROGMEM = {
+    "BLAKE2b #4",
+    "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn"
+    "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+    {0xce, 0x74, 0x1a, 0xc5, 0x93, 0x0f, 0xe3, 0x46,
+     0x81, 0x11, 0x75, 0xc5, 0x22, 0x7b, 0xb7, 0xbf,
+     0xcd, 0x47, 0xf4, 0x26, 0x12, 0xfa, 0xe4, 0x6c,
+     0x08, 0x09, 0x51, 0x4f, 0x9e, 0x0e, 0x3a, 0x11,
+     0xee, 0x17, 0x73, 0x28, 0x71, 0x47, 0xcd, 0xea,
+     0xee, 0xdf, 0xf5, 0x07, 0x09, 0xaa, 0x71, 0x63,
+     0x41, 0xfe, 0x65, 0x24, 0x0f, 0x4a, 0xd6, 0x77,
+     0x7d, 0x6b, 0xfa, 0xf9, 0x72, 0x6e, 0x5e, 0x52}
+};
+
+BLAKE2b blake2b;
+
+byte buffer[BLOCK_SIZE + 2];
+
+bool testHash_N(Hash *hash, const struct TestHashVector *test, size_t inc)
+{
+    size_t size = strlen(test->data);
+    size_t posn, len;
+    uint8_t value[HASH_SIZE];
+
+    hash->reset();
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        hash->update(test->data + posn, len);
+    }
+    hash->finalize(value, sizeof(value));
+    if (memcmp(value, test->hash, sizeof(value)) != 0)
+        return false;
+
+    return true;
+}
+
+void testHash(Hash *hash, const struct TestHashVector *test)
+{
+    bool ok;
+    TestHashVector vec;
+
+    memcpy_P(&vec, test, sizeof(vec));
+    test = &vec;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    ok  = testHash_N(hash, test, strlen(test->data));
+    ok &= testHash_N(hash, test, 1);
+    ok &= testHash_N(hash, test, 2);
+    ok &= testHash_N(hash, test, 5);
+    ok &= testHash_N(hash, test, 8);
+    ok &= testHash_N(hash, test, 13);
+    ok &= testHash_N(hash, test, 16);
+    ok &= testHash_N(hash, test, 24);
+    ok &= testHash_N(hash, test, 63);
+    ok &= testHash_N(hash, test, 64);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfHash(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Hashing ... ");
+
+    for (size_t posn = 0; posn < sizeof(buffer); ++posn)
+        buffer[posn] = (uint8_t)posn;
+
+    hash->reset();
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->update(buffer, sizeof(buffer));
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 1000.0));
+    Serial.print("us per byte, ");
+    Serial.print((sizeof(buffer) * 1000.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+// Very simple method for hashing a HMAC inner or outer key.
+void hashKey(Hash *hash, const uint8_t *key, size_t keyLen, uint8_t pad)
+{
+    size_t posn;
+    uint8_t buf;
+    uint8_t result[HASH_SIZE];
+    if (keyLen <= BLOCK_SIZE) {
+        hash->reset();
+        for (posn = 0; posn < BLOCK_SIZE; ++posn) {
+            if (posn < keyLen)
+                buf = key[posn] ^ pad;
+            else
+                buf = pad;
+            hash->update(&buf, 1);
+        }
+    } else {
+        hash->reset();
+        hash->update(key, keyLen);
+        hash->finalize(result, HASH_SIZE);
+        hash->reset();
+        for (posn = 0; posn < BLOCK_SIZE; ++posn) {
+            if (posn < HASH_SIZE)
+                buf = result[posn] ^ pad;
+            else
+                buf = pad;
+            hash->update(&buf, 1);
+        }
+    }
+}
+
+void testHMAC(Hash *hash, size_t keyLen)
+{
+    uint8_t result[HASH_SIZE];
+
+    Serial.print("HMAC-BLAKE2b keysize=");
+    Serial.print(keyLen);
+    Serial.print(" ... ");
+
+    // Construct the expected result with a simple HMAC implementation.
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hashKey(hash, buffer, keyLen, 0x36);
+    memset(buffer, 0xBA, sizeof(buffer));
+    hash->update(buffer, sizeof(buffer));
+    hash->finalize(result, HASH_SIZE);
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hashKey(hash, buffer, keyLen, 0x5C);
+    hash->update(result, HASH_SIZE);
+    hash->finalize(result, HASH_SIZE);
+
+    // Now use the library to compute the HMAC.
+    hash->resetHMAC(buffer, keyLen);
+    memset(buffer, 0xBA, sizeof(buffer));
+    hash->update(buffer, sizeof(buffer));
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hash->finalizeHMAC(buffer, keyLen, buffer, HASH_SIZE);
+
+    // Check the result.
+    if (!memcmp(result, buffer, HASH_SIZE))
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+// Deterministic sequences (Fibonacci generator).  From RFC 7693.
+static void selftest_seq(uint8_t *out, size_t len, uint32_t seed)
+{
+    size_t i;
+    uint32_t t, a , b;
+
+    a = 0xDEAD4BAD * seed;              // prime
+    b = 1;
+
+    for (i = 0; i < len; i++) {         // fill the buf
+        t = a + b;
+        a = b;
+        b = t;
+        out[i] = (t >> 24) & 0xFF;
+    }
+}
+
+// Incremental version of above to save memory.
+static void selftest_seq_incremental(BLAKE2b *blake, size_t len, uint32_t seed)
+{
+    size_t i;
+    uint32_t t, a , b;
+
+    a = 0xDEAD4BAD * seed;              // prime
+    b = 1;
+
+    for (i = 0; i < len; i++) {         // fill the buf
+        t = a + b;
+        a = b;
+        b = t;
+        buffer[i % 128] = (t >> 24) & 0xFF;
+        if ((i % 128) == 127)
+            blake->update(buffer, 128);
+    }
+
+    blake->update(buffer, len % 128);
+}
+
+// Run the self-test from Appendix E of RFC 7693.  Most of this code
+// is from RFC 7693, with modifications to use the Crypto library.
+void testRFC7693()
+{
+    // Grand hash of hash results.
+    static const uint8_t blake2b_res[32] PROGMEM = {
+        0xC2, 0x3A, 0x78, 0x00, 0xD9, 0x81, 0x23, 0xBD,
+        0x10, 0xF5, 0x06, 0xC6, 0x1E, 0x29, 0xDA, 0x56,
+        0x03, 0xD7, 0x63, 0xB8, 0xBB, 0xAD, 0x2E, 0x73,
+        0x7F, 0x5E, 0x76, 0x5A, 0x7B, 0xCC, 0xD4, 0x75
+    };
+    // Parameter sets.
+    static const uint8_t b2b_md_len[4] PROGMEM = { 20, 32, 48, 64 };
+    static const uint16_t b2b_in_len[6] PROGMEM = { 0, 3, 128, 129, 255, 1024 };
+
+    size_t i, j, outlen, inlen;
+    uint8_t md[64], key[64];
+    BLAKE2b inner;
+
+    Serial.print("BLAKE2b RFC 7693 ... ");
+
+    // 256-bit hash for testing.
+    blake2b.reset(32);
+
+    for (i = 0; i < 4; i++) {
+        outlen = pgm_read_byte(&(b2b_md_len[i]));
+        for (j = 0; j < 6; j++) {
+            inlen = pgm_read_word(&(b2b_in_len[j]));
+
+            inner.reset(outlen);                // unkeyed hash
+            selftest_seq_incremental(&inner, inlen, inlen);
+            inner.finalize(md, outlen);
+            blake2b.update(md, outlen);         // hash the hash
+
+            selftest_seq(key, outlen, outlen);  // keyed hash
+            inner.reset(key, outlen, outlen);
+            selftest_seq_incremental(&inner, inlen, inlen);
+            inner.finalize(md, outlen);
+            blake2b.update(md, outlen);         // hash the hash
+        }
+    }
+
+    // Compute and compare the hash of hashes.
+    bool ok = true;
+    blake2b.finalize(md, 32);
+    for (i = 0; i < 32; i++) {
+        if (md[i] != pgm_read_byte(&(blake2b_res[i])))
+            ok = false;
+    }
+
+    // Report the results.
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfKeyed(BLAKE2b *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Keyed Reset ... ");
+
+    for (size_t posn = 0; posn < sizeof(buffer); ++posn)
+        buffer[posn] = (uint8_t)posn;
+
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->reset(buffer, hash->hashSize());
+        hash->update(buffer, 1);    // To flush the key chunk.
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void perfFinalize(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Finalizing ... ");
+
+    hash->reset();
+    hash->update("abc", 3);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->finalize(buffer, hash->hashSize());
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.print("State Size ...");
+    Serial.println(sizeof(BLAKE2b));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testHash(&blake2b, &testVectorBLAKE2b_1);
+    testHash(&blake2b, &testVectorBLAKE2b_2);
+    testHash(&blake2b, &testVectorBLAKE2b_3);
+    testHash(&blake2b, &testVectorBLAKE2b_4);
+    testHMAC(&blake2b, (size_t)0);
+    testHMAC(&blake2b, 1);
+    testHMAC(&blake2b, HASH_SIZE);
+    testHMAC(&blake2b, BLOCK_SIZE);
+    testHMAC(&blake2b, BLOCK_SIZE + 1);
+    testHMAC(&blake2b, BLOCK_SIZE + 2);
+    testRFC7693();
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfHash(&blake2b);
+    perfKeyed(&blake2b);
+    perfFinalize(&blake2b);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestBLAKE2s/TestBLAKE2s.ino b/src/modules/Crypto/examples/TestBLAKE2s/TestBLAKE2s.ino
new file mode 100644
index 0000000..65ec25f
--- /dev/null
+++ b/src/modules/Crypto/examples/TestBLAKE2s/TestBLAKE2s.ino
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the BLAKE2s implementation to verify correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <BLAKE2s.h>
+#include <string.h>
+#if defined(ESP8266) || defined(ESP32)
+#include <pgmspace.h>
+#else
+#include <avr/pgmspace.h>
+#endif
+
+#define HASH_SIZE 32
+#define BLOCK_SIZE 64
+
+struct TestHashVector
+{
+    const char *name;
+    const char *data;
+    uint8_t hash[HASH_SIZE];
+};
+
+// Test vectors generated with the reference implementation of BLAKE2s.
+static TestHashVector const testVectorBLAKE2s_1 = {
+    "BLAKE2s #1",
+    "",
+    {0x69, 0x21, 0x7a, 0x30, 0x79, 0x90, 0x80, 0x94,
+     0xe1, 0x11, 0x21, 0xd0, 0x42, 0x35, 0x4a, 0x7c,
+     0x1f, 0x55, 0xb6, 0x48, 0x2c, 0xa1, 0xa5, 0x1e,
+     0x1b, 0x25, 0x0d, 0xfd, 0x1e, 0xd0, 0xee, 0xf9}
+};
+static TestHashVector const testVectorBLAKE2s_2 = {
+    "BLAKE2s #2",
+    "abc",
+    {0x50, 0x8c, 0x5e, 0x8c, 0x32, 0x7c, 0x14, 0xe2,
+     0xe1, 0xa7, 0x2b, 0xa3, 0x4e, 0xeb, 0x45, 0x2f,
+     0x37, 0x45, 0x8b, 0x20, 0x9e, 0xd6, 0x3a, 0x29,
+     0x4d, 0x99, 0x9b, 0x4c, 0x86, 0x67, 0x59, 0x82}
+};
+static TestHashVector const testVectorBLAKE2s_3 = {
+    "BLAKE2s #3",
+    "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+    {0x6f, 0x4d, 0xf5, 0x11, 0x6a, 0x6f, 0x33, 0x2e,
+     0xda, 0xb1, 0xd9, 0xe1, 0x0e, 0xe8, 0x7d, 0xf6,
+     0x55, 0x7b, 0xea, 0xb6, 0x25, 0x9d, 0x76, 0x63,
+     0xf3, 0xbc, 0xd5, 0x72, 0x2c, 0x13, 0xf1, 0x89}
+};
+static TestHashVector const testVectorBLAKE2s_4 = {
+    "BLAKE2s #4",
+    "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn"
+    "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+    {0x35, 0x8d, 0xd2, 0xed, 0x07, 0x80, 0xd4, 0x05,
+     0x4e, 0x76, 0xcb, 0x6f, 0x3a, 0x5b, 0xce, 0x28,
+     0x41, 0xe8, 0xe2, 0xf5, 0x47, 0x43, 0x1d, 0x4d,
+     0x09, 0xdb, 0x21, 0xb6, 0x6d, 0x94, 0x1f, 0xc7}
+};
+
+BLAKE2s blake2s;
+
+byte buffer[128];
+
+bool testHash_N(Hash *hash, const struct TestHashVector *test, size_t inc)
+{
+    size_t size = strlen(test->data);
+    size_t posn, len;
+    uint8_t value[HASH_SIZE];
+
+    hash->reset();
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        hash->update(test->data + posn, len);
+    }
+    hash->finalize(value, sizeof(value));
+    if (memcmp(value, test->hash, sizeof(value)) != 0)
+        return false;
+
+    return true;
+}
+
+void testHash(Hash *hash, const struct TestHashVector *test)
+{
+    bool ok;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    ok  = testHash_N(hash, test, strlen(test->data));
+    ok &= testHash_N(hash, test, 1);
+    ok &= testHash_N(hash, test, 2);
+    ok &= testHash_N(hash, test, 5);
+    ok &= testHash_N(hash, test, 8);
+    ok &= testHash_N(hash, test, 13);
+    ok &= testHash_N(hash, test, 16);
+    ok &= testHash_N(hash, test, 24);
+    ok &= testHash_N(hash, test, 63);
+    ok &= testHash_N(hash, test, 64);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfHash(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Hashing ... ");
+
+    for (size_t posn = 0; posn < sizeof(buffer); ++posn)
+        buffer[posn] = (uint8_t)posn;
+
+    hash->reset();
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->update(buffer, sizeof(buffer));
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 1000.0));
+    Serial.print("us per byte, ");
+    Serial.print((sizeof(buffer) * 1000.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+// Very simple method for hashing a HMAC inner or outer key.
+void hashKey(Hash *hash, const uint8_t *key, size_t keyLen, uint8_t pad)
+{
+    size_t posn;
+    uint8_t buf;
+    uint8_t result[HASH_SIZE];
+    if (keyLen <= BLOCK_SIZE) {
+        hash->reset();
+        for (posn = 0; posn < BLOCK_SIZE; ++posn) {
+            if (posn < keyLen)
+                buf = key[posn] ^ pad;
+            else
+                buf = pad;
+            hash->update(&buf, 1);
+        }
+    } else {
+        hash->reset();
+        hash->update(key, keyLen);
+        hash->finalize(result, HASH_SIZE);
+        hash->reset();
+        for (posn = 0; posn < BLOCK_SIZE; ++posn) {
+            if (posn < HASH_SIZE)
+                buf = result[posn] ^ pad;
+            else
+                buf = pad;
+            hash->update(&buf, 1);
+        }
+    }
+}
+
+void testHMAC(Hash *hash, size_t keyLen)
+{
+    uint8_t result[HASH_SIZE];
+
+    Serial.print("HMAC-BLAKE2s keysize=");
+    Serial.print(keyLen);
+    Serial.print(" ... ");
+
+    // Construct the expected result with a simple HMAC implementation.
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hashKey(hash, buffer, keyLen, 0x36);
+    memset(buffer, 0xBA, sizeof(buffer));
+    hash->update(buffer, sizeof(buffer));
+    hash->finalize(result, HASH_SIZE);
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hashKey(hash, buffer, keyLen, 0x5C);
+    hash->update(result, HASH_SIZE);
+    hash->finalize(result, HASH_SIZE);
+
+    // Now use the library to compute the HMAC.
+    hash->resetHMAC(buffer, keyLen);
+    memset(buffer, 0xBA, sizeof(buffer));
+    hash->update(buffer, sizeof(buffer));
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hash->finalizeHMAC(buffer, keyLen, buffer, HASH_SIZE);
+
+    // Check the result.
+    if (!memcmp(result, buffer, HASH_SIZE))
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+// Deterministic sequences (Fibonacci generator).  From RFC 7693.
+static void selftest_seq(uint8_t *out, size_t len, uint32_t seed)
+{
+    size_t i;
+    uint32_t t, a , b;
+
+    a = 0xDEAD4BAD * seed;              // prime
+    b = 1;
+
+    for (i = 0; i < len; i++) {         // fill the buf
+        t = a + b;
+        a = b;
+        b = t;
+        out[i] = (t >> 24) & 0xFF;
+    }
+}
+
+// Incremental version of above to save memory.
+static void selftest_seq_incremental(BLAKE2s *blake, size_t len, uint32_t seed)
+{
+    size_t i;
+    uint32_t t, a , b;
+
+    a = 0xDEAD4BAD * seed;              // prime
+    b = 1;
+
+    for (i = 0; i < len; i++) {         // fill the buf
+        t = a + b;
+        a = b;
+        b = t;
+        buffer[i % 128] = (t >> 24) & 0xFF;
+        if ((i % 128) == 127)
+            blake->update(buffer, sizeof(buffer));
+    }
+
+    blake->update(buffer, len % 128);
+}
+
+// Run the self-test from Appendix E of RFC 7693.  Most of this code
+// is from RFC 7693, with modifications to use the Crypto library.
+void testRFC7693()
+{
+    // Grand hash of hash results.
+    static const uint8_t blake2s_res[32] PROGMEM = {
+        0x6A, 0x41, 0x1F, 0x08, 0xCE, 0x25, 0xAD, 0xCD,
+        0xFB, 0x02, 0xAB, 0xA6, 0x41, 0x45, 0x1C, 0xEC,
+        0x53, 0xC5, 0x98, 0xB2, 0x4F, 0x4F, 0xC7, 0x87,
+        0xFB, 0xDC, 0x88, 0x79, 0x7F, 0x4C, 0x1D, 0xFE
+    };
+    // Parameter sets.
+    static const uint8_t b2s_md_len[4] PROGMEM = { 16, 20, 28, 32 };
+    static const uint16_t b2s_in_len[6] PROGMEM = { 0,  3,  64, 65, 255, 1024 };
+
+    size_t i, j, outlen, inlen;
+    uint8_t md[32], key[32];
+    BLAKE2s inner;
+
+    Serial.print("BLAKE2s RFC 7693 ... ");
+
+    // 256-bit hash for testing.
+    blake2s.reset(32);
+
+    for (i = 0; i < 4; i++) {
+        outlen = pgm_read_byte(&(b2s_md_len[i]));
+        for (j = 0; j < 6; j++) {
+            inlen = pgm_read_word(&(b2s_in_len[j]));
+
+            inner.reset(outlen);                // unkeyed hash
+            selftest_seq_incremental(&inner, inlen, inlen);
+            inner.finalize(md, outlen);
+            blake2s.update(md, outlen);         // hash the hash
+
+            selftest_seq(key, outlen, outlen);  // keyed hash
+            inner.reset(key, outlen, outlen);
+            selftest_seq_incremental(&inner, inlen, inlen);
+            inner.finalize(md, outlen);
+            blake2s.update(md, outlen);         // hash the hash
+        }
+    }
+
+    // Compute and compare the hash of hashes.
+    bool ok = true;
+    blake2s.finalize(md, 32);
+    for (i = 0; i < 32; i++) {
+        if (md[i] != pgm_read_byte(&(blake2s_res[i])))
+            ok = false;
+    }
+
+    // Report the results.
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfFinalize(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Finalizing ... ");
+
+    hash->reset();
+    hash->update("abc", 3);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->finalize(buffer, hash->hashSize());
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void perfKeyed(BLAKE2s *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Keyed Reset ... ");
+
+    for (size_t posn = 0; posn < sizeof(buffer); ++posn)
+        buffer[posn] = (uint8_t)posn;
+
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->reset(buffer, hash->hashSize());
+        hash->update(buffer, 1);    // To flush the key chunk.
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void perfHMAC(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("HMAC Reset ... ");
+
+    for (size_t posn = 0; posn < sizeof(buffer); ++posn)
+        buffer[posn] = (uint8_t)posn;
+
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->resetHMAC(buffer, hash->hashSize());
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+
+    Serial.print("HMAC Finalize ... ");
+
+    hash->resetHMAC(buffer, hash->hashSize());
+    hash->update("abc", 3);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->finalizeHMAC(buffer, hash->hashSize(), buffer, hash->hashSize());
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.print("State Size ... ");
+    Serial.println(sizeof(BLAKE2s));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testHash(&blake2s, &testVectorBLAKE2s_1);
+    testHash(&blake2s, &testVectorBLAKE2s_2);
+    testHash(&blake2s, &testVectorBLAKE2s_3);
+    testHash(&blake2s, &testVectorBLAKE2s_4);
+    testHMAC(&blake2s, (size_t)0);
+    testHMAC(&blake2s, 1);
+    testHMAC(&blake2s, HASH_SIZE);
+    testHMAC(&blake2s, BLOCK_SIZE);
+    testHMAC(&blake2s, BLOCK_SIZE + 1);
+    testHMAC(&blake2s, sizeof(buffer));
+    testRFC7693();
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfHash(&blake2s);
+    perfFinalize(&blake2s);
+    perfKeyed(&blake2s);
+    perfHMAC(&blake2s);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestBigNumberUtil/TestBigNumberUtil.ino b/src/modules/Crypto/examples/TestBigNumberUtil/TestBigNumberUtil.ino
new file mode 100644
index 0000000..126ad21
--- /dev/null
+++ b/src/modules/Crypto/examples/TestBigNumberUtil/TestBigNumberUtil.ino
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the utility functions in BigNumberUtil.
+*/
+
+#include <Crypto.h>
+#include <BigNumberUtil.h>
+#include <utility/ProgMemUtil.h>
+#include <string.h>
+
+#define NUM_SIZE_512BIT (64 / sizeof(limb_t))
+#define LIMB_BITS       (sizeof(limb_t) * 8)
+
+// Convert a decimal string in program memory into a big number.
+void fromString(limb_t *x, size_t xsize, const char *str)
+{
+    uint8_t ch;
+    size_t posn;
+    memset(x, 0, sizeof(limb_t) * xsize);
+    while ((ch = pgm_read_byte((uint8_t *)str)) != '\0') {
+        if (ch >= '0' && ch <= '9') {
+            // Quick and simple method to multiply by 10 and add the new digit.
+            dlimb_t carry = ch - '0';
+            for (posn = 0; posn < xsize; ++posn) {
+                carry += ((dlimb_t)x[posn]) * 10U;
+                x[posn] = (limb_t)carry;
+                carry >>= LIMB_BITS;
+            }
+        }
+        ++str;
+    }
+}
+
+// Convert a decimal string in program memory into a byte array.
+void bytesFromString(uint8_t *x, size_t xsize, const char *str)
+{
+    uint8_t ch;
+    size_t posn;
+    memset(x, 0, xsize);
+    while ((ch = pgm_read_byte((uint8_t *)str)) != '\0') {
+        if (ch >= '0' && ch <= '9') {
+            // Quick and simple method to multiply by 10 and add the new digit.
+            uint16_t carry = ch - '0';
+            for (posn = 0; posn < xsize; ++posn) {
+                carry += ((uint16_t)x[posn]) * 10U;
+                x[posn] = (uint8_t)carry;
+                carry >>= 8;
+            }
+        }
+        ++str;
+    }
+}
+
+// Compare two big numbers.  Returns -1, 0, or 1.
+int compare(const limb_t *x, size_t xsize, const limb_t *y, size_t ysize)
+{
+    limb_t a, b;
+    while (xsize > 0 || ysize > 0) {
+        if (xsize > ysize) {
+            --xsize;
+            a = x[xsize];
+            b = 0;
+        } else if (ysize > xsize) {
+            --ysize;
+            a = 0;
+            b = y[ysize];
+        } else {
+            --xsize;
+            --ysize;
+            a = x[xsize];
+            b = y[ysize];
+        }
+        if (a < b)
+            return -1;
+        else if (a > b)
+            return 1;
+    }
+    return 0;
+}
+
+// Compare two numbers where one is a decimal string.  Returns -1, 0, or 1.
+int compare(const limb_t *x, size_t xsize, const char *y)
+{
+    limb_t val[NUM_SIZE_512BIT];
+    fromString(val, NUM_SIZE_512BIT, y);
+    return compare(x, xsize, val, NUM_SIZE_512BIT);
+}
+
+// Prints a number in hexadecimal.
+void printNumber(const char *name, const limb_t *x, size_t xsize)
+{
+    static const char hexchars[] = "0123456789ABCDEF";
+    Serial.print(name);
+    Serial.print(" = ");
+    for (size_t posn = 0; posn < xsize; ++posn) {
+        for (uint8_t bit = LIMB_BITS; bit > 0; ) {
+            bit -= 4;
+            Serial.print(hexchars[(x[xsize - 1 - posn] >> bit) & 0x0F]);
+        }
+        Serial.print(' ');
+    }
+    Serial.println();
+}
+
+// Standard numbers that are useful in big number tests.
+char const num_0[] PROGMEM = "0";
+char const num_1[] PROGMEM = "1";
+char const num_2[] PROGMEM = "2";
+char const num_4[] PROGMEM = "4";
+char const num_5[] PROGMEM = "5";
+char const num_128[] PROGMEM = "128";
+char const num_256[] PROGMEM = "256";
+char const num_2_64_m7[] PROGMEM = "18446744073709551609"; // 2^64 - 7
+char const num_2_129_m5[] PROGMEM = "680564733841876926926749214863536422907"; // 2^129 - 5
+char const num_pi_77[] PROGMEM = "31415926535897932384626433832795028841971693993751058209749445923078164062862"; // 77 digits of pi
+char const num_pi_154[] PROGMEM = "3141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481"; // 154 digits of pi
+char const num_2_255_m253[] PROGMEM = "57896044618658097711785492504343953926634992332820282019728792003956564819715"; // 2^255 - 253
+char const num_2_255_m20[] PROGMEM = "57896044618658097711785492504343953926634992332820282019728792003956564819948"; // 2^255 - 20
+char const num_2_255_m19[] PROGMEM = "57896044618658097711785492504343953926634992332820282019728792003956564819949"; // 2^255 - 19
+char const num_2_255_m19_x2[] PROGMEM = "115792089237316195423570985008687907853269984665640564039457584007913129639898"; // (2^255 - 19) * 2
+char const num_a24[] PROGMEM = "121665";
+char const num_2_512_m19[] PROGMEM = "13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084077"; // (2^512 - 19)
+
+// Table of useful numbers.
+const char * const numbers[] = {
+    num_0,
+    num_1,
+    num_2,
+    num_4,
+    num_5,
+    num_128,
+    num_256,
+    num_2_64_m7,
+    num_2_129_m5,
+    num_pi_77,
+    num_2_255_m253,
+    num_2_255_m20,
+    num_pi_154,
+    num_2_512_m19,
+    0
+};
+#define numbers_count   ((sizeof(numbers) / sizeof(numbers[0])) - 1)
+
+#define foreach_number(var) \
+    const char *var = numbers[0]; \
+    for (unsigned index##var = 0; index##var < numbers_count; \
+         ++index##var, var = numbers[index##var])
+
+// Print a decimal string from program memory.
+void printProgMem(const char *str)
+{
+    uint8_t ch;
+    while ((ch = pgm_read_byte((uint8_t *)str)) != '\0') {
+        Serial.print((char)ch);
+        ++str;
+    }
+}
+
+// Determine if an array consists of all zero bytes.
+static bool isAllZero(const uint8_t *bytes, size_t size)
+{
+    while (size > 0) {
+        if (*bytes++ != 0)
+            return false;
+        --size;
+    }
+    return true;
+}
+
+// Determine if an array consists of all 0xBA bytes.
+static bool isAllBad(const uint8_t *bytes, size_t size)
+{
+    while (size > 0) {
+        if (*bytes++ != 0xBA)
+            return false;
+        --size;
+    }
+    return true;
+}
+
+#if !BIGNUMBER_LIMB_8BIT
+
+static bool isAllBad(const limb_t *limbs, size_t size)
+{
+    return isAllBad((uint8_t *)limbs, size * sizeof(limb_t));
+}
+
+#endif
+
+// Truncate the limb representation of a number to a specific byte length.
+static void truncateNumber(limb_t *limbs, size_t bytes)
+{
+    size_t posn = NUM_SIZE_512BIT * sizeof(limb_t);
+    size_t posn2;
+    limb_t mask;
+    while (posn > bytes) {
+        --posn;
+        posn2 = posn % sizeof(limb_t);
+        if (posn2 == 0)
+            mask = 0;
+        else if (posn2 == 1)
+            mask = 0x000000FF;
+        else if (posn2 == 2)
+            mask = 0x0000FFFF;
+#if BIGNUMBER_LIMB_64BIT
+        else if (posn2 == 3)
+            mask = 0x00FFFFFF;
+        else if (posn2 == 4)
+            mask = 0xFFFFFFFF;
+        else if (posn2 == 5)
+            mask = 0xFFFFFFFFFF;
+        else if (posn2 == 6)
+            mask = 0xFFFFFFFFFFFF;
+        else
+            mask = 0xFFFFFFFFFFFFFF;
+#else
+        else
+            mask = 0x00FFFFFF;
+#endif
+        limbs[posn / sizeof(limb_t)] &= mask;
+    }
+}
+
+// Test byte array pack and unpack operations.
+void testPackUnpack(void)
+{
+    limb_t num[NUM_SIZE_512BIT];
+    limb_t tnum[NUM_SIZE_512BIT];
+    limb_t limbs[NUM_SIZE_512BIT];
+    uint8_t bytes[64];
+    uint8_t expected[64];
+    size_t posn;
+    uint8_t temp;
+
+    foreach_number(x) {
+        // What number are we on?
+        Serial.print("pack ");
+        printProgMem(x);
+        Serial.print(": ");
+        Serial.flush();
+        bool ok = true;
+
+        // Convert the number into limbs and bytes in a simple way.
+        fromString(num, NUM_SIZE_512BIT, x);
+        bytesFromString(expected, sizeof(expected), x);
+
+        // Check packLE() and unpackLE() against the expected values.
+        for (posn = 0; posn < 64; ++posn) {
+            memset(bytes, 0xBA, sizeof(bytes));
+            BigNumberUtil::packLE(bytes, posn, num, NUM_SIZE_512BIT);
+            if (memcmp(bytes, expected, posn) != 0)
+                ok = false;
+            if (!isAllBad(bytes + posn, sizeof(bytes) - posn))
+                ok = false;
+        }
+        for (posn = 0; posn < NUM_SIZE_512BIT; ++posn) {
+            BigNumberUtil::packLE(bytes, sizeof(bytes), num, posn);
+            if (memcmp(bytes, expected, posn * sizeof(limb_t)) != 0)
+                ok = false;
+            if (!isAllZero(bytes + posn * sizeof(limb_t), sizeof(bytes) - posn * sizeof(limb_t)))
+                ok = false;
+        }
+        for (posn = 0; posn < NUM_SIZE_512BIT; ++posn) {
+            memset(limbs, 0xBA, sizeof(limbs));
+            BigNumberUtil::unpackLE(limbs, posn, expected, sizeof(expected));
+            if (memcmp(limbs, num, posn) != 0)
+                ok = false;
+            if (!isAllBad(limbs + posn, NUM_SIZE_512BIT - posn))
+                ok = false;
+        }
+        for (posn = 0; posn < 64; ++posn) {
+            memset(limbs, 0xBA, sizeof(limbs));
+            BigNumberUtil::unpackLE(limbs, NUM_SIZE_512BIT,
+                                    expected, sizeof(expected) - posn);
+            memcpy(tnum, num, sizeof(num));
+            truncateNumber(tnum, sizeof(expected) - posn);
+            if (memcmp(limbs, tnum, NUM_SIZE_512BIT) != 0)
+                ok = false;
+        }
+        for (posn = 0; posn < NUM_SIZE_512BIT; ++posn) {
+            memset(limbs, 0xBA, sizeof(limbs));
+            BigNumberUtil::unpackLE(limbs, posn, expected, sizeof(expected));
+            if (memcmp(limbs, num, posn * sizeof(limb_t)) != 0)
+                ok = false;
+            if (!isAllBad(limbs + posn, NUM_SIZE_512BIT - posn))
+                ok = false;
+        }
+
+        // Swap the expected byte array into big-endian order.
+        for (posn = 0; posn < 32; ++posn) {
+            temp = expected[posn];
+            expected[posn] = expected[63 - posn];
+            expected[63 - posn] = temp;
+        }
+
+        // Check packBE() and unpackBE() against the expected values.
+        for (posn = 0; posn < 64; ++posn) {
+            memset(bytes, 0xBA, sizeof(bytes));
+            BigNumberUtil::packBE(bytes, posn, num, NUM_SIZE_512BIT);
+            if (memcmp(bytes, expected + 64 - posn, posn) != 0)
+                ok = false;
+            if (!isAllBad(bytes + posn, sizeof(bytes) - posn))
+                ok = false;
+        }
+        for (posn = 0; posn < NUM_SIZE_512BIT; ++posn) {
+            BigNumberUtil::packBE(bytes, sizeof(bytes), num, posn);
+            if (memcmp(bytes + 64 - posn * sizeof(limb_t),
+                       expected + 64 - posn * sizeof(limb_t),
+                       posn * sizeof(limb_t)) != 0)
+                ok = false;
+            if (!isAllZero(bytes, sizeof(bytes) - posn * sizeof(limb_t)))
+                ok = false;
+        }
+        for (posn = 0; posn < NUM_SIZE_512BIT; ++posn) {
+            memset(limbs, 0xBA, sizeof(limbs));
+            BigNumberUtil::unpackBE(limbs, posn, expected, sizeof(expected));
+            if (memcmp(limbs, num, posn) != 0)
+                ok = false;
+            if (!isAllBad(limbs + posn, NUM_SIZE_512BIT - posn))
+                ok = false;
+        }
+        for (posn = 0; posn < 64; ++posn) {
+            memset(limbs, 0xBA, sizeof(limbs));
+            BigNumberUtil::unpackBE(limbs, NUM_SIZE_512BIT,
+                                    expected + posn, sizeof(expected) - posn);
+            memcpy(tnum, num, sizeof(num));
+            truncateNumber(tnum, sizeof(expected) - posn);
+            if (memcmp(limbs, tnum, NUM_SIZE_512BIT) != 0)
+                ok = false;
+        }
+        for (posn = 0; posn < NUM_SIZE_512BIT; ++posn) {
+            memset(limbs, 0xBA, sizeof(limbs));
+            BigNumberUtil::unpackBE(limbs, posn, expected, sizeof(expected));
+            if (memcmp(limbs, num, posn * sizeof(limb_t)) != 0)
+                ok = false;
+            if (!isAllBad(limbs + posn, NUM_SIZE_512BIT - posn))
+                ok = false;
+        }
+
+        // Report the results.
+        if (ok)
+            Serial.println("ok");
+        else
+            Serial.println("failed");
+    }
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    testPackUnpack();
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestCTR/TestCTR.ino b/src/modules/Crypto/examples/TestCTR/TestCTR.ino
new file mode 100644
index 0000000..a164662
--- /dev/null
+++ b/src/modules/Crypto/examples/TestCTR/TestCTR.ino
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the CTR implementation to verify correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <AES.h>
+#include <CTR.h>
+#include <string.h>
+
+#define MAX_PLAINTEXT_SIZE  36
+#define MAX_CIPHERTEXT_SIZE 36
+
+struct TestVector
+{
+    const char *name;
+    byte key[16];
+    byte plaintext[MAX_PLAINTEXT_SIZE];
+    byte ciphertext[MAX_CIPHERTEXT_SIZE];
+    byte iv[16];
+    size_t size;
+};
+
+// Test vectors for AES-128 in CTR mode from RFC 3686.
+static TestVector const testVectorAES128CTR1 = {
+    .name        = "AES-128-CTR #1",
+    .key         = {0xAE, 0x68, 0x52, 0xF8, 0x12, 0x10, 0x67, 0xCC,
+                    0x4B, 0xF7, 0xA5, 0x76, 0x55, 0x77, 0xF3, 0x9E},
+    .plaintext   = {0x53, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, 0x62,
+                    0x6C, 0x6F, 0x63, 0x6B, 0x20, 0x6D, 0x73, 0x67},
+    .ciphertext  = {0xE4, 0x09, 0x5D, 0x4F, 0xB7, 0xA7, 0xB3, 0x79,
+                    0x2D, 0x61, 0x75, 0xA3, 0x26, 0x13, 0x11, 0xB8},
+    .iv          = {0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
+    .size        = 16
+};
+static TestVector const testVectorAES128CTR2 = {
+    .name        = "AES-128-CTR #2",
+    .key         = {0x7E, 0x24, 0x06, 0x78, 0x17, 0xFA, 0xE0, 0xD7,
+                    0x43, 0xD6, 0xCE, 0x1F, 0x32, 0x53, 0x91, 0x63},
+    .plaintext   = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+                    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+                    0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F},
+    .ciphertext  = {0x51, 0x04, 0xA1, 0x06, 0x16, 0x8A, 0x72, 0xD9,
+                    0x79, 0x0D, 0x41, 0xEE, 0x8E, 0xDA, 0xD3, 0x88,
+                    0xEB, 0x2E, 0x1E, 0xFC, 0x46, 0xDA, 0x57, 0xC8,
+                    0xFC, 0xE6, 0x30, 0xDF, 0x91, 0x41, 0xBE, 0x28},
+    .iv          = {0x00, 0x6C, 0xB6, 0xDB, 0xC0, 0x54, 0x3B, 0x59,
+                    0xDA, 0x48, 0xD9, 0x0B, 0x00, 0x00, 0x00, 0x01},
+    .size        = 32
+};
+static TestVector const testVectorAES128CTR3 = {
+    .name        = "AES-128-CTR #3",
+    .key         = {0x76, 0x91, 0xBE, 0x03, 0x5E, 0x50, 0x20, 0xA8,
+                    0xAC, 0x6E, 0x61, 0x85, 0x29, 0xF9, 0xA0, 0xDC},
+    .plaintext   = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+                    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+                    0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+                    0x20, 0x21, 0x22, 0x23},
+    .ciphertext  = {0xC1, 0xCF, 0x48, 0xA8, 0x9F, 0x2F, 0xFD, 0xD9,
+                    0xCF, 0x46, 0x52, 0xE9, 0xEF, 0xDB, 0x72, 0xD7,
+                    0x45, 0x40, 0xA4, 0x2B, 0xDE, 0x6D, 0x78, 0x36,
+                    0xD5, 0x9A, 0x5C, 0xEA, 0xAE, 0xF3, 0x10, 0x53,
+                    0x25, 0xB2, 0x07, 0x2F},
+    .iv          = {0x00, 0xE0, 0x01, 0x7B, 0x27, 0x77, 0x7F, 0x3F,
+                    0x4A, 0x17, 0x86, 0xF0, 0x00, 0x00, 0x00, 0x01},
+    .size        = 36
+};
+
+CTR<AES128> ctraes128;
+
+byte buffer[128];
+
+bool testCipher_N(Cipher *cipher, const struct TestVector *test, size_t inc)
+{
+    byte output[MAX_CIPHERTEXT_SIZE];
+    size_t posn, len;
+
+    cipher->clear();
+    if (!cipher->setKey(test->key, cipher->keySize())) {
+        Serial.print("setKey ");
+        return false;
+    }
+    if (!cipher->setIV(test->iv, cipher->ivSize())) {
+        Serial.print("setIV ");
+        return false;
+    }
+
+    memset(output, 0xBA, sizeof(output));
+
+    for (posn = 0; posn < test->size; posn += inc) {
+        len = test->size - posn;
+        if (len > inc)
+            len = inc;
+        cipher->encrypt(output + posn, test->plaintext + posn, len);
+    }
+
+    if (memcmp(output, test->ciphertext, test->size) != 0) {
+        Serial.print(output[0], HEX);
+        Serial.print("->");
+        Serial.print(test->ciphertext[0], HEX);
+        return false;
+    }
+
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->setIV(test->iv, cipher->ivSize());
+
+    for (posn = 0; posn < test->size; posn += inc) {
+        len = test->size - posn;
+        if (len > inc)
+            len = inc;
+        cipher->decrypt(output + posn, test->ciphertext + posn, len);
+    }
+
+    if (memcmp(output, test->plaintext, test->size) != 0)
+        return false;
+
+    return true;
+}
+
+void testCipher(Cipher *cipher, const struct TestVector *test)
+{
+    bool ok;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    ok  = testCipher_N(cipher, test, test->size);
+    ok &= testCipher_N(cipher, test, 1);
+    ok &= testCipher_N(cipher, test, 2);
+    ok &= testCipher_N(cipher, test, 5);
+    ok &= testCipher_N(cipher, test, 8);
+    ok &= testCipher_N(cipher, test, 13);
+    ok &= testCipher_N(cipher, test, 16);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfCipherEncrypt(const char *name, Cipher *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print(name);
+    Serial.print(" ... ");
+
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->setIV(test->iv, cipher->ivSize());
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        cipher->encrypt(buffer, buffer, sizeof(buffer));
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((sizeof(buffer) * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfCipherDecrypt(const char *name, Cipher *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print(name);
+    Serial.print(" ... ");
+
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->setIV(test->iv, cipher->ivSize());
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        cipher->decrypt(buffer, buffer, sizeof(buffer));
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((sizeof(buffer) * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testCipher(&ctraes128, &testVectorAES128CTR1);
+    testCipher(&ctraes128, &testVectorAES128CTR2);
+    testCipher(&ctraes128, &testVectorAES128CTR3);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfCipherEncrypt("AES-128-CTR Encrypt", &ctraes128, &testVectorAES128CTR1);
+    perfCipherDecrypt("AES-128-CTR Decrypt", &ctraes128, &testVectorAES128CTR1);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestChaCha/TestChaCha.ino b/src/modules/Crypto/examples/TestChaCha/TestChaCha.ino
new file mode 100644
index 0000000..7101f97
--- /dev/null
+++ b/src/modules/Crypto/examples/TestChaCha/TestChaCha.ino
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the ChaCha implementation to verify correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <ChaCha.h>
+#include <string.h>
+#if defined(ESP8266) || defined(ESP32)
+#include <pgmspace.h>
+#else
+#include <avr/pgmspace.h>
+#endif
+
+#define MAX_PLAINTEXT_SIZE  64
+#define MAX_CIPHERTEXT_SIZE 64
+
+struct TestVector
+{
+    const char *name;
+    byte key[32];
+    size_t keySize;
+    uint8_t rounds;
+    byte plaintext[MAX_PLAINTEXT_SIZE];
+    byte ciphertext[MAX_CIPHERTEXT_SIZE];
+    byte iv[8];
+    byte counter[8];
+    size_t size;
+};
+
+// Use the test vectors from section 9 of the Salsa20 specification,
+// http://cr.yp.to/snuffle/spec.pdf, but modify the ciphertext to
+// the expected output from ChaCha20/12/8.  Unfortunately the ChaCha
+// specification doesn't contain test vectors - these were generated
+// using the reference implementation from http://cr.yp.to/chacha.html.
+
+static TestVector const testVectorChaCha20_128 PROGMEM = {
+    .name        = "ChaCha20 128-bit",
+    .key         = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
+    .keySize     = 16,
+    .rounds      = 20,
+    .plaintext   = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .ciphertext  = {0x1C, 0x91, 0xE7, 0x99, 0x71, 0xC0, 0x1C, 0x2A,
+                    0xEC, 0xE9, 0x24, 0x35, 0xB1, 0x6E, 0xBF, 0xFD,
+                    0x33, 0x05, 0xCC, 0x17, 0x24, 0x9D, 0x66, 0xA7,
+                    0xA0, 0xCA, 0xB8, 0x36, 0x03, 0xA6, 0x9D, 0x93,
+                    0x9A, 0x4C, 0x10, 0x40, 0xD9, 0x2A, 0x86, 0x78,
+                    0x3A, 0xAD, 0x71, 0x87, 0x55, 0x9F, 0x5B, 0x9A,
+                    0x68, 0x52, 0xA0, 0xAD, 0x59, 0xAE, 0x04, 0x10,
+                    0x25, 0x74, 0x5C, 0x05, 0x62, 0x78, 0xF4, 0x8A},
+    .iv          = {101,102,103,104,105,106,107,108},
+    .counter     = {109, 110, 111, 112, 113, 114, 115, 116},
+    .size        = 64
+};
+
+static TestVector const testVectorChaCha20_256 PROGMEM = {
+    .name        = "ChaCha20 256-bit",
+    .key         = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+                    201, 202, 203, 204, 205, 206, 207, 208, 209, 210,
+                    211, 212, 213, 214, 215, 216},
+    .keySize     = 32,
+    .rounds      = 20,
+    .plaintext   = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .ciphertext  = {0x2A, 0x7E, 0x73, 0xC2, 0x2A, 0xE5, 0xCF, 0x4E,
+                    0x21, 0x75, 0xB1, 0x26, 0x38, 0x3F, 0x60, 0x84,
+                    0x11, 0x25, 0xFC, 0xAD, 0xFD, 0x16, 0x54, 0xF2,
+                    0xD7, 0x8C, 0x5D, 0x49, 0x8D, 0x96, 0xBE, 0x15,
+                    0xC9, 0x00, 0x12, 0x09, 0x14, 0x43, 0x2D, 0x6D,
+                    0x64, 0x33, 0x88, 0xA6, 0x16, 0x39, 0x86, 0xFD,
+                    0xD8, 0x85, 0x4D, 0x76, 0x42, 0xEC, 0x0A, 0x0C,
+                    0x8A, 0xF2, 0x99, 0x2E, 0x54, 0xAE, 0xB4, 0xD9},
+    .iv          = {101,102,103,104,105,106,107,108},
+    .counter     = {109, 110, 111, 112, 113, 114, 115, 116},
+    .size        = 64
+};
+
+static TestVector const testVectorChaCha12_128 PROGMEM = {
+    .name        = "ChaCha12 128-bit",
+    .key         = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
+    .keySize     = 16,
+    .rounds      = 12,
+    .plaintext   = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .ciphertext  = {0xCB, 0xC1, 0xCF, 0x63, 0xE8, 0xD0, 0x62, 0x83,
+                    0xFC, 0x12, 0x87, 0x8C, 0x62, 0x09, 0x5B, 0xF8,
+                    0x84, 0x93, 0x30, 0xC6, 0xE6, 0x26, 0x87, 0x99,
+                    0xB0, 0xD9, 0xC1, 0xE1, 0xD7, 0x58, 0xCA, 0x05,
+                    0xFE, 0x46, 0x40, 0xD1, 0xDC, 0x14, 0x68, 0x3C,
+                    0xFF, 0x25, 0xF7, 0x70, 0x5F, 0xBF, 0x37, 0xC5,
+                    0x29, 0x8F, 0x3C, 0x55, 0x74, 0xDF, 0xF7, 0x49,
+                    0x8D, 0xD8, 0xE9, 0xBA, 0x5D, 0xF1, 0x9F, 0xA5},
+    .iv          = {101,102,103,104,105,106,107,108},
+    .counter     = {109, 110, 111, 112, 113, 114, 115, 116},
+    .size        = 64
+};
+
+static TestVector const testVectorChaCha12_256 PROGMEM = {
+    .name        = "ChaCha12 256-bit",
+    .key         = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+                    201, 202, 203, 204, 205, 206, 207, 208, 209, 210,
+                    211, 212, 213, 214, 215, 216},
+    .keySize     = 32,
+    .rounds      = 12,
+    .plaintext   = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .ciphertext  = {0xB8, 0x49, 0xD4, 0x70, 0xE0, 0xFF, 0x57, 0x12,
+                    0x95, 0xBF, 0xD9, 0xCD, 0x26, 0xFD, 0x4D, 0x6E,
+                    0x70, 0xA2, 0xBC, 0x58, 0x63, 0xF6, 0x2C, 0xC3,
+                    0xC7, 0x1C, 0x9B, 0x1A, 0x54, 0xDC, 0xF9, 0xF8,
+                    0xFD, 0x59, 0xEA, 0xC9, 0xC3, 0x10, 0xA1, 0xDE,
+                    0xD1, 0x53, 0x84, 0xD6, 0x8D, 0xC6, 0x61, 0x09,
+                    0x2E, 0x62, 0x14, 0xC5, 0x77, 0x4B, 0x6B, 0x5B,
+                    0x0D, 0x35, 0xE6, 0x17, 0x41, 0x51, 0xA6, 0xA4},
+    .iv          = {101,102,103,104,105,106,107,108},
+    .counter     = {109, 110, 111, 112, 113, 114, 115, 116},
+    .size        = 64
+};
+
+static TestVector const testVectorChaCha8_128 PROGMEM = {
+    .name        = "ChaCha8 128-bit",
+    .key         = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
+    .keySize     = 16,
+    .rounds      = 8,
+    .plaintext   = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .ciphertext  = {0x76, 0x42, 0x84, 0xB4, 0x87, 0x1F, 0x54, 0xAE,
+                    0x33, 0xBF, 0x79, 0x3C, 0xE2, 0x78, 0x5B, 0x4D,
+                    0xE7, 0x90, 0xF3, 0x8C, 0xB8, 0xF4, 0xA1, 0x56,
+                    0x87, 0x8B, 0x54, 0x06, 0xBE, 0x5A, 0x1B, 0x1C,
+                    0x30, 0x31, 0xD3, 0xCD, 0x90, 0x34, 0xC8, 0x93,
+                    0x2C, 0x0A, 0x5E, 0xC9, 0x4A, 0x1A, 0x66, 0x4C,
+                    0x28, 0x94, 0xA9, 0x61, 0xBB, 0xB4, 0xF0, 0x2D,
+                    0x59, 0x73, 0x9F, 0xC9, 0xF1, 0xF0, 0x66, 0x05},
+    .iv          = {101,102,103,104,105,106,107,108},
+    .counter     = {109, 110, 111, 112, 113, 114, 115, 116},
+    .size        = 64
+};
+
+static TestVector const testVectorChaCha8_256 PROGMEM = {
+    .name        = "ChaCha8 256-bit",
+    .key         = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+                    201, 202, 203, 204, 205, 206, 207, 208, 209, 210,
+                    211, 212, 213, 214, 215, 216},
+    .keySize     = 32,
+    .rounds      = 8,
+    .plaintext   = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .ciphertext  = {0x38, 0x0F, 0x75, 0xD6, 0x32, 0xF8, 0xBB, 0x2C,
+                    0x44, 0x81, 0xF4, 0x27, 0x90, 0xB8, 0xAA, 0xE3,
+                    0x09, 0xD1, 0xB9, 0x55, 0xC2, 0xF5, 0x85, 0x27,
+                    0xBB, 0x8F, 0x43, 0x00, 0x68, 0x2B, 0x2A, 0x1B,
+                    0x7A, 0xC1, 0x5B, 0xC3, 0xA3, 0xFF, 0x29, 0xC9,
+                    0xD2, 0x95, 0x98, 0xF6, 0x3C, 0xAC, 0x9B, 0x2C,
+                    0xA3, 0xF1, 0x40, 0x1E, 0xFA, 0x7C, 0xAC, 0xA3,
+                    0xB1, 0x61, 0x27, 0x50, 0xBB, 0x03, 0x24, 0x36},
+    .iv          = {101,102,103,104,105,106,107,108},
+    .counter     = {109, 110, 111, 112, 113, 114, 115, 116},
+    .size        = 64
+};
+
+TestVector testVector;
+
+ChaCha chacha;
+
+byte buffer[128];
+
+bool testCipher_N(ChaCha *cipher, const struct TestVector *test, size_t inc)
+{
+    byte output[MAX_CIPHERTEXT_SIZE];
+    size_t posn, len;
+
+    cipher->clear();
+    if (!cipher->setKey(test->key, test->keySize)) {
+        Serial.print("setKey ");
+        return false;
+    }
+    if (!cipher->setIV(test->iv, cipher->ivSize())) {
+        Serial.print("setIV ");
+        return false;
+    }
+    if (!cipher->setCounter(test->counter, 8)) {
+        Serial.print("setCounter ");
+        return false;
+    }
+
+    memset(output, 0xBA, sizeof(output));
+
+    for (posn = 0; posn < test->size; posn += inc) {
+        len = test->size - posn;
+        if (len > inc)
+            len = inc;
+        cipher->encrypt(output + posn, test->plaintext + posn, len);
+    }
+
+    if (memcmp(output, test->ciphertext, test->size) != 0) {
+        Serial.print(output[0], HEX);
+        Serial.print("->");
+        Serial.print(test->ciphertext[0], HEX);
+        return false;
+    }
+
+    cipher->setKey(test->key, test->keySize);
+    cipher->setIV(test->iv, cipher->ivSize());
+    cipher->setCounter(test->counter, 8);
+
+    for (posn = 0; posn < test->size; posn += inc) {
+        len = test->size - posn;
+        if (len > inc)
+            len = inc;
+        cipher->decrypt(output + posn, test->ciphertext + posn, len);
+    }
+
+    if (memcmp(output, test->plaintext, test->size) != 0)
+        return false;
+
+    return true;
+}
+
+void testCipher(ChaCha *cipher, const struct TestVector *test)
+{
+    bool ok;
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    cipher->setNumRounds(test->rounds);
+
+    ok  = testCipher_N(cipher, test, test->size);
+    ok &= testCipher_N(cipher, test, 1);
+    ok &= testCipher_N(cipher, test, 2);
+    ok &= testCipher_N(cipher, test, 5);
+    ok &= testCipher_N(cipher, test, 8);
+    ok &= testCipher_N(cipher, test, 13);
+    ok &= testCipher_N(cipher, test, 16);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfCipherSetKey(ChaCha *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(test->name);
+    Serial.print(" SetKey ... ");
+
+    cipher->setNumRounds(test->rounds);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        cipher->setKey(test->key, test->keySize);
+        cipher->setIV(test->iv, 8);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per operation, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" per second");
+}
+
+void perfCipherEncrypt(ChaCha *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(test->name);
+    Serial.print(" Encrypt ... ");
+
+    cipher->setNumRounds(test->rounds);
+    cipher->setKey(test->key, test->keySize);
+    cipher->setIV(test->iv, cipher->ivSize());
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        cipher->encrypt(buffer, buffer, sizeof(buffer));
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((sizeof(buffer) * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfCipherDecrypt(ChaCha *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(test->name);
+    Serial.print(" Decrypt ... ");
+
+    cipher->setNumRounds(test->rounds);
+    cipher->setKey(test->key, test->keySize);
+    cipher->setIV(test->iv, cipher->ivSize());
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        cipher->decrypt(buffer, buffer, sizeof(buffer));
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((sizeof(buffer) * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfCipher(ChaCha *cipher, const struct TestVector *test)
+{
+    perfCipherSetKey(cipher, test);
+    perfCipherEncrypt(cipher, test);
+    perfCipherDecrypt(cipher, test);
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.print("State Size ...");
+    Serial.println(sizeof(ChaCha));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testCipher(&chacha, &testVectorChaCha20_128);
+    testCipher(&chacha, &testVectorChaCha20_256);
+    testCipher(&chacha, &testVectorChaCha12_128);
+    testCipher(&chacha, &testVectorChaCha12_256);
+    testCipher(&chacha, &testVectorChaCha8_128);
+    testCipher(&chacha, &testVectorChaCha8_256);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfCipher(&chacha, &testVectorChaCha20_128);
+    perfCipher(&chacha, &testVectorChaCha20_256);
+    perfCipher(&chacha, &testVectorChaCha12_128);
+    perfCipher(&chacha, &testVectorChaCha12_256);
+    perfCipher(&chacha, &testVectorChaCha8_128);
+    perfCipher(&chacha, &testVectorChaCha8_256);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestChaChaPoly/TestChaChaPoly.ino b/src/modules/Crypto/examples/TestChaChaPoly/TestChaChaPoly.ino
new file mode 100644
index 0000000..41fbe9c
--- /dev/null
+++ b/src/modules/Crypto/examples/TestChaChaPoly/TestChaChaPoly.ino
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the ChaChaPoly implementation to verify
+correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <ChaChaPoly.h>
+#include <string.h>
+#if defined(ESP8266) || defined(ESP32)
+#include <pgmspace.h>
+#else
+#include <avr/pgmspace.h>
+#endif
+
+#define MAX_PLAINTEXT_LEN 265
+
+struct TestVector
+{
+    const char *name;
+    uint8_t key[32];
+    uint8_t plaintext[MAX_PLAINTEXT_LEN];
+    uint8_t ciphertext[MAX_PLAINTEXT_LEN];
+    uint8_t authdata[16];
+    uint8_t iv[16];
+    uint8_t tag[16];
+    size_t authsize;
+    size_t datasize;
+    size_t tagsize;
+    size_t ivsize;
+};
+
+// Test vectors for ChaChaPoly from draft-nir-cfrg-chacha20-poly1305-04.txt
+static TestVector const testVectorChaChaPoly_1 PROGMEM = {
+    .name        = "ChaChaPoly #1",
+    .key         = {0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+                    0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+                    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+                    0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f},
+    .plaintext   = {0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61,
+                    0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,
+                    0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20,
+                    0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,
+                    0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39,
+                    0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,
+                    0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66,
+                    0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f,
+                    0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20,
+                    0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20,
+                    0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75,
+                    0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73,
+                    0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f,
+                    0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
+                    0x74, 0x2e},
+    .ciphertext  = {0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb,
+                    0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2,
+                    0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe,
+                    0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6,
+                    0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12,
+                    0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b,
+                    0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29,
+                    0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36,
+                    0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c,
+                    0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58,
+                    0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94,
+                    0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc,
+                    0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d,
+                    0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b,
+                    0x61, 0x16},
+    .authdata    = {0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3,
+                    0xc4, 0xc5, 0xc6, 0xc7},
+    .iv          = {0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43,
+                    0x44, 0x45, 0x46, 0x47},
+    .tag         = {0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a,
+                    0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91},
+    .authsize    = 12,
+    .datasize    = 114,
+    .tagsize     = 16,
+    .ivsize      = 12
+};
+static TestVector const testVectorChaChaPoly_2 PROGMEM = {
+    .name        = "ChaChaPoly #2",
+    .key         = {0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a,
+                    0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0,
+                    0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09,
+                    0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0},
+    .plaintext   = {0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
+                    0x2d, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x20,
+                    0x61, 0x72, 0x65, 0x20, 0x64, 0x72, 0x61, 0x66,
+                    0x74, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65,
+                    0x6e, 0x74, 0x73, 0x20, 0x76, 0x61, 0x6c, 0x69,
+                    0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20,
+                    0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x20,
+                    0x6f, 0x66, 0x20, 0x73, 0x69, 0x78, 0x20, 0x6d,
+                    0x6f, 0x6e, 0x74, 0x68, 0x73, 0x20, 0x61, 0x6e,
+                    0x64, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x62, 0x65,
+                    0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64,
+                    0x2c, 0x20, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63,
+                    0x65, 0x64, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x6f,
+                    0x62, 0x73, 0x6f, 0x6c, 0x65, 0x74, 0x65, 0x64,
+                    0x20, 0x62, 0x79, 0x20, 0x6f, 0x74, 0x68, 0x65,
+                    0x72, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65,
+                    0x6e, 0x74, 0x73, 0x20, 0x61, 0x74, 0x20, 0x61,
+                    0x6e, 0x79, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x2e,
+                    0x20, 0x49, 0x74, 0x20, 0x69, 0x73, 0x20, 0x69,
+                    0x6e, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x70, 0x72,
+                    0x69, 0x61, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20,
+                    0x75, 0x73, 0x65, 0x20, 0x49, 0x6e, 0x74, 0x65,
+                    0x72, 0x6e, 0x65, 0x74, 0x2d, 0x44, 0x72, 0x61,
+                    0x66, 0x74, 0x73, 0x20, 0x61, 0x73, 0x20, 0x72,
+                    0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65,
+                    0x20, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61,
+                    0x6c, 0x20, 0x6f, 0x72, 0x20, 0x74, 0x6f, 0x20,
+                    0x63, 0x69, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65,
+                    0x6d, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20,
+                    0x74, 0x68, 0x61, 0x6e, 0x20, 0x61, 0x73, 0x20,
+                    0x2f, 0xe2, 0x80, 0x9c, 0x77, 0x6f, 0x72, 0x6b,
+                    0x20, 0x69, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x67,
+                    0x72, 0x65, 0x73, 0x73, 0x2e, 0x2f, 0xe2, 0x80,
+                    0x9d},
+    .ciphertext  = {0x64, 0xa0, 0x86, 0x15, 0x75, 0x86, 0x1a, 0xf4,
+                    0x60, 0xf0, 0x62, 0xc7, 0x9b, 0xe6, 0x43, 0xbd,
+                    0x5e, 0x80, 0x5c, 0xfd, 0x34, 0x5c, 0xf3, 0x89,
+                    0xf1, 0x08, 0x67, 0x0a, 0xc7, 0x6c, 0x8c, 0xb2,
+                    0x4c, 0x6c, 0xfc, 0x18, 0x75, 0x5d, 0x43, 0xee,
+                    0xa0, 0x9e, 0xe9, 0x4e, 0x38, 0x2d, 0x26, 0xb0,
+                    0xbd, 0xb7, 0xb7, 0x3c, 0x32, 0x1b, 0x01, 0x00,
+                    0xd4, 0xf0, 0x3b, 0x7f, 0x35, 0x58, 0x94, 0xcf,
+                    0x33, 0x2f, 0x83, 0x0e, 0x71, 0x0b, 0x97, 0xce,
+                    0x98, 0xc8, 0xa8, 0x4a, 0xbd, 0x0b, 0x94, 0x81,
+                    0x14, 0xad, 0x17, 0x6e, 0x00, 0x8d, 0x33, 0xbd,
+                    0x60, 0xf9, 0x82, 0xb1, 0xff, 0x37, 0xc8, 0x55,
+                    0x97, 0x97, 0xa0, 0x6e, 0xf4, 0xf0, 0xef, 0x61,
+                    0xc1, 0x86, 0x32, 0x4e, 0x2b, 0x35, 0x06, 0x38,
+                    0x36, 0x06, 0x90, 0x7b, 0x6a, 0x7c, 0x02, 0xb0,
+                    0xf9, 0xf6, 0x15, 0x7b, 0x53, 0xc8, 0x67, 0xe4,
+                    0xb9, 0x16, 0x6c, 0x76, 0x7b, 0x80, 0x4d, 0x46,
+                    0xa5, 0x9b, 0x52, 0x16, 0xcd, 0xe7, 0xa4, 0xe9,
+                    0x90, 0x40, 0xc5, 0xa4, 0x04, 0x33, 0x22, 0x5e,
+                    0xe2, 0x82, 0xa1, 0xb0, 0xa0, 0x6c, 0x52, 0x3e,
+                    0xaf, 0x45, 0x34, 0xd7, 0xf8, 0x3f, 0xa1, 0x15,
+                    0x5b, 0x00, 0x47, 0x71, 0x8c, 0xbc, 0x54, 0x6a,
+                    0x0d, 0x07, 0x2b, 0x04, 0xb3, 0x56, 0x4e, 0xea,
+                    0x1b, 0x42, 0x22, 0x73, 0xf5, 0x48, 0x27, 0x1a,
+                    0x0b, 0xb2, 0x31, 0x60, 0x53, 0xfa, 0x76, 0x99,
+                    0x19, 0x55, 0xeb, 0xd6, 0x31, 0x59, 0x43, 0x4e,
+                    0xce, 0xbb, 0x4e, 0x46, 0x6d, 0xae, 0x5a, 0x10,
+                    0x73, 0xa6, 0x72, 0x76, 0x27, 0x09, 0x7a, 0x10,
+                    0x49, 0xe6, 0x17, 0xd9, 0x1d, 0x36, 0x10, 0x94,
+                    0xfa, 0x68, 0xf0, 0xff, 0x77, 0x98, 0x71, 0x30,
+                    0x30, 0x5b, 0xea, 0xba, 0x2e, 0xda, 0x04, 0xdf,
+                    0x99, 0x7b, 0x71, 0x4d, 0x6c, 0x6f, 0x2c, 0x29,
+                    0xa6, 0xad, 0x5c, 0xb4, 0x02, 0x2b, 0x02, 0x70,
+                    0x9b},
+    .authdata    = {0xf3, 0x33, 0x88, 0x86, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x4e, 0x91},
+    .iv          = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
+    .tag         = {0xee, 0xad, 0x9d, 0x67, 0x89, 0x0c, 0xbb, 0x22,
+                    0x39, 0x23, 0x36, 0xfe, 0xa1, 0x85, 0x1f, 0x38},
+    .authsize    = 12,
+    .datasize    = 265,
+    .tagsize     = 16,
+    .ivsize      = 8
+};
+
+TestVector testVector;
+
+ChaChaPoly chachapoly;
+
+byte buffer[MAX_PLAINTEXT_LEN];
+
+bool testCipher_N(ChaChaPoly *cipher, const struct TestVector *test, size_t inc)
+{
+    size_t posn, len;
+    uint8_t tag[16];
+
+    cipher->clear();
+    if (!cipher->setKey(test->key, 32)) {
+        Serial.print("setKey ");
+        return false;
+    }
+    if (!cipher->setIV(test->iv, test->ivsize)) {
+        Serial.print("setIV ");
+        return false;
+    }
+
+    memset(buffer, 0xBA, sizeof(buffer));
+
+    for (posn = 0; posn < test->authsize; posn += inc) {
+        len = test->authsize - posn;
+        if (len > inc)
+            len = inc;
+        cipher->addAuthData(test->authdata + posn, len);
+    }
+
+    for (posn = 0; posn < test->datasize; posn += inc) {
+        len = test->datasize - posn;
+        if (len > inc)
+            len = inc;
+        cipher->encrypt(buffer + posn, test->plaintext + posn, len);
+    }
+
+    if (memcmp(buffer, test->ciphertext, test->datasize) != 0) {
+        Serial.print(buffer[0], HEX);
+        Serial.print("->");
+        Serial.print(test->ciphertext[0], HEX);
+        return false;
+    }
+
+    cipher->computeTag(tag, sizeof(tag));
+    if (memcmp(tag, test->tag, sizeof(tag)) != 0) {
+        Serial.print("computed wrong tag ... ");
+        return false;
+    }
+
+    cipher->setKey(test->key, 32);
+    cipher->setIV(test->iv, test->ivsize);
+
+    for (posn = 0; posn < test->authsize; posn += inc) {
+        len = test->authsize - posn;
+        if (len > inc)
+            len = inc;
+        cipher->addAuthData(test->authdata + posn, len);
+    }
+
+    for (posn = 0; posn < test->datasize; posn += inc) {
+        len = test->datasize - posn;
+        if (len > inc)
+            len = inc;
+        cipher->decrypt(buffer + posn, test->ciphertext + posn, len);
+    }
+
+    if (memcmp(buffer, test->plaintext, test->datasize) != 0)
+        return false;
+
+    if (!cipher->checkTag(tag, sizeof(tag))) {
+        Serial.print("tag did not check ... ");
+        return false;
+    }
+
+    return true;
+}
+
+void testCipher(ChaChaPoly *cipher, const struct TestVector *test)
+{
+    bool ok;
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    ok  = testCipher_N(cipher, test, test->datasize);
+    ok &= testCipher_N(cipher, test, 1);
+    ok &= testCipher_N(cipher, test, 2);
+    ok &= testCipher_N(cipher, test, 5);
+    ok &= testCipher_N(cipher, test, 8);
+    ok &= testCipher_N(cipher, test, 13);
+    ok &= testCipher_N(cipher, test, 16);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfCipherSetKey(ChaChaPoly *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(test->name);
+    Serial.print(" SetKey ... ");
+
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        cipher->setKey(test->key, 32);
+        cipher->setIV(test->iv, test->ivsize);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per operation, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" per second");
+}
+
+void perfCipherEncrypt(ChaChaPoly *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(test->name);
+    Serial.print(" Encrypt ... ");
+
+    cipher->setKey(test->key, 32);
+    cipher->setIV(test->iv, test->ivsize);
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        cipher->encrypt(buffer, buffer, 128);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (128.0 * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((128.0 * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfCipherDecrypt(ChaChaPoly *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(test->name);
+    Serial.print(" Decrypt ... ");
+
+    cipher->setKey(test->key, 32);
+    cipher->setIV(test->iv, test->ivsize);
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        cipher->decrypt(buffer, buffer, 128);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (128.0 * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((128.0 * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfCipherAddAuthData(ChaChaPoly *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(test->name);
+    Serial.print(" AddAuthData ... ");
+
+    cipher->setKey(test->key, 32);
+    cipher->setIV(test->iv, test->ivsize);
+    start = micros();
+    memset(buffer, 0xBA, 128);
+    for (count = 0; count < 500; ++count) {
+        cipher->addAuthData(buffer, 128);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (128.0 * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((128.0 * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+/*
+void perfCipherComputeTag(ChaChaPoly *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(test->name);
+    Serial.print(" ComputeTag ... ");
+
+    cipher->setKey(test->key, 32);
+    cipher->setIV(test->iv, test->ivsize);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        cipher->computeTag(buffer, 16);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per operation, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" per second");
+}
+*/
+
+void perfCipher(ChaChaPoly *cipher, const struct TestVector *test)
+{
+    perfCipherSetKey(cipher, test);
+    perfCipherEncrypt(cipher, test);
+    perfCipherDecrypt(cipher, test);
+    perfCipherAddAuthData(cipher, test);
+    //perfCipherComputeTag(cipher, test);
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.print("State Size ... ");
+    Serial.println(sizeof(ChaChaPoly));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testCipher(&chachapoly, &testVectorChaChaPoly_1);
+    testCipher(&chachapoly, &testVectorChaChaPoly_2);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfCipher(&chachapoly, &testVectorChaChaPoly_1);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestCurve25519/TestCurve25519.ino b/src/modules/Crypto/examples/TestCurve25519/TestCurve25519.ino
new file mode 100644
index 0000000..9143359
--- /dev/null
+++ b/src/modules/Crypto/examples/TestCurve25519/TestCurve25519.ino
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the Curve25519 algorithm.
+*/
+
+#include <Crypto.h>
+#include <Curve25519.h>
+#include <RNG.h>
+#include <string.h>
+
+void printNumber(const char *name, const uint8_t *x)
+{
+    static const char hexchars[] = "0123456789ABCDEF";
+    Serial.print(name);
+    Serial.print(" = ");
+    for (uint8_t posn = 0; posn < 32; ++posn) {
+        Serial.print(hexchars[(x[posn] >> 4) & 0x0F]);
+        Serial.print(hexchars[x[posn] & 0x0F]);
+    }
+    Serial.println();
+}
+
+// Check the eval() function using the test vectors from
+// section 6.1 of RFC 7748.
+void testEval()
+{
+    static uint8_t alice_private[32] = {
+        0x77, 0x07, 0x6d, 0x0a, 0x73, 0x18, 0xa5, 0x7d,
+        0x3c, 0x16, 0xc1, 0x72, 0x51, 0xb2, 0x66, 0x45,
+        0xdf, 0x4c, 0x2f, 0x87, 0xeb, 0xc0, 0x99, 0x2a,
+        0xb1, 0x77, 0xfb, 0xa5, 0x1d, 0xb9, 0x2c, 0x2a
+    };
+    static uint8_t const alice_public[32] = {
+        0x85, 0x20, 0xf0, 0x09, 0x89, 0x30, 0xa7, 0x54,
+        0x74, 0x8b, 0x7d, 0xdc, 0xb4, 0x3e, 0xf7, 0x5a,
+        0x0d, 0xbf, 0x3a, 0x0d, 0x26, 0x38, 0x1a, 0xf4,
+        0xeb, 0xa4, 0xa9, 0x8e, 0xaa, 0x9b, 0x4e, 0x6a
+    };
+    static uint8_t bob_private[32] = {
+        0x5d, 0xab, 0x08, 0x7e, 0x62, 0x4a, 0x8a, 0x4b,
+        0x79, 0xe1, 0x7f, 0x8b, 0x83, 0x80, 0x0e, 0xe6,
+        0x6f, 0x3b, 0xb1, 0x29, 0x26, 0x18, 0xb6, 0xfd,
+        0x1c, 0x2f, 0x8b, 0x27, 0xff, 0x88, 0xe0, 0xeb
+    };
+    static uint8_t const bob_public[32] = {
+        0xde, 0x9e, 0xdb, 0x7d, 0x7b, 0x7d, 0xc1, 0xb4,
+        0xd3, 0x5b, 0x61, 0xc2, 0xec, 0xe4, 0x35, 0x37,
+        0x3f, 0x83, 0x43, 0xc8, 0x5b, 0x78, 0x67, 0x4d,
+        0xad, 0xfc, 0x7e, 0x14, 0x6f, 0x88, 0x2b, 0x4f
+    };
+    static uint8_t const shared_secret[32] = {
+        0x4a, 0x5d, 0x9d, 0x5b, 0xa4, 0xce, 0x2d, 0xe1,
+        0x72, 0x8e, 0x3b, 0xf4, 0x80, 0x35, 0x0f, 0x25,
+        0xe0, 0x7e, 0x21, 0xc9, 0x47, 0xd1, 0x9e, 0x33,
+        0x76, 0xf0, 0x9b, 0x3c, 0x1e, 0x16, 0x17, 0x42
+    };
+
+    // Fix up the private keys by applying the standard masks.
+    alice_private[0] &= 0xF8;
+    alice_private[31] = (alice_private[31] & 0x7F) | 0x40;
+    bob_private[0] &= 0xF8;
+    bob_private[31] = (bob_private[31] & 0x7F) | 0x40;
+
+    // Evaluate the curve function and check the public keys.
+    uint8_t result[32];
+    Serial.println("Fixed test vectors:");
+    Serial.print("Computing Alice's public key ... ");
+    Serial.flush();
+    unsigned long start = micros();
+    Curve25519::eval(result, alice_private, 0);
+    unsigned long elapsed = micros() - start;
+    if (memcmp(result, alice_public, 32) == 0) {
+        Serial.print("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result);
+        printNumber("expected", alice_public);
+    }
+    Serial.print(" (elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us)");
+    Serial.print("Computing Bob's public key ... ");
+    Serial.flush();
+    start = micros();
+    Curve25519::eval(result, bob_private, 0);
+    elapsed = micros() - start;
+    if (memcmp(result, bob_public, 32) == 0) {
+        Serial.print("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result);
+        printNumber("expected", bob_public);
+    }
+    Serial.print(" (elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us)");
+
+    // Compute the shared secret from each side.
+    Serial.print("Computing Alice's shared secret ... ");
+    Serial.flush();
+    start = micros();
+    Curve25519::eval(result, alice_private, bob_public);
+    elapsed = micros() - start;
+    if (memcmp(result, shared_secret, 32) == 0) {
+        Serial.print("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result);
+        printNumber("expected", shared_secret);
+    }
+    Serial.print(" (elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us)");
+    Serial.print("Computing Bob's shared secret ... ");
+    Serial.flush();
+    start = micros();
+    Curve25519::eval(result, bob_private, alice_public);
+    elapsed = micros() - start;
+    if (memcmp(result, shared_secret, 32) == 0) {
+        Serial.print("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result);
+        printNumber("expected", shared_secret);
+    }
+    Serial.print(" (elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us)");
+}
+
+void testDH()
+{
+    static uint8_t alice_k[32];
+    static uint8_t alice_f[32];
+    static uint8_t bob_k[32];
+    static uint8_t bob_f[32];
+
+    Serial.println("Diffie-Hellman key exchange:");
+    Serial.print("Generate random k/f for Alice ... ");
+    Serial.flush();
+    unsigned long start = micros();
+    Curve25519::dh1(alice_k, alice_f);
+    unsigned long elapsed = micros() - start;
+    Serial.print("elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us");
+
+    Serial.print("Generate random k/f for Bob ... ");
+    Serial.flush();
+    start = micros();
+    Curve25519::dh1(bob_k, bob_f);
+    elapsed = micros() - start;
+    Serial.print("elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us");
+
+    Serial.print("Generate shared secret for Alice ... ");
+    Serial.flush();
+    start = micros();
+    Curve25519::dh2(bob_k, alice_f);
+    elapsed = micros() - start;
+    Serial.print("elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us");
+
+    Serial.print("Generate shared secret for Bob ... ");
+    Serial.flush();
+    start = micros();
+    Curve25519::dh2(alice_k, bob_f);
+    elapsed = micros() - start;
+    Serial.print("elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us");
+
+    Serial.print("Check that the shared secrets match ... ");
+    if (memcmp(alice_k, bob_k, 32) == 0)
+        Serial.println("ok");
+    else
+        Serial.println("failed");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    // Start the random number generator.  We don't initialise a noise
+    // source here because we don't need one for testing purposes.
+    // Real DH applications should of course use a proper noise source.
+    RNG.begin("TestCurve25519 1.0");
+
+    // Perform the tests.
+    testEval();
+    Serial.println();
+    testDH();
+    Serial.println();
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestCurve25519Math/TestCurve25519Math.ino b/src/modules/Crypto/examples/TestCurve25519Math/TestCurve25519Math.ino
new file mode 100644
index 0000000..76c09b8
--- /dev/null
+++ b/src/modules/Crypto/examples/TestCurve25519Math/TestCurve25519Math.ino
@@ -0,0 +1,683 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the Curve25519 field mathematics independent
+of the full curve operation itself.
+*/
+
+// Enable access to the internals of Curve25519 to test the raw field ops.
+#define TEST_CURVE25519_FIELD_OPS 1
+
+#include <Crypto.h>
+#include <Curve25519.h>
+#include <utility/ProgMemUtil.h>
+#include <string.h>
+
+// Copy some definitions from the Curve25519 class for convenience.
+#define NUM_LIMBS   (32 / sizeof(limb_t))
+#define LIMB_BITS   (8 * sizeof(limb_t))
+#define INVERSE_LIMB (~((limb_t)0))
+
+// For simpleMod() below we need a type that is 4 times the size of limb_t.
+#if BIGNUMBER_LIMB_8BIT
+#define qlimb_t uint32_t
+#elif BIGNUMBER_LIMB_16BIT
+#define qlimb_t uint64_t
+#else
+#define BIGNUMBER_NO_QLIMB 1
+#endif
+
+limb_t arg1[NUM_LIMBS];
+limb_t arg2[NUM_LIMBS];
+limb_t result[NUM_LIMBS];
+limb_t result2[NUM_LIMBS * 2 + 1];
+limb_t temp[NUM_LIMBS];
+
+// Convert a decimal string in program memory into a number.
+void fromString(limb_t *x, uint8_t size, const char *str)
+{
+    uint8_t ch, posn;
+    memset(x, 0, sizeof(limb_t) * size);
+    while ((ch = pgm_read_byte((uint8_t *)str)) != '\0') {
+        if (ch >= '0' && ch <= '9') {
+            // Quick and simple method to multiply by 10 and add the new digit.
+            dlimb_t carry = ch - '0';
+            for (posn = 0; posn < size; ++posn) {
+                carry += ((dlimb_t)x[posn]) * 10U;
+                x[posn] = (limb_t)carry;
+                carry >>= LIMB_BITS;
+            }
+        }
+        ++str;
+    }
+}
+
+// Compare two numbers of NUM_LIMBS in length.  Returns -1, 0, or 1.
+int compare(const limb_t *x, const limb_t *y)
+{
+    for (uint8_t posn = NUM_LIMBS; posn > 0; --posn) {
+        limb_t a = x[posn - 1];
+        limb_t b = y[posn - 1];
+        if (a < b)
+            return -1;
+        else if (a > b)
+            return 1;
+    }
+    return 0;
+}
+
+// Compare two numbers where one is a decimal string.  Returns -1, 0, or 1.
+int compare(const limb_t *x, const char *y)
+{
+    limb_t val[NUM_LIMBS];
+    fromString(val, NUM_LIMBS, y);
+    return compare(x, val);
+}
+
+void printNumber(const char *name, const limb_t *x)
+{
+    static const char hexchars[] = "0123456789ABCDEF";
+    Serial.print(name);
+    Serial.print(" = ");
+    for (uint8_t posn = NUM_LIMBS; posn > 0; --posn) {
+        for (uint8_t bit = LIMB_BITS; bit > 0; ) {
+            bit -= 4;
+            Serial.print(hexchars[(x[posn - 1] >> bit) & 0x0F]);
+        }
+        Serial.print(' ');
+    }
+    Serial.println();
+}
+
+// Standard numbers that are useful in field operation tests.
+char const num_0[] PROGMEM = "0";
+char const num_1[] PROGMEM = "1";
+char const num_2[] PROGMEM = "2";
+char const num_4[] PROGMEM = "4";
+char const num_5[] PROGMEM = "5";
+char const num_128[] PROGMEM = "128";
+char const num_256[] PROGMEM = "256";
+char const num_2_64_m7[] PROGMEM = "18446744073709551609"; // 2^64 - 7
+char const num_2_129_m5[] PROGMEM = "680564733841876926926749214863536422907"; // 2^129 - 5
+char const num_pi[] PROGMEM = "31415926535897932384626433832795028841971693993751058209749445923078164062862"; // 77 digits of pi
+char const num_2_255_m253[] PROGMEM = "57896044618658097711785492504343953926634992332820282019728792003956564819715"; // 2^255 - 253
+char const num_2_255_m20[] PROGMEM = "57896044618658097711785492504343953926634992332820282019728792003956564819948"; // 2^255 - 20
+char const num_2_255_m19[] PROGMEM = "57896044618658097711785492504343953926634992332820282019728792003956564819949"; // 2^255 - 19
+char const num_2_255_m19_x2[] PROGMEM = "115792089237316195423570985008687907853269984665640564039457584007913129639898"; // (2^255 - 19) * 2
+char const num_a24[] PROGMEM = "121665";
+
+// Table of useful numbers less than 2^255 - 19.
+const char * const numbers[] = {
+    num_0,
+    num_1,
+    num_2,
+    num_4,
+    num_5,
+    num_128,
+    num_256,
+    num_2_64_m7,
+    num_2_129_m5,
+    num_pi,
+    num_2_255_m253,
+    num_2_255_m20,
+    0
+};
+#define numbers_count   ((sizeof(numbers) / sizeof(numbers[0])) - 1)
+
+#define foreach_number(var) \
+    const char *var = numbers[0]; \
+    for (unsigned index##var = 0; index##var < numbers_count; \
+         ++index##var, var = numbers[index##var])
+
+void printProgMem(const char *str)
+{
+    uint8_t ch;
+    while ((ch = pgm_read_byte((uint8_t *)str)) != '\0') {
+        Serial.print((char)ch);
+        ++str;
+    }
+}
+
+// Simple implementation of modular addition to cross-check the library.
+void simpleAdd(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    uint8_t posn;
+    dlimb_t carry = 0;
+    for (posn = 0; posn < NUM_LIMBS; ++posn) {
+        carry += x[posn];
+        carry += y[posn];
+        result[posn] = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    if (compare(result, num_2_255_m19) >= 0) {
+        // Subtract 2^255 - 19 to get the final result.
+        // Same as add 19 and then subtract 2^255.
+        carry = 19;
+        for (posn = 0; posn < NUM_LIMBS; ++posn) {
+            carry += result[posn];
+            result[posn] = (limb_t)carry;
+            carry >>= LIMB_BITS;
+        }
+        result[NUM_LIMBS - 1] -= ((limb_t)1) << (LIMB_BITS - 1);
+    }
+}
+
+// Simple implementation of subtraction to cross-check the library.
+// Note: this does not reduce the result modulo 2^255 - 19 and we
+// assume that x is greater than or equal to y.
+void simpleSub(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    uint8_t posn;
+    dlimb_t borrow = 0;
+    for (posn = 0; posn < NUM_LIMBS; ++posn) {
+        borrow = ((dlimb_t)x[posn]) - y[posn] - borrow;
+        result[posn] = (limb_t)borrow;
+        borrow = (borrow >> LIMB_BITS) != 0;
+    }
+}
+
+// Simple implementation of multiplication to cross-check the library.
+// Note: this does not reduce the result modulo 2^255 - 19.
+// The "result" buffer must contain at least NUM_LIMBS * 2 limbs.
+void simpleMul(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    memset(result, 0, NUM_LIMBS * 2 * sizeof(limb_t));
+    for (uint8_t i = 0; i < NUM_LIMBS; ++i) {
+        for (uint8_t j = 0; j < NUM_LIMBS; ++j) {
+            uint8_t n = i + j;
+            dlimb_t carry =
+                ((dlimb_t)x[i]) * y[j] + result[n];
+            result[n] = (limb_t)carry;
+            carry >>= LIMB_BITS;
+            ++n;
+            while (carry != 0 && n < (NUM_LIMBS * 2)) {
+                carry += result[n];
+                result[n] = (limb_t)carry;
+                carry >>= LIMB_BITS;
+                ++n;
+            }
+        }
+    }
+}
+
+#if defined(BIGNUMBER_NO_QLIMB)
+
+// Quick check to correct the estimate on a quotient word.
+static inline limb_t correctEstimate
+    (limb_t q, limb_t y1, limb_t y2, dlimb_t x01, limb_t x2)
+{
+    // Algorithm D from section 4.3.1 of "The Art Of Computer Programming",
+    // D. Knuth, Volume 2, "Seminumerical Algorithms", Second Edition, 1981.
+    //
+    // We want to check if (y2 * q) > ((x01 - y1 * q) * b + x2) where
+    // b is (1 << LIMB_BITS).  If it is, then q must be reduced by 1.
+    //
+    // One wrinkle that isn't obvious from Knuth's description is that it
+    // is possible for (x01 - y1 * q) >= b, especially in the case where
+    // x0 = y1 and q = b - 1.  This will cause an overflow of the intermediate
+    // double-word result ((x01 - y1 * q) * b).
+    //
+    // In assembly language, we could use the carry flag to detect when
+    // (x01 - y1 * q) * b overflows, but we can't access the carry flag
+    // in C++.  So we have to account for the carry in a different way here.
+
+    // Calculate the remainder using the estimated quotient.
+    dlimb_t r = x01 - ((dlimb_t)y1) * q;
+
+    // If there will be a double-word carry when we calculate (r * b),
+    // then (y2 * q) is obviously going to be less than (r * b), so we
+    // can stop here.  The estimated quotient is correct.
+    if (r & (((dlimb_t)INVERSE_LIMB) << LIMB_BITS))
+        return q;
+
+    // Bail out if (y2 * q) <= (r * b + x2).  The estimate is correct.
+    dlimb_t y2q = ((dlimb_t)y2) * q;
+    if (y2q <= ((r << LIMB_BITS) + x2))
+        return q;
+
+    // Correct for the estimated quotient being off by 1.
+    --q;
+
+    // Now repeat the check to correct for q values that are off by 2.
+    r += y1;    // r' = (x01 - y1 * (q - 1)) = (x01 - y1 * q + y2) = r + y1
+    if (r & (((dlimb_t)INVERSE_LIMB) << LIMB_BITS))
+        return q;
+    // y2q' = (y2 * (q - 1)) = (y2 * q - y2) = y2q - y2
+    if ((y2q - y2) <= ((r << LIMB_BITS) + x2))
+        return q;
+
+    // Perform the final correction for q values that are off by 2.
+    return q - 1;
+}
+
+#endif
+
+// Simple implementation of modular division to cross-check the library.
+// Calling this "simple" is a bit of a misnomer.  It is a full implementation
+// of Algorithm D from section 4.3.1 of "The Art Of Computer Programming",
+// D. Knuth, Volume 2, "Seminumerical Algorithms", Second Edition, 1981.
+// This is quite slow on embedded platforms, but it should be correct.
+// Note: "x" is assumed to be (NUM_LIMBS * 2 + 1) limbs in size because
+// we need a limb for the extra leading zero word added by step D1.
+void simpleMod(limb_t *x)
+{
+    limb_t divisor[NUM_LIMBS];
+    uint8_t j, k;
+
+    // Step D1. Normalize.
+    // The divisor (2^255 - 19) and "x" need to be shifted left until
+    // the top-most bit of the divisor is 1.  Since we know that the
+    // next-to-top-most bit of (2^255 - 19) is already 1 and the top-most
+    // bit of "x" is zero, shifting everything into place is pretty easy.
+    fromString(divisor, NUM_LIMBS, num_2_255_m19_x2);
+    for (j = (NUM_LIMBS * 2); j > 1; --j) {
+        x[j - 1] = (x[j - 1] << 1) | (x[j - 2] >> (LIMB_BITS - 1));
+    }
+    x[0] <<= 1;
+    x[NUM_LIMBS * 2] = 0;   // Extra leading word.
+
+    // Step D2/D7. Loop on j
+    for (j = 0; j <= NUM_LIMBS; ++j) {
+        // Step D3. Calculate an estimate of the top-most quotient word.
+        limb_t *u = x + NUM_LIMBS * 2 - 2 - j;
+        limb_t *v = divisor + NUM_LIMBS - 2;
+        limb_t q;
+        dlimb_t uword = ((((dlimb_t)u[2]) << LIMB_BITS) + u[1]);
+        if (u[2] == v[1])
+            q = ~((limb_t)0);
+        else
+            q = (limb_t)(uword / v[1]);
+
+        // Step D3, part 2.  Correct the estimate downwards by 1 or 2.
+        // One subtlety of Knuth's algorithm is that it looks like the test
+        // is working with double-word quantities but it is actually using
+        // double-word plus a carry bit.  So we need to use qlimb_t for this.
+#if !defined(BIGNUMBER_NO_QLIMB)
+        qlimb_t test = ((((qlimb_t)uword) - ((dlimb_t)q) * v[1]) << LIMB_BITS) + u[0];
+        if ((((dlimb_t)q) * v[0]) > test) {
+            --q;
+            test = ((((qlimb_t)uword) - ((dlimb_t)q) * v[1]) << LIMB_BITS) + u[0];
+            if ((((dlimb_t)q) * v[0]) > test)
+                --q;
+        }
+#else
+        // 32-bit platform - we don't have a 128-bit numeric type so we have
+        // to calculate the estimate in another way to preserve the carry bit.
+        q = correctEstimate(q, v[0], v[1], uword, u[0]);
+#endif
+
+        // Step D4. Multiply and subtract.
+        u = x + (NUM_LIMBS - j);
+        v = divisor;
+        dlimb_t carry = 0;
+        dlimb_t borrow = 0;
+        for (k = 0; k < NUM_LIMBS; ++k) {
+            carry += ((dlimb_t)v[k]) * q;
+            borrow = ((dlimb_t)u[k]) - ((limb_t)carry) - borrow;
+            u[k] = (dlimb_t)borrow;
+            carry >>= LIMB_BITS;
+            borrow = ((borrow >> LIMB_BITS) != 0);
+        }
+        borrow = ((dlimb_t)u[k]) - ((limb_t)carry) - borrow;
+        u[k] = (dlimb_t)borrow;
+
+        // Step D5. Test remainder.  Nothing further to do if no borrow.
+        if ((borrow >> LIMB_BITS) == 0)
+            continue;
+
+        // Step D6. Borrow occurred: add back.
+        carry = 0;
+        for (k = 0; k < NUM_LIMBS; ++k) {
+            carry += u[k];
+            carry += v[k];
+            u[k] = (limb_t)carry;
+            carry >>= LIMB_BITS;
+        }
+        u[k] += (limb_t)carry;
+    }
+
+    // Step D8. Unnormalize.
+    // Shift the remainder right by 1 bit to undo the earlier left shift.
+    for (j = 0; j < (NUM_LIMBS - 1); ++j) {
+        x[j] = (x[j] >> 1) | (x[j + 1] << (LIMB_BITS - 1));
+    }
+    x[NUM_LIMBS - 1] >>= 1;
+}
+
+void testAdd(const char *x, const char *y)
+{
+    printProgMem(x);
+    Serial.print(" + ");
+    printProgMem(y);
+    Serial.print(": ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    fromString(arg2, NUM_LIMBS, y);
+    Curve25519::add(result, arg1, arg2);
+
+    simpleAdd(result2, arg1, arg2);
+
+    if (compare(result, result2) == 0) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result);
+        printNumber("expected", result2);
+    }
+}
+
+void testAdd()
+{
+    Serial.println("Addition:");
+    foreach_number (x) {
+        foreach_number (y) {
+            testAdd(x, y);
+        }
+    }
+    Serial.println();
+}
+
+void testSub(const char *x, const char *y)
+{
+    printProgMem(x);
+    Serial.print(" - ");
+    printProgMem(y);
+    Serial.print(": ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    fromString(arg2, NUM_LIMBS, y);
+    Curve25519::sub(result, arg1, arg2);
+
+    if (compare(arg1, arg2) >= 0) {
+        // First argument is larger than the second.
+        simpleSub(result2, arg1, arg2);
+    } else {
+        // First argument is smaller than the second.
+        // Compute arg1 + (2^255 - 19 - arg2).
+        fromString(temp, NUM_LIMBS, num_2_255_m19);
+        simpleSub(result2, temp, arg2);
+        simpleAdd(result2, arg1, result2);
+    }
+
+    if (compare(result, result2) == 0) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result);
+        printNumber("expected", result2);
+    }
+}
+
+void testSub()
+{
+    Serial.println("Subtraction:");
+    foreach_number (x) {
+        foreach_number (y) {
+            testSub(x, y);
+        }
+    }
+    Serial.println();
+}
+
+void testMul(const char *x, const char *y)
+{
+    printProgMem(x);
+    Serial.print(" * ");
+    printProgMem(y);
+    Serial.print(": ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    fromString(arg2, NUM_LIMBS, y);
+
+    if (compare(arg1, arg2) != 0)
+        Curve25519::mul(result, arg1, arg2);
+    else
+        Curve25519::square(result, arg1);
+
+    simpleMul(result2, arg1, arg2);
+    simpleMod(result2);
+
+    if (compare(result, result2) == 0) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result);
+        printNumber("expected", result2);
+    }
+}
+
+void testMul()
+{
+    Serial.println("Multiplication:");
+    foreach_number (x) {
+        foreach_number (y) {
+            testMul(x, y);
+        }
+    }
+    Serial.println();
+}
+
+void testMulA24(const char *x)
+{
+    printProgMem(x);
+    Serial.print(" * ");
+    printProgMem(num_a24);
+    Serial.print(": ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    fromString(arg2, NUM_LIMBS, num_a24);
+    Curve25519::mulA24(result, arg1);
+
+    simpleMul(result2, arg1, arg2);
+    simpleMod(result2);
+
+    if (compare(result, result2) == 0) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result);
+        printNumber("expected", result2);
+    }
+}
+
+void testMulA24()
+{
+    Serial.println("Multiplication by a24:");
+    foreach_number (x) {
+        testMulA24(x);
+    }
+    Serial.println();
+}
+
+void testSwap(const char *x, const char *y, uint8_t select)
+{
+    printProgMem(x);
+    Serial.print(" <-> ");
+    printProgMem(y);
+    Serial.print(": ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    fromString(arg2, NUM_LIMBS, y);
+
+    memcpy(result, arg1, NUM_LIMBS * sizeof(limb_t));
+    memcpy(result2, arg2, NUM_LIMBS * sizeof(limb_t));
+
+    // Swap the values using the selection bit.
+    Curve25519::cswap(select, result, result2);
+    bool ok = compare(result, arg2) == 0 && compare(result2, arg1) == 0;
+
+    // Don't swap the values back yet.
+    Curve25519::cswap(0, result, result2);
+    if (ok)
+        ok = compare(result, arg2) == 0 && compare(result2, arg1) == 0;
+
+    // Swap the values back.
+    Curve25519::cswap(select, result, result2);
+    if (ok)
+        ok = compare(result, arg1) == 0 && compare(result2, arg2) == 0;
+
+    // No swap.
+    Curve25519::cswap(0, result, result2);
+    if (ok)
+        ok = compare(result, arg1) == 0 && compare(result2, arg2) == 0;
+
+    if (ok) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+    }
+}
+
+void testSwap()
+{
+    Serial.println("Swap:");
+    uint8_t bit = 0;
+    foreach_number (x) {
+        foreach_number (y) {
+            testSwap(x, y, ((uint8_t)1) << bit);
+            bit = (bit + 1) % 8;
+        }
+    }
+    Serial.println();
+}
+
+void testRecip(const char *x)
+{
+    printProgMem(x);
+    Serial.print("^-1");
+    Serial.print(": ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    Curve25519::recip(result, arg1);
+
+    bool ok;
+    if (compare(arg1, num_0) == 0) {
+        // 0^-1 = 0
+        ok = (compare(result, num_0) == 0);
+    } else {
+        // Multiply the result with arg1 - we expect 1 as the result.
+        Curve25519::mul(result2, result, arg1);
+        ok = (compare(result2, num_1) == 0);
+    }
+
+    if (ok) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual", result);
+    }
+}
+
+void testRecip()
+{
+    Serial.println("Reciprocal:");
+    foreach_number (x) {
+        testRecip(x);
+    }
+    Serial.println();
+}
+
+void testSqrt(const char *x)
+{
+    Serial.print("sqrt(");
+    printProgMem(x);
+    Serial.print("^2): ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    Curve25519::square(arg2, arg1);
+    bool ok = Curve25519::sqrt(result, arg2);
+
+    if (ok) {
+        ok = (compare(result, arg1) == 0);
+        if (!ok) {
+            // Check the negation of arg1 as well because we could
+            // have ended up with the inverse of the original value.
+            memset(temp, 0, sizeof(temp));
+            Curve25519::sub(temp, temp, arg1);
+            ok = (compare(result, temp) == 0);
+        }
+    } else {
+        Serial.println("no sqrt ... ");
+    }
+
+    if (ok) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual", result);
+        printNumber("expected", arg1);
+    }
+}
+
+void testNoSqrt(const char *x)
+{
+    Serial.print("no sqrt(");
+    printProgMem(x);
+    Serial.print("): ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    bool ok = !Curve25519::sqrt(result, arg1);
+
+    if (ok) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual", result);
+    }
+}
+
+void testSqrt()
+{
+    Serial.println("Square root:");
+    foreach_number (x) {
+        testSqrt(x);
+    }
+    testNoSqrt(num_128);
+    testNoSqrt(num_pi);
+    Serial.println();
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    testAdd();
+    testSub();
+    testMul();
+    testMulA24();
+    testSwap();
+    testRecip();
+    testSqrt();
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestEAX/TestEAX.ino b/src/modules/Crypto/examples/TestEAX/TestEAX.ino
new file mode 100644
index 0000000..fadea5d
--- /dev/null
+++ b/src/modules/Crypto/examples/TestEAX/TestEAX.ino
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the EAX implementation to verify correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <CryptoLW.h>
+#include <EAX.h>
+#include <AES.h>
+#include <Speck.h>
+#include <SpeckTiny.h>
+#include <string.h>
+#if defined(ESP8266) || defined(ESP32)
+#include <pgmspace.h>
+#else
+#include <avr/pgmspace.h>
+#endif
+
+#define MAX_PLAINTEXT_LEN 64
+
+struct TestVector
+{
+    const char *name;
+    uint8_t key[16];
+    uint8_t plaintext[MAX_PLAINTEXT_LEN];
+    uint8_t ciphertext[MAX_PLAINTEXT_LEN];
+    uint8_t authdata[20];
+    uint8_t iv[16];
+    uint8_t tag[16];
+    size_t authsize;
+    size_t datasize;
+    size_t tagsize;
+    size_t ivsize;
+};
+
+// Test vectors for AES in EAX mode from Appendix G of:
+// http://www.cs.ucdavis.edu/~rogaway/papers/eax.pdf
+static TestVector const testVectorEAX1 PROGMEM = {
+    .name        = "EAX #1",
+    .key         = {0x23, 0x39, 0x52, 0xDE, 0xE4, 0xD5, 0xED, 0x5F,
+                    0x9B, 0x9C, 0x6D, 0x6F, 0xF8, 0x0F, 0xF4, 0x78},
+    .plaintext   = {0x00},
+    .ciphertext  = {0x00},
+    .authdata    = {0x6B, 0xFB, 0x91, 0x4F, 0xD0, 0x7E, 0xAE, 0x6B},
+    .iv          = {0x62, 0xEC, 0x67, 0xF9, 0xC3, 0xA4, 0xA4, 0x07,
+                    0xFC, 0xB2, 0xA8, 0xC4, 0x90, 0x31, 0xA8, 0xB3},
+    .tag         = {0xE0, 0x37, 0x83, 0x0E, 0x83, 0x89, 0xF2, 0x7B,
+                    0x02, 0x5A, 0x2D, 0x65, 0x27, 0xE7, 0x9D, 0x01},
+    .authsize    = 8,
+    .datasize    = 0,
+    .tagsize     = 16,
+    .ivsize      = 16
+};
+static TestVector const testVectorEAX2 PROGMEM = {
+    .name        = "EAX #2",
+    .key         = {0x91, 0x94, 0x5D, 0x3F, 0x4D, 0xCB, 0xEE, 0x0B,
+                    0xF4, 0x5E, 0xF5, 0x22, 0x55, 0xF0, 0x95, 0xA4},
+    .plaintext   = {0xF7, 0xFB},
+    .ciphertext  = {0x19, 0xDD},
+    .authdata    = {0xFA, 0x3B, 0xFD, 0x48, 0x06, 0xEB, 0x53, 0xFA},
+    .iv          = {0xBE, 0xCA, 0xF0, 0x43, 0xB0, 0xA2, 0x3D, 0x84,
+                    0x31, 0x94, 0xBA, 0x97, 0x2C, 0x66, 0xDE, 0xBD},
+    .tag         = {0x5C, 0x4C, 0x93, 0x31, 0x04, 0x9D, 0x0B, 0xDA,
+                    0xB0, 0x27, 0x74, 0x08, 0xF6, 0x79, 0x67, 0xE5},
+    .authsize    = 8,
+    .datasize    = 2,
+    .tagsize     = 16,
+    .ivsize      = 16
+};
+static TestVector const testVectorEAX3 PROGMEM = {
+    .name        = "EAX #3",
+    .key         = {0x01, 0xF7, 0x4A, 0xD6, 0x40, 0x77, 0xF2, 0xE7,
+                    0x04, 0xC0, 0xF6, 0x0A, 0xDA, 0x3D, 0xD5, 0x23},
+    .plaintext   = {0x1A, 0x47, 0xCB, 0x49, 0x33},
+    .ciphertext  = {0xD8, 0x51, 0xD5, 0xBA, 0xE0},
+    .authdata    = {0x23, 0x4A, 0x34, 0x63, 0xC1, 0x26, 0x4A, 0xC6},
+    .iv          = {0x70, 0xC3, 0xDB, 0x4F, 0x0D, 0x26, 0x36, 0x84,
+                    0x00, 0xA1, 0x0E, 0xD0, 0x5D, 0x2B, 0xFF, 0x5E},
+    .tag         = {0x3A, 0x59, 0xF2, 0x38, 0xA2, 0x3E, 0x39, 0x19,
+                    0x9D, 0xC9, 0x26, 0x66, 0x26, 0xC4, 0x0F, 0x80},
+    .authsize    = 8,
+    .datasize    = 5,
+    .tagsize     = 16,
+    .ivsize      = 16
+};
+static TestVector const testVectorEAX4 PROGMEM = {
+    .name        = "EAX #4",
+    .key         = {0xD0, 0x7C, 0xF6, 0xCB, 0xB7, 0xF3, 0x13, 0xBD,
+                    0xDE, 0x66, 0xB7, 0x27, 0xAF, 0xD3, 0xC5, 0xE8},
+    .plaintext   = {0x48, 0x1C, 0x9E, 0x39, 0xB1},
+    .ciphertext  = {0x63, 0x2A, 0x9D, 0x13, 0x1A},
+    .authdata    = {0x33, 0xCC, 0xE2, 0xEA, 0xBF, 0xF5, 0xA7, 0x9D},
+    .iv          = {0x84, 0x08, 0xDF, 0xFF, 0x3C, 0x1A, 0x2B, 0x12,
+                    0x92, 0xDC, 0x19, 0x9E, 0x46, 0xB7, 0xD6, 0x17},
+    .tag         = {0xD4, 0xC1, 0x68, 0xA4, 0x22, 0x5D, 0x8E, 0x1F,
+                    0xF7, 0x55, 0x93, 0x99, 0x74, 0xA7, 0xBE, 0xDE},
+    .authsize    = 8,
+    .datasize    = 5,
+    .tagsize     = 16,
+    .ivsize      = 16
+};
+static TestVector const testVectorEAX5 PROGMEM = {
+    .name        = "EAX #5",
+    .key         = {0x35, 0xB6, 0xD0, 0x58, 0x00, 0x05, 0xBB, 0xC1,
+                    0x2B, 0x05, 0x87, 0x12, 0x45, 0x57, 0xD2, 0xC2},
+    .plaintext   = {0x40, 0xD0, 0xC0, 0x7D, 0xA5, 0xE4},
+    .ciphertext  = {0x07, 0x1D, 0xFE, 0x16, 0xC6, 0x75},
+    .authdata    = {0xAE, 0xB9, 0x6E, 0xAE, 0xBE, 0x29, 0x70, 0xE9},
+    .iv          = {0xFD, 0xB6, 0xB0, 0x66, 0x76, 0xEE, 0xDC, 0x5C,
+                    0x61, 0xD7, 0x42, 0x76, 0xE1, 0xF8, 0xE8, 0x16},
+    .tag         = {0xCB, 0x06, 0x77, 0xE5, 0x36, 0xF7, 0x3A, 0xFE,
+                    0x6A, 0x14, 0xB7, 0x4E, 0xE4, 0x98, 0x44, 0xDD},
+    .authsize    = 8,
+    .datasize    = 6,
+    .tagsize     = 16,
+    .ivsize      = 16
+};
+static TestVector const testVectorEAX6 PROGMEM = {
+    .name        = "EAX #6",
+    .key         = {0xBD, 0x8E, 0x6E, 0x11, 0x47, 0x5E, 0x60, 0xB2,
+                    0x68, 0x78, 0x4C, 0x38, 0xC6, 0x2F, 0xEB, 0x22},
+    .plaintext   = {0x4D, 0xE3, 0xB3, 0x5C, 0x3F, 0xC0, 0x39, 0x24,
+                    0x5B, 0xD1, 0xFB, 0x7D},
+    .ciphertext  = {0x83, 0x5B, 0xB4, 0xF1, 0x5D, 0x74, 0x3E, 0x35,
+                    0x0E, 0x72, 0x84, 0x14},
+    .authdata    = {0xD4, 0x48, 0x2D, 0x1C, 0xA7, 0x8D, 0xCE, 0x0F},
+    .iv          = {0x6E, 0xAC, 0x5C, 0x93, 0x07, 0x2D, 0x8E, 0x85,
+                    0x13, 0xF7, 0x50, 0x93, 0x5E, 0x46, 0xDA, 0x1B},
+    .tag         = {0xAB, 0xB8, 0x64, 0x4F, 0xD6, 0xCC, 0xB8, 0x69,
+                    0x47, 0xC5, 0xE1, 0x05, 0x90, 0x21, 0x0A, 0x4F},
+    .authsize    = 8,
+    .datasize    = 12,
+    .tagsize     = 16,
+    .ivsize      = 16
+};
+static TestVector const testVectorEAX7 PROGMEM = {
+    .name        = "EAX #7",
+    .key         = {0x7C, 0x77, 0xD6, 0xE8, 0x13, 0xBE, 0xD5, 0xAC,
+                    0x98, 0xBA, 0xA4, 0x17, 0x47, 0x7A, 0x2E, 0x7D},
+    .plaintext   = {0x8B, 0x0A, 0x79, 0x30, 0x6C, 0x9C, 0xE7, 0xED,
+                    0x99, 0xDA, 0xE4, 0xF8, 0x7F, 0x8D, 0xD6, 0x16,
+                    0x36},
+    .ciphertext  = {0x02, 0x08, 0x3E, 0x39, 0x79, 0xDA, 0x01, 0x48,
+                    0x12, 0xF5, 0x9F, 0x11, 0xD5, 0x26, 0x30, 0xDA,
+                    0x30},
+    .authdata    = {0x65, 0xD2, 0x01, 0x79, 0x90, 0xD6, 0x25, 0x28},
+    .iv          = {0x1A, 0x8C, 0x98, 0xDC, 0xD7, 0x3D, 0x38, 0x39,
+                    0x3B, 0x2B, 0xF1, 0x56, 0x9D, 0xEE, 0xFC, 0x19},
+    .tag         = {0x13, 0x73, 0x27, 0xD1, 0x06, 0x49, 0xB0, 0xAA,
+                    0x6E, 0x1C, 0x18, 0x1D, 0xB6, 0x17, 0xD7, 0xF2},
+    .authsize    = 8,
+    .datasize    = 17,
+    .tagsize     = 16,
+    .ivsize      = 16
+};
+static TestVector const testVectorEAX8 PROGMEM = {
+    .name        = "EAX #8",
+    .key         = {0x5F, 0xFF, 0x20, 0xCA, 0xFA, 0xB1, 0x19, 0xCA,
+                    0x2F, 0xC7, 0x35, 0x49, 0xE2, 0x0F, 0x5B, 0x0D},
+    .plaintext   = {0x1B, 0xDA, 0x12, 0x2B, 0xCE, 0x8A, 0x8D, 0xBA,
+                    0xF1, 0x87, 0x7D, 0x96, 0x2B, 0x85, 0x92, 0xDD,
+                    0x2D, 0x56},
+    .ciphertext  = {0x2E, 0xC4, 0x7B, 0x2C, 0x49, 0x54, 0xA4, 0x89,
+                    0xAF, 0xC7, 0xBA, 0x48, 0x97, 0xED, 0xCD, 0xAE,
+                    0x8C, 0xC3},
+    .authdata    = {0x54, 0xB9, 0xF0, 0x4E, 0x6A, 0x09, 0x18, 0x9A},
+    .iv          = {0xDD, 0xE5, 0x9B, 0x97, 0xD7, 0x22, 0x15, 0x6D,
+                    0x4D, 0x9A, 0xFF, 0x2B, 0xC7, 0x55, 0x98, 0x26},
+    .tag         = {0x3B, 0x60, 0x45, 0x05, 0x99, 0xBD, 0x02, 0xC9,
+                    0x63, 0x82, 0x90, 0x2A, 0xEF, 0x7F, 0x83, 0x2A},
+    .authsize    = 8,
+    .datasize    = 18,
+    .tagsize     = 16,
+    .ivsize      = 16
+};
+static TestVector const testVectorEAX9 PROGMEM = {
+    .name        = "EAX #9",
+    .key         = {0xA4, 0xA4, 0x78, 0x2B, 0xCF, 0xFD, 0x3E, 0xC5,
+                    0xE7, 0xEF, 0x6D, 0x8C, 0x34, 0xA5, 0x61, 0x23},
+    .plaintext   = {0x6C, 0xF3, 0x67, 0x20, 0x87, 0x2B, 0x85, 0x13,
+                    0xF6, 0xEA, 0xB1, 0xA8, 0xA4, 0x44, 0x38, 0xD5,
+                    0xEF, 0x11},
+    .ciphertext  = {0x0D, 0xE1, 0x8F, 0xD0, 0xFD, 0xD9, 0x1E, 0x7A,
+                    0xF1, 0x9F, 0x1D, 0x8E, 0xE8, 0x73, 0x39, 0x38,
+                    0xB1, 0xE8},
+    .authdata    = {0x89, 0x9A, 0x17, 0x58, 0x97, 0x56, 0x1D, 0x7E},
+    .iv          = {0xB7, 0x81, 0xFC, 0xF2, 0xF7, 0x5F, 0xA5, 0xA8,
+                    0xDE, 0x97, 0xA9, 0xCA, 0x48, 0xE5, 0x22, 0xEC},
+    .tag         = {0xE7, 0xF6, 0xD2, 0x23, 0x16, 0x18, 0x10, 0x2F,
+                    0xDB, 0x7F, 0xE5, 0x5F, 0xF1, 0x99, 0x17, 0x00},
+    .authsize    = 8,
+    .datasize    = 18,
+    .tagsize     = 16,
+    .ivsize      = 16
+};
+static TestVector const testVectorEAX10 PROGMEM = {
+    .name        = "EAX #10",
+    .key         = {0x83, 0x95, 0xFC, 0xF1, 0xE9, 0x5B, 0xEB, 0xD6,
+                    0x97, 0xBD, 0x01, 0x0B, 0xC7, 0x66, 0xAA, 0xC3},
+    .plaintext   = {0xCA, 0x40, 0xD7, 0x44, 0x6E, 0x54, 0x5F, 0xFA,
+                    0xED, 0x3B, 0xD1, 0x2A, 0x74, 0x0A, 0x65, 0x9F,
+                    0xFB, 0xBB, 0x3C, 0xEA, 0xB7},
+    .ciphertext  = {0xCB, 0x89, 0x20, 0xF8, 0x7A, 0x6C, 0x75, 0xCF,
+                    0xF3, 0x96, 0x27, 0xB5, 0x6E, 0x3E, 0xD1, 0x97,
+                    0xC5, 0x52, 0xD2, 0x95, 0xA7},
+    .authdata    = {0x12, 0x67, 0x35, 0xFC, 0xC3, 0x20, 0xD2, 0x5A},
+    .iv          = {0x22, 0xE7, 0xAD, 0xD9, 0x3C, 0xFC, 0x63, 0x93,
+                    0xC5, 0x7E, 0xC0, 0xB3, 0xC1, 0x7D, 0x6B, 0x44},
+    .tag         = {0xCF, 0xC4, 0x6A, 0xFC, 0x25, 0x3B, 0x46, 0x52,
+                    0xB1, 0xAF, 0x37, 0x95, 0xB1, 0x24, 0xAB, 0x6E},
+    .authsize    = 8,
+    .datasize    = 21,
+    .tagsize     = 16,
+    .ivsize      = 16
+};
+
+TestVector testVector;
+
+EAX<AES128> *eax;
+EAX<AES256> *eax256;
+EAX<Speck> *eaxSpeck;
+EAX<SpeckTiny> *eaxSpeckTiny;
+
+byte buffer[128];
+
+bool testCipher_N(AuthenticatedCipher *cipher, const struct TestVector *test, size_t inc)
+{
+    size_t posn, len;
+    uint8_t tag[16];
+
+    crypto_feed_watchdog();
+
+    cipher->clear();
+    if (!cipher->setKey(test->key, 16)) {
+        Serial.print("setKey ");
+        return false;
+    }
+    if (!cipher->setIV(test->iv, test->ivsize)) {
+        Serial.print("setIV ");
+        return false;
+    }
+
+    memset(buffer, 0xBA, sizeof(buffer));
+
+    if (!inc)
+        inc = 1;
+
+    for (posn = 0; posn < test->authsize; posn += inc) {
+        len = test->authsize - posn;
+        if (len > inc)
+            len = inc;
+        cipher->addAuthData(test->authdata + posn, len);
+    }
+
+    for (posn = 0; posn < test->datasize; posn += inc) {
+        len = test->datasize - posn;
+        if (len > inc)
+            len = inc;
+        cipher->encrypt(buffer + posn, test->plaintext + posn, len);
+    }
+
+    if (memcmp(buffer, test->ciphertext, test->datasize) != 0) {
+        Serial.print(buffer[0], HEX);
+        Serial.print("->");
+        Serial.print(test->ciphertext[0], HEX);
+        return false;
+    }
+
+    cipher->computeTag(tag, sizeof(tag));
+    if (memcmp(tag, test->tag, sizeof(tag)) != 0) {
+        Serial.print("computed wrong tag ... ");
+        return false;
+    }
+
+    cipher->setKey(test->key, 16);
+    cipher->setIV(test->iv, test->ivsize);
+
+    for (posn = 0; posn < test->authsize; posn += inc) {
+        len = test->authsize - posn;
+        if (len > inc)
+            len = inc;
+        cipher->addAuthData(test->authdata + posn, len);
+    }
+
+    for (posn = 0; posn < test->datasize; posn += inc) {
+        len = test->datasize - posn;
+        if (len > inc)
+            len = inc;
+        cipher->decrypt(buffer + posn, test->ciphertext + posn, len);
+    }
+
+    if (memcmp(buffer, test->plaintext, test->datasize) != 0)
+        return false;
+
+    if (!cipher->checkTag(tag, sizeof(tag))) {
+        Serial.print("tag did not check ... ");
+        return false;
+    }
+
+    return true;
+}
+
+void testCipher(AuthenticatedCipher *cipher, const struct TestVector *test)
+{
+    bool ok;
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    ok  = testCipher_N(cipher, test, test->datasize);
+    ok &= testCipher_N(cipher, test, 1);
+    ok &= testCipher_N(cipher, test, 2);
+    ok &= testCipher_N(cipher, test, 5);
+    ok &= testCipher_N(cipher, test, 8);
+    ok &= testCipher_N(cipher, test, 13);
+    ok &= testCipher_N(cipher, test, 16);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfCipherSetKey(AuthenticatedCipher *cipher, const struct TestVector *test, const char *cipherName)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(cipherName);
+    Serial.print(' ');
+    Serial.print(test->name);
+    Serial.print(" SetKey ... ");
+
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        cipher->setKey(test->key, cipher->keySize());
+        cipher->setIV(test->iv, test->ivsize);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per operation, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" per second");
+}
+
+void perfCipherEncrypt(AuthenticatedCipher *cipher, const struct TestVector *test, const char *cipherName)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(cipherName);
+    Serial.print(' ');
+    Serial.print(test->name);
+    Serial.print(" Encrypt ... ");
+
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->setIV(test->iv, test->ivsize);
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        cipher->encrypt(buffer, buffer, 128);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (128.0 * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((128.0 * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfCipherDecrypt(AuthenticatedCipher *cipher, const struct TestVector *test, const char *cipherName)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(cipherName);
+    Serial.print(' ');
+    Serial.print(test->name);
+    Serial.print(" Decrypt ... ");
+
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->setIV(test->iv, test->ivsize);
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        cipher->decrypt(buffer, buffer, 128);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (128.0 * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((128.0 * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfCipherAddAuthData(AuthenticatedCipher *cipher, const struct TestVector *test, const char *cipherName)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(cipherName);
+    Serial.print(' ');
+    Serial.print(test->name);
+    Serial.print(" AddAuthData ... ");
+
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->setIV(test->iv, test->ivsize);
+    start = micros();
+    memset(buffer, 0xBA, 128);
+    for (count = 0; count < 500; ++count) {
+        cipher->addAuthData(buffer, 128);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (128.0 * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((128.0 * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfCipherComputeTag(AuthenticatedCipher *cipher, const struct TestVector *test, const char *cipherName)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(cipherName);
+    Serial.print(' ');
+    Serial.print(test->name);
+    Serial.print(" ComputeTag ... ");
+
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->setIV(test->iv, test->ivsize);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        cipher->computeTag(buffer, 16);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per operation, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" per second");
+}
+
+void perfCipher(AuthenticatedCipher *cipher, const struct TestVector *test, const char *cipherName)
+{
+    perfCipherSetKey(cipher, test, cipherName);
+    perfCipherEncrypt(cipher, test, cipherName);
+    perfCipherDecrypt(cipher, test, cipherName);
+    perfCipherAddAuthData(cipher, test, cipherName);
+    perfCipherComputeTag(cipher, test, cipherName);
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.println("State Sizes:");
+    Serial.print("EAX<AES128> ... ");
+    Serial.println(sizeof(*eax));
+    Serial.print("EAX<AES256> ... ");
+    Serial.println(sizeof(*eax256));
+    Serial.print("EAX<Speck> ... ");
+    Serial.println(sizeof(*eaxSpeck));
+    Serial.print("EAX<SpeckTiny> ... ");
+    Serial.println(sizeof(*eaxSpeckTiny));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    eax = new EAX<AES128>();
+    testCipher(eax, &testVectorEAX1);
+    testCipher(eax, &testVectorEAX2);
+    testCipher(eax, &testVectorEAX3);
+    testCipher(eax, &testVectorEAX4);
+    testCipher(eax, &testVectorEAX5);
+    testCipher(eax, &testVectorEAX6);
+    testCipher(eax, &testVectorEAX7);
+    testCipher(eax, &testVectorEAX8);
+    testCipher(eax, &testVectorEAX9);
+    testCipher(eax, &testVectorEAX10);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfCipher(eax, &testVectorEAX1, "AES-128");
+    Serial.println();
+    delete eax;
+    eax256 = new EAX<AES256>();
+    perfCipher(eax256, &testVectorEAX1, "AES-256");
+    Serial.println();
+    delete eax256;
+    eaxSpeck = new EAX<Speck>();
+    perfCipher(eaxSpeck, &testVectorEAX1, "Speck");
+    Serial.println();
+    delete eaxSpeck;
+    eaxSpeckTiny = new EAX<SpeckTiny>();
+    perfCipher(eaxSpeckTiny, &testVectorEAX1, "SpeckTiny");
+    delete eaxSpeckTiny;
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestEd25519/TestEd25519.ino b/src/modules/Crypto/examples/TestEd25519/TestEd25519.ino
new file mode 100644
index 0000000..4f616ca
--- /dev/null
+++ b/src/modules/Crypto/examples/TestEd25519/TestEd25519.ino
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the Ed25519 algorithm.
+*/
+
+#include <Crypto.h>
+#include <Ed25519.h>
+#include <RNG.h>
+#include <utility/ProgMemUtil.h>
+#include <string.h>
+
+struct TestVector
+{
+    const char *name;
+    uint8_t privateKey[32];
+    uint8_t publicKey[32];
+    uint8_t message[2];
+    size_t len;
+    uint8_t signature[64];
+};
+
+// Test vectors for Ed25519 from:
+// https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-05
+static TestVector const testVectorEd25519_1 PROGMEM = {
+    .name       = "Ed25519 #1",
+    .privateKey = {0x9d, 0x61, 0xb1, 0x9d, 0xef, 0xfd, 0x5a, 0x60,
+                   0xba, 0x84, 0x4a, 0xf4, 0x92, 0xec, 0x2c, 0xc4,
+                   0x44, 0x49, 0xc5, 0x69, 0x7b, 0x32, 0x69, 0x19,
+                   0x70, 0x3b, 0xac, 0x03, 0x1c, 0xae, 0x7f, 0x60},
+    .publicKey  = {0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7,
+                   0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07, 0x3a,
+                   0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25,
+                   0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07, 0x51, 0x1a},
+    .message    = {0x00, 0x00},
+    .len        = 0,
+    .signature  = {0xe5, 0x56, 0x43, 0x00, 0xc3, 0x60, 0xac, 0x72,
+                   0x90, 0x86, 0xe2, 0xcc, 0x80, 0x6e, 0x82, 0x8a,
+                   0x84, 0x87, 0x7f, 0x1e, 0xb8, 0xe5, 0xd9, 0x74,
+                   0xd8, 0x73, 0xe0, 0x65, 0x22, 0x49, 0x01, 0x55,
+                   0x5f, 0xb8, 0x82, 0x15, 0x90, 0xa3, 0x3b, 0xac,
+                   0xc6, 0x1e, 0x39, 0x70, 0x1c, 0xf9, 0xb4, 0x6b,
+                   0xd2, 0x5b, 0xf5, 0xf0, 0x59, 0x5b, 0xbe, 0x24,
+                   0x65, 0x51, 0x41, 0x43, 0x8e, 0x7a, 0x10, 0x0b}
+};
+static TestVector const testVectorEd25519_2 PROGMEM = {
+    .name       = "Ed25519 #2",
+    .privateKey = {0x4c, 0xcd, 0x08, 0x9b, 0x28, 0xff, 0x96, 0xda,
+                   0x9d, 0xb6, 0xc3, 0x46, 0xec, 0x11, 0x4e, 0x0f,
+                   0x5b, 0x8a, 0x31, 0x9f, 0x35, 0xab, 0xa6, 0x24,
+                   0xda, 0x8c, 0xf6, 0xed, 0x4f, 0xb8, 0xa6, 0xfb},
+    .publicKey  = {0x3d, 0x40, 0x17, 0xc3, 0xe8, 0x43, 0x89, 0x5a,
+                   0x92, 0xb7, 0x0a, 0xa7, 0x4d, 0x1b, 0x7e, 0xbc,
+                   0x9c, 0x98, 0x2c, 0xcf, 0x2e, 0xc4, 0x96, 0x8c,
+                   0xc0, 0xcd, 0x55, 0xf1, 0x2a, 0xf4, 0x66, 0x0c},
+    .message    = {0x72, 0x00},
+    .len        = 1,
+    .signature  = {0x92, 0xa0, 0x09, 0xa9, 0xf0, 0xd4, 0xca, 0xb8,
+                   0x72, 0x0e, 0x82, 0x0b, 0x5f, 0x64, 0x25, 0x40,
+                   0xa2, 0xb2, 0x7b, 0x54, 0x16, 0x50, 0x3f, 0x8f,
+                   0xb3, 0x76, 0x22, 0x23, 0xeb, 0xdb, 0x69, 0xda,
+                   0x08, 0x5a, 0xc1, 0xe4, 0x3e, 0x15, 0x99, 0x6e,
+                   0x45, 0x8f, 0x36, 0x13, 0xd0, 0xf1, 0x1d, 0x8c,
+                   0x38, 0x7b, 0x2e, 0xae, 0xb4, 0x30, 0x2a, 0xee,
+                   0xb0, 0x0d, 0x29, 0x16, 0x12, 0xbb, 0x0c, 0x00}
+};
+
+static TestVector testVector;
+
+void printNumber(const char *name, const uint8_t *x, uint8_t len)
+{
+    static const char hexchars[] = "0123456789ABCDEF";
+    Serial.print(name);
+    Serial.print(" = ");
+    for (uint8_t posn = 0; posn < len; ++posn) {
+        Serial.print(hexchars[(x[posn] >> 4) & 0x0F]);
+        Serial.print(hexchars[x[posn] & 0x0F]);
+    }
+    Serial.println();
+}
+
+void testFixedVectors(const struct TestVector *test)
+{
+    // Copy the test vector out of program memory.
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    // Sign using the test vector.
+    uint8_t signature[64];
+    Serial.print(test->name);
+    Serial.print(" sign ... ");
+    Serial.flush();
+    unsigned long start = micros();
+    Ed25519::sign(signature, test->privateKey, test->publicKey,
+                  test->message, test->len);
+    unsigned long elapsed = micros() - start;
+    if (memcmp(signature, test->signature, 64) == 0) {
+        Serial.print("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", signature, 64);
+        printNumber("expected", test->signature, 64);
+    }
+    Serial.print(" (elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us)");
+
+    // Verify using the test vector.
+    Serial.print(test->name);
+    Serial.print(" verify ... ");
+    Serial.flush();
+    start = micros();
+    bool verified = Ed25519::verify(signature, test->publicKey, test->message, test->len);
+    elapsed = micros() - start;
+    if (verified) {
+        Serial.print("ok");
+    } else {
+        Serial.println("failed");
+    }
+    Serial.print(" (elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us)");
+
+    // Check derivation of the public key from the private key.
+    Serial.print(test->name);
+    Serial.print(" derive public key ... ");
+    Serial.flush();
+    start = micros();
+    Ed25519::derivePublicKey(signature, test->privateKey);
+    elapsed = micros() - start;
+    if (memcmp(signature, test->publicKey, 32) == 0) {
+        Serial.print("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", signature, 32);
+        printNumber("expected", test->publicKey, 32);
+    }
+    Serial.print(" (elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us)");
+}
+
+void testFixedVectors()
+{
+    testFixedVectors(&testVectorEd25519_1);
+    testFixedVectors(&testVectorEd25519_2);
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    // Start the random number generator.  We don't initialise a noise
+    // source here because we don't need one for testing purposes.
+    // Real applications should of course use a proper noise source.
+    RNG.begin("TestEd25519 1.0");
+
+    // Perform the tests.
+    testFixedVectors();
+    Serial.println();
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestGCM/TestGCM.ino b/src/modules/Crypto/examples/TestGCM/TestGCM.ino
new file mode 100644
index 0000000..5692314
--- /dev/null
+++ b/src/modules/Crypto/examples/TestGCM/TestGCM.ino
@@ -0,0 +1,584 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the GCM implementation to verify correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <CryptoLW.h>
+#include <AES.h>
+#include <Speck.h>
+#include <SpeckTiny.h>
+#include <GCM.h>
+#include <string.h>
+#if defined(ESP8266) || defined(ESP32)
+#include <pgmspace.h>
+#else
+#include <avr/pgmspace.h>
+#endif
+
+// There isn't enough memory to test both AES and Speck on the Uno,
+// so disable Speck testing on AVR platforms unless explicitly enabled.
+// When enabled, some of the AES tests are disabled to reclaim memory.
+#if defined(__AVR__)
+//#define TEST_SPECK 1
+#endif
+
+#define MAX_PLAINTEXT_LEN 64
+
+struct TestVector
+{
+    const char *name;
+    uint8_t key[32];
+    uint8_t plaintext[MAX_PLAINTEXT_LEN];
+    uint8_t ciphertext[MAX_PLAINTEXT_LEN];
+    uint8_t authdata[20];
+    uint8_t iv[12];
+    uint8_t tag[16];
+    size_t authsize;
+    size_t datasize;
+    size_t tagsize;
+    size_t ivsize;
+};
+
+// Test vectors for AES in GCM mode from Appendix B of:
+// http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf
+static TestVector const testVectorGCM1 PROGMEM = {
+    .name        = "AES-128 GCM #1",
+    .key         = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .plaintext   = {0x00},
+    .ciphertext  = {0x00},
+    .authdata    = {0x00},
+    .iv          = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00},
+    .tag         = {0x58, 0xe2, 0xfc, 0xce, 0xfa, 0x7e, 0x30, 0x61,
+                    0x36, 0x7f, 0x1d, 0x57, 0xa4, 0xe7, 0x45, 0x5a},
+    .authsize    = 0,
+    .datasize    = 0,
+    .tagsize     = 16,
+    .ivsize      = 12
+};
+#ifndef TEST_SPECK
+static TestVector const testVectorGCM2 PROGMEM = {
+    .name        = "AES-128 GCM #2",
+    .key         = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .plaintext   = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .ciphertext  = {0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92,
+                    0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78},
+    .authdata    = {0x00},
+    .iv          = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00},
+    .tag         = {0xab, 0x6e, 0x47, 0xd4, 0x2c, 0xec, 0x13, 0xbd,
+                    0xf5, 0x3a, 0x67, 0xb2, 0x12, 0x57, 0xbd, 0xdf},
+    .authsize    = 0,
+    .datasize    = 16,
+    .tagsize     = 16,
+    .ivsize      = 12
+};
+static TestVector const testVectorGCM3 PROGMEM = {
+    .name        = "AES-128 GCM #3",
+    .key         = {0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+                    0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08},
+    .plaintext   = {0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+                    0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+                    0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+                    0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+                    0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+                    0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+                    0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+                    0xba, 0x63, 0x7b, 0x39, 0x1a, 0xaf, 0xd2, 0x55},
+    .ciphertext  = {0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24,
+                    0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c,
+                    0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0,
+                    0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e,
+                    0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c,
+                    0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05,
+                    0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
+                    0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85},
+    .authdata    = {0x00},
+    .iv          = {0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+                    0xde, 0xca, 0xf8, 0x88},
+    .tag         = {0x4d, 0x5c, 0x2a, 0xf3, 0x27, 0xcd, 0x64, 0xa6,
+                    0x2c, 0xf3, 0x5a, 0xbd, 0x2b, 0xa6, 0xfa, 0xb4},
+    .authsize    = 0,
+    .datasize    = 64,
+    .tagsize     = 16,
+    .ivsize      = 12
+};
+static TestVector const testVectorGCM4 PROGMEM = {
+    .name        = "AES-128 GCM #4",
+    .key         = {0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+                    0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08},
+    .plaintext   = {0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+                    0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+                    0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+                    0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+                    0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+                    0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+                    0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+                    0xba, 0x63, 0x7b, 0x39},
+    .ciphertext  = {0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24,
+                    0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c,
+                    0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0,
+                    0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e,
+                    0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c,
+                    0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05,
+                    0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
+                    0x3d, 0x58, 0xe0, 0x91},
+    .authdata    = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+                    0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+                    0xab, 0xad, 0xda, 0xd2},
+    .iv          = {0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+                    0xde, 0xca, 0xf8, 0x88},
+    .tag         = {0x5b, 0xc9, 0x4f, 0xbc, 0x32, 0x21, 0xa5, 0xdb,
+                    0x94, 0xfa, 0xe9, 0x5a, 0xe7, 0x12, 0x1a, 0x47},
+    .authsize    = 20,
+    .datasize    = 60,
+    .tagsize     = 16,
+    .ivsize      = 12
+};
+static TestVector const testVectorGCM5 PROGMEM = {
+    .name        = "AES-128 GCM #5",
+    .key         = {0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+                    0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08},
+    .plaintext   = {0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+                    0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+                    0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+                    0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+                    0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+                    0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+                    0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+                    0xba, 0x63, 0x7b, 0x39},
+    .ciphertext  = {0x61, 0x35, 0x3b, 0x4c, 0x28, 0x06, 0x93, 0x4a,
+                    0x77, 0x7f, 0xf5, 0x1f, 0xa2, 0x2a, 0x47, 0x55,
+                    0x69, 0x9b, 0x2a, 0x71, 0x4f, 0xcd, 0xc6, 0xf8,
+                    0x37, 0x66, 0xe5, 0xf9, 0x7b, 0x6c, 0x74, 0x23,
+                    0x73, 0x80, 0x69, 0x00, 0xe4, 0x9f, 0x24, 0xb2,
+                    0x2b, 0x09, 0x75, 0x44, 0xd4, 0x89, 0x6b, 0x42,
+                    0x49, 0x89, 0xb5, 0xe1, 0xeb, 0xac, 0x0f, 0x07,
+                    0xc2, 0x3f, 0x45, 0x98},
+    .authdata    = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+                    0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+                    0xab, 0xad, 0xda, 0xd2},
+    .iv          = {0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad},
+    .tag         = {0x36, 0x12, 0xd2, 0xe7, 0x9e, 0x3b, 0x07, 0x85,
+                    0x56, 0x1b, 0xe1, 0x4a, 0xac, 0xa2, 0xfc, 0xcb},
+    .authsize    = 20,
+    .datasize    = 60,
+    .tagsize     = 16,
+    .ivsize      = 8
+};
+#endif // !TEST_SPECK
+static TestVector const testVectorGCM10 PROGMEM = {
+    .name        = "AES-192 GCM #10",
+    .key         = {0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+                    0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08,
+                    0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c},
+    .plaintext   = {0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+                    0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+                    0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+                    0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+                    0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+                    0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+                    0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+                    0xba, 0x63, 0x7b, 0x39},
+    .ciphertext  = {0x39, 0x80, 0xca, 0x0b, 0x3c, 0x00, 0xe8, 0x41,
+                    0xeb, 0x06, 0xfa, 0xc4, 0x87, 0x2a, 0x27, 0x57,
+                    0x85, 0x9e, 0x1c, 0xea, 0xa6, 0xef, 0xd9, 0x84,
+                    0x62, 0x85, 0x93, 0xb4, 0x0c, 0xa1, 0xe1, 0x9c,
+                    0x7d, 0x77, 0x3d, 0x00, 0xc1, 0x44, 0xc5, 0x25,
+                    0xac, 0x61, 0x9d, 0x18, 0xc8, 0x4a, 0x3f, 0x47,
+                    0x18, 0xe2, 0x44, 0x8b, 0x2f, 0xe3, 0x24, 0xd9,
+                    0xcc, 0xda, 0x27, 0x10},
+    .authdata    = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+                    0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+                    0xab, 0xad, 0xda, 0xd2},
+    .iv          = {0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+                    0xde, 0xca, 0xf8, 0x88},
+    .tag         = {0x25, 0x19, 0x49, 0x8e, 0x80, 0xf1, 0x47, 0x8f,
+                    0x37, 0xba, 0x55, 0xbd, 0x6d, 0x27, 0x61, 0x8c},
+    .authsize    = 20,
+    .datasize    = 60,
+    .tagsize     = 16,
+    .ivsize      = 12
+};
+static TestVector const testVectorGCM16 PROGMEM = {
+    .name        = "AES-256 GCM #16",
+    .key         = {0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+                    0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08,
+                    0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+                    0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08},
+    .plaintext   = {0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+                    0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+                    0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+                    0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+                    0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+                    0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+                    0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+                    0xba, 0x63, 0x7b, 0x39},
+    .ciphertext  = {0x52, 0x2d, 0xc1, 0xf0, 0x99, 0x56, 0x7d, 0x07,
+                    0xf4, 0x7f, 0x37, 0xa3, 0x2a, 0x84, 0x42, 0x7d,
+                    0x64, 0x3a, 0x8c, 0xdc, 0xbf, 0xe5, 0xc0, 0xc9,
+                    0x75, 0x98, 0xa2, 0xbd, 0x25, 0x55, 0xd1, 0xaa,
+                    0x8c, 0xb0, 0x8e, 0x48, 0x59, 0x0d, 0xbb, 0x3d,
+                    0xa7, 0xb0, 0x8b, 0x10, 0x56, 0x82, 0x88, 0x38,
+                    0xc5, 0xf6, 0x1e, 0x63, 0x93, 0xba, 0x7a, 0x0a,
+                    0xbc, 0xc9, 0xf6, 0x62},
+    .authdata    = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+                    0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+                    0xab, 0xad, 0xda, 0xd2},
+    .iv          = {0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+                    0xde, 0xca, 0xf8, 0x88},
+    .tag         = {0x76, 0xfc, 0x6e, 0xce, 0x0f, 0x4e, 0x17, 0x68,
+                    0xcd, 0xdf, 0x88, 0x53, 0xbb, 0x2d, 0x55, 0x1b},
+    .authsize    = 20,
+    .datasize    = 60,
+    .tagsize     = 16,
+    .ivsize      = 12
+};
+
+TestVector testVector;
+
+GCM<AES128> *gcmaes128 = 0;
+GCM<AES192> *gcmaes192 = 0;
+GCM<AES256> *gcmaes256 = 0;
+GCM<Speck> *gcmspeck = 0;
+GCM<SpeckTiny> *gcmspecklm = 0;
+
+byte buffer[128];
+
+bool testCipher_N(AuthenticatedCipher *cipher, const struct TestVector *test, size_t inc)
+{
+    size_t posn, len;
+    uint8_t tag[16];
+
+    crypto_feed_watchdog();
+
+    cipher->clear();
+    if (!cipher->setKey(test->key, cipher->keySize())) {
+        Serial.print("setKey ");
+        return false;
+    }
+    if (!cipher->setIV(test->iv, test->ivsize)) {
+        Serial.print("setIV ");
+        return false;
+    }
+
+    memset(buffer, 0xBA, sizeof(buffer));
+
+    for (posn = 0; posn < test->authsize; posn += inc) {
+        len = test->authsize - posn;
+        if (len > inc)
+            len = inc;
+        cipher->addAuthData(test->authdata + posn, len);
+    }
+
+    for (posn = 0; posn < test->datasize; posn += inc) {
+        len = test->datasize - posn;
+        if (len > inc)
+            len = inc;
+        cipher->encrypt(buffer + posn, test->plaintext + posn, len);
+    }
+
+    if (memcmp(buffer, test->ciphertext, test->datasize) != 0) {
+        Serial.print(buffer[0], HEX);
+        Serial.print("->");
+        Serial.print(test->ciphertext[0], HEX);
+        return false;
+    }
+
+    cipher->computeTag(tag, sizeof(tag));
+    if (memcmp(tag, test->tag, sizeof(tag)) != 0) {
+        Serial.print("computed wrong tag ... ");
+        return false;
+    }
+
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->setIV(test->iv, test->ivsize);
+
+    for (posn = 0; posn < test->authsize; posn += inc) {
+        len = test->authsize - posn;
+        if (len > inc)
+            len = inc;
+        cipher->addAuthData(test->authdata + posn, len);
+    }
+
+    for (posn = 0; posn < test->datasize; posn += inc) {
+        len = test->datasize - posn;
+        if (len > inc)
+            len = inc;
+        cipher->decrypt(buffer + posn, test->ciphertext + posn, len);
+    }
+
+    if (memcmp(buffer, test->plaintext, test->datasize) != 0)
+        return false;
+
+    if (!cipher->checkTag(tag, sizeof(tag))) {
+        Serial.print("tag did not check ... ");
+        return false;
+    }
+
+    return true;
+}
+
+void testCipher(AuthenticatedCipher *cipher, const struct TestVector *test)
+{
+    bool ok;
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    ok  = testCipher_N(cipher, test, test->datasize);
+    ok &= testCipher_N(cipher, test, 1);
+    ok &= testCipher_N(cipher, test, 2);
+    ok &= testCipher_N(cipher, test, 5);
+    ok &= testCipher_N(cipher, test, 8);
+    ok &= testCipher_N(cipher, test, 13);
+    ok &= testCipher_N(cipher, test, 16);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfCipherSetKey(AuthenticatedCipher *cipher, const struct TestVector *test, const char *name)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(name);
+    Serial.print(" SetKey ... ");
+
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        cipher->setKey(test->key, cipher->keySize());
+        cipher->setIV(test->iv, test->ivsize);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per operation, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" per second");
+}
+
+void perfCipherEncrypt(AuthenticatedCipher *cipher, const struct TestVector *test, const char *name)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(name);
+    Serial.print(" Encrypt ... ");
+
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->setIV(test->iv, test->ivsize);
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        cipher->encrypt(buffer, buffer, 128);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (128.0 * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((128.0 * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfCipherDecrypt(AuthenticatedCipher *cipher, const struct TestVector *test, const char *name)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(name);
+    Serial.print(" Decrypt ... ");
+
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->setIV(test->iv, test->ivsize);
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        cipher->decrypt(buffer, buffer, 128);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (128.0 * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((128.0 * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfCipherAddAuthData(AuthenticatedCipher *cipher, const struct TestVector *test, const char *name)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(name);
+    Serial.print(" AddAuthData ... ");
+
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->setIV(test->iv, test->ivsize);
+    start = micros();
+    memset(buffer, 0xBA, 128);
+    for (count = 0; count < 500; ++count) {
+        cipher->addAuthData(buffer, 128);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (128.0 * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((128.0 * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfCipherComputeTag(AuthenticatedCipher *cipher, const struct TestVector *test, const char *name)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(TestVector));
+    test = &testVector;
+
+    Serial.print(name);
+    Serial.print(" ComputeTag ... ");
+
+    cipher->setKey(test->key, cipher->keySize());
+    cipher->setIV(test->iv, test->ivsize);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        cipher->computeTag(buffer, 16);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per operation, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" per second");
+}
+
+void perfCipher(AuthenticatedCipher *cipher, const struct TestVector *test, const char *name)
+{
+    perfCipherSetKey(cipher, test, name);
+    perfCipherEncrypt(cipher, test, name);
+    perfCipherDecrypt(cipher, test, name);
+    perfCipherAddAuthData(cipher, test, name);
+    perfCipherComputeTag(cipher, test, name);
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+#ifndef TEST_SPECK
+    Serial.println("State Sizes:");
+    Serial.print("GCM<AES128> ... ");
+    Serial.println(sizeof(*gcmaes128));
+    Serial.print("GCM<AES192> ... ");
+    Serial.println(sizeof(*gcmaes192));
+    Serial.print("GCM<AES256> ... ");
+    Serial.println(sizeof(*gcmaes256));
+    Serial.print("GCM<Speck> ... ");
+    Serial.println(sizeof(*gcmspeck));
+    Serial.print("GCM<SpeckTiny> ... ");
+    Serial.println(sizeof(*gcmspecklm));
+    Serial.println();
+#endif
+
+    Serial.println("Test Vectors:");
+    gcmaes128 = new GCM<AES128>();
+    testCipher(gcmaes128, &testVectorGCM1);
+#ifndef TEST_SPECK
+    testCipher(gcmaes128, &testVectorGCM2);
+    testCipher(gcmaes128, &testVectorGCM3);
+    testCipher(gcmaes128, &testVectorGCM4);
+    testCipher(gcmaes128, &testVectorGCM5);
+#endif
+    delete gcmaes128;
+    gcmaes192 = new GCM<AES192>();
+    testCipher(gcmaes192, &testVectorGCM10);
+    delete gcmaes192;
+    gcmaes256 = new GCM<AES256>();
+    testCipher(gcmaes256, &testVectorGCM16);
+    delete gcmaes256;
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+#ifndef TEST_SPECK
+    gcmaes128 = new GCM<AES128>();
+    perfCipher(gcmaes128, &testVectorGCM1, testVectorGCM1.name);
+    delete gcmaes128;
+    gcmaes192 = new GCM<AES192>();
+    perfCipher(gcmaes192, &testVectorGCM10, testVectorGCM10.name);
+    delete gcmaes192;
+    gcmaes256 = new GCM<AES256>();
+    perfCipher(gcmaes256, &testVectorGCM16, testVectorGCM16.name);
+    delete gcmaes256;
+#endif
+#if defined(TEST_SPECK) || !defined(__AVR__)
+    gcmspeck = new GCM<Speck>();
+    perfCipher(gcmspeck, &testVectorGCM16, "GCM-Speck-256");
+    delete gcmspeck;
+    gcmspecklm = new GCM<SpeckTiny>();
+    perfCipher(gcmspecklm, &testVectorGCM16, "GCM-SpeckTiny-256");
+    delete gcmspecklm;
+#endif
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestGHASH/TestGHASH.ino b/src/modules/Crypto/examples/TestGHASH/TestGHASH.ino
new file mode 100644
index 0000000..70305d2
--- /dev/null
+++ b/src/modules/Crypto/examples/TestGHASH/TestGHASH.ino
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the GHASH implementation to verify correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <GHASH.h>
+#include <string.h>
+
+// Test vectors from Appendix B of:
+// http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf
+
+struct TestVector
+{
+    const char *name;
+    uint8_t key[16];
+    uint8_t data[112];
+    size_t dataLen;
+    uint8_t hash[16];
+};
+
+static TestVector const testVectorGHASH_1 = {
+    .name     = "GHASH #1",
+    .key      = {0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b,
+                 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34, 0x2b, 0x2e},
+    .data     = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .dataLen  = 16,
+    .hash     = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+static TestVector const testVectorGHASH_2 = {
+    .name     = "GHASH #2",
+    .key      = {0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b,
+                 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34, 0x2b, 0x2e},
+    .data     = {0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92,
+                 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80},
+    .dataLen  = 32,
+    .hash     = {0xf3, 0x8c, 0xbb, 0x1a, 0xd6, 0x92, 0x23, 0xdc,
+                 0xc3, 0x45, 0x7a, 0xe5, 0xb6, 0xb0, 0xf8, 0x85}
+};
+static TestVector const testVectorGHASH_3 = {
+    .name     = "GHASH #3",
+    .key      = {0xb8, 0x3b, 0x53, 0x37, 0x08, 0xbf, 0x53, 0x5d,
+                 0x0a, 0xa6, 0xe5, 0x29, 0x80, 0xd5, 0x3b, 0x78},
+    .data     = {0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24,
+                 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c,
+                 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0,
+                 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e,
+                 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c,
+                 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05,
+                 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
+                 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00},
+    .dataLen  = 80,
+    .hash     = {0x7f, 0x1b, 0x32, 0xb8, 0x1b, 0x82, 0x0d, 0x02,
+                 0x61, 0x4f, 0x88, 0x95, 0xac, 0x1d, 0x4e, 0xac}
+};
+static TestVector const testVectorGHASH_4 = {
+    .name     = "GHASH #4",
+    .key      = {0xb8, 0x3b, 0x53, 0x37, 0x08, 0xbf, 0x53, 0x5d,
+                 0x0a, 0xa6, 0xe5, 0x29, 0x80, 0xd5, 0x3b, 0x78},
+    .data     = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+                 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+                 0xab, 0xad, 0xda, 0xd2, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24,
+                 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c,
+                 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0,
+                 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e,
+                 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c,
+                 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05,
+                 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
+                 0x3d, 0x58, 0xe0, 0x91, 0x00, 0x00, 0x00, 0x00,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0,
+                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0},
+    .dataLen  = 112,
+    .hash     = {0x69, 0x8e, 0x57, 0xf7, 0x0e, 0x6e, 0xcc, 0x7f,
+                 0xd9, 0x46, 0x3b, 0x72, 0x60, 0xa9, 0xae, 0x5f}
+};
+
+GHASH ghash;
+
+byte buffer[128];
+
+bool testGHASH_N(GHASH *hash, const struct TestVector *test, size_t inc)
+{
+    size_t size = test->dataLen;
+    size_t posn, len;
+
+    hash->reset(test->key);
+
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        hash->update(test->data + posn, len);
+    }
+
+    hash->finalize(buffer, 16);
+
+    return !memcmp(buffer, test->hash, 16);
+}
+
+void testGHASH(GHASH *hash, const struct TestVector *test)
+{
+    bool ok;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    ok  = testGHASH_N(hash, test, test->dataLen);
+    ok &= testGHASH_N(hash, test, 1);
+    ok &= testGHASH_N(hash, test, 2);
+    ok &= testGHASH_N(hash, test, 5);
+    ok &= testGHASH_N(hash, test, 8);
+    ok &= testGHASH_N(hash, test, 13);
+    ok &= testGHASH_N(hash, test, 16);
+    ok &= testGHASH_N(hash, test, 24);
+    ok &= testGHASH_N(hash, test, 63);
+    ok &= testGHASH_N(hash, test, 64);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfGHASH(GHASH *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Hashing ... ");
+
+    for (size_t posn = 0; posn < sizeof(buffer); ++posn)
+        buffer[posn] = (uint8_t)posn;
+
+    hash->reset(testVectorGHASH_1.key);
+    start = micros();
+    for (count = 0; count < 200; ++count) {
+        hash->update(buffer, sizeof(buffer));
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 200.0));
+    Serial.print("us per byte, ");
+    Serial.print((sizeof(buffer) * 200.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfGHASHSetKey(GHASH *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Set Key ... ");
+
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->reset(testVectorGHASH_1.key);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void perfGHASHFinalize(GHASH *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Finalize ... ");
+
+    hash->reset(testVectorGHASH_1.key);
+    hash->update("abc", 3);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->finalize(buffer, 16);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.print("State Size ... ");
+    Serial.println(sizeof(GHASH));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testGHASH(&ghash, &testVectorGHASH_1);
+    testGHASH(&ghash, &testVectorGHASH_2);
+    testGHASH(&ghash, &testVectorGHASH_3);
+    testGHASH(&ghash, &testVectorGHASH_4);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfGHASH(&ghash);
+    perfGHASHSetKey(&ghash);
+    perfGHASHFinalize(&ghash);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestP521/TestP521.ino b/src/modules/Crypto/examples/TestP521/TestP521.ino
new file mode 100644
index 0000000..6e50ade
--- /dev/null
+++ b/src/modules/Crypto/examples/TestP521/TestP521.ino
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the P521 algorithm.
+
+Note: This example is too large to run on the Arduino Uno or other
+AVR platforms with 32K or less of flash memory.
+*/
+
+#include <Crypto.h>
+#include <P521.h>
+#include <SHA256.h>
+#include <SHA512.h>
+#include <RNG.h>
+#include <RNG.h>
+#include <string.h>
+#if defined(ESP8266) || defined(ESP32)
+#include <pgmspace.h>
+#else
+#include <avr/pgmspace.h>
+#endif
+
+void printNumber(const char *name, const uint8_t *x, size_t len)
+{
+    static const char hexchars[] = "0123456789ABCDEF";
+    Serial.print(name);
+    Serial.print(" = ");
+    for (size_t posn = 0; posn < len; ++posn) {
+        Serial.print(hexchars[(x[posn] >> 4) & 0x0F]);
+        Serial.print(hexchars[x[posn] & 0x0F]);
+    }
+    Serial.println();
+}
+
+static int P521_memcmp_P(const void *s1, const void *s2, size_t len)
+{
+    const uint8_t *u1 = (const uint8_t *)s1;
+    const uint8_t *u2 = (const uint8_t *)s2;
+    while (len > 0) {
+        int ch1 = *u1++;
+        int ch2 = pgm_read_byte(u2++);
+        if (ch1 != ch2)
+            return ch1 - ch2;
+        --len;
+    }
+    return 0;
+}
+
+static uint8_t alice_k[132];
+static uint8_t alice_f[66];
+static uint8_t bob_k[132];
+static uint8_t bob_f[66];
+
+// Check the eval() function using the test vectors from RFC 5903.
+void testEval()
+{
+    static uint8_t const alice_private[66] PROGMEM = {
+        0x00, 0x37, 0xAD, 0xE9, 0x31, 0x9A, 0x89, 0xF4,
+        0xDA, 0xBD, 0xB3, 0xEF, 0x41, 0x1A, 0xAC, 0xCC,
+        0xA5, 0x12, 0x3C, 0x61, 0xAC, 0xAB, 0x57, 0xB5,
+        0x39, 0x3D, 0xCE, 0x47, 0x60, 0x81, 0x72, 0xA0,
+        0x95, 0xAA, 0x85, 0xA3, 0x0F, 0xE1, 0xC2, 0x95,
+        0x2C, 0x67, 0x71, 0xD9, 0x37, 0xBA, 0x97, 0x77,
+        0xF5, 0x95, 0x7B, 0x26, 0x39, 0xBA, 0xB0, 0x72,
+        0x46, 0x2F, 0x68, 0xC2, 0x7A, 0x57, 0x38, 0x2D,
+        0x4A, 0x52
+    };
+    static uint8_t const alice_public[132] PROGMEM = {
+        0x00, 0x15, 0x41, 0x7E, 0x84, 0xDB, 0xF2, 0x8C,
+        0x0A, 0xD3, 0xC2, 0x78, 0x71, 0x33, 0x49, 0xDC,
+        0x7D, 0xF1, 0x53, 0xC8, 0x97, 0xA1, 0x89, 0x1B,
+        0xD9, 0x8B, 0xAB, 0x43, 0x57, 0xC9, 0xEC, 0xBE,
+        0xE1, 0xE3, 0xBF, 0x42, 0xE0, 0x0B, 0x8E, 0x38,
+        0x0A, 0xEA, 0xE5, 0x7C, 0x2D, 0x10, 0x75, 0x64,
+        0x94, 0x18, 0x85, 0x94, 0x2A, 0xF5, 0xA7, 0xF4,
+        0x60, 0x17, 0x23, 0xC4, 0x19, 0x5D, 0x17, 0x6C,
+        0xED, 0x3E, 0x01, 0x7C, 0xAE, 0x20, 0xB6, 0x64,
+        0x1D, 0x2E, 0xEB, 0x69, 0x57, 0x86, 0xD8, 0xC9,
+        0x46, 0x14, 0x62, 0x39, 0xD0, 0x99, 0xE1, 0x8E,
+        0x1D, 0x5A, 0x51, 0x4C, 0x73, 0x9D, 0x7C, 0xB4,
+        0xA1, 0x0A, 0xD8, 0xA7, 0x88, 0x01, 0x5A, 0xC4,
+        0x05, 0xD7, 0x79, 0x9D, 0xC7, 0x5E, 0x7B, 0x7D,
+        0x5B, 0x6C, 0xF2, 0x26, 0x1A, 0x6A, 0x7F, 0x15,
+        0x07, 0x43, 0x8B, 0xF0, 0x1B, 0xEB, 0x6C, 0xA3,
+        0x92, 0x6F, 0x95, 0x82
+    };
+    static uint8_t const bob_private[66] PROGMEM = {
+        0x01, 0x45, 0xBA, 0x99, 0xA8, 0x47, 0xAF, 0x43,
+        0x79, 0x3F, 0xDD, 0x0E, 0x87, 0x2E, 0x7C, 0xDF,
+        0xA1, 0x6B, 0xE3, 0x0F, 0xDC, 0x78, 0x0F, 0x97,
+        0xBC, 0xCC, 0x3F, 0x07, 0x83, 0x80, 0x20, 0x1E,
+        0x9C, 0x67, 0x7D, 0x60, 0x0B, 0x34, 0x37, 0x57,
+        0xA3, 0xBD, 0xBF, 0x2A, 0x31, 0x63, 0xE4, 0xC2,
+        0xF8, 0x69, 0xCC, 0xA7, 0x45, 0x8A, 0xA4, 0xA4,
+        0xEF, 0xFC, 0x31, 0x1F, 0x5C, 0xB1, 0x51, 0x68,
+        0x5E, 0xB9
+    };
+    static uint8_t const bob_public[132] PROGMEM = {
+        0x00, 0xD0, 0xB3, 0x97, 0x5A, 0xC4, 0xB7, 0x99,
+        0xF5, 0xBE, 0xA1, 0x6D, 0x5E, 0x13, 0xE9, 0xAF,
+        0x97, 0x1D, 0x5E, 0x9B, 0x98, 0x4C, 0x9F, 0x39,
+        0x72, 0x8B, 0x5E, 0x57, 0x39, 0x73, 0x5A, 0x21,
+        0x9B, 0x97, 0xC3, 0x56, 0x43, 0x6A, 0xDC, 0x6E,
+        0x95, 0xBB, 0x03, 0x52, 0xF6, 0xBE, 0x64, 0xA6,
+        0xC2, 0x91, 0x2D, 0x4E, 0xF2, 0xD0, 0x43, 0x3C,
+        0xED, 0x2B, 0x61, 0x71, 0x64, 0x00, 0x12, 0xD9,
+        0x46, 0x0F, 0x01, 0x5C, 0x68, 0x22, 0x63, 0x83,
+        0x95, 0x6E, 0x3B, 0xD0, 0x66, 0xE7, 0x97, 0xB6,
+        0x23, 0xC2, 0x7C, 0xE0, 0xEA, 0xC2, 0xF5, 0x51,
+        0xA1, 0x0C, 0x2C, 0x72, 0x4D, 0x98, 0x52, 0x07,
+        0x7B, 0x87, 0x22, 0x0B, 0x65, 0x36, 0xC5, 0xC4,
+        0x08, 0xA1, 0xD2, 0xAE, 0xBB, 0x8E, 0x86, 0xD6,
+        0x78, 0xAE, 0x49, 0xCB, 0x57, 0x09, 0x1F, 0x47,
+        0x32, 0x29, 0x65, 0x79, 0xAB, 0x44, 0xFC, 0xD1,
+        0x7F, 0x0F, 0xC5, 0x6A
+    };
+    static uint8_t const shared_secret[66] PROGMEM = {
+        0x01, 0x14, 0x4C, 0x7D, 0x79, 0xAE, 0x69, 0x56,
+        0xBC, 0x8E, 0xDB, 0x8E, 0x7C, 0x78, 0x7C, 0x45,
+        0x21, 0xCB, 0x08, 0x6F, 0xA6, 0x44, 0x07, 0xF9,
+        0x78, 0x94, 0xE5, 0xE6, 0xB2, 0xD7, 0x9B, 0x04,
+        0xD1, 0x42, 0x7E, 0x73, 0xCA, 0x4B, 0xAA, 0x24,
+        0x0A, 0x34, 0x78, 0x68, 0x59, 0x81, 0x0C, 0x06,
+        0xB3, 0xC7, 0x15, 0xA3, 0xA8, 0xCC, 0x31, 0x51,
+        0xF2, 0xBE, 0xE4, 0x17, 0x99, 0x6D, 0x19, 0xF3,
+        0xDD, 0xEA
+    };
+
+    // Evaluate the curve function and check the public keys.
+    uint8_t result[132];
+    Serial.println("Fixed test vectors:");
+    Serial.print("Computing Alice's public key ... ");
+    Serial.flush();
+    memcpy_P(alice_f, alice_private, 66);
+    unsigned long start = micros();
+    P521::eval(result, alice_f, 0);
+    unsigned long elapsed = micros() - start;
+    if (P521_memcmp_P(result, alice_public, 132) == 0) {
+        Serial.print("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result, 132);
+        printNumber("expected", alice_f, 132);
+    }
+    Serial.print(" (elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us)");
+    Serial.print("Computing Bob's public key ... ");
+    Serial.flush();
+    memcpy_P(bob_f, bob_private, 66);
+    start = micros();
+    P521::eval(result, bob_f, 0);
+    elapsed = micros() - start;
+    if (P521_memcmp_P(result, bob_public, 132) == 0) {
+        Serial.print("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result, 132);
+        printNumber("expected", bob_f, 132);
+    }
+    Serial.print(" (elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us)");
+
+    // Compute the shared secret from each side.
+    Serial.print("Computing Alice's shared secret ... ");
+    Serial.flush();
+    memcpy_P(alice_f, alice_private, 66);
+    memcpy_P(bob_k, bob_public, 132);
+    memcpy_P(bob_f, shared_secret, 66);
+    start = micros();
+    P521::eval(result, alice_f, bob_k);
+    elapsed = micros() - start;
+    if (P521_memcmp_P(result, shared_secret, 66) == 0) {
+        Serial.print("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result, 66);
+        printNumber("expected", bob_f, 66);
+    }
+    Serial.print(" (elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us)");
+    Serial.print("Computing Bob's shared secret ... ");
+    Serial.flush();
+    memcpy_P(bob_f, bob_private, 66);
+    memcpy_P(alice_k, alice_public, 132);
+    memcpy_P(alice_f, shared_secret, 66);
+    start = micros();
+    P521::eval(result, bob_f, alice_k);
+    elapsed = micros() - start;
+    if (P521_memcmp_P(result, shared_secret, 66) == 0) {
+        Serial.print("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result, 66);
+        printNumber("expected", alice_f, 66);
+    }
+    Serial.print(" (elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us)");
+}
+
+void testDH()
+{
+    Serial.println("Diffie-Hellman key exchange:");
+    Serial.print("Generate random k/f for Alice ... ");
+    Serial.flush();
+    unsigned long start = micros();
+    P521::dh1(alice_k, alice_f);
+    unsigned long elapsed = micros() - start;
+    Serial.print("elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us");
+
+    Serial.print("Generate random k/f for Bob ... ");
+    Serial.flush();
+    start = micros();
+    P521::dh1(bob_k, bob_f);
+    elapsed = micros() - start;
+    Serial.print("elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us");
+
+    Serial.print("Generate shared secret for Alice ... ");
+    Serial.flush();
+    start = micros();
+    P521::dh2(bob_k, alice_f);
+    elapsed = micros() - start;
+    Serial.print("elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us");
+
+    Serial.print("Generate shared secret for Bob ... ");
+    Serial.flush();
+    start = micros();
+    P521::dh2(alice_k, bob_f);
+    elapsed = micros() - start;
+    Serial.print("elapsed ");
+    Serial.print(elapsed);
+    Serial.println(" us");
+
+    Serial.print("Check that the shared secrets match ... ");
+    if (memcmp(alice_f, bob_f, 66) == 0) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", alice_f, 66);
+        printNumber("expected", bob_f, 66);
+    }
+}
+
+struct TestSignKey
+{
+    uint8_t privateKey[66];
+    uint8_t publicKey[132];
+};
+
+// Test key from RFC 6979, Appendix A.2.7.
+static TestSignKey const testKeyP521 PROGMEM = {
+    {0x00, 0xFA, 0xD0, 0x6D, 0xAA, 0x62, 0xBA, 0x3B,        // x
+     0x25, 0xD2, 0xFB, 0x40, 0x13, 0x3D, 0xA7, 0x57,
+     0x20, 0x5D, 0xE6, 0x7F, 0x5B, 0xB0, 0x01, 0x8F,
+     0xEE, 0x8C, 0x86, 0xE1, 0xB6, 0x8C, 0x7E, 0x75,
+     0xCA, 0xA8, 0x96, 0xEB, 0x32, 0xF1, 0xF4, 0x7C,
+     0x70, 0x85, 0x58, 0x36, 0xA6, 0xD1, 0x6F, 0xCC,
+     0x14, 0x66, 0xF6, 0xD8, 0xFB, 0xEC, 0x67, 0xDB,
+     0x89, 0xEC, 0x0C, 0x08, 0xB0, 0xE9, 0x96, 0xB8,
+     0x35, 0x38},
+    {0x01, 0x89, 0x45, 0x50, 0xD0, 0x78, 0x59, 0x32,        // Ux
+     0xE0, 0x0E, 0xAA, 0x23, 0xB6, 0x94, 0xF2, 0x13,
+     0xF8, 0xC3, 0x12, 0x1F, 0x86, 0xDC, 0x97, 0xA0,
+     0x4E, 0x5A, 0x71, 0x67, 0xDB, 0x4E, 0x5B, 0xCD,
+     0x37, 0x11, 0x23, 0xD4, 0x6E, 0x45, 0xDB, 0x6B,
+     0x5D, 0x53, 0x70, 0xA7, 0xF2, 0x0F, 0xB6, 0x33,
+     0x15, 0x5D, 0x38, 0xFF, 0xA1, 0x6D, 0x2B, 0xD7,
+     0x61, 0xDC, 0xAC, 0x47, 0x4B, 0x9A, 0x2F, 0x50,
+     0x23, 0xA4,
+     0x00, 0x49, 0x31, 0x01, 0xC9, 0x62, 0xCD, 0x4D,        // Uy
+     0x2F, 0xDD, 0xF7, 0x82, 0x28, 0x5E, 0x64, 0x58,
+     0x41, 0x39, 0xC2, 0xF9, 0x1B, 0x47, 0xF8, 0x7F,
+     0xF8, 0x23, 0x54, 0xD6, 0x63, 0x0F, 0x74, 0x6A,
+     0x28, 0xA0, 0xDB, 0x25, 0x74, 0x1B, 0x5B, 0x34,
+     0xA8, 0x28, 0x00, 0x8B, 0x22, 0xAC, 0xC2, 0x3F,
+     0x92, 0x4F, 0xAA, 0xFB, 0xD4, 0xD3, 0x3F, 0x81,
+     0xEA, 0x66, 0x95, 0x6D, 0xFE, 0xAA, 0x2B, 0xFD,
+     0xFC, 0xF5}
+};
+
+struct TestSignVector
+{
+    const char *name;
+    const char *data;
+    uint8_t signature[132];
+};
+
+// Test vectors from RFC 6979, Appendix A.2.7.
+static TestSignVector const testVectorP521_1 PROGMEM = {
+    // P-521 test case with SHA-256 and message "sample".
+    "P-521 #1",
+    "sample",
+    {0x01, 0x51, 0x1B, 0xB4, 0xD6, 0x75, 0x11, 0x4F,        // r
+     0xE2, 0x66, 0xFC, 0x43, 0x72, 0xB8, 0x76, 0x82,
+     0xBA, 0xEC, 0xC0, 0x1D, 0x3C, 0xC6, 0x2C, 0xF2,
+     0x30, 0x3C, 0x92, 0xB3, 0x52, 0x60, 0x12, 0x65,
+     0x9D, 0x16, 0x87, 0x6E, 0x25, 0xC7, 0xC1, 0xE5,
+     0x76, 0x48, 0xF2, 0x3B, 0x73, 0x56, 0x4D, 0x67,
+     0xF6, 0x1C, 0x6F, 0x14, 0xD5, 0x27, 0xD5, 0x49,
+     0x72, 0x81, 0x04, 0x21, 0xE7, 0xD8, 0x75, 0x89,
+     0xE1, 0xA7,
+     0x00, 0x4A, 0x17, 0x11, 0x43, 0xA8, 0x31, 0x63,        // s
+     0xD6, 0xDF, 0x46, 0x0A, 0xAF, 0x61, 0x52, 0x26,
+     0x95, 0xF2, 0x07, 0xA5, 0x8B, 0x95, 0xC0, 0x64,
+     0x4D, 0x87, 0xE5, 0x2A, 0xA1, 0xA3, 0x47, 0x91,
+     0x6E, 0x4F, 0x7A, 0x72, 0x93, 0x0B, 0x1B, 0xC0,
+     0x6D, 0xBE, 0x22, 0xCE, 0x3F, 0x58, 0x26, 0x4A,
+     0xFD, 0x23, 0x70, 0x4C, 0xBB, 0x63, 0xB2, 0x9B,
+     0x93, 0x1F, 0x7D, 0xE6, 0xC9, 0xD9, 0x49, 0xA7,
+     0xEC, 0xFC}
+};
+static TestSignVector const testVectorP521_2 PROGMEM = {
+    // P-521 test case with SHA-512 and message "sample".
+    "P-521 #2",
+    "sample",
+    {0x00, 0xC3, 0x28, 0xFA, 0xFC, 0xBD, 0x79, 0xDD,        // r
+     0x77, 0x85, 0x03, 0x70, 0xC4, 0x63, 0x25, 0xD9,
+     0x87, 0xCB, 0x52, 0x55, 0x69, 0xFB, 0x63, 0xC5,
+     0xD3, 0xBC, 0x53, 0x95, 0x0E, 0x6D, 0x4C, 0x5F,
+     0x17, 0x4E, 0x25, 0xA1, 0xEE, 0x90, 0x17, 0xB5,
+     0xD4, 0x50, 0x60, 0x6A, 0xDD, 0x15, 0x2B, 0x53,
+     0x49, 0x31, 0xD7, 0xD4, 0xE8, 0x45, 0x5C, 0xC9,
+     0x1F, 0x9B, 0x15, 0xBF, 0x05, 0xEC, 0x36, 0xE3,
+     0x77, 0xFA,
+     0x00, 0x61, 0x7C, 0xCE, 0x7C, 0xF5, 0x06, 0x48,        // s
+     0x06, 0xC4, 0x67, 0xF6, 0x78, 0xD3, 0xB4, 0x08,
+     0x0D, 0x6F, 0x1C, 0xC5, 0x0A, 0xF2, 0x6C, 0xA2,
+     0x09, 0x41, 0x73, 0x08, 0x28, 0x1B, 0x68, 0xAF,
+     0x28, 0x26, 0x23, 0xEA, 0xA6, 0x3E, 0x5B, 0x5C,
+     0x07, 0x23, 0xD8, 0xB8, 0xC3, 0x7F, 0xF0, 0x77,
+     0x7B, 0x1A, 0x20, 0xF8, 0xCC, 0xB1, 0xDC, 0xCC,
+     0x43, 0x99, 0x7F, 0x1E, 0xE0, 0xE4, 0x4D, 0xA4,
+     0xA6, 0x7A}
+};
+static TestSignVector const testVectorP521_3 PROGMEM = {
+    // P-521 test case with SHA-256 and message "test".
+    "P-521 #3",
+    "test",
+    {0x00, 0x0E, 0x87, 0x1C, 0x4A, 0x14, 0xF9, 0x93,        // r
+     0xC6, 0xC7, 0x36, 0x95, 0x01, 0x90, 0x0C, 0x4B,
+     0xC1, 0xE9, 0xC7, 0xB0, 0xB4, 0xBA, 0x44, 0xE0,
+     0x48, 0x68, 0xB3, 0x0B, 0x41, 0xD8, 0x07, 0x10,
+     0x42, 0xEB, 0x28, 0xC4, 0xC2, 0x50, 0x41, 0x1D,
+     0x0C, 0xE0, 0x8C, 0xD1, 0x97, 0xE4, 0x18, 0x8E,
+     0xA4, 0x87, 0x6F, 0x27, 0x9F, 0x90, 0xB3, 0xD8,
+     0xD7, 0x4A, 0x3C, 0x76, 0xE6, 0xF1, 0xE4, 0x65,
+     0x6A, 0xA8,
+     0x00, 0xCD, 0x52, 0xDB, 0xAA, 0x33, 0xB0, 0x63,        // s
+     0xC3, 0xA6, 0xCD, 0x80, 0x58, 0xA1, 0xFB, 0x0A,
+     0x46, 0xA4, 0x75, 0x4B, 0x03, 0x4F, 0xCC, 0x64,
+     0x47, 0x66, 0xCA, 0x14, 0xDA, 0x8C, 0xA5, 0xCA,
+     0x9F, 0xDE, 0x00, 0xE8, 0x8C, 0x1A, 0xD6, 0x0C,
+     0xCB, 0xA7, 0x59, 0x02, 0x52, 0x99, 0x07, 0x9D,
+     0x7A, 0x42, 0x7E, 0xC3, 0xCC, 0x5B, 0x61, 0x9B,
+     0xFB, 0xC8, 0x28, 0xE7, 0x76, 0x9B, 0xCD, 0x69,
+     0x4E, 0x86}
+};
+static TestSignVector const testVectorP521_4 PROGMEM = {
+    // P-521 test case with SHA-512 and message "test".
+    "P-521 #4",
+    "test",
+    {0x01, 0x3E, 0x99, 0x02, 0x0A, 0xBF, 0x5C, 0xEE,        // r
+     0x75, 0x25, 0xD1, 0x6B, 0x69, 0xB2, 0x29, 0x65,
+     0x2A, 0xB6, 0xBD, 0xF2, 0xAF, 0xFC, 0xAE, 0xF3,
+     0x87, 0x73, 0xB4, 0xB7, 0xD0, 0x87, 0x25, 0xF1,
+     0x0C, 0xDB, 0x93, 0x48, 0x2F, 0xDC, 0xC5, 0x4E,
+     0xDC, 0xEE, 0x91, 0xEC, 0xA4, 0x16, 0x6B, 0x2A,
+     0x7C, 0x62, 0x65, 0xEF, 0x0C, 0xE2, 0xBD, 0x70,
+     0x51, 0xB7, 0xCE, 0xF9, 0x45, 0xBA, 0xBD, 0x47,
+     0xEE, 0x6D,
+     0x01, 0xFB, 0xD0, 0x01, 0x3C, 0x67, 0x4A, 0xA7,        // s
+     0x9C, 0xB3, 0x98, 0x49, 0x52, 0x79, 0x16, 0xCE,
+     0x30, 0x1C, 0x66, 0xEA, 0x7C, 0xE8, 0xB8, 0x06,
+     0x82, 0x78, 0x6A, 0xD6, 0x0F, 0x98, 0xF7, 0xE7,
+     0x8A, 0x19, 0xCA, 0x69, 0xEF, 0xF5, 0xC5, 0x74,
+     0x00, 0xE3, 0xB3, 0xA0, 0xAD, 0x66, 0xCE, 0x09,
+     0x78, 0x21, 0x4D, 0x13, 0xBA, 0xF4, 0xE9, 0xAC,
+     0x60, 0x75, 0x2F, 0x7B, 0x15, 0x5E, 0x2D, 0xE4,
+     0xDC, 0xE3}
+};
+
+void testSignCommon(const struct TestSignVector *_test, Hash *hash)
+{
+    uint8_t *privateKey = alice_f;
+    uint8_t *publicKey = alice_k;
+    uint8_t *sig = bob_k;
+    static TestSignVector test;
+
+    memcpy_P(&test, _test, sizeof(test));
+
+    Serial.print(test.name);
+    Serial.print(" Sign ... ");
+    Serial.flush();
+
+    memcpy_P(privateKey, testKeyP521.privateKey, 66);
+
+    unsigned long start = micros();
+    P521::sign(sig, privateKey, test.data, strlen(test.data), hash);
+    unsigned long elapsed = micros() - start;
+    Serial.print(elapsed);
+    Serial.print(" us ... ");
+
+    bool ok = !memcmp(sig, test.signature, 132);
+    if (ok) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", sig, 132);
+        printNumber("expected", test.signature, 132);
+    }
+
+    Serial.print(test.name);
+    Serial.print(" Verify ... ");
+    Serial.flush();
+
+    memcpy_P(publicKey, testKeyP521.publicKey, 132);
+
+    start = micros();
+    bool verified = P521::verify
+        (test.signature, publicKey, test.data, strlen(test.data), hash);
+    elapsed = micros() - start;
+    Serial.print(elapsed);
+    Serial.print(" us ... ");
+
+    if (verified)
+        Serial.println("ok");
+    else
+        Serial.println("failed");
+
+    Serial.print(test.name);
+    Serial.print(" Derive Public Key ... ");
+    Serial.flush();
+
+    memcpy_P(privateKey, testKeyP521.privateKey, 66);
+
+    start = micros();
+    P521::derivePublicKey(publicKey, privateKey);
+    elapsed = micros() - start;
+    Serial.print(elapsed);
+    Serial.print(" us ... ");
+
+    ok = !P521_memcmp_P(publicKey, testKeyP521.publicKey, 132);
+    if (ok) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", publicKey, 132);
+        memcpy_P(publicKey, testKeyP521.publicKey, 132);
+        printNumber("expected", publicKey, 132);
+    }
+}
+
+void testSignSHA256(const struct TestSignVector *test)
+{
+    SHA256 hash;
+    testSignCommon(test, &hash);
+}
+
+void testSignSHA512(const struct TestSignVector *test)
+{
+    SHA512 hash;
+    testSignCommon(test, &hash);
+}
+
+void testSign()
+{
+    Serial.println("Digital signatures:");
+    testSignSHA256(&testVectorP521_1);
+    testSignSHA512(&testVectorP521_2);
+    testSignSHA256(&testVectorP521_3);
+    testSignSHA512(&testVectorP521_4);
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    // Start the random number generator.  We don't initialise a noise
+    // source here because we don't need one for testing purposes.
+    // Real DH applications should of course use a proper noise source.
+    RNG.begin("TestP521 1.0");
+
+    // Perform the tests.
+    testEval();
+    Serial.println();
+    testDH();
+    Serial.println();
+    testSign();
+    Serial.println();
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestP521Math/TestP521Math.ino b/src/modules/Crypto/examples/TestP521Math/TestP521Math.ino
new file mode 100644
index 0000000..6997191
--- /dev/null
+++ b/src/modules/Crypto/examples/TestP521Math/TestP521Math.ino
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the P521 field mathematics independent
+of the full curve operation itself.
+*/
+
+// Enable access to the internals of P521 to test the raw field ops.
+#define TEST_P521_FIELD_OPS 1
+
+#include <Crypto.h>
+#include <P521.h>
+#include <utility/ProgMemUtil.h>
+#include <string.h>
+
+// Copy some definitions from the P521 class for convenience.
+#define NUM_LIMBS   ((66 + sizeof(limb_t) - 1) / sizeof(limb_t))
+#define LIMB_BITS   (8 * sizeof(limb_t))
+#define INVERSE_LIMB (~((limb_t)0))
+
+// For simpleMod() below we need a type that is 4 times the size of limb_t.
+#if BIGNUMBER_LIMB_8BIT
+#define qlimb_t uint32_t
+#elif BIGNUMBER_LIMB_16BIT
+#define qlimb_t uint64_t
+#else
+#define BIGNUMBER_NO_QLIMB 1
+#endif
+
+limb_t arg1[NUM_LIMBS];
+limb_t arg2[NUM_LIMBS];
+limb_t result[NUM_LIMBS];
+limb_t result2[NUM_LIMBS * 2 + 1];
+limb_t temp[NUM_LIMBS];
+
+// Convert a decimal string in program memory into a number.
+void fromString(limb_t *x, uint8_t size, const char *str)
+{
+    uint8_t ch, posn;
+    memset(x, 0, sizeof(limb_t) * size);
+    while ((ch = pgm_read_byte((uint8_t *)str)) != '\0') {
+        if (ch >= '0' && ch <= '9') {
+            // Quick and simple method to multiply by 10 and add the new digit.
+            dlimb_t carry = ch - '0';
+            for (posn = 0; posn < size; ++posn) {
+                carry += ((dlimb_t)x[posn]) * 10U;
+                x[posn] = (limb_t)carry;
+                carry >>= LIMB_BITS;
+            }
+        }
+        ++str;
+    }
+}
+
+// Compare two numbers of NUM_LIMBS in length.  Returns -1, 0, or 1.
+int compare(const limb_t *x, const limb_t *y)
+{
+    for (uint8_t posn = NUM_LIMBS; posn > 0; --posn) {
+        limb_t a = x[posn - 1];
+        limb_t b = y[posn - 1];
+        if (a < b)
+            return -1;
+        else if (a > b)
+            return 1;
+    }
+    return 0;
+}
+
+// Compare two numbers where one is a decimal string.  Returns -1, 0, or 1.
+int compare(const limb_t *x, const char *y)
+{
+    limb_t val[NUM_LIMBS];
+    fromString(val, NUM_LIMBS, y);
+    return compare(x, val);
+}
+
+void printNumber(const char *name, const limb_t *x)
+{
+    static const char hexchars[] = "0123456789ABCDEF";
+    Serial.print(name);
+    Serial.print(" = ");
+    for (uint8_t posn = NUM_LIMBS; posn > 0; --posn) {
+        for (uint8_t bit = LIMB_BITS; bit > 0; ) {
+            bit -= 4;
+            Serial.print(hexchars[(x[posn - 1] >> bit) & 0x0F]);
+        }
+        Serial.print(' ');
+    }
+    Serial.println();
+}
+
+// Standard numbers that are useful in field operation tests.
+char const num_0[] PROGMEM = "0";
+char const num_1[] PROGMEM = "1";
+char const num_2[] PROGMEM = "2";
+char const num_4[] PROGMEM = "4";
+char const num_5[] PROGMEM = "5";
+char const num_128[] PROGMEM = "128";
+char const num_256[] PROGMEM = "256";
+char const num_2_64_m7[] PROGMEM = "18446744073709551609"; // 2^64 - 7
+char const num_2_129_m5[] PROGMEM = "680564733841876926926749214863536422907"; // 2^129 - 5
+char const num_pi[] PROGMEM = "31415926535897932384626433832795028841971693993751058209749445923078164062862"; // 77 digits of pi
+char const num_2_255_m253[] PROGMEM = "57896044618658097711785492504343953926634992332820282019728792003956564819715"; // 2^255 - 253
+char const num_2_255_m20[] PROGMEM = "57896044618658097711785492504343953926634992332820282019728792003956564819948"; // 2^255 - 20
+char const num_2_255_m19[] PROGMEM = "57896044618658097711785492504343953926634992332820282019728792003956564819949"; // 2^255 - 19
+char const num_2_255_m19_x2[] PROGMEM = "115792089237316195423570985008687907853269984665640564039457584007913129639898"; // (2^255 - 19) * 2
+char const num_2_521_m1[] PROGMEM = "6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151"; // 2^521 - 1
+char const num_2_521_m2[] PROGMEM = "6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057150"; // 2^521 - 2
+char const num_2_521_m20[] PROGMEM = "6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057132"; // 2^521 - 20
+char const num_pi_big[] PROGMEM = "3141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117"; // 157 digits of pi
+
+// Table of useful numbers less than 2^521 - 1.
+const char * const numbers[] = {
+    num_0,
+    num_1,
+    num_2,
+    num_4,
+    num_5,
+    num_128,
+    num_256,
+    num_2_64_m7,
+    num_2_129_m5,
+    num_pi,
+    num_2_255_m253,
+    num_2_255_m20,
+    num_2_255_m19_x2,
+    num_pi_big,
+    num_2_521_m2,
+    num_2_521_m20,
+    0
+};
+#define numbers_count   ((sizeof(numbers) / sizeof(numbers[0])) - 1)
+
+#define foreach_number(var) \
+    const char *var = numbers[0]; \
+    for (unsigned index##var = 0; index##var < numbers_count; \
+         ++index##var, var = numbers[index##var])
+
+void printProgMem(const char *str)
+{
+    uint8_t ch;
+    while ((ch = pgm_read_byte((uint8_t *)str)) != '\0') {
+        Serial.print((char)ch);
+        ++str;
+    }
+}
+
+// Simple implementation of modular addition to cross-check the library.
+void simpleAdd(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    uint8_t posn;
+    dlimb_t carry = 0;
+    for (posn = 0; posn < NUM_LIMBS; ++posn) {
+        carry += x[posn];
+        carry += y[posn];
+        result[posn] = (limb_t)carry;
+        carry >>= LIMB_BITS;
+    }
+    if (compare(result, num_2_521_m1) >= 0) {
+        // Subtract 2^521 - 1 to get the final result.
+        // Same as add 1 and then subtract 2^521.
+        carry = 1;
+        for (posn = 0; posn < NUM_LIMBS; ++posn) {
+            carry += result[posn];
+            result[posn] = (limb_t)carry;
+            carry >>= LIMB_BITS;
+        }
+#if BIGNUMBER_LIMB_8BIT
+        result[NUM_LIMBS - 1] &= 0x01;
+#else
+        result[NUM_LIMBS - 1] &= 0x1FF;
+#endif
+    }
+}
+
+// Simple implementation of subtraction to cross-check the library.
+// Note: this does not reduce the result modulo 2^521 - 1 and we
+// assume that x is greater than or equal to y.
+void simpleSub(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    uint8_t posn;
+    dlimb_t borrow = 0;
+    for (posn = 0; posn < NUM_LIMBS; ++posn) {
+        borrow = ((dlimb_t)x[posn]) - y[posn] - borrow;
+        result[posn] = (limb_t)borrow;
+        borrow = (borrow >> LIMB_BITS) != 0;
+    }
+}
+
+// Simple implementation of multiplication to cross-check the library.
+// Note: this does not reduce the result modulo 2^521 - 1.
+// The "result" buffer must contain at least NUM_LIMBS * 2 limbs.
+void simpleMul(limb_t *result, const limb_t *x, const limb_t *y)
+{
+    memset(result, 0, NUM_LIMBS * 2 * sizeof(limb_t));
+    for (uint8_t i = 0; i < NUM_LIMBS; ++i) {
+        for (uint8_t j = 0; j < NUM_LIMBS; ++j) {
+            uint8_t n = i + j;
+            dlimb_t carry =
+                ((dlimb_t)x[i]) * y[j] + result[n];
+            result[n] = (limb_t)carry;
+            carry >>= LIMB_BITS;
+            ++n;
+            while (carry != 0 && n < (NUM_LIMBS * 2)) {
+                carry += result[n];
+                result[n] = (limb_t)carry;
+                carry >>= LIMB_BITS;
+                ++n;
+            }
+        }
+    }
+}
+
+#if defined(BIGNUMBER_NO_QLIMB)
+
+// Quick check to correct the estimate on a quotient word.
+static inline limb_t correctEstimate
+    (limb_t q, limb_t y1, limb_t y2, dlimb_t x01, limb_t x2)
+{
+    // Algorithm D from section 4.3.1 of "The Art Of Computer Programming",
+    // D. Knuth, Volume 2, "Seminumerical Algorithms", Second Edition, 1981.
+    //
+    // We want to check if (y2 * q) > ((x01 - y1 * q) * b + x2) where
+    // b is (1 << LIMB_BITS).  If it is, then q must be reduced by 1.
+    //
+    // One wrinkle that isn't obvious from Knuth's description is that it
+    // is possible for (x01 - y1 * q) >= b, especially in the case where
+    // x0 = y1 and q = b - 1.  This will cause an overflow of the intermediate
+    // double-word result ((x01 - y1 * q) * b).
+    //
+    // In assembly language, we could use the carry flag to detect when
+    // (x01 - y1 * q) * b overflows, but we can't access the carry flag
+    // in C++.  So we have to account for the carry in a different way here.
+
+    // Calculate the remainder using the estimated quotient.
+    dlimb_t r = x01 - ((dlimb_t)y1) * q;
+
+    // If there will be a double-word carry when we calculate (r * b),
+    // then (y2 * q) is obviously going to be less than (r * b), so we
+    // can stop here.  The estimated quotient is correct.
+    if (r & (((dlimb_t)INVERSE_LIMB) << LIMB_BITS))
+        return q;
+
+    // Bail out if (y2 * q) <= (r * b + x2).  The estimate is correct.
+    dlimb_t y2q = ((dlimb_t)y2) * q;
+    if (y2q <= ((r << LIMB_BITS) + x2))
+        return q;
+
+    // Correct for the estimated quotient being off by 1.
+    --q;
+
+    // Now repeat the check to correct for q values that are off by 2.
+    r += y1;    // r' = (x01 - y1 * (q - 1)) = (x01 - y1 * q + y2) = r + y1
+    if (r & (((dlimb_t)INVERSE_LIMB) << LIMB_BITS))
+        return q;
+    // y2q' = (y2 * (q - 1)) = (y2 * q - y2) = y2q - y2
+    if ((y2q - y2) <= ((r << LIMB_BITS) + x2))
+        return q;
+
+    // Perform the final correction for q values that are off by 2.
+    return q - 1;
+}
+
+#endif
+
+// Shift a big number left by a number of bits.
+void shiftLeft(limb_t *x, size_t size, uint8_t shift)
+{
+    dlimb_t carry = 0;
+    while (size > 0) {
+        carry += ((dlimb_t)(*x)) << shift;
+        *x++ = (limb_t)carry;
+        carry >>= LIMB_BITS;
+        --size;
+    }
+}
+
+// Shift a big number right by a number of bits.
+void shiftRight(limb_t *x, size_t size, uint8_t shift)
+{
+    limb_t carry = 0;
+    limb_t word;
+    while (size > 0) {
+        --size;
+        word = x[size];
+        x[size] = (word >> shift) | carry;
+        carry = (word << (LIMB_BITS - shift));
+    }
+}
+
+// Simple implementation of modular division to cross-check the library.
+// Calling this "simple" is a bit of a misnomer.  It is a full implementation
+// of Algorithm D from section 4.3.1 of "The Art Of Computer Programming",
+// D. Knuth, Volume 2, "Seminumerical Algorithms", Second Edition, 1981.
+// This is quite slow on embedded platforms, but it should be correct.
+// Note: "x" is assumed to be (NUM_LIMBS * 2 + 1) limbs in size because
+// we need a limb for the extra leading zero word added by step D1.
+void simpleMod(limb_t *x)
+{
+    limb_t divisor[NUM_LIMBS];
+    uint8_t j, k, shift;
+
+    // Step D1. Normalize.
+    // The divisor (2^521 - 1) and "x" need to be shifted left until
+    // the top-most bit of the divisor is 1.
+#if BIGNUMBER_LIMB_8BIT
+    shift = 7;
+#else
+    shift = LIMB_BITS - 9;
+#endif
+    fromString(divisor, NUM_LIMBS, num_2_521_m1);
+    shiftLeft(divisor, NUM_LIMBS, shift);
+    x[NUM_LIMBS * 2] = 0;
+    shiftLeft(x, NUM_LIMBS * 2 + 1, shift);
+
+    // Step D2/D7. Loop on j
+    for (j = 0; j <= NUM_LIMBS; ++j) {
+        // Step D3. Calculate an estimate of the top-most quotient word.
+        limb_t *u = x + NUM_LIMBS * 2 - 2 - j;
+        limb_t *v = divisor + NUM_LIMBS - 2;
+        limb_t q;
+        dlimb_t uword = ((((dlimb_t)u[2]) << LIMB_BITS) + u[1]);
+        if (u[2] == v[1])
+            q = ~((limb_t)0);
+        else
+            q = (limb_t)(uword / v[1]);
+
+        // Step D3, part 2.  Correct the estimate downwards by 1 or 2.
+        // One subtlety of Knuth's algorithm is that it looks like the test
+        // is working with double-word quantities but it is actually using
+        // double-word plus a carry bit.  So we need to use qlimb_t for this.
+#if !defined(BIGNUMBER_NO_QLIMB)
+        qlimb_t test = ((((qlimb_t)uword) - ((dlimb_t)q) * v[1]) << LIMB_BITS) + u[0];
+        if ((((dlimb_t)q) * v[0]) > test) {
+            --q;
+            test = ((((qlimb_t)uword) - ((dlimb_t)q) * v[1]) << LIMB_BITS) + u[0];
+            if ((((dlimb_t)q) * v[0]) > test)
+                --q;
+        }
+#else
+        // 32-bit platform - we don't have a 128-bit numeric type so we have
+        // to calculate the estimate in another way to preserve the carry bit.
+        q = correctEstimate(q, v[0], v[1], uword, u[0]);
+#endif
+
+        // Step D4. Multiply and subtract.
+        u = x + (NUM_LIMBS - j);
+        v = divisor;
+        dlimb_t carry = 0;
+        dlimb_t borrow = 0;
+        for (k = 0; k < NUM_LIMBS; ++k) {
+            carry += ((dlimb_t)v[k]) * q;
+            borrow = ((dlimb_t)u[k]) - ((limb_t)carry) - borrow;
+            u[k] = (dlimb_t)borrow;
+            carry >>= LIMB_BITS;
+            borrow = ((borrow >> LIMB_BITS) != 0);
+        }
+        borrow = ((dlimb_t)u[k]) - ((limb_t)carry) - borrow;
+        u[k] = (dlimb_t)borrow;
+
+        // Step D5. Test remainder.  Nothing further to do if no borrow.
+        if ((borrow >> LIMB_BITS) == 0)
+            continue;
+
+        // Step D6. Borrow occurred: add back.
+        carry = 0;
+        for (k = 0; k < NUM_LIMBS; ++k) {
+            carry += u[k];
+            carry += v[k];
+            u[k] = (limb_t)carry;
+            carry >>= LIMB_BITS;
+        }
+        u[k] += (limb_t)carry;
+    }
+
+    // Step D8. Unnormalize.
+    // Shift the remainder right to undo the earlier left shift.
+    shiftRight(x, NUM_LIMBS, shift);
+}
+
+void testAdd(const char *x, const char *y)
+{
+    printProgMem(x);
+    Serial.print(" + ");
+    printProgMem(y);
+    Serial.print(": ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    fromString(arg2, NUM_LIMBS, y);
+    P521::add(result, arg1, arg2);
+
+    simpleAdd(result2, arg1, arg2);
+
+    if (compare(result, result2) == 0) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result);
+        printNumber("expected", result2);
+    }
+}
+
+void testAdd()
+{
+    Serial.println("Addition:");
+    foreach_number (x) {
+        foreach_number (y) {
+            testAdd(x, y);
+        }
+    }
+    Serial.println();
+}
+
+void testSub(const char *x, const char *y)
+{
+    printProgMem(x);
+    Serial.print(" - ");
+    printProgMem(y);
+    Serial.print(": ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    fromString(arg2, NUM_LIMBS, y);
+    P521::sub(result, arg1, arg2);
+
+    if (compare(arg1, arg2) >= 0) {
+        // First argument is larger than the second.
+        simpleSub(result2, arg1, arg2);
+    } else {
+        // First argument is smaller than the second.
+        // Compute arg1 + (2^521 - 1 - arg2).
+        fromString(temp, NUM_LIMBS, num_2_521_m1);
+        simpleSub(result2, temp, arg2);
+        simpleAdd(result2, arg1, result2);
+    }
+
+    if (compare(result, result2) == 0) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result);
+        printNumber("expected", result2);
+    }
+}
+
+void testSub()
+{
+    Serial.println("Subtraction:");
+    foreach_number (x) {
+        foreach_number (y) {
+            testSub(x, y);
+        }
+    }
+    Serial.println();
+}
+
+void testMul(const char *x, const char *y)
+{
+    printProgMem(x);
+    Serial.print(" * ");
+    printProgMem(y);
+    Serial.print(": ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    fromString(arg2, NUM_LIMBS, y);
+
+    if (compare(arg1, arg2) != 0)
+        P521::mul(result, arg1, arg2);
+    else
+        P521::square(result, arg1);
+
+    simpleMul(result2, arg1, arg2);
+    simpleMod(result2);
+
+    if (compare(result, result2) == 0) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual  ", result);
+        printNumber("expected", result2);
+    }
+}
+
+void testMul()
+{
+    Serial.println("Multiplication:");
+    foreach_number (x) {
+        foreach_number (y) {
+            testMul(x, y);
+        }
+    }
+    Serial.println();
+}
+
+void testMove(const char *x, const char *y, uint8_t select)
+{
+    printProgMem(x);
+    Serial.print(" <- ");
+    printProgMem(y);
+    Serial.print(": ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    fromString(arg2, NUM_LIMBS, y);
+
+    memcpy(result, arg1, NUM_LIMBS * sizeof(limb_t));
+    memcpy(result2, arg2, NUM_LIMBS * sizeof(limb_t));
+
+    // Perform the move using the selection bit.
+    P521::cmove(select, result, result2);
+    bool ok = compare(result, arg2) == 0;
+
+    // Reset and test not moving.
+    memcpy(result, arg1, NUM_LIMBS * sizeof(limb_t));
+    memcpy(result2, arg2, NUM_LIMBS * sizeof(limb_t));
+    P521::cmove(0, result, result2);
+    if (ok)
+        ok = compare(result, arg1) == 0;
+
+    if (ok) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+    }
+}
+
+void testMove()
+{
+    Serial.println("Move:");
+    uint8_t bit = 0;
+    foreach_number (x) {
+        foreach_number (y) {
+            testMove(x, y, ((uint8_t)1) << bit);
+            bit = (bit + 1) % 8;
+        }
+    }
+    Serial.println();
+}
+
+void testRecip(const char *x)
+{
+    printProgMem(x);
+    Serial.print("^-1");
+    Serial.print(": ");
+    Serial.flush();
+
+    fromString(arg1, NUM_LIMBS, x);
+    P521::recip(result, arg1);
+
+    bool ok;
+    if (compare(arg1, num_0) == 0) {
+        // 0^-1 = 0
+        ok = (compare(result, num_0) == 0);
+    } else {
+        // Multiply the result with arg1 - we expect 1 as the result.
+        P521::mul(result2, result, arg1);
+        ok = (compare(result2, num_1) == 0);
+    }
+
+    if (ok) {
+        Serial.println("ok");
+    } else {
+        Serial.println("failed");
+        printNumber("actual", result);
+    }
+}
+
+void testRecip()
+{
+    Serial.println("Reciprocal:");
+    foreach_number (x) {
+        testRecip(x);
+    }
+    Serial.println();
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    testAdd();
+    testSub();
+    testMul();
+    testMove();
+    testRecip();
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestPoly1305/TestPoly1305.ino b/src/modules/Crypto/examples/TestPoly1305/TestPoly1305.ino
new file mode 100644
index 0000000..55660e9
--- /dev/null
+++ b/src/modules/Crypto/examples/TestPoly1305/TestPoly1305.ino
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the Poly1305 implementation to verify correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <Poly1305.h>
+#include <string.h>
+
+// Test vectors from the Poly1305 specification.
+
+struct TestPoly1305Vector
+{
+    const char *name;
+    uint8_t key[16];
+    uint8_t nonce[16];
+    uint8_t data[64];
+    size_t dataLen;
+    uint8_t hash[16];
+};
+
+static TestPoly1305Vector const testVectorPoly1305_1 = {
+    .name     = "Poly1305 #1",
+    .key      = {0x85, 0x1f, 0xc4, 0x0c, 0x34, 0x67, 0xac, 0x0b,
+                 0xe0, 0x5c, 0xc2, 0x04, 0x04, 0xf3, 0xf7, 0x00},
+    .nonce    = {0x58, 0x0b, 0x3b, 0x0f, 0x94, 0x47, 0xbb, 0x1e,
+                 0x69, 0xd0, 0x95, 0xb5, 0x92, 0x8b, 0x6d, 0xbc},
+    .data     = {0xf3, 0xf6},
+    .dataLen  = 2,
+    .hash     = {0xf4, 0xc6, 0x33, 0xc3, 0x04, 0x4f, 0xc1, 0x45,
+                 0xf8, 0x4f, 0x33, 0x5c, 0xb8, 0x19, 0x53, 0xde}
+
+};
+static TestPoly1305Vector const testVectorPoly1305_2 = {
+    .name     = "Poly1305 #2",
+    .key      = {0xa0, 0xf3, 0x08, 0x00, 0x00, 0xf4, 0x64, 0x00,
+                 0xd0, 0xc7, 0xe9, 0x07, 0x6c, 0x83, 0x44, 0x03},
+    .nonce    = {0xdd, 0x3f, 0xab, 0x22, 0x51, 0xf1, 0x1a, 0xc7,
+                 0x59, 0xf0, 0x88, 0x71, 0x29, 0xcc, 0x2e, 0xe7},
+    .data     = {0},
+    .dataLen  = 0,
+    .hash     = {0xdd, 0x3f, 0xab, 0x22, 0x51, 0xf1, 0x1a, 0xc7,
+                 0x59, 0xf0, 0x88, 0x71, 0x29, 0xcc, 0x2e, 0xe7}
+};
+static TestPoly1305Vector const testVectorPoly1305_3 = {
+    .name     = "Poly1305 #3",
+    .key      = {0x48, 0x44, 0x3d, 0x0b, 0xb0, 0xd2, 0x11, 0x09,
+                 0xc8, 0x9a, 0x10, 0x0b, 0x5c, 0xe2, 0xc2, 0x08},
+    .nonce    = {0x83, 0x14, 0x9c, 0x69, 0xb5, 0x61, 0xdd, 0x88,
+                 0x29, 0x8a, 0x17, 0x98, 0xb1, 0x07, 0x16, 0xef},
+    .data     = {0x66, 0x3c, 0xea, 0x19, 0x0f, 0xfb, 0x83, 0xd8,
+                 0x95, 0x93, 0xf3, 0xf4, 0x76, 0xb6, 0xbc, 0x24,
+                 0xd7, 0xe6, 0x79, 0x10, 0x7e, 0xa2, 0x6a, 0xdb,
+                 0x8c, 0xaf, 0x66, 0x52, 0xd0, 0x65, 0x61, 0x36},
+    .dataLen  = 32,
+    .hash     = {0x0e, 0xe1, 0xc1, 0x6b, 0xb7, 0x3f, 0x0f, 0x4f,
+                 0xd1, 0x98, 0x81, 0x75, 0x3c, 0x01, 0xcd, 0xbe}
+};
+static TestPoly1305Vector const testVectorPoly1305_4 = {
+    .name     = "Poly1305 #4",
+    .key      = {0x12, 0x97, 0x6a, 0x08, 0xc4, 0x42, 0x6d, 0x0c,
+                 0xe8, 0xa8, 0x24, 0x07, 0xc4, 0xf4, 0x82, 0x07},
+    .nonce    = {0x80, 0xf8, 0xc2, 0x0a, 0xa7, 0x12, 0x02, 0xd1,
+                 0xe2, 0x91, 0x79, 0xcb, 0xcb, 0x55, 0x5a, 0x57},
+    .data     = {0xab, 0x08, 0x12, 0x72, 0x4a, 0x7f, 0x1e, 0x34,
+                 0x27, 0x42, 0xcb, 0xed, 0x37, 0x4d, 0x94, 0xd1,
+                 0x36, 0xc6, 0xb8, 0x79, 0x5d, 0x45, 0xb3, 0x81,
+                 0x98, 0x30, 0xf2, 0xc0, 0x44, 0x91, 0xfa, 0xf0,
+                 0x99, 0x0c, 0x62, 0xe4, 0x8b, 0x80, 0x18, 0xb2,
+                 0xc3, 0xe4, 0xa0, 0xfa, 0x31, 0x34, 0xcb, 0x67,
+                 0xfa, 0x83, 0xe1, 0x58, 0xc9, 0x94, 0xd9, 0x61,
+                 0xc4, 0xcb, 0x21, 0x09, 0x5c, 0x1b, 0xf9},
+    .dataLen  = 63,
+    .hash     = {0x51, 0x54, 0xad, 0x0d, 0x2c, 0xb2, 0x6e, 0x01,
+                 0x27, 0x4f, 0xc5, 0x11, 0x48, 0x49, 0x1f, 0x1b}
+};
+
+Poly1305 poly1305;
+
+byte buffer[128];
+
+bool testPoly1305_N(Poly1305 *hash, const struct TestPoly1305Vector *test, size_t inc)
+{
+    size_t size = test->dataLen;
+    size_t posn, len;
+
+    hash->reset(test->key);
+
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        hash->update(test->data + posn, len);
+    }
+
+    hash->finalize(test->nonce, buffer, 16);
+
+    return !memcmp(buffer, test->hash, 16);
+}
+
+void testPoly1305(Poly1305 *hash, const struct TestPoly1305Vector *test)
+{
+    bool ok;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    ok  = testPoly1305_N(hash, test, test->dataLen);
+    ok &= testPoly1305_N(hash, test, 1);
+    ok &= testPoly1305_N(hash, test, 2);
+    ok &= testPoly1305_N(hash, test, 5);
+    ok &= testPoly1305_N(hash, test, 8);
+    ok &= testPoly1305_N(hash, test, 13);
+    ok &= testPoly1305_N(hash, test, 16);
+    ok &= testPoly1305_N(hash, test, 24);
+    ok &= testPoly1305_N(hash, test, 63);
+    ok &= testPoly1305_N(hash, test, 64);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfPoly1305(Poly1305 *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Hashing ... ");
+
+    for (size_t posn = 0; posn < sizeof(buffer); ++posn)
+        buffer[posn] = (uint8_t)posn;
+
+    hash->reset(testVectorPoly1305_1.key);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->update(buffer, sizeof(buffer));
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 1000.0));
+    Serial.print("us per byte, ");
+    Serial.print((sizeof(buffer) * 1000.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfPoly1305SetKey(Poly1305 *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Set Key ... ");
+
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->reset(testVectorPoly1305_1.key);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void perfPoly1305Finalize(Poly1305 *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Finalize ... ");
+
+    hash->reset(testVectorPoly1305_1.key);
+    hash->update("abc", 3);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->finalize(testVectorPoly1305_1.nonce, buffer, 16);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.print("State Size ...");
+    Serial.println(sizeof(Poly1305));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testPoly1305(&poly1305, &testVectorPoly1305_1);
+    testPoly1305(&poly1305, &testVectorPoly1305_2);
+    testPoly1305(&poly1305, &testVectorPoly1305_3);
+    testPoly1305(&poly1305, &testVectorPoly1305_4);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfPoly1305(&poly1305);
+    perfPoly1305SetKey(&poly1305);
+    perfPoly1305Finalize(&poly1305);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestRNG/TestRNG.ino b/src/modules/Crypto/examples/TestRNG/TestRNG.ino
new file mode 100644
index 0000000..196cf4d
--- /dev/null
+++ b/src/modules/Crypto/examples/TestRNG/TestRNG.ino
@@ -0,0 +1,72 @@
+
+// Example of initializing and using the random number generator.
+
+#include <Crypto.h>
+#include <RNG.h>
+#include <TransistorNoiseSource.h>
+//#include <RingOscillatorNoiseSource.h>
+
+// Change "MyApp 1.0" to some other tag for your application
+// so that different applications will generate different results
+// even if the input noise or seed data is otherwise identical.
+#define RNG_APP_TAG "MyApp 1.0"
+
+// Noise source to seed the random number generator.
+TransistorNoiseSource noise(A1);
+//RingOscillatorNoiseSource noise;
+
+bool calibrating = false;
+byte data[32];
+unsigned long startTime;
+size_t length = 48; // First block should wait for the pool to fill up.
+
+void setup() {
+    Serial.begin(9600);
+    Serial.println("start");
+
+    // Initialize the random number generator.
+    RNG.begin(RNG_APP_TAG);
+
+    // Add the noise source to the list of sources known to RNG.
+    RNG.addNoiseSource(noise);
+
+    startTime = millis();
+}
+
+void printHex(const byte *data, unsigned len)
+{
+    static char const hexchars[] = "0123456789ABCDEF";
+    unsigned long time = millis() - startTime;
+    Serial.print(time / 1000);
+    Serial.print('.');
+    Serial.print((time / 100) % 10);
+    Serial.print(": ");
+    while (len > 0) {
+        int b = *data++;
+        Serial.print(hexchars[(b >> 4) & 0x0F]);
+        Serial.print(hexchars[b & 0x0F]);
+        --len;
+    }
+    Serial.println();
+}
+
+void loop() {
+    // Track changes to the calibration state on the noise source.
+    bool newCalibrating = noise.calibrating();
+    if (newCalibrating != calibrating) {
+        calibrating = newCalibrating;
+        if (calibrating)
+            Serial.println("calibrating");
+    }
+
+    // Perform regular housekeeping on the random number generator.
+    RNG.loop();
+
+    // Generate output whenever 32 bytes of entropy have been accumulated.
+    // The first time through, we wait for 48 bytes for a full entropy pool.
+    if (RNG.available(length)) {
+        RNG.rand(data, sizeof(data));
+        printHex(data, sizeof(data));
+        length = 32;
+    }
+}
diff --git a/src/modules/Crypto/examples/TestSHA256/TestSHA256.ino b/src/modules/Crypto/examples/TestSHA256/TestSHA256.ino
new file mode 100644
index 0000000..87f78cd
--- /dev/null
+++ b/src/modules/Crypto/examples/TestSHA256/TestSHA256.ino
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the SHA256 implementation to verify correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <SHA256.h>
+#include <string.h>
+
+#define HASH_SIZE 32
+#define BLOCK_SIZE 64
+
+struct TestHashVector
+{
+    const char *name;
+    const char *key;
+    const char *data;
+    uint8_t hash[HASH_SIZE];
+};
+
+static TestHashVector const testVectorSHA256_1 = {
+    "SHA-256 #1",
+    0,
+    "abc",
+    {0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
+     0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
+     0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
+     0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}
+};
+static TestHashVector const testVectorSHA256_2 = {
+    "SHA-256 #2",
+    0,
+    "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+    {0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8,
+     0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39,
+     0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67,
+     0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1}
+};
+static TestHashVector const testVectorHMAC_SHA256_1 = {
+    "HMAC-SHA-256 #1",
+    "",
+    "",
+    {0xb6, 0x13, 0x67, 0x9a, 0x08, 0x14, 0xd9, 0xec,
+     0x77, 0x2f, 0x95, 0xd7, 0x78, 0xc3, 0x5f, 0xc5,
+     0xff, 0x16, 0x97, 0xc4, 0x93, 0x71, 0x56, 0x53,
+     0xc6, 0xc7, 0x12, 0x14, 0x42, 0x92, 0xc5, 0xad}
+};
+static TestHashVector const testVectorHMAC_SHA256_2 = {
+    "HMAC-SHA-256 #2",
+    "key",
+    "The quick brown fox jumps over the lazy dog",
+    {0xf7, 0xbc, 0x83, 0xf4, 0x30, 0x53, 0x84, 0x24,
+     0xb1, 0x32, 0x98, 0xe6, 0xaa, 0x6f, 0xb1, 0x43,
+     0xef, 0x4d, 0x59, 0xa1, 0x49, 0x46, 0x17, 0x59,
+     0x97, 0x47, 0x9d, 0xbc, 0x2d, 0x1a, 0x3c, 0xd8}
+};
+
+SHA256 sha256;
+
+byte buffer[128];
+
+bool testHash_N(Hash *hash, const struct TestHashVector *test, size_t inc)
+{
+    size_t size = strlen(test->data);
+    size_t posn, len;
+    uint8_t value[HASH_SIZE];
+
+    hash->reset();
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        hash->update(test->data + posn, len);
+    }
+    hash->finalize(value, sizeof(value));
+    if (memcmp(value, test->hash, sizeof(value)) != 0)
+        return false;
+
+    return true;
+}
+
+void testHash(Hash *hash, const struct TestHashVector *test)
+{
+    bool ok;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    ok  = testHash_N(hash, test, strlen(test->data));
+    ok &= testHash_N(hash, test, 1);
+    ok &= testHash_N(hash, test, 2);
+    ok &= testHash_N(hash, test, 5);
+    ok &= testHash_N(hash, test, 8);
+    ok &= testHash_N(hash, test, 13);
+    ok &= testHash_N(hash, test, 16);
+    ok &= testHash_N(hash, test, 24);
+    ok &= testHash_N(hash, test, 63);
+    ok &= testHash_N(hash, test, 64);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+// Very simple method for hashing a HMAC inner or outer key.
+void hashKey(Hash *hash, const uint8_t *key, size_t keyLen, uint8_t pad)
+{
+    size_t posn;
+    uint8_t buf;
+    uint8_t result[HASH_SIZE];
+    if (keyLen <= BLOCK_SIZE) {
+        hash->reset();
+        for (posn = 0; posn < BLOCK_SIZE; ++posn) {
+            if (posn < keyLen)
+                buf = key[posn] ^ pad;
+            else
+                buf = pad;
+            hash->update(&buf, 1);
+        }
+    } else {
+        hash->reset();
+        hash->update(key, keyLen);
+        hash->finalize(result, HASH_SIZE);
+        hash->reset();
+        for (posn = 0; posn < BLOCK_SIZE; ++posn) {
+            if (posn < HASH_SIZE)
+                buf = result[posn] ^ pad;
+            else
+                buf = pad;
+            hash->update(&buf, 1);
+        }
+    }
+}
+
+void testHMAC(Hash *hash, size_t keyLen)
+{
+    uint8_t result[HASH_SIZE];
+
+    Serial.print("HMAC-SHA-256 keysize=");
+    Serial.print(keyLen);
+    Serial.print(" ... ");
+
+    // Construct the expected result with a simple HMAC implementation.
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hashKey(hash, buffer, keyLen, 0x36);
+    memset(buffer, 0xBA, sizeof(buffer));
+    hash->update(buffer, sizeof(buffer));
+    hash->finalize(result, HASH_SIZE);
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hashKey(hash, buffer, keyLen, 0x5C);
+    hash->update(result, HASH_SIZE);
+    hash->finalize(result, HASH_SIZE);
+
+    // Now use the library to compute the HMAC.
+    hash->resetHMAC(buffer, keyLen);
+    memset(buffer, 0xBA, sizeof(buffer));
+    hash->update(buffer, sizeof(buffer));
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hash->finalizeHMAC(buffer, keyLen, buffer, HASH_SIZE);
+
+    // Check the result.
+    if (!memcmp(result, buffer, HASH_SIZE))
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void testHMAC(Hash *hash, const struct TestHashVector *test)
+{
+    uint8_t result[HASH_SIZE];
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    hash->resetHMAC(test->key, strlen(test->key));
+    hash->update(test->data, strlen(test->data));
+    hash->finalizeHMAC(test->key, strlen(test->key), result, sizeof(result));
+
+    if (!memcmp(result, test->hash, HASH_SIZE))
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfHash(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Hashing ... ");
+
+    for (size_t posn = 0; posn < sizeof(buffer); ++posn)
+        buffer[posn] = (uint8_t)posn;
+
+    hash->reset();
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        hash->update(buffer, sizeof(buffer));
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((sizeof(buffer) * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfFinalize(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Finalizing ... ");
+
+    hash->reset();
+    hash->update("abc", 3);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->finalize(buffer, hash->hashSize());
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void perfHMAC(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("HMAC Reset ... ");
+
+    for (size_t posn = 0; posn < sizeof(buffer); ++posn)
+        buffer[posn] = (uint8_t)posn;
+
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->resetHMAC(buffer, hash->hashSize());
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+
+    Serial.print("HMAC Finalize ... ");
+
+    hash->resetHMAC(buffer, hash->hashSize());
+    hash->update("abc", 3);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->finalizeHMAC(buffer, hash->hashSize(), buffer, hash->hashSize());
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.print("State Size ...");
+    Serial.println(sizeof(SHA256));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testHash(&sha256, &testVectorSHA256_1);
+    testHash(&sha256, &testVectorSHA256_2);
+    testHMAC(&sha256, &testVectorHMAC_SHA256_1);
+    testHMAC(&sha256, &testVectorHMAC_SHA256_2);
+    testHMAC(&sha256, (size_t)0);
+    testHMAC(&sha256, 1);
+    testHMAC(&sha256, HASH_SIZE);
+    testHMAC(&sha256, BLOCK_SIZE);
+    testHMAC(&sha256, BLOCK_SIZE + 1);
+    testHMAC(&sha256, sizeof(buffer));
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfHash(&sha256);
+    perfFinalize(&sha256);
+    perfHMAC(&sha256);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestSHA3_256/TestSHA3_256.ino b/src/modules/Crypto/examples/TestSHA3_256/TestSHA3_256.ino
new file mode 100644
index 0000000..14159d7
--- /dev/null
+++ b/src/modules/Crypto/examples/TestSHA3_256/TestSHA3_256.ino
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the SHA3_256 implementation to verify
+correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <SHA3.h>
+#include <string.h>
+
+#define DATA_SIZE 136
+#define HASH_SIZE 32
+#define BLOCK_SIZE 136
+
+struct TestHashVector
+{
+    const char *name;
+    uint8_t data[DATA_SIZE];
+    uint8_t dataSize;
+    uint8_t hash[HASH_SIZE];
+};
+
+// Some test vectors from https://github.com/gvanas/KeccakCodePackage
+static TestHashVector const testVectorSHA3_256_1 = {
+    "SHA3-256 #1",
+    {0},
+    0,
+    {0xA7, 0xFF, 0xC6, 0xF8, 0xBF, 0x1E, 0xD7, 0x66,
+     0x51, 0xC1, 0x47, 0x56, 0xA0, 0x61, 0xD6, 0x62,
+     0xF5, 0x80, 0xFF, 0x4D, 0xE4, 0x3B, 0x49, 0xFA,
+     0x82, 0xD8, 0x0A, 0x4B, 0x80, 0xF8, 0x43, 0x4A}
+};
+static TestHashVector const testVectorSHA3_256_2 = {
+    "SHA3-256 #2",
+    {0x1F, 0x87, 0x7C},
+    3,
+    {0xBC, 0x22, 0x34, 0x5E, 0x4B, 0xD3, 0xF7, 0x92,
+     0xA3, 0x41, 0xCF, 0x18, 0xAC, 0x07, 0x89, 0xF1,
+     0xC9, 0xC9, 0x66, 0x71, 0x2A, 0x50, 0x1B, 0x19,
+     0xD1, 0xB6, 0x63, 0x2C, 0xCD, 0x40, 0x8E, 0xC5}
+};
+static TestHashVector const testVectorSHA3_256_3 = {
+    "SHA3-256 #3",
+    {0xE2, 0x61, 0x93, 0x98, 0x9D, 0x06, 0x56, 0x8F,
+     0xE6, 0x88, 0xE7, 0x55, 0x40, 0xAE, 0xA0, 0x67,
+     0x47, 0xD9, 0xF8, 0x51},
+    20,
+    {0x2C, 0x1E, 0x61, 0xE5, 0xD4, 0x52, 0x03, 0xF2,
+     0x7B, 0x86, 0xF1, 0x29, 0x3A, 0x80, 0xBA, 0xB3,
+     0x41, 0x92, 0xDA, 0xF4, 0x2B, 0x86, 0x23, 0xB1,
+     0x20, 0x05, 0xB2, 0xFB, 0x1C, 0x18, 0xAC, 0xB1}
+};
+static TestHashVector const testVectorSHA3_256_4 = {
+    "SHA3-256 #4",
+    {0xB7, 0x71, 0xD5, 0xCE, 0xF5, 0xD1, 0xA4, 0x1A,
+     0x93, 0xD1, 0x56, 0x43, 0xD7, 0x18, 0x1D, 0x2A,
+     0x2E, 0xF0, 0xA8, 0xE8, 0x4D, 0x91, 0x81, 0x2F,
+     0x20, 0xED, 0x21, 0xF1, 0x47, 0xBE, 0xF7, 0x32,
+     0xBF, 0x3A, 0x60, 0xEF, 0x40, 0x67, 0xC3, 0x73,
+     0x4B, 0x85, 0xBC, 0x8C, 0xD4, 0x71, 0x78, 0x0F,
+     0x10, 0xDC, 0x9E, 0x82, 0x91, 0xB5, 0x83, 0x39,
+     0xA6, 0x77, 0xB9, 0x60, 0x21, 0x8F, 0x71, 0xE7,
+     0x93, 0xF2, 0x79, 0x7A, 0xEA, 0x34, 0x94, 0x06,
+     0x51, 0x28, 0x29, 0x06, 0x5D, 0x37, 0xBB, 0x55,
+     0xEA, 0x79, 0x6F, 0xA4, 0xF5, 0x6F, 0xD8, 0x89,
+     0x6B, 0x49, 0xB2, 0xCD, 0x19, 0xB4, 0x32, 0x15,
+     0xAD, 0x96, 0x7C, 0x71, 0x2B, 0x24, 0xE5, 0x03,
+     0x2D, 0x06, 0x52, 0x32, 0xE0, 0x2C, 0x12, 0x74,
+     0x09, 0xD2, 0xED, 0x41, 0x46, 0xB9, 0xD7, 0x5D,
+     0x76, 0x3D, 0x52, 0xDB, 0x98, 0xD9, 0x49, 0xD3,
+     0xB0, 0xFE, 0xD6, 0xA8, 0x05, 0x2F, 0xBB},
+    135,
+    {0xA1, 0x9E, 0xEE, 0x92, 0xBB, 0x20, 0x97, 0xB6,
+     0x4E, 0x82, 0x3D, 0x59, 0x77, 0x98, 0xAA, 0x18,
+     0xBE, 0x9B, 0x7C, 0x73, 0x6B, 0x80, 0x59, 0xAB,
+     0xFD, 0x67, 0x79, 0xAC, 0x35, 0xAC, 0x81, 0xB5}
+};
+static TestHashVector testVectorSHA3_256_5 = {
+    "SHA3-256 #5",
+    {0xB3, 0x2D, 0x95, 0xB0, 0xB9, 0xAA, 0xD2, 0xA8,
+     0x81, 0x6D, 0xE6, 0xD0, 0x6D, 0x1F, 0x86, 0x00,
+     0x85, 0x05, 0xBD, 0x8C, 0x14, 0x12, 0x4F, 0x6E,
+     0x9A, 0x16, 0x3B, 0x5A, 0x2A, 0xDE, 0x55, 0xF8,
+     0x35, 0xD0, 0xEC, 0x38, 0x80, 0xEF, 0x50, 0x70,
+     0x0D, 0x3B, 0x25, 0xE4, 0x2C, 0xC0, 0xAF, 0x05,
+     0x0C, 0xCD, 0x1B, 0xE5, 0xE5, 0x55, 0xB2, 0x30,
+     0x87, 0xE0, 0x4D, 0x7B, 0xF9, 0x81, 0x36, 0x22,
+     0x78, 0x0C, 0x73, 0x13, 0xA1, 0x95, 0x4F, 0x87,
+     0x40, 0xB6, 0xEE, 0x2D, 0x3F, 0x71, 0xF7, 0x68,
+     0xDD, 0x41, 0x7F, 0x52, 0x04, 0x82, 0xBD, 0x3A,
+     0x08, 0xD4, 0xF2, 0x22, 0xB4, 0xEE, 0x9D, 0xBD,
+     0x01, 0x54, 0x47, 0xB3, 0x35, 0x07, 0xDD, 0x50,
+     0xF3, 0xAB, 0x42, 0x47, 0xC5, 0xDE, 0x9A, 0x8A,
+     0xBD, 0x62, 0xA8, 0xDE, 0xCE, 0xA0, 0x1E, 0x3B,
+     0x87, 0xC8, 0xB9, 0x27, 0xF5, 0xB0, 0x8B, 0xEB,
+     0x37, 0x67, 0x4C, 0x6F, 0x8E, 0x38, 0x0C, 0x04},
+    136,
+    {0xDF, 0x67, 0x3F, 0x41, 0x05, 0x37, 0x9F, 0xF6, 
+     0xB7, 0x55, 0xEE, 0xAB, 0x20, 0xCE, 0xB0, 0xDC,
+     0x77, 0xB5, 0x28, 0x63, 0x64, 0xFE, 0x16, 0xC5,
+     0x9C, 0xC8, 0xA9, 0x07, 0xAF, 0xF0, 0x77, 0x32}
+};
+
+SHA3_256 sha3_256;
+
+bool testHash_N(Hash *hash, const struct TestHashVector *test, size_t inc)
+{
+    size_t size = test->dataSize;
+    size_t posn, len;
+    uint8_t value[HASH_SIZE];
+
+    hash->reset();
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        hash->update(test->data + posn, len);
+    }
+    hash->finalize(value, sizeof(value));
+    if (memcmp(value, test->hash, sizeof(value)) != 0)
+        return false;
+
+    return true;
+}
+
+void testHash(Hash *hash, const struct TestHashVector *test)
+{
+    bool ok;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    ok  = testHash_N(hash, test, test->dataSize);
+    ok &= testHash_N(hash, test, 1);
+    ok &= testHash_N(hash, test, 2);
+    ok &= testHash_N(hash, test, 5);
+    ok &= testHash_N(hash, test, 8);
+    ok &= testHash_N(hash, test, 13);
+    ok &= testHash_N(hash, test, 16);
+    ok &= testHash_N(hash, test, 24);
+    ok &= testHash_N(hash, test, 63);
+    ok &= testHash_N(hash, test, 64);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfHash(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+    // Reuse one of the test vectors as a large temporary buffer.
+    uint8_t *buffer = (uint8_t *)&testVectorSHA3_256_5;
+
+    Serial.print("Hashing ... ");
+
+    for (size_t posn = 0; posn < 128; ++posn)
+        buffer[posn] = (uint8_t)posn;
+
+    hash->reset();
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        hash->update(buffer, 128);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (128 * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((128 * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+// Very simple method for hashing a HMAC inner or outer key.
+void hashKey(Hash *hash, const uint8_t *key, size_t keyLen, uint8_t pad)
+{
+    size_t posn;
+    uint8_t buf;
+    uint8_t result[HASH_SIZE];
+    if (keyLen <= BLOCK_SIZE) {
+        hash->reset();
+        for (posn = 0; posn < BLOCK_SIZE; ++posn) {
+            if (posn < keyLen)
+                buf = key[posn] ^ pad;
+            else
+                buf = pad;
+            hash->update(&buf, 1);
+        }
+    } else {
+        hash->reset();
+        hash->update(key, keyLen);
+        hash->finalize(result, HASH_SIZE);
+        hash->reset();
+        for (posn = 0; posn < BLOCK_SIZE; ++posn) {
+            if (posn < HASH_SIZE)
+                buf = result[posn] ^ pad;
+            else
+                buf = pad;
+            hash->update(&buf, 1);
+        }
+    }
+}
+
+void testHMAC(Hash *hash, size_t keyLen)
+{
+    uint8_t result[HASH_SIZE];
+    // Reuse one of the test vectors as a large temporary buffer.
+    uint8_t *buffer = (uint8_t *)&testVectorSHA3_256_5;
+
+    Serial.print("HMAC-SHA3-256 keysize=");
+    Serial.print(keyLen);
+    Serial.print(" ... ");
+
+    // Construct the expected result with a simple HMAC implementation.
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hashKey(hash, buffer, keyLen, 0x36);
+    memset(buffer, 0xBA, sizeof(testVectorSHA3_256_5));
+    hash->update(buffer, sizeof(testVectorSHA3_256_5));
+    hash->finalize(result, HASH_SIZE);
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hashKey(hash, buffer, keyLen, 0x5C);
+    hash->update(result, HASH_SIZE);
+    hash->finalize(result, HASH_SIZE);
+
+    // Now use the library to compute the HMAC.
+    hash->resetHMAC(buffer, keyLen);
+    memset(buffer, 0xBA, sizeof(testVectorSHA3_256_5));
+    hash->update(buffer, sizeof(testVectorSHA3_256_5));
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hash->finalizeHMAC(buffer, keyLen, buffer, HASH_SIZE);
+
+    // Check the result.
+    if (!memcmp(result, buffer, HASH_SIZE))
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfFinalize(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+    // Reuse one of the test vectors as a large temporary buffer.
+    uint8_t *buffer = (uint8_t *)&testVectorSHA3_256_5;
+
+    Serial.print("Finalizing ... ");
+
+    hash->reset();
+    hash->update("abc", 3);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->finalize(buffer, hash->hashSize());
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.print("State Size ...");
+    Serial.println(sizeof(SHA3_256));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testHash(&sha3_256, &testVectorSHA3_256_1);
+    testHash(&sha3_256, &testVectorSHA3_256_2);
+    testHash(&sha3_256, &testVectorSHA3_256_3);
+    testHash(&sha3_256, &testVectorSHA3_256_4);
+    testHash(&sha3_256, &testVectorSHA3_256_5);
+    testHMAC(&sha3_256, (size_t)0);
+    testHMAC(&sha3_256, 1);
+    testHMAC(&sha3_256, HASH_SIZE);
+    testHMAC(&sha3_256, BLOCK_SIZE);
+    testHMAC(&sha3_256, BLOCK_SIZE + 1);
+    testHMAC(&sha3_256, BLOCK_SIZE + 2);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfHash(&sha3_256);
+    perfFinalize(&sha3_256);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestSHA3_512/TestSHA3_512.ino b/src/modules/Crypto/examples/TestSHA3_512/TestSHA3_512.ino
new file mode 100644
index 0000000..85dddf6
--- /dev/null
+++ b/src/modules/Crypto/examples/TestSHA3_512/TestSHA3_512.ino
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the SHA3_512 implementation to verify
+correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <SHA3.h>
+#include <string.h>
+
+#define DATA_SIZE 72
+#define HASH_SIZE 64
+#define BLOCK_SIZE 72
+
+struct TestHashVector
+{
+    const char *name;
+    uint8_t data[DATA_SIZE];
+    uint8_t dataSize;
+    uint8_t hash[HASH_SIZE];
+};
+
+// Some test vectors from https://github.com/gvanas/KeccakCodePackage
+static TestHashVector const testVectorSHA3_512_1 = {
+    "SHA3-512 #1",
+    {0},
+    0,
+    {0xA6, 0x9F, 0x73, 0xCC, 0xA2, 0x3A, 0x9A, 0xC5,
+     0xC8, 0xB5, 0x67, 0xDC, 0x18, 0x5A, 0x75, 0x6E,
+     0x97, 0xC9, 0x82, 0x16, 0x4F, 0xE2, 0x58, 0x59,
+     0xE0, 0xD1, 0xDC, 0xC1, 0x47, 0x5C, 0x80, 0xA6,
+     0x15, 0xB2, 0x12, 0x3A, 0xF1, 0xF5, 0xF9, 0x4C,
+     0x11, 0xE3, 0xE9, 0x40, 0x2C, 0x3A, 0xC5, 0x58,
+     0xF5, 0x00, 0x19, 0x9D, 0x95, 0xB6, 0xD3, 0xE3,
+     0x01, 0x75, 0x85, 0x86, 0x28, 0x1D, 0xCD, 0x26}
+};
+static TestHashVector const testVectorSHA3_512_2 = {
+    "SHA3-512 #2",
+    {0x1F, 0x87, 0x7C},
+    3,
+    {0xCB, 0x20, 0xDC, 0xF5, 0x49, 0x55, 0xF8, 0x09,
+     0x11, 0x11, 0x68, 0x8B, 0xEC, 0xCE, 0xF4, 0x8C,
+     0x1A, 0x2F, 0x0D, 0x06, 0x08, 0xC3, 0xA5, 0x75,
+     0x16, 0x37, 0x51, 0xF0, 0x02, 0xDB, 0x30, 0xF4,
+     0x0F, 0x2F, 0x67, 0x18, 0x34, 0xB2, 0x2D, 0x20,
+     0x85, 0x91, 0xCF, 0xAF, 0x1F, 0x5E, 0xCF, 0xE4,
+     0x3C, 0x49, 0x86, 0x3A, 0x53, 0xB3, 0x22, 0x5B,
+     0xDF, 0xD7, 0xC6, 0x59, 0x1B, 0xA7, 0x65, 0x8B}
+};
+static TestHashVector const testVectorSHA3_512_3 = {
+    "SHA3-512 #3",
+    {0xE2, 0x61, 0x93, 0x98, 0x9D, 0x06, 0x56, 0x8F,
+     0xE6, 0x88, 0xE7, 0x55, 0x40, 0xAE, 0xA0, 0x67,
+     0x47, 0xD9, 0xF8, 0x51},
+    20,
+    {0x19, 0x1C, 0xEF, 0x1C, 0x6A, 0xA0, 0x09, 0xB1,
+     0xAB, 0xA6, 0x74, 0xBE, 0x2B, 0x3F, 0x0D, 0xA4,
+     0x18, 0xFD, 0xF9, 0xE6, 0xA7, 0xEC, 0xF2, 0xBE,
+     0x42, 0xAC, 0x14, 0xF7, 0xD6, 0xE0, 0x73, 0x31,
+     0x42, 0x51, 0x33, 0xA8, 0x3B, 0x4E, 0x01, 0x61,
+     0xCC, 0x7D, 0xEB, 0xF9, 0xDC, 0xD7, 0xFE, 0x37,
+     0x87, 0xDC, 0xB6, 0x62, 0x2A, 0x38, 0x47, 0x51,
+     0x89, 0xED, 0xFE, 0x1D, 0xE6, 0xB0, 0x53, 0xD6}
+};
+static TestHashVector const testVectorSHA3_512_4 = {
+    "SHA3-512 #4",
+    {0x13, 0xBD, 0x28, 0x11, 0xF6, 0xED, 0x2B, 0x6F,
+     0x04, 0xFF, 0x38, 0x95, 0xAC, 0xEE, 0xD7, 0xBE,
+     0xF8, 0xDC, 0xD4, 0x5E, 0xB1, 0x21, 0x79, 0x1B,
+     0xC1, 0x94, 0xA0, 0xF8, 0x06, 0x20, 0x6B, 0xFF,
+     0xC3, 0xB9, 0x28, 0x1C, 0x2B, 0x30, 0x8B, 0x1A,
+     0x72, 0x9C, 0xE0, 0x08, 0x11, 0x9D, 0xD3, 0x06,
+     0x6E, 0x93, 0x78, 0xAC, 0xDC, 0xC5, 0x0A, 0x98,
+     0xA8, 0x2E, 0x20, 0x73, 0x88, 0x00, 0xB6, 0xCD,
+     0xDB, 0xE5, 0xFE, 0x96, 0x94, 0xAD, 0x6D},
+    71,
+    {0xDE, 0xF4, 0xAB, 0x6C, 0xDA, 0x88, 0x39, 0x72,
+     0x9A, 0x03, 0xE0, 0x00, 0x84, 0x66, 0x04, 0xB1,
+     0x7F, 0x03, 0xC5, 0xD5, 0xD7, 0xEC, 0x23, 0xC4,
+     0x83, 0x67, 0x0A, 0x13, 0xE1, 0x15, 0x73, 0xC1,
+     0xE9, 0x34, 0x7A, 0x63, 0xEC, 0x69, 0xA5, 0xAB,
+     0xB2, 0x13, 0x05, 0xF9, 0x38, 0x2E, 0xCD, 0xAA,
+     0xAB, 0xC6, 0x85, 0x0F, 0x92, 0x84, 0x0E, 0x86,
+     0xF8, 0x8F, 0x4D, 0xAB, 0xFC, 0xD9, 0x3C, 0xC0}
+};
+static TestHashVector const testVectorSHA3_512_5 = {
+    "SHA3-512 #5",
+    {0x1E, 0xED, 0x9C, 0xBA, 0x17, 0x9A, 0x00, 0x9E,
+     0xC2, 0xEC, 0x55, 0x08, 0x77, 0x3D, 0xD3, 0x05,
+     0x47, 0x7C, 0xA1, 0x17, 0xE6, 0xD5, 0x69, 0xE6,
+     0x6B, 0x5F, 0x64, 0xC6, 0xBC, 0x64, 0x80, 0x1C,
+     0xE2, 0x5A, 0x84, 0x24, 0xCE, 0x4A, 0x26, 0xD5,
+     0x75, 0xB8, 0xA6, 0xFB, 0x10, 0xEA, 0xD3, 0xFD,
+     0x19, 0x92, 0xED, 0xDD, 0xEE, 0xC2, 0xEB, 0xE7,
+     0x15, 0x0D, 0xC9, 0x8F, 0x63, 0xAD, 0xC3, 0x23,
+     0x7E, 0xF5, 0x7B, 0x91, 0x39, 0x7A, 0xA8, 0xA7},
+    72,
+    {0xA3, 0xE1, 0x68, 0xB0, 0xD6, 0xC1, 0x43, 0xEE,
+     0x9E, 0x17, 0xEA, 0xE9, 0x29, 0x30, 0xB9, 0x7E,
+     0x66, 0x00, 0x35, 0x6B, 0x73, 0xAE, 0xBB, 0x5D,
+     0x68, 0x00, 0x5D, 0xD1, 0xD0, 0x74, 0x94, 0x45,
+     0x1A, 0x37, 0x05, 0x2F, 0x7B, 0x39, 0xFF, 0x03,
+     0x0C, 0x1A, 0xE1, 0xD7, 0xEF, 0xC4, 0xE0, 0xC3,
+     0x66, 0x7E, 0xB7, 0xA7, 0x6C, 0x62, 0x7E, 0xC1,
+     0x43, 0x54, 0xC4, 0xF6, 0xA7, 0x96, 0xE2, 0xC6}
+};
+
+SHA3_512 sha3_512;
+
+byte buffer[128];
+
+bool testHash_N(Hash *hash, const struct TestHashVector *test, size_t inc)
+{
+    size_t size = test->dataSize;
+    size_t posn, len;
+    uint8_t value[HASH_SIZE];
+
+    hash->reset();
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        hash->update(test->data + posn, len);
+    }
+    hash->finalize(value, sizeof(value));
+    if (memcmp(value, test->hash, sizeof(value)) != 0)
+        return false;
+
+    return true;
+}
+
+void testHash(Hash *hash, const struct TestHashVector *test)
+{
+    bool ok;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    ok  = testHash_N(hash, test, test->dataSize);
+    ok &= testHash_N(hash, test, 1);
+    ok &= testHash_N(hash, test, 2);
+    ok &= testHash_N(hash, test, 5);
+    ok &= testHash_N(hash, test, 8);
+    ok &= testHash_N(hash, test, 13);
+    ok &= testHash_N(hash, test, 16);
+    ok &= testHash_N(hash, test, 24);
+    ok &= testHash_N(hash, test, 63);
+    ok &= testHash_N(hash, test, 64);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfHash(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Hashing ... ");
+
+    for (size_t posn = 0; posn < sizeof(buffer); ++posn)
+        buffer[posn] = (uint8_t)posn;
+
+    hash->reset();
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        hash->update(buffer, sizeof(buffer));
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 500.0));
+    Serial.print("us per byte, ");
+    Serial.print((sizeof(buffer) * 500.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+// Very simple method for hashing a HMAC inner or outer key.
+void hashKey(Hash *hash, const uint8_t *key, size_t keyLen, uint8_t pad)
+{
+    size_t posn;
+    uint8_t buf;
+    uint8_t result[HASH_SIZE];
+    if (keyLen <= BLOCK_SIZE) {
+        hash->reset();
+        for (posn = 0; posn < BLOCK_SIZE; ++posn) {
+            if (posn < keyLen)
+                buf = key[posn] ^ pad;
+            else
+                buf = pad;
+            hash->update(&buf, 1);
+        }
+    } else {
+        hash->reset();
+        hash->update(key, keyLen);
+        hash->finalize(result, HASH_SIZE);
+        hash->reset();
+        for (posn = 0; posn < BLOCK_SIZE; ++posn) {
+            if (posn < HASH_SIZE)
+                buf = result[posn] ^ pad;
+            else
+                buf = pad;
+            hash->update(&buf, 1);
+        }
+    }
+}
+
+void testHMAC(Hash *hash, size_t keyLen)
+{
+    uint8_t result[HASH_SIZE];
+
+    Serial.print("HMAC-SHA3-512 keysize=");
+    Serial.print(keyLen);
+    Serial.print(" ... ");
+
+    // Construct the expected result with a simple HMAC implementation.
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hashKey(hash, buffer, keyLen, 0x36);
+    memset(buffer, 0xBA, sizeof(buffer));
+    hash->update(buffer, sizeof(buffer));
+    hash->finalize(result, HASH_SIZE);
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hashKey(hash, buffer, keyLen, 0x5C);
+    hash->update(result, HASH_SIZE);
+    hash->finalize(result, HASH_SIZE);
+
+    // Now use the library to compute the HMAC.
+    hash->resetHMAC(buffer, keyLen);
+    memset(buffer, 0xBA, sizeof(buffer));
+    hash->update(buffer, sizeof(buffer));
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hash->finalizeHMAC(buffer, keyLen, buffer, HASH_SIZE);
+
+    // Check the result.
+    if (!memcmp(result, buffer, HASH_SIZE))
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfFinalize(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Finalizing ... ");
+
+    hash->reset();
+    hash->update("abc", 3);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->finalize(buffer, hash->hashSize());
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.print("State Size ...");
+    Serial.println(sizeof(SHA3_512));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testHash(&sha3_512, &testVectorSHA3_512_1);
+    testHash(&sha3_512, &testVectorSHA3_512_2);
+    testHash(&sha3_512, &testVectorSHA3_512_3);
+    testHash(&sha3_512, &testVectorSHA3_512_4);
+    testHash(&sha3_512, &testVectorSHA3_512_5);
+    testHMAC(&sha3_512, (size_t)0);
+    testHMAC(&sha3_512, 1);
+    testHMAC(&sha3_512, HASH_SIZE);
+    testHMAC(&sha3_512, BLOCK_SIZE);
+    testHMAC(&sha3_512, BLOCK_SIZE + 1);
+    testHMAC(&sha3_512, sizeof(buffer));
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfHash(&sha3_512);
+    perfFinalize(&sha3_512);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestSHA512/TestSHA512.ino b/src/modules/Crypto/examples/TestSHA512/TestSHA512.ino
new file mode 100644
index 0000000..4a8b100
--- /dev/null
+++ b/src/modules/Crypto/examples/TestSHA512/TestSHA512.ino
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the SHA512 implementation to verify correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <SHA512.h>
+#include <string.h>
+
+#define HASH_SIZE 64
+#define BLOCK_SIZE 128
+
+struct TestHashVector
+{
+    const char *name;
+    const char *data;
+    uint8_t hash[HASH_SIZE];
+};
+
+static TestHashVector const testVectorSHA512_1 = {
+    "SHA-512 #1",
+    "",
+    {0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd,
+     0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07,
+     0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc,
+     0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce,
+     0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0,
+     0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f,
+     0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81,
+     0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e}
+};
+static TestHashVector const testVectorSHA512_2 = {
+    "SHA-512 #2",
+    "abc",
+    {0xdd, 0xaf, 0x35, 0xa1, 0x93, 0x61, 0x7a, 0xba,
+     0xcc, 0x41, 0x73, 0x49, 0xae, 0x20, 0x41, 0x31,
+     0x12, 0xe6, 0xfa, 0x4e, 0x89, 0xa9, 0x7e, 0xa2,
+     0x0a, 0x9e, 0xee, 0xe6, 0x4b, 0x55, 0xd3, 0x9a,
+     0x21, 0x92, 0x99, 0x2a, 0x27, 0x4f, 0xc1, 0xa8,
+     0x36, 0xba, 0x3c, 0x23, 0xa3, 0xfe, 0xeb, 0xbd,
+     0x45, 0x4d, 0x44, 0x23, 0x64, 0x3c, 0xe8, 0x0e,
+     0x2a, 0x9a, 0xc9, 0x4f, 0xa5, 0x4c, 0xa4, 0x9f}
+};
+static TestHashVector const testVectorSHA512_3 = {
+    "SHA-512 #3",
+    "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn"
+    "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+    {0x8e, 0x95, 0x9b, 0x75, 0xda, 0xe3, 0x13, 0xda,
+     0x8c, 0xf4, 0xf7, 0x28, 0x14, 0xfc, 0x14, 0x3f,
+     0x8f, 0x77, 0x79, 0xc6, 0xeb, 0x9f, 0x7f, 0xa1,
+     0x72, 0x99, 0xae, 0xad, 0xb6, 0x88, 0x90, 0x18,
+     0x50, 0x1d, 0x28, 0x9e, 0x49, 0x00, 0xf7, 0xe4,
+     0x33, 0x1b, 0x99, 0xde, 0xc4, 0xb5, 0x43, 0x3a,
+     0xc7, 0xd3, 0x29, 0xee, 0xb6, 0xdd, 0x26, 0x54,
+     0x5e, 0x96, 0xe5, 0x5b, 0x87, 0x4b, 0xe9, 0x09}
+};
+
+SHA512 sha512;
+
+byte buffer[BLOCK_SIZE + 2];
+
+bool testHash_N(Hash *hash, const struct TestHashVector *test, size_t inc)
+{
+    size_t size = strlen(test->data);
+    size_t posn, len;
+    uint8_t value[HASH_SIZE];
+
+    hash->reset();
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        hash->update(test->data + posn, len);
+    }
+    hash->finalize(value, sizeof(value));
+    if (memcmp(value, test->hash, sizeof(value)) != 0)
+        return false;
+
+    return true;
+}
+
+void testHash(Hash *hash, const struct TestHashVector *test)
+{
+    bool ok;
+
+    Serial.print(test->name);
+    Serial.print(" ... ");
+
+    ok  = testHash_N(hash, test, strlen(test->data));
+    ok &= testHash_N(hash, test, 1);
+    ok &= testHash_N(hash, test, 2);
+    ok &= testHash_N(hash, test, 5);
+    ok &= testHash_N(hash, test, 8);
+    ok &= testHash_N(hash, test, 13);
+    ok &= testHash_N(hash, test, 16);
+    ok &= testHash_N(hash, test, 24);
+    ok &= testHash_N(hash, test, 63);
+    ok &= testHash_N(hash, test, 64);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfHash(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Hashing ... ");
+
+    for (size_t posn = 0; posn < sizeof(buffer); ++posn)
+        buffer[posn] = (uint8_t)posn;
+
+    hash->reset();
+    start = micros();
+    for (count = 0; count < 250; ++count) {
+        hash->update(buffer, sizeof(buffer));
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 250.0));
+    Serial.print("us per byte, ");
+    Serial.print((sizeof(buffer) * 250.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+// Very simple method for hashing a HMAC inner or outer key.
+void hashKey(Hash *hash, const uint8_t *key, size_t keyLen, uint8_t pad)
+{
+    size_t posn;
+    uint8_t buf;
+    uint8_t result[HASH_SIZE];
+    if (keyLen <= BLOCK_SIZE) {
+        hash->reset();
+        for (posn = 0; posn < BLOCK_SIZE; ++posn) {
+            if (posn < keyLen)
+                buf = key[posn] ^ pad;
+            else
+                buf = pad;
+            hash->update(&buf, 1);
+        }
+    } else {
+        hash->reset();
+        hash->update(key, keyLen);
+        hash->finalize(result, HASH_SIZE);
+        hash->reset();
+        for (posn = 0; posn < BLOCK_SIZE; ++posn) {
+            if (posn < HASH_SIZE)
+                buf = result[posn] ^ pad;
+            else
+                buf = pad;
+            hash->update(&buf, 1);
+        }
+    }
+}
+
+void testHMAC(Hash *hash, size_t keyLen)
+{
+    uint8_t result[HASH_SIZE];
+
+    Serial.print("HMAC-SHA-512 keysize=");
+    Serial.print(keyLen);
+    Serial.print(" ... ");
+
+    // Construct the expected result with a simple HMAC implementation.
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hashKey(hash, buffer, keyLen, 0x36);
+    memset(buffer, 0xBA, sizeof(buffer));
+    hash->update(buffer, sizeof(buffer));
+    hash->finalize(result, HASH_SIZE);
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hashKey(hash, buffer, keyLen, 0x5C);
+    hash->update(result, HASH_SIZE);
+    hash->finalize(result, HASH_SIZE);
+
+    // Now use the library to compute the HMAC.
+    hash->resetHMAC(buffer, keyLen);
+    memset(buffer, 0xBA, sizeof(buffer));
+    hash->update(buffer, sizeof(buffer));
+    memset(buffer, (uint8_t)keyLen, keyLen);
+    hash->finalizeHMAC(buffer, keyLen, buffer, HASH_SIZE);
+
+    // Check the result.
+    if (!memcmp(result, buffer, HASH_SIZE))
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfFinalize(Hash *hash)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Finalizing ... ");
+
+    hash->reset();
+    hash->update("abc", 3);
+    start = micros();
+    for (count = 0; count < 1000; ++count) {
+        hash->finalize(buffer, hash->hashSize());
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 1000.0);
+    Serial.print("us per op, ");
+    Serial.print((1000.0 * 1000000.0) / elapsed);
+    Serial.println(" ops per second");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.print("State Size ...");
+    Serial.println(sizeof(SHA512));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testHash(&sha512, &testVectorSHA512_1);
+    testHash(&sha512, &testVectorSHA512_2);
+    testHash(&sha512, &testVectorSHA512_3);
+    testHMAC(&sha512, (size_t)0);
+    testHMAC(&sha512, 1);
+    testHMAC(&sha512, HASH_SIZE);
+    testHMAC(&sha512, BLOCK_SIZE);
+    testHMAC(&sha512, BLOCK_SIZE + 1);
+    testHMAC(&sha512, BLOCK_SIZE + 2);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfHash(&sha512);
+    perfFinalize(&sha512);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestSHAKE128/TestSHAKE128.ino b/src/modules/Crypto/examples/TestSHAKE128/TestSHAKE128.ino
new file mode 100644
index 0000000..806f061
--- /dev/null
+++ b/src/modules/Crypto/examples/TestSHAKE128/TestSHAKE128.ino
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the SHAKE128 implementation to verify
+correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <SHAKE.h>
+#include <string.h>
+#if defined(__AVR__)
+#include <avr/pgmspace.h>
+#else
+#define PROGMEM
+#define memcpy_P(d, s, l)   memcpy((d), (s), (l))
+#endif
+
+#define MAX_HASH_DATA_SIZE  167
+#define MAX_SHAKE_OUTPUT    256
+
+struct TestHashVectorSHAKE
+{
+    const char *name;
+    uint8_t data[MAX_HASH_DATA_SIZE];
+    size_t dataLen;
+    uint8_t hash[MAX_SHAKE_OUTPUT];
+};
+
+// Some test vectors from https://github.com/gvanas/KeccakCodePackage
+static TestHashVectorSHAKE const testVectorSHAKE128_1 PROGMEM = {
+    "SHAKE128 #1",
+    {0},
+    0,
+    {0x7F, 0x9C, 0x2B, 0xA4, 0xE8, 0x8F, 0x82, 0x7D,
+     0x61, 0x60, 0x45, 0x50, 0x76, 0x05, 0x85, 0x3E,
+     0xD7, 0x3B, 0x80, 0x93, 0xF6, 0xEF, 0xBC, 0x88,
+     0xEB, 0x1A, 0x6E, 0xAC, 0xFA, 0x66, 0xEF, 0x26,
+     0x3C, 0xB1, 0xEE, 0xA9, 0x88, 0x00, 0x4B, 0x93,
+     0x10, 0x3C, 0xFB, 0x0A, 0xEE, 0xFD, 0x2A, 0x68,
+     0x6E, 0x01, 0xFA, 0x4A, 0x58, 0xE8, 0xA3, 0x63,
+     0x9C, 0xA8, 0xA1, 0xE3, 0xF9, 0xAE, 0x57, 0xE2,
+     0x35, 0xB8, 0xCC, 0x87, 0x3C, 0x23, 0xDC, 0x62,
+     0xB8, 0xD2, 0x60, 0x16, 0x9A, 0xFA, 0x2F, 0x75,
+     0xAB, 0x91, 0x6A, 0x58, 0xD9, 0x74, 0x91, 0x88,
+     0x35, 0xD2, 0x5E, 0x6A, 0x43, 0x50, 0x85, 0xB2,
+     0xBA, 0xDF, 0xD6, 0xDF, 0xAA, 0xC3, 0x59, 0xA5,
+     0xEF, 0xBB, 0x7B, 0xCC, 0x4B, 0x59, 0xD5, 0x38,
+     0xDF, 0x9A, 0x04, 0x30, 0x2E, 0x10, 0xC8, 0xBC,
+     0x1C, 0xBF, 0x1A, 0x0B, 0x3A, 0x51, 0x20, 0xEA,
+     0x17, 0xCD, 0xA7, 0xCF, 0xAD, 0x76, 0x5F, 0x56,
+     0x23, 0x47, 0x4D, 0x36, 0x8C, 0xCC, 0xA8, 0xAF,
+     0x00, 0x07, 0xCD, 0x9F, 0x5E, 0x4C, 0x84, 0x9F,
+     0x16, 0x7A, 0x58, 0x0B, 0x14, 0xAA, 0xBD, 0xEF,
+     0xAE, 0xE7, 0xEE, 0xF4, 0x7C, 0xB0, 0xFC, 0xA9,
+     0x76, 0x7B, 0xE1, 0xFD, 0xA6, 0x94, 0x19, 0xDF,
+     0xB9, 0x27, 0xE9, 0xDF, 0x07, 0x34, 0x8B, 0x19,
+     0x66, 0x91, 0xAB, 0xAE, 0xB5, 0x80, 0xB3, 0x2D,
+     0xEF, 0x58, 0x53, 0x8B, 0x8D, 0x23, 0xF8, 0x77,
+     0x32, 0xEA, 0x63, 0xB0, 0x2B, 0x4F, 0xA0, 0xF4,
+     0x87, 0x33, 0x60, 0xE2, 0x84, 0x19, 0x28, 0xCD,
+     0x60, 0xDD, 0x4C, 0xEE, 0x8C, 0xC0, 0xD4, 0xC9,
+     0x22, 0xA9, 0x61, 0x88, 0xD0, 0x32, 0x67, 0x5C,
+     0x8A, 0xC8, 0x50, 0x93, 0x3C, 0x7A, 0xFF, 0x15,
+     0x33, 0xB9, 0x4C, 0x83, 0x4A, 0xDB, 0xB6, 0x9C,
+     0x61, 0x15, 0xBA, 0xD4, 0x69, 0x2D, 0x86, 0x19}
+};
+static TestHashVectorSHAKE const testVectorSHAKE128_2 PROGMEM = {
+    "SHAKE128 #2",
+    {0x1F, 0x87, 0x7C},
+    3,
+    {0xE2, 0xD3, 0x14, 0x46, 0x69, 0xAB, 0x57, 0x83,
+     0x47, 0xFC, 0xCA, 0x0B, 0x57, 0x27, 0x83, 0xA2,
+     0x69, 0xA8, 0xCF, 0x9A, 0xDD, 0xA4, 0xD8, 0x77,
+     0x82, 0x05, 0x3D, 0x80, 0xD5, 0xF0, 0xFD, 0xD2,
+     0x78, 0x35, 0xCF, 0x88, 0x30, 0x36, 0xE5, 0x36,
+     0xCE, 0x76, 0xFE, 0xF6, 0x89, 0xA5, 0xE7, 0xBD,
+     0x64, 0x6A, 0x7F, 0xB7, 0xD7, 0x4F, 0x09, 0x01,
+     0x93, 0xB2, 0x39, 0x0E, 0x61, 0x47, 0x59, 0xB7,
+     0xEB, 0x7D, 0xE9, 0x15, 0xA3, 0x83, 0x28, 0x74,
+     0x58, 0x90, 0xB1, 0xEF, 0x1E, 0x7A, 0xED, 0x78,
+     0x16, 0x8E, 0x99, 0x6D, 0x7A, 0xC7, 0x74, 0xD4,
+     0x7F, 0x8F, 0x11, 0x8B, 0x3E, 0x00, 0xA7, 0xBD,
+     0x15, 0x11, 0x31, 0xBA, 0x37, 0x05, 0xAE, 0x81,
+     0xB5, 0x7F, 0xB7, 0xCB, 0xFF, 0xE1, 0x14, 0xE2,
+     0xF4, 0xC3, 0xCA, 0x15, 0x2B, 0x88, 0x74, 0xFB,
+     0x90, 0x6E, 0x86, 0x28, 0x40, 0x62, 0x4E, 0x02,
+     0xBB, 0xF9, 0x50, 0x2E, 0x46, 0xD8, 0x88, 0x84,
+     0x33, 0xA3, 0x8E, 0x82, 0xE0, 0x4C, 0xAA, 0xCB,
+     0x60, 0x01, 0x92, 0x22, 0xD4, 0x33, 0xE8, 0xF2,
+     0xE7, 0x58, 0xBD, 0x41, 0xAA, 0xB3, 0x95, 0xBF,
+     0x83, 0x61, 0x1F, 0xD0, 0xC3, 0xF7, 0xFD, 0x51,
+     0x73, 0x30, 0x61, 0x82, 0x44, 0x9B, 0x9A, 0x22,
+     0xC4, 0x01, 0x3F, 0x22, 0x63, 0xB4, 0x1E, 0xAC,
+     0x4D, 0x0E, 0xDA, 0x16, 0x85, 0x49, 0x61, 0xFB,
+     0xAA, 0x6A, 0xD0, 0x4A, 0x89, 0xE7, 0x2A, 0x60,
+     0x2A, 0xC5, 0x96, 0x59, 0xEC, 0x2A, 0x60, 0xC1,
+     0xD0, 0x20, 0xBA, 0xCC, 0x74, 0xA7, 0x11, 0xD4,
+     0x25, 0x4A, 0x2E, 0xCC, 0x5F, 0x8F, 0x06, 0x27,
+     0xB4, 0xF7, 0x2A, 0xE1, 0x30, 0xC5, 0x05, 0x90,
+     0xF8, 0xB9, 0x1C, 0x52, 0x95, 0x7B, 0x79, 0x5D,
+     0x12, 0xDA, 0x09, 0xBD, 0xD4, 0x0D, 0x41, 0xE3,
+     0xCD, 0x48, 0xE3, 0x0E, 0x37, 0xFE, 0x5F, 0xD0}
+};
+static TestHashVectorSHAKE const testVectorSHAKE128_3 PROGMEM = {
+    "SHAKE128 #3",
+    {0x0D, 0x8D, 0x09, 0xAE, 0xD1, 0x9F, 0x10, 0x13,
+     0x96, 0x9C, 0xE5, 0xE7, 0xEB, 0x92, 0xF8, 0x3A,
+     0x20, 0x9A, 0xE7, 0x6B, 0xE3, 0x1C, 0x75, 0x48,
+     0x44, 0xEA, 0x91, 0x16, 0xCE, 0xB3, 0x9A, 0x22,
+     0xEB, 0xB6, 0x00, 0x30, 0x17, 0xBB, 0xCF, 0x26,
+     0x55, 0x5F, 0xA6, 0x62, 0x41, 0x85, 0x18, 0x7D,
+     0xB8, 0xF0, 0xCB, 0x35, 0x64, 0xB8, 0xB1, 0xC0,
+     0x6B, 0xF6, 0x85, 0xD4, 0x7F, 0x32, 0x86, 0xED,
+     0xA2, 0x0B, 0x83, 0x35, 0x8F, 0x59, 0x9D, 0x20,
+     0x44, 0xBB, 0xF0, 0x58, 0x3F, 0xAB, 0x8D, 0x78,
+     0xF8, 0x54, 0xFE, 0x0A, 0x59, 0x61, 0x83, 0x23,
+     0x0C, 0x5E, 0xF8, 0xE5, 0x44, 0x26, 0x75, 0x0E,
+     0xAF, 0x2C, 0xC4, 0xE2, 0x9D, 0x3B, 0xDD, 0x03,
+     0x7E, 0x73, 0x4D, 0x86, 0x3C, 0x2B, 0xD9, 0x78,
+     0x9B, 0x4C, 0x24, 0x30, 0x96, 0x13, 0x8F, 0x76,
+     0x72, 0xC2, 0x32, 0x31, 0x4E, 0xFF, 0xDF, 0xC6,
+     0x51, 0x34, 0x27, 0xE2, 0xDA, 0x76, 0x91, 0x6B,
+     0x52, 0x48, 0x93, 0x3B, 0xE3, 0x12, 0xEB, 0x5D,
+     0xDE, 0x4C, 0xF7, 0x08, 0x04, 0xFB, 0x25, 0x8A,
+     0xC5, 0xFB, 0x82, 0xD5, 0x8D, 0x08, 0x17, 0x7A,
+     0xC6, 0xF4, 0x75, 0x60, 0x17, 0xFF, 0xF5},
+    167,
+    {0xC7, 0x3D, 0x8F, 0xAA, 0xB5, 0xD0, 0xB4, 0xD6,
+     0x60, 0xBD, 0x50, 0x82, 0xE4, 0x4C, 0x3C, 0xAC,
+     0x97, 0xE6, 0x16, 0x48, 0xBE, 0x0A, 0x04, 0xB1,
+     0x16, 0x72, 0x4E, 0x6F, 0x6B, 0x65, 0x76, 0x84,
+     0x67, 0x4B, 0x4B, 0x0E, 0x90, 0xD0, 0xAE, 0x96,
+     0xC0, 0x85, 0x3E, 0xBD, 0x83, 0x7B, 0xD8, 0x24,
+     0x9A, 0xDB, 0xD3, 0xB6, 0x0A, 0x1A, 0xD1, 0xFC,
+     0xF8, 0xA6, 0xAB, 0x8E, 0x2F, 0x5A, 0xA7, 0xFF,
+     0x19, 0x7A, 0x3D, 0x7D, 0xBE, 0xDE, 0xFB, 0x43,
+     0x3B, 0x61, 0x35, 0x36, 0xAE, 0xC4, 0xD6, 0x55,
+     0xB7, 0xBC, 0xD7, 0x78, 0x52, 0x6B, 0xE6, 0x67,
+     0x84, 0x7A, 0xCD, 0x2E, 0x05, 0x64, 0xD9, 0x6C,
+     0xE5, 0x14, 0x0C, 0x91, 0x35, 0x7F, 0xAD, 0xE0,
+     0x00, 0xEF, 0xCB, 0x40, 0x45, 0x7E, 0x1B, 0x6C,
+     0xED, 0x41, 0xFA, 0x10, 0x2E, 0x36, 0xE7, 0x99,
+     0x79, 0x2D, 0xB0, 0x3E, 0x9A, 0x40, 0xC7, 0x99,
+     0xBC, 0xA9, 0x12, 0x62, 0x94, 0x8E, 0x17, 0x60,
+     0x50, 0x65, 0xFB, 0xF6, 0x38, 0xFB, 0x40, 0xA1,
+     0x57, 0xB4, 0x5C, 0xF7, 0x91, 0x1A, 0x75, 0x3D,
+     0x0D, 0x20, 0x5D, 0xF8, 0x47, 0x16, 0xA5, 0x71,
+     0x12, 0xBE, 0xAB, 0x44, 0xF6, 0x20, 0x1F, 0xF7,
+     0x5A, 0xAD, 0xE0, 0xBA, 0xFB, 0xA5, 0x04, 0x74,
+     0x5C, 0xFE, 0x23, 0xE4, 0xE6, 0x0E, 0x67, 0xE3,
+     0x99, 0x36, 0x22, 0xAE, 0xD7, 0x3A, 0x1D, 0xD6,
+     0xA4, 0x65, 0xBD, 0x45, 0x3D, 0xD3, 0xC5, 0xBA,
+     0x7D, 0x2C, 0xDF, 0x3F, 0x1D, 0x39, 0x37, 0x6A,
+     0x67, 0xC2, 0x3E, 0x55, 0x5F, 0x5A, 0xCF, 0x25,
+     0xBC, 0xE1, 0xE5, 0x5F, 0x30, 0x72, 0x52, 0xB9,
+     0xAA, 0xC2, 0xC0, 0xA3, 0x9C, 0x88, 0x5C, 0x7E,
+     0x44, 0xF2, 0x04, 0xCB, 0x82, 0x1C, 0x0D, 0x37,
+     0xA2, 0x2D, 0xE3, 0xA7, 0x1F, 0x3A, 0x19, 0x09,
+     0xB1, 0x1B, 0x71, 0x81, 0xC4, 0x2B, 0xE9, 0xB7}
+};
+
+SHAKE128 shake128;
+TestHashVectorSHAKE tst;
+uint8_t output[MAX_SHAKE_OUTPUT];
+
+bool testSHAKE_N(SHAKE *shake, const struct TestHashVectorSHAKE *test, size_t inc, bool printName = false)
+{
+    size_t size;
+    size_t posn, len;
+
+    // Copy the test case out of program memory.
+    memcpy_P(&tst, test, sizeof(tst));
+    test = &tst;
+
+    // Print the test name if necessary.
+    if (printName) {
+        Serial.print(test->name);
+        Serial.print(" ... ");
+    }
+
+    // Hash the input data.
+    if (!inc)
+        inc = 1;
+    size = test->dataLen;
+    shake->reset();
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        shake->update(test->data + posn, len);
+    }
+
+    // Generate data using extend() and check it.
+    size = MAX_SHAKE_OUTPUT;
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        shake->extend(output + posn, len);
+    }
+    if (memcmp(output, test->hash, size) != 0)
+        return false;
+
+    // Restart and use encrypt() this time.
+    shake->reset();
+    shake->update(test->data, test->dataLen);
+    for (posn = 0; posn < size; ++posn)
+        output[posn] = (uint8_t)(posn + 0xAA);
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        shake->encrypt(output + posn, output + posn, len);
+    }
+    for (posn = 0; posn < size; ++posn)
+        output[posn] ^= (uint8_t)(posn + 0xAA);
+    if (memcmp(output, test->hash, size) != 0)
+        return false;
+
+    return true;
+}
+
+void testSHAKE(SHAKE *shake, const struct TestHashVectorSHAKE *test)
+{
+    bool ok;
+    size_t dataLen;
+
+    memcpy_P(&dataLen, &(test->dataLen), sizeof(size_t));
+
+    ok  = testSHAKE_N(shake, test, dataLen, true);
+    ok &= testSHAKE_N(shake, test, 1);
+    ok &= testSHAKE_N(shake, test, 2);
+    ok &= testSHAKE_N(shake, test, 5);
+    ok &= testSHAKE_N(shake, test, 8);
+    ok &= testSHAKE_N(shake, test, 13);
+    ok &= testSHAKE_N(shake, test, 16);
+    ok &= testSHAKE_N(shake, test, 24);
+    ok &= testSHAKE_N(shake, test, 63);
+    ok &= testSHAKE_N(shake, test, 64);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfUpdate(SHAKE *shake)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Updating ... ");
+
+    for (size_t posn = 0; posn < MAX_SHAKE_OUTPUT; ++posn)
+        output[posn] = (uint8_t)posn;
+
+    shake->reset();
+    start = micros();
+    for (count = 0; count < 300; ++count) {
+        shake->update(output, MAX_SHAKE_OUTPUT);
+    }
+    shake->extend(output, 0);   // Force a finalize after the update.
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (MAX_SHAKE_OUTPUT * 300.0));
+    Serial.print("us per byte, ");
+    Serial.print((MAX_SHAKE_OUTPUT * 300.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfExtend(SHAKE *shake)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Extending ... ");
+
+    for (size_t posn = 0; posn < MAX_SHAKE_OUTPUT; ++posn)
+        output[posn] = (uint8_t)posn;
+
+    shake->reset();
+    shake->update(output, MAX_SHAKE_OUTPUT);
+    shake->extend(output, 0);   // Force a finalize after the update.
+    start = micros();
+    for (count = 0; count < 300; ++count) {
+        shake->extend(output, MAX_SHAKE_OUTPUT);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (MAX_SHAKE_OUTPUT * 300.0));
+    Serial.print("us per byte, ");
+    Serial.print((MAX_SHAKE_OUTPUT * 300.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfEncrypt(SHAKE *shake)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Encrypting ... ");
+
+    for (size_t posn = 0; posn < MAX_SHAKE_OUTPUT; ++posn)
+        output[posn] = (uint8_t)posn;
+
+    shake->reset();
+    shake->update(output, MAX_SHAKE_OUTPUT);
+    shake->extend(output, 0);   // Force a finalize after the update.
+    start = micros();
+    for (count = 0; count < 300; ++count) {
+        shake->encrypt(output, output, MAX_SHAKE_OUTPUT);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (MAX_SHAKE_OUTPUT * 300.0));
+    Serial.print("us per byte, ");
+    Serial.print((MAX_SHAKE_OUTPUT * 300.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.print("State Size ...");
+    Serial.println(sizeof(SHAKE128));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testSHAKE(&shake128, &testVectorSHAKE128_1);
+    testSHAKE(&shake128, &testVectorSHAKE128_2);
+    testSHAKE(&shake128, &testVectorSHAKE128_3);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfUpdate(&shake128);
+    perfExtend(&shake128);
+    perfEncrypt(&shake128);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestSHAKE256/TestSHAKE256.ino b/src/modules/Crypto/examples/TestSHAKE256/TestSHAKE256.ino
new file mode 100644
index 0000000..dede3e6
--- /dev/null
+++ b/src/modules/Crypto/examples/TestSHAKE256/TestSHAKE256.ino
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the SHAKE256 implementation to verify
+correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <SHAKE.h>
+#include <string.h>
+#if defined(__AVR__)
+#include <avr/pgmspace.h>
+#else
+#define PROGMEM
+#define memcpy_P(d, s, l)   memcpy((d), (s), (l))
+#endif
+
+#define MAX_HASH_DATA_SIZE  135
+#define MAX_SHAKE_OUTPUT    256
+
+struct TestHashVectorSHAKE
+{
+    const char *name;
+    uint8_t data[MAX_HASH_DATA_SIZE];
+    size_t dataLen;
+    uint8_t hash[MAX_SHAKE_OUTPUT];
+};
+
+// Some test vectors from https://github.com/gvanas/KeccakCodePackage
+static TestHashVectorSHAKE const testVectorSHAKE256_1 PROGMEM = {
+    "SHAKE256 #1",
+    {0},
+    0,
+    {0x46, 0xB9, 0xDD, 0x2B, 0x0B, 0xA8, 0x8D, 0x13,
+     0x23, 0x3B, 0x3F, 0xEB, 0x74, 0x3E, 0xEB, 0x24,
+     0x3F, 0xCD, 0x52, 0xEA, 0x62, 0xB8, 0x1B, 0x82,
+     0xB5, 0x0C, 0x27, 0x64, 0x6E, 0xD5, 0x76, 0x2F,
+     0xD7, 0x5D, 0xC4, 0xDD, 0xD8, 0xC0, 0xF2, 0x00,
+     0xCB, 0x05, 0x01, 0x9D, 0x67, 0xB5, 0x92, 0xF6,
+     0xFC, 0x82, 0x1C, 0x49, 0x47, 0x9A, 0xB4, 0x86,
+     0x40, 0x29, 0x2E, 0xAC, 0xB3, 0xB7, 0xC4, 0xBE,
+     0x14, 0x1E, 0x96, 0x61, 0x6F, 0xB1, 0x39, 0x57,
+     0x69, 0x2C, 0xC7, 0xED, 0xD0, 0xB4, 0x5A, 0xE3,
+     0xDC, 0x07, 0x22, 0x3C, 0x8E, 0x92, 0x93, 0x7B,
+     0xEF, 0x84, 0xBC, 0x0E, 0xAB, 0x86, 0x28, 0x53,
+     0x34, 0x9E, 0xC7, 0x55, 0x46, 0xF5, 0x8F, 0xB7,
+     0xC2, 0x77, 0x5C, 0x38, 0x46, 0x2C, 0x50, 0x10,
+     0xD8, 0x46, 0xC1, 0x85, 0xC1, 0x51, 0x11, 0xE5,
+     0x95, 0x52, 0x2A, 0x6B, 0xCD, 0x16, 0xCF, 0x86,
+     0xF3, 0xD1, 0x22, 0x10, 0x9E, 0x3B, 0x1F, 0xDD,
+     0x94, 0x3B, 0x6A, 0xEC, 0x46, 0x8A, 0x2D, 0x62,
+     0x1A, 0x7C, 0x06, 0xC6, 0xA9, 0x57, 0xC6, 0x2B,
+     0x54, 0xDA, 0xFC, 0x3B, 0xE8, 0x75, 0x67, 0xD6,
+     0x77, 0x23, 0x13, 0x95, 0xF6, 0x14, 0x72, 0x93,
+     0xB6, 0x8C, 0xEA, 0xB7, 0xA9, 0xE0, 0xC5, 0x8D,
+     0x86, 0x4E, 0x8E, 0xFD, 0xE4, 0xE1, 0xB9, 0xA4,
+     0x6C, 0xBE, 0x85, 0x47, 0x13, 0x67, 0x2F, 0x5C,
+     0xAA, 0xAE, 0x31, 0x4E, 0xD9, 0x08, 0x3D, 0xAB,
+     0x4B, 0x09, 0x9F, 0x8E, 0x30, 0x0F, 0x01, 0xB8,
+     0x65, 0x0F, 0x1F, 0x4B, 0x1D, 0x8F, 0xCF, 0x3F,
+     0x3C, 0xB5, 0x3F, 0xB8, 0xE9, 0xEB, 0x2E, 0xA2,
+     0x03, 0xBD, 0xC9, 0x70, 0xF5, 0x0A, 0xE5, 0x54,
+     0x28, 0xA9, 0x1F, 0x7F, 0x53, 0xAC, 0x26, 0x6B,
+     0x28, 0x41, 0x9C, 0x37, 0x78, 0xA1, 0x5F, 0xD2,
+     0x48, 0xD3, 0x39, 0xED, 0xE7, 0x85, 0xFB, 0x7F}
+};
+static TestHashVectorSHAKE const testVectorSHAKE256_2 PROGMEM = {
+    "SHAKE256 #2",
+    {0x1F, 0x87, 0x7C},
+    3,
+    {0xF6, 0xBF, 0x03, 0x97, 0xDB, 0xFB, 0xB2, 0x0E,
+     0x4A, 0xE3, 0x0F, 0x0A, 0x47, 0xFE, 0x97, 0x6C,
+     0xD1, 0x09, 0xB3, 0xAA, 0x09, 0xB0, 0xE3, 0xF2,
+     0x9F, 0x56, 0x0E, 0x4E, 0xD3, 0x33, 0xC0, 0xD0,
+     0x83, 0x32, 0x6B, 0x03, 0xF6, 0xEA, 0xEB, 0x57,
+     0xE2, 0x77, 0xBB, 0xFE, 0x1C, 0xCE, 0x36, 0xC4,
+     0x99, 0x43, 0x4D, 0x83, 0x8C, 0xB4, 0xC8, 0xCD,
+     0x8B, 0x02, 0xA8, 0x77, 0x90, 0xF4, 0xA6, 0x71,
+     0x7B, 0x22, 0xD4, 0x6F, 0x92, 0x20, 0x39, 0x1C,
+     0x42, 0x0A, 0x1A, 0x1B, 0xFA, 0xA9, 0xED, 0x5B,
+     0x85, 0x11, 0x6B, 0xA1, 0xD9, 0xE1, 0x7F, 0xF1,
+     0x6F, 0x6B, 0xCE, 0x67, 0x04, 0xC8, 0x0A, 0x49,
+     0xFD, 0x9A, 0xC4, 0x26, 0x89, 0xDB, 0x09, 0x96,
+     0xC6, 0xBD, 0x32, 0x66, 0x69, 0x40, 0x77, 0xC6,
+     0xDE, 0x12, 0x00, 0x43, 0xA8, 0x27, 0xD4, 0x49,
+     0x79, 0xCE, 0x8C, 0xCC, 0x6A, 0xA7, 0xE5, 0x30,
+     0x8E, 0xBA, 0x64, 0xAC, 0xF9, 0xFF, 0xFF, 0x51,
+     0xD3, 0x6B, 0xC4, 0x40, 0x1F, 0x81, 0x17, 0xD4,
+     0xB9, 0x63, 0x40, 0xC6, 0x2D, 0x10, 0x6B, 0x0A,
+     0x64, 0x45, 0xF0, 0x19, 0x87, 0xF9, 0xC4, 0xC0,
+     0xA4, 0x20, 0xE1, 0xA9, 0xBA, 0xEB, 0x59, 0x4B,
+     0xCB, 0x1B, 0xDB, 0xFE, 0x59, 0xB6, 0x06, 0x5E,
+     0xB9, 0x1C, 0xBE, 0xB2, 0x52, 0x47, 0x3C, 0x78,
+     0x58, 0xEC, 0xA4, 0x75, 0xE1, 0xC8, 0x1E, 0x84,
+     0x25, 0xC7, 0xE2, 0xC1, 0x70, 0x6C, 0x4C, 0x4A,
+     0xBB, 0x3A, 0xEA, 0xE3, 0x93, 0x32, 0x47, 0x9E,
+     0xCD, 0xEF, 0xDF, 0xA9, 0x3C, 0x60, 0xEC, 0x40,
+     0x07, 0xA5, 0x1C, 0x5D, 0xD0, 0x93, 0xB5, 0x27,
+     0x26, 0x41, 0x55, 0xF2, 0x20, 0x2E, 0x01, 0xD2,
+     0x08, 0x3D, 0x27, 0xD7, 0x1A, 0x6F, 0x6C, 0x92,
+     0xD8, 0x39, 0xE6, 0xEA, 0x7D, 0x24, 0xAF, 0xDB,
+     0x5C, 0x43, 0x63, 0x0F, 0x1B, 0xD0, 0x6E, 0x2B}
+};
+static TestHashVectorSHAKE const testVectorSHAKE256_3 PROGMEM = {
+    "SHAKE256 #3",
+    {0xB7, 0x71, 0xD5, 0xCE, 0xF5, 0xD1, 0xA4, 0x1A,
+     0x93, 0xD1, 0x56, 0x43, 0xD7, 0x18, 0x1D, 0x2A,
+     0x2E, 0xF0, 0xA8, 0xE8, 0x4D, 0x91, 0x81, 0x2F,
+     0x20, 0xED, 0x21, 0xF1, 0x47, 0xBE, 0xF7, 0x32,
+     0xBF, 0x3A, 0x60, 0xEF, 0x40, 0x67, 0xC3, 0x73,
+     0x4B, 0x85, 0xBC, 0x8C, 0xD4, 0x71, 0x78, 0x0F,
+     0x10, 0xDC, 0x9E, 0x82, 0x91, 0xB5, 0x83, 0x39,
+     0xA6, 0x77, 0xB9, 0x60, 0x21, 0x8F, 0x71, 0xE7,
+     0x93, 0xF2, 0x79, 0x7A, 0xEA, 0x34, 0x94, 0x06,
+     0x51, 0x28, 0x29, 0x06, 0x5D, 0x37, 0xBB, 0x55,
+     0xEA, 0x79, 0x6F, 0xA4, 0xF5, 0x6F, 0xD8, 0x89,
+     0x6B, 0x49, 0xB2, 0xCD, 0x19, 0xB4, 0x32, 0x15,
+     0xAD, 0x96, 0x7C, 0x71, 0x2B, 0x24, 0xE5, 0x03,
+     0x2D, 0x06, 0x52, 0x32, 0xE0, 0x2C, 0x12, 0x74,
+     0x09, 0xD2, 0xED, 0x41, 0x46, 0xB9, 0xD7, 0x5D,
+     0x76, 0x3D, 0x52, 0xDB, 0x98, 0xD9, 0x49, 0xD3,
+     0xB0, 0xFE, 0xD6, 0xA8, 0x05, 0x2F, 0xBB},
+    135,
+    {0x6C, 0x60, 0x95, 0x5D, 0xCB, 0x8A, 0x66, 0x3B,
+     0x6D, 0xC7, 0xF5, 0xEF, 0x7E, 0x06, 0x9C, 0xA8,
+     0xFE, 0x3D, 0xA9, 0x9A, 0x66, 0xDF, 0x65, 0x96,
+     0x92, 0x5D, 0x55, 0x7F, 0xED, 0x91, 0xF4, 0x70,
+     0x91, 0x40, 0x7D, 0x6F, 0xDE, 0x32, 0x02, 0x3B,
+     0x57, 0xE2, 0xEE, 0x4C, 0x6A, 0xC9, 0x7B, 0x07,
+     0x76, 0x24, 0xFA, 0xC2, 0x5F, 0x6E, 0x13, 0xF4,
+     0x19, 0x16, 0x96, 0xB4, 0x0A, 0x4D, 0xF7, 0x5F,
+     0x61, 0xCD, 0x55, 0x21, 0xD9, 0x82, 0xC6, 0xD0,
+     0x9D, 0x83, 0x42, 0xC1, 0x7A, 0x36, 0x6E, 0xC6,
+     0x34, 0x6E, 0x35, 0x28, 0xB2, 0x6C, 0xFF, 0x91,
+     0x5B, 0xE9, 0x44, 0x2B, 0x9E, 0xBC, 0xC3, 0x0F,
+     0xF2, 0xF6, 0xAD, 0xD0, 0xE8, 0x2B, 0xA9, 0x04,
+     0xC7, 0x37, 0x00, 0xCC, 0x99, 0xAC, 0xFF, 0x48,
+     0x0C, 0xAF, 0x04, 0x87, 0xCE, 0xE5, 0x4C, 0xBA,
+     0x37, 0x53, 0xB6, 0xA5, 0xDD, 0x6F, 0x0D, 0xFE,
+     0x65, 0x71, 0xF0, 0x11, 0x5E, 0x87, 0x37, 0xB0,
+     0x71, 0x03, 0x10, 0x23, 0xB6, 0xBB, 0x0D, 0x79,
+     0x86, 0x4C, 0x3F, 0x33, 0x16, 0x2E, 0x78, 0x26,
+     0x9C, 0xEE, 0x23, 0xFC, 0xE4, 0x7B, 0x91, 0xB4,
+     0xFD, 0xF9, 0x1F, 0x98, 0x46, 0x4A, 0x1D, 0x21,
+     0xE7, 0x99, 0xD1, 0x7F, 0x76, 0xC1, 0xBB, 0x80,
+     0x7D, 0xEE, 0x66, 0x7B, 0x0B, 0x27, 0x30, 0x54,
+     0xBE, 0x29, 0x82, 0x99, 0xBD, 0x12, 0xB7, 0xA8,
+     0x0F, 0xB3, 0x54, 0xCE, 0x3E, 0x6D, 0x1A, 0xCF,
+     0x98, 0x44, 0x38, 0x79, 0xA5, 0x54, 0xEC, 0xA6,
+     0xB9, 0x6D, 0xF0, 0x61, 0xD0, 0x4A, 0x11, 0x7C,
+     0x98, 0xAE, 0xEC, 0x1C, 0xDE, 0x1A, 0xFA, 0x9C,
+     0xEF, 0x62, 0xDD, 0x68, 0x6D, 0xA9, 0x1B, 0xB2,
+     0xB1, 0xF1, 0x23, 0x79, 0xBB, 0xDC, 0x9F, 0xA3,
+     0x2A, 0x6B, 0x69, 0x98, 0xB7, 0x7E, 0x8E, 0xB0,
+     0xB5, 0x05, 0x07, 0x86, 0x2A, 0xFA, 0x77, 0x99}
+};
+
+SHAKE256 shake256;
+TestHashVectorSHAKE tst;
+uint8_t output[MAX_SHAKE_OUTPUT];
+
+bool testSHAKE_N(SHAKE *shake, const struct TestHashVectorSHAKE *test, size_t inc, bool printName = false)
+{
+    size_t size;
+    size_t posn, len;
+
+    // Copy the test case out of program memory.
+    memcpy_P(&tst, test, sizeof(tst));
+    test = &tst;
+
+    // Print the test name if necessary.
+    if (printName) {
+        Serial.print(test->name);
+        Serial.print(" ... ");
+    }
+
+    // Hash the input data.
+    if (!inc)
+        inc = 1;
+    size = test->dataLen;
+    shake->reset();
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        shake->update(test->data + posn, len);
+    }
+
+    // Generate data using extend() and check it.
+    size = MAX_SHAKE_OUTPUT;
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        shake->extend(output + posn, len);
+    }
+    if (memcmp(output, test->hash, size) != 0)
+        return false;
+
+    // Restart and use encrypt() this time.
+    shake->reset();
+    shake->update(test->data, test->dataLen);
+    for (posn = 0; posn < size; ++posn)
+        output[posn] = (uint8_t)(posn + 0xAA);
+    for (posn = 0; posn < size; posn += inc) {
+        len = size - posn;
+        if (len > inc)
+            len = inc;
+        shake->encrypt(output + posn, output + posn, len);
+    }
+    for (posn = 0; posn < size; ++posn)
+        output[posn] ^= (uint8_t)(posn + 0xAA);
+    if (memcmp(output, test->hash, size) != 0)
+        return false;
+
+    return true;
+}
+
+void testSHAKE(SHAKE *shake, const struct TestHashVectorSHAKE *test)
+{
+    bool ok;
+    size_t dataLen;
+
+    memcpy_P(&dataLen, &(test->dataLen), sizeof(size_t));
+
+    ok  = testSHAKE_N(shake, test, dataLen, true);
+    ok &= testSHAKE_N(shake, test, 1);
+    ok &= testSHAKE_N(shake, test, 2);
+    ok &= testSHAKE_N(shake, test, 5);
+    ok &= testSHAKE_N(shake, test, 8);
+    ok &= testSHAKE_N(shake, test, 13);
+    ok &= testSHAKE_N(shake, test, 16);
+    ok &= testSHAKE_N(shake, test, 24);
+    ok &= testSHAKE_N(shake, test, 63);
+    ok &= testSHAKE_N(shake, test, 64);
+
+    if (ok)
+        Serial.println("Passed");
+    else
+        Serial.println("Failed");
+}
+
+void perfUpdate(SHAKE *shake)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Updating ... ");
+
+    for (size_t posn = 0; posn < MAX_SHAKE_OUTPUT; ++posn)
+        output[posn] = (uint8_t)posn;
+
+    shake->reset();
+    start = micros();
+    for (count = 0; count < 300; ++count) {
+        shake->update(output, MAX_SHAKE_OUTPUT);
+    }
+    shake->extend(output, 0);   // Force a finalize after the update.
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (MAX_SHAKE_OUTPUT * 300.0));
+    Serial.print("us per byte, ");
+    Serial.print((MAX_SHAKE_OUTPUT * 300.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfExtend(SHAKE *shake)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Extending ... ");
+
+    for (size_t posn = 0; posn < MAX_SHAKE_OUTPUT; ++posn)
+        output[posn] = (uint8_t)posn;
+
+    shake->reset();
+    shake->update(output, MAX_SHAKE_OUTPUT);
+    shake->extend(output, 0);   // Force a finalize after the update.
+    start = micros();
+    for (count = 0; count < 300; ++count) {
+        shake->extend(output, MAX_SHAKE_OUTPUT);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (MAX_SHAKE_OUTPUT * 300.0));
+    Serial.print("us per byte, ");
+    Serial.print((MAX_SHAKE_OUTPUT * 300.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void perfEncrypt(SHAKE *shake)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    Serial.print("Encrypting ... ");
+
+    for (size_t posn = 0; posn < MAX_SHAKE_OUTPUT; ++posn)
+        output[posn] = (uint8_t)posn;
+
+    shake->reset();
+    shake->update(output, MAX_SHAKE_OUTPUT);
+    shake->extend(output, 0);   // Force a finalize after the update.
+    start = micros();
+    for (count = 0; count < 300; ++count) {
+        shake->encrypt(output, output, MAX_SHAKE_OUTPUT);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (MAX_SHAKE_OUTPUT * 300.0));
+    Serial.print("us per byte, ");
+    Serial.print((MAX_SHAKE_OUTPUT * 300.0 * 1000000.0) / elapsed);
+    Serial.println(" bytes per second");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    Serial.print("State Size ...");
+    Serial.println(sizeof(SHAKE256));
+    Serial.println();
+
+    Serial.println("Test Vectors:");
+    testSHAKE(&shake256, &testVectorSHAKE256_1);
+    testSHAKE(&shake256, &testVectorSHAKE256_2);
+    testSHAKE(&shake256, &testVectorSHAKE256_3);
+
+    Serial.println();
+
+    Serial.println("Performance Tests:");
+    perfUpdate(&shake256);
+    perfExtend(&shake256);
+    perfEncrypt(&shake256);
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/examples/TestXTS/TestXTS.ino b/src/modules/Crypto/examples/TestXTS/TestXTS.ino
new file mode 100644
index 0000000..ad1064e
--- /dev/null
+++ b/src/modules/Crypto/examples/TestXTS/TestXTS.ino
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2016 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+This example runs tests on the XTS implementation to verify correct behaviour.
+*/
+
+#include <Crypto.h>
+#include <CryptoLW.h>
+#include <AES.h>
+#include <Speck.h>
+#include <SpeckSmall.h>
+#include <SpeckTiny.h>
+#include <XTS.h>
+#include <string.h>
+#if defined(ESP8266) || defined(ESP32)
+#include <pgmspace.h>
+#else
+#include <avr/pgmspace.h>
+#endif
+
+#define MAX_SECTOR_SIZE 64
+
+struct TestVector
+{
+    const char *name;
+    byte key1[16];
+    byte key2[16];
+    byte plaintext[MAX_SECTOR_SIZE];
+    byte ciphertext[MAX_SECTOR_SIZE];
+    byte tweak[16];
+    size_t sectorSize;
+};
+
+// Selected test vectors for XTS-AES-128 from:
+// http://libeccio.di.unisa.it/Crypto14/Lab/p1619.pdf
+static TestVector const testVectorXTSAES128_1 PROGMEM = {
+    .name        = "XTS-AES-128 #1",
+    .key1        = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .key2        = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .plaintext   = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+    .ciphertext  = {0x91, 0x7c, 0xf6, 0x9e, 0xbd, 0x68, 0xb2, 0xec,
+                    0x9b, 0x9f, 0xe9, 0xa3, 0xea, 0xdd, 0xa6, 0x92,
+                    0xcd, 0x43, 0xd2, 0xf5, 0x95, 0x98, 0xed, 0x85,
+                    0x8c, 0x02, 0xc2, 0x65, 0x2f, 0xbf, 0x92, 0x2e},
+    .tweak       = {0x00},
+    .sectorSize  = 32
+};
+static TestVector const testVectorXTSAES128_2 PROGMEM = {
+    .name        = "XTS-AES-128 #2",
+    .key1        = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+                    0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+    .key2        = {0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+                    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22},
+    .plaintext   = {0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+                    0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+                    0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+                    0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44},
+    .ciphertext  = {0xc4, 0x54, 0x18, 0x5e, 0x6a, 0x16, 0x93, 0x6e,
+                    0x39, 0x33, 0x40, 0x38, 0xac, 0xef, 0x83, 0x8b,
+                    0xfb, 0x18, 0x6f, 0xff, 0x74, 0x80, 0xad, 0xc4,
+                    0x28, 0x93, 0x82, 0xec, 0xd6, 0xd3, 0x94, 0xf0},
+    .tweak       = {0x33, 0x33, 0x33, 0x33, 0x33},
+    .sectorSize  = 32
+};
+static TestVector const testVectorXTSAES128_3 PROGMEM = {
+    .name        = "XTS-AES-128 #3",
+    .key1        = {0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
+                    0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0},
+    .key2        = {0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+                    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22},
+    .plaintext   = {0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+                    0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+                    0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
+                    0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44},
+    .ciphertext  = {0xaf, 0x85, 0x33, 0x6b, 0x59, 0x7a, 0xfc, 0x1a,
+                    0x90, 0x0b, 0x2e, 0xb2, 0x1e, 0xc9, 0x49, 0xd2,
+                    0x92, 0xdf, 0x4c, 0x04, 0x7e, 0x0b, 0x21, 0x53,
+                    0x21, 0x86, 0xa5, 0x97, 0x1a, 0x22, 0x7a, 0x89},
+    .tweak       = {0x33, 0x33, 0x33, 0x33, 0x33},
+    .sectorSize  = 32
+};
+static TestVector const testVectorXTSAES128_4 PROGMEM = {
+    // 512 byte test vector from the spec truncated to the first 64 bytes.
+    .name        = "XTS-AES-128 #4",
+    .key1        = {0x27, 0x18, 0x28, 0x18, 0x28, 0x45, 0x90, 0x45,
+                    0x23, 0x53, 0x60, 0x28, 0x74, 0x71, 0x35, 0x26},
+    .key2        = {0x31, 0x41, 0x59, 0x26, 0x53, 0x58, 0x97, 0x93,
+                    0x23, 0x84, 0x62, 0x64, 0x33, 0x83, 0x27, 0x95},
+    .plaintext   = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+                    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+                    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+                    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+                    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+                    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+                    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f},
+    .ciphertext  = {0x27, 0xa7, 0x47, 0x9b, 0xef, 0xa1, 0xd4, 0x76,
+                    0x48, 0x9f, 0x30, 0x8c, 0xd4, 0xcf, 0xa6, 0xe2,
+                    0xa9, 0x6e, 0x4b, 0xbe, 0x32, 0x08, 0xff, 0x25,
+                    0x28, 0x7d, 0xd3, 0x81, 0x96, 0x16, 0xe8, 0x9c,
+                    0xc7, 0x8c, 0xf7, 0xf5, 0xe5, 0x43, 0x44, 0x5f,
+                    0x83, 0x33, 0xd8, 0xfa, 0x7f, 0x56, 0x00, 0x00,
+                    0x05, 0x27, 0x9f, 0xa5, 0xd8, 0xb5, 0xe4, 0xad,
+                    0x40, 0xe7, 0x36, 0xdd, 0xb4, 0xd3, 0x54, 0x12},
+    .tweak       = {0x00},
+    .sectorSize  = 64
+};
+static TestVector const testVectorXTSAES128_15 PROGMEM = {
+    .name        = "XTS-AES-128 #15",
+    .key1        = {0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
+                    0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0},
+    .key2        = {0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8,
+                    0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0},
+    .plaintext   = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+                    0x10},
+    .ciphertext  = {0x6c, 0x16, 0x25, 0xdb, 0x46, 0x71, 0x52, 0x2d,
+                    0x3d, 0x75, 0x99, 0x60, 0x1d, 0xe7, 0xca, 0x09,
+                    0xed},
+    .tweak       = {0x9a, 0x78, 0x56, 0x34, 0x12},
+    .sectorSize  = 17
+};
+// This test vector is from:
+// https://github.com/heisencoder/XTS-AES/blob/master/testvals/xts.4
+// We use this one because the main specification doesn't have an odd
+// block size greater than 32 bytes but less than 64 bytes.
+static TestVector const testVectorXTSAES128_16 PROGMEM = {
+    .name        = "XTS-AES-128 #16",
+    .key1        = {0x27, 0x18, 0x28, 0x18, 0x28, 0x45, 0x90, 0x45,
+                    0x23, 0x53, 0x60, 0x28, 0x74, 0x71, 0x35, 0x26},
+    .key2        = {0x31, 0x41, 0x59, 0x26, 0x53, 0x58, 0x97, 0x93,
+                    0x23, 0x84, 0x62, 0x64, 0x33, 0x83, 0x27, 0x95},
+    .plaintext   = {0x50, 0x00, 0xec, 0xa5, 0xa1, 0xf6, 0xa4, 0x93,
+                    0x78, 0x03, 0x0d, 0x9e, 0xe8, 0x05, 0xac, 0xef,
+                    0x46, 0x0f, 0x31, 0x4e, 0xe0, 0x4b, 0xb5, 0x14,
+                    0x03, 0x4e, 0xb2, 0x7f, 0xb8, 0xdf, 0x2b, 0xc8,
+                    0x12, 0xae, 0x5b, 0xdf, 0x8c},
+    .ciphertext  = {0xe5, 0x9e, 0x6f, 0x23, 0x3b, 0xe0, 0xe0, 0x83,
+                    0x04, 0x83, 0xc6, 0xbd, 0x4e, 0x82, 0xf4, 0xc3,
+                    0x95, 0x43, 0x55, 0x8a, 0x25, 0xe3, 0xdb, 0x60,
+                    0xa5, 0x53, 0xa5, 0x94, 0x81, 0x45, 0xa6, 0xff,
+                    0xb5, 0xe6, 0xbe, 0x1d, 0xb5},
+    .tweak       = {0x33, 0x22, 0x11, 0x00},
+    .sectorSize  = 37
+};
+
+XTS<AES128> *xtsaes128;
+TestVector testVector;
+
+byte buffer[MAX_SECTOR_SIZE];
+
+#if defined(__AVR__)
+
+void _printProgMem(const char *str)
+{
+    for (;;) {
+        uint8_t ch = pgm_read_byte((const uint8_t *)str);
+        if (!ch)
+            break;
+        Serial.write(ch);
+        ++str;
+    }
+}
+
+#define printProgMem(str) \
+    do { \
+        static char const temp_str[] PROGMEM = str; \
+        _printProgMem(temp_str); \
+    } while (0)
+
+#define printlnProgMem(str) \
+    do { \
+        static char const temp_str[] PROGMEM = str; \
+        _printProgMem(temp_str); \
+        Serial.println(); \
+    } while (0)
+
+#else
+
+#define printProgMem(str) \
+    Serial.print(str)
+
+#define printlnProgMem(str) \
+    Serial.println(str)
+
+#endif
+
+void testXTS(XTSCommon *cipher, const struct TestVector *test)
+{
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(testVector));
+
+    Serial.print(testVector.name);
+    printProgMem(" Encrypt ... ");
+
+    cipher->setSectorSize(testVector.sectorSize);
+    cipher->setKey(testVector.key1, 32);
+    cipher->setTweak(testVector.tweak, sizeof(testVector.tweak));
+    cipher->encryptSector(buffer, testVector.plaintext);
+
+    if (!memcmp(buffer, testVector.ciphertext, testVector.sectorSize))
+        printlnProgMem("Passed");
+    else
+        printlnProgMem("Failed");
+
+    Serial.print(testVector.name);
+    printProgMem(" Decrypt ... ");
+
+    cipher->decryptSector(buffer, testVector.ciphertext);
+
+    if (!memcmp(buffer, testVector.plaintext, testVector.sectorSize))
+        printlnProgMem("Passed");
+    else
+        printlnProgMem("Failed");
+
+    Serial.print(testVector.name);
+    printProgMem(" Encrypt In-Place ... ");
+
+    memcpy(buffer, testVector.plaintext, testVector.sectorSize);
+    cipher->encryptSector(buffer, buffer);
+
+    if (!memcmp(buffer, testVector.ciphertext, testVector.sectorSize))
+        printlnProgMem("Passed");
+    else
+        printlnProgMem("Failed");
+
+    Serial.print(testVector.name);
+    printProgMem(" Decrypt In-Place ... ");
+
+    memcpy(buffer, testVector.ciphertext, testVector.sectorSize);
+    cipher->decryptSector(buffer, buffer);
+
+    if (!memcmp(buffer, testVector.plaintext, testVector.sectorSize))
+        printlnProgMem("Passed");
+    else
+        printlnProgMem("Failed");
+}
+
+void perfEncrypt(const char *name, XTSCommon *cipher, const struct TestVector *test, size_t keySize = 32)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(testVector));
+
+    Serial.print(name);
+    printProgMem(" ... ");
+
+    cipher->setSectorSize(sizeof(buffer));
+    cipher->setKey(testVector.key1, keySize);
+    cipher->setTweak(testVector.tweak, sizeof(testVector.tweak));
+    memset(buffer, 0xAA, sizeof(buffer));
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        cipher->encryptSector(buffer, buffer);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 500.0));
+    printProgMem("us per byte, ");
+    Serial.print((sizeof(buffer) * 500.0 * 1000000.0) / elapsed);
+    printlnProgMem(" bytes per second");
+}
+
+void perfDecrypt(const char *name, XTSCommon *cipher, const struct TestVector *test, size_t keySize = 32)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(testVector));
+
+    Serial.print(name);
+    printProgMem(" ... ");
+
+    cipher->setSectorSize(sizeof(buffer));
+    cipher->setKey(testVector.key1, keySize);
+    cipher->setTweak(testVector.tweak, sizeof(testVector.tweak));
+    start = micros();
+    for (count = 0; count < 500; ++count) {
+        cipher->decryptSector(buffer, buffer);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / (sizeof(buffer) * 500.0));
+    printProgMem("us per byte, ");
+    Serial.print((sizeof(buffer) * 500.0 * 1000000.0) / elapsed);
+    printlnProgMem(" bytes per second");
+}
+
+void perfSetKey(const char *name, XTSCommon *cipher, const struct TestVector *test, size_t keySize = 32)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(testVector));
+
+    Serial.print(name);
+    printProgMem(" ... ");
+
+    start = micros();
+    for (count = 0; count < 2000; ++count) {
+        cipher->setKey(testVector.key1, keySize);
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 2000.0);
+    printProgMem("us per operation, ");
+    Serial.print((2000.0 * 1000000.0) / elapsed);
+    printlnProgMem(" operations per second");
+}
+
+void perfSetTweak(const char *name, XTSCommon *cipher, const struct TestVector *test)
+{
+    unsigned long start;
+    unsigned long elapsed;
+    int count;
+
+    crypto_feed_watchdog();
+
+    memcpy_P(&testVector, test, sizeof(testVector));
+
+    Serial.print(name);
+    printProgMem(" ... ");
+
+    start = micros();
+    for (count = 0; count < 2000; ++count) {
+        cipher->setTweak(testVector.tweak, sizeof(testVector.tweak));
+    }
+    elapsed = micros() - start;
+
+    Serial.print(elapsed / 2000.0);
+    printProgMem("us per operation, ");
+    Serial.print((2000.0 * 1000000.0) / elapsed);
+    printlnProgMem(" operations per second");
+}
+
+void setup()
+{
+    Serial.begin(9600);
+
+    Serial.println();
+
+    xtsaes128 = new XTS<AES128>();
+    printlnProgMem("State Sizes:");
+    printProgMem("XTS<AES128> ... ");
+    Serial.println(sizeof(*xtsaes128));
+    printProgMem("XTS<AES256> ... ");
+    Serial.println(sizeof(XTS<AES256>));
+    printProgMem("XTS<Speck> ... ");
+    Serial.println(sizeof(XTS<Speck>));
+    printProgMem("XTS<SpeckSmall> ... ");
+    Serial.println(sizeof(XTS<SpeckSmall>));
+    printProgMem("XTS<SpeckSmall, SpeckTiny> ... ");
+    Serial.println(sizeof(XTS<SpeckSmall, SpeckTiny>));
+
+    printProgMem("XTSSingleKey<AES128> ... ");
+    Serial.println(sizeof(XTSSingleKey<AES128>));
+    printProgMem("XTSSingleKey<AES256> ... ");
+    Serial.println(sizeof(XTSSingleKey<AES256>));
+    printProgMem("XTSSingleKey<Speck> ... ");
+    Serial.println(sizeof(XTSSingleKey<Speck>));
+    printProgMem("XTSSingleKey<SpeckSmall> ... ");
+    Serial.println(sizeof(XTSSingleKey<SpeckSmall>));
+
+    Serial.println();
+
+    printlnProgMem("Test Vectors:");
+    testXTS(xtsaes128, &testVectorXTSAES128_1);
+    testXTS(xtsaes128, &testVectorXTSAES128_2);
+    testXTS(xtsaes128, &testVectorXTSAES128_3);
+    testXTS(xtsaes128, &testVectorXTSAES128_4);
+    testXTS(xtsaes128, &testVectorXTSAES128_15);
+    testXTS(xtsaes128, &testVectorXTSAES128_16);
+
+    Serial.println();
+
+    printlnProgMem("Performance Tests:");
+    Serial.println();
+
+    printlnProgMem("XTS-AES-128:");
+    perfEncrypt("Encrypt", xtsaes128, &testVectorXTSAES128_4);
+    perfDecrypt("Decrypt", xtsaes128, &testVectorXTSAES128_4);
+    perfSetKey("Set Key", xtsaes128, &testVectorXTSAES128_4);
+    perfSetTweak("Set Tweak", xtsaes128, &testVectorXTSAES128_4);
+    delete xtsaes128;
+    Serial.println();
+
+    printlnProgMem("XTS-AES-128 Single Key:");
+    XTSSingleKey<AES128> *singleaes128 = new XTSSingleKey<AES128>();
+    perfEncrypt("Encrypt", singleaes128, &testVectorXTSAES128_4, 16);
+    perfDecrypt("Decrypt", singleaes128, &testVectorXTSAES128_4, 16);
+    perfSetKey("Set Key", singleaes128, &testVectorXTSAES128_4, 16);
+    perfSetTweak("Set Tweak", singleaes128, &testVectorXTSAES128_4);
+    delete singleaes128;
+    Serial.println();
+
+    printlnProgMem("XTS-AES-256 Single Key:");
+    XTSSingleKey<AES256> *xtsaes256 = new XTSSingleKey<AES256>();
+    perfEncrypt("Encrypt", xtsaes256, &testVectorXTSAES128_4, 32);
+    perfDecrypt("Decrypt", xtsaes256, &testVectorXTSAES128_4, 32);
+    perfSetKey("Set Key", xtsaes256, &testVectorXTSAES128_4, 32);
+    perfSetTweak("Set Tweak", xtsaes256, &testVectorXTSAES128_4);
+    delete xtsaes256;
+    Serial.println();
+
+    printlnProgMem("XTS-SpeckSmall-256:");
+    XTS<SpeckSmall, SpeckTiny> *xtsspeck = new XTS<SpeckSmall, SpeckTiny>();
+    perfEncrypt("Encrypt", xtsspeck, &testVectorXTSAES128_4, 64);
+    perfDecrypt("Decrypt", xtsspeck, &testVectorXTSAES128_4, 64);
+    perfSetKey("Set Key", xtsspeck, &testVectorXTSAES128_4, 64);
+    perfSetTweak("Set Tweak", xtsspeck, &testVectorXTSAES128_4);
+    delete xtsspeck;
+    Serial.println();
+
+    printlnProgMem("XTS-SpeckSmall-256 Single Key:");
+    XTSSingleKey<SpeckSmall> *singlespeck = new XTSSingleKey<SpeckSmall>();
+    perfEncrypt("Encrypt", singlespeck, &testVectorXTSAES128_4, 32);
+    perfDecrypt("Decrypt", singlespeck, &testVectorXTSAES128_4, 32);
+    perfSetKey("Set Key", singlespeck, &testVectorXTSAES128_4, 32);
+    perfSetTweak("Set Tweak", singlespeck, &testVectorXTSAES128_4);
+    delete singlespeck;
+    Serial.println();
+
+    printlnProgMem("XTS-Speck-256:");
+    XTS<Speck> *xtsspeck2 = new XTS<Speck>();
+    perfEncrypt("Encrypt", xtsspeck2, &testVectorXTSAES128_4, 64);
+    perfDecrypt("Decrypt", xtsspeck2, &testVectorXTSAES128_4, 64);
+    perfSetKey("Set Key", xtsspeck2, &testVectorXTSAES128_4, 64);
+    perfSetTweak("Set Tweak", xtsspeck2, &testVectorXTSAES128_4);
+    delete xtsspeck2;
+    Serial.println();
+
+    printlnProgMem("XTS-Speck-256 Single Key:");
+    XTSSingleKey<Speck> *singlespeck2 = new XTSSingleKey<Speck>();
+    perfEncrypt("Encrypt", singlespeck2, &testVectorXTSAES128_4, 32);
+    perfDecrypt("Decrypt", singlespeck2, &testVectorXTSAES128_4, 32);
+    perfSetKey("Set Key", singlespeck2, &testVectorXTSAES128_4, 32);
+    perfSetTweak("Set Tweak", singlespeck2, &testVectorXTSAES128_4);
+    delete singlespeck2;
+    Serial.println();
+}
+
+void loop()
+{
+}
diff --git a/src/modules/Crypto/keywords.txt b/src/modules/Crypto/keywords.txt
new file mode 100644
index 0000000..068542f
--- /dev/null
+++ b/src/modules/Crypto/keywords.txt
@@ -0,0 +1,71 @@
+AES128	KEYWORD1
+AES192	KEYWORD1
+AES256	KEYWORD1
+AESTiny128	KEYWORD1
+AESTiny256	KEYWORD1
+AESSmall128	KEYWORD1
+AESSmall256	KEYWORD1
+ChaCha	KEYWORD1
+ChaChaPoly	KEYWORD1
+
+BLAKE2b	KEYWORD1
+BLAKE2s	KEYWORD1
+SHA256	KEYWORD1
+SHA512	KEYWORD1
+SHA3_256	KEYWORD1
+SHA3_512	KEYWORD1
+KeccakCore	KEYWORD1
+Poly1305	KEYWORD1
+GHASH	KEYWORD1
+OMAC	KEYWORD1
+GF128	KEYWORD1
+
+SHAKE128	KEYWORD1
+SHAKE256	KEYWORD1
+
+Curve25519	KEYWORD1
+Ed25519	KEYWORD1
+
+CBC	KEYWORD1
+CFB	KEYWORD1
+CTR	KEYWORD1
+OFB	KEYWORD1
+GCM	KEYWORD1
+EAX	KEYWORD1
+
+RNG	KEYWORD1
+
+keySize	KEYWORD2
+ivSize	KEYWORD2
+tagSize	KEYWORD2
+setKey	KEYWORD2
+setIV	KEYWORD2
+encrypt	KEYWORD2
+decrypt	KEYWORD2
+clear	KEYWORD2
+addAuthData	KEYWORD2
+
+hashSize	KEYWORD2
+blockSize	KEYWORD2
+reset	KEYWORD2
+update	KEYWORD2
+finalize	KEYWORD2
+
+begin	KEYWORD2
+setAutoSaveTime	KEYWORD2
+rand	KEYWORD2
+available	KEYWORD2
+stir	KEYWORD2
+save	KEYWORD2
+loop	KEYWORD2
+destroy	KEYWORD2
+calibrating	KEYWORD2
+
+eval	KEYWORD2
+dh1	KEYWORD2
+dh2	KEYWORD2
+
+sign	KEYWORD2
+verify	KEYWORD2
+generatePrivateKey	KEYWORD2
+derivePublicKey	KEYWORD2
diff --git a/src/modules/Crypto/library.json b/src/modules/Crypto/library.json
new file mode 100644
index 0000000..d6343bc
--- /dev/null
+++ b/src/modules/Crypto/library.json
@@ -0,0 +1,22 @@
+{
+    "name": "Crypto",
+    "version": "0.2.0",
+    "keywords": "AES128,AES192,AES256,Speck,CTR,CFB,CBC,OFB,EAX,GCM,XTS,ChaCha,ChaChaPoly,EAX,GCM,SHA256,SHA512,SHA3_256,SHA3_512,BLAKE2s,BLAKE2b,SHAKE128,SHAKE256,Poly1305,GHASH,OMAC,Curve25519,Ed25519,P521,RNG,NOISE",
+    "description": "Arduino CryptoLibs - All cryptographic algorithms have been optimized for 8-bit Arduino platforms like the Uno",
+    "authors":
+    {
+        "name": "Rhys Weatherley",
+        "email": "rhys.weatherley@gmail.com",
+        "url": "https://rweather.github.io/arduinolibs/crypto.html"
+    },
+    "export": {
+      "include": "libraries/Crypto"
+    },
+    "repository":
+    {
+        "type": "git",
+        "url": "https://github.com/rweather/arduinolibs.git"
+    },
+    "frameworks": "arduino",
+    "platforms": "*"
+}
diff --git a/src/modules/Crypto/utility/EndianUtil.h b/src/modules/Crypto/utility/EndianUtil.h
new file mode 100644
index 0000000..b4e31aa
--- /dev/null
+++ b/src/modules/Crypto/utility/EndianUtil.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_ENDIANUTIL_H
+#define CRYPTO_ENDIANUTIL_H
+
+#include <inttypes.h>
+
+#if !defined(HOST_BUILD)
+
+// CPU is assumed to be little endian.   Edit this file if you
+// need to port this library to a big endian CPU.
+
+#define CRYPTO_LITTLE_ENDIAN 1
+
+#define htole16(x)  (x)
+#define le16toh(x)  (x)
+#define htobe16(x)  \
+    (__extension__ ({ \
+        uint16_t _temp = (x); \
+        ((_temp >> 8) & 0x00FF) | \
+        ((_temp << 8) & 0xFF00); \
+    }))
+#define be16toh(x)  (htobe16((x)))
+
+#define htole32(x)  (x)
+#define le32toh(x)  (x)
+#define htobe32(x)  \
+    (__extension__ ({ \
+        uint32_t _temp = (x); \
+        ((_temp >> 24) & 0x000000FF) | \
+        ((_temp >>  8) & 0x0000FF00) | \
+        ((_temp <<  8) & 0x00FF0000) | \
+        ((_temp << 24) & 0xFF000000); \
+    }))
+#define be32toh(x)  (htobe32((x)))
+
+#define htole64(x)  (x)
+#define le64toh(x)  (x)
+#define htobe64(x)  \
+    (__extension__ ({ \
+        uint64_t __temp = (x); \
+        uint32_t __low = htobe32((uint32_t)__temp); \
+        uint32_t __high = htobe32((uint32_t)(__temp >> 32)); \
+        (((uint64_t)__low) << 32) | __high; \
+    }))
+#define be64toh(x)  (htobe64((x)))
+
+#else // HOST_BUILD
+
+#include <endian.h>
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define CRYPTO_LITTLE_ENDIAN 1
+#endif
+
+#endif // HOST_BUILD
+
+#endif
diff --git a/src/modules/Crypto/utility/LimbUtil.h b/src/modules/Crypto/utility/LimbUtil.h
new file mode 100644
index 0000000..2451f43
--- /dev/null
+++ b/src/modules/Crypto/utility/LimbUtil.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_LIMBUTIL_H
+#define CRYPTO_LIMBUTIL_H
+
+#include "ProgMemUtil.h"
+
+// Number of limbs in a big number value of various sizes.
+#define NUM_LIMBS_BITS(n) \
+    (((n) + sizeof(limb_t) * 8 - 1) / (8 * sizeof(limb_t)))
+#define NUM_LIMBS_128BIT NUM_LIMBS_BITS(128)
+#define NUM_LIMBS_256BIT NUM_LIMBS_BITS(256)
+#define NUM_LIMBS_512BIT NUM_LIMBS_BITS(512)
+
+// The number of bits in a limb.
+#define LIMB_BITS   (8 * sizeof(limb_t))
+
+// Read a limb-sized quantity from program memory.
+#if BIGNUMBER_LIMB_8BIT
+#define pgm_read_limb(x)    (pgm_read_byte((x)))
+#elif BIGNUMBER_LIMB_16BIT
+#define pgm_read_limb(x)    (pgm_read_word((x)))
+#elif BIGNUMBER_LIMB_32BIT
+#define pgm_read_limb(x)    (pgm_read_dword((x)))
+#elif BIGNUMBER_LIMB_64BIT
+#define pgm_read_limb(x)    (pgm_read_qword((x)))
+#endif
+
+// Expand a 32-bit value into a set of limbs depending upon the limb size.
+// This is used when initializing constant big number values in the code.
+// For 64-bit system compatibility it is necessary to use LIMB_PAIR(x, y).
+#if BIGNUMBER_LIMB_8BIT
+#define LIMB(value)     ((uint8_t)(value)), \
+                        ((uint8_t)((value) >> 8)), \
+                        ((uint8_t)((value) >> 16)), \
+                        ((uint8_t)((value) >> 24))
+#define LIMB_PAIR(x,y)  LIMB((x)), LIMB((y))
+#elif BIGNUMBER_LIMB_16BIT
+#define LIMB(value)     ((uint16_t)(value)), \
+                        ((uint16_t)(((uint32_t)(value)) >> 16))
+#define LIMB_PAIR(x,y)  LIMB((x)), LIMB((y))
+#elif BIGNUMBER_LIMB_32BIT
+#define LIMB(value)     (value)
+#define LIMB_PAIR(x,y)  LIMB((x)), LIMB((y))
+#elif BIGNUMBER_LIMB_64BIT
+#define LIMB(value)     (value)
+#define LIMB_PAIR(x,y)  ((((uint64_t)(y)) << 32) | ((uint64_t)(x)))
+#endif
+
+#endif
diff --git a/src/modules/Crypto/utility/ProgMemUtil.h b/src/modules/Crypto/utility/ProgMemUtil.h
new file mode 100644
index 0000000..d7588e8
--- /dev/null
+++ b/src/modules/Crypto/utility/ProgMemUtil.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_PROGMEMUTIL_H
+#define CRYPTO_PROGMEMUTIL_H
+
+#if defined(__AVR__)
+#include <avr/pgmspace.h>
+#define pgm_read_qword(x)   \
+    (__extension__ ({ \
+        const uint32_t *_temp = (const uint32_t *)(x); \
+        ((uint64_t)pgm_read_dword(_temp)) | \
+        (((uint64_t)pgm_read_dword(_temp + 1)) << 32); \
+    }))
+#elif defined(ESP8266) || defined(ESP32)
+#include <pgmspace.h>
+#define pgm_read_qword(x)   \
+    (__extension__ ({ \
+        const uint32_t *_temp = (const uint32_t *)(x); \
+        ((uint64_t)pgm_read_dword(_temp)) | \
+        (((uint64_t)pgm_read_dword(_temp + 1)) << 32); \
+    }))
+#else
+#include <string.h>
+#define PROGMEM
+#define pgm_read_byte(x)    (*(x))
+#define pgm_read_word(x)    (*(x))
+#define pgm_read_dword(x)   (*(x))
+#define pgm_read_qword(x)   (*(x))
+#define memcpy_P(d,s,l)     memcpy((d), (s), (l))
+#endif
+
+#endif
diff --git a/src/modules/Crypto/utility/RotateUtil.h b/src/modules/Crypto/utility/RotateUtil.h
new file mode 100644
index 0000000..fcabc82
--- /dev/null
+++ b/src/modules/Crypto/utility/RotateUtil.h
@@ -0,0 +1,696 @@
+/*
+ * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef CRYPTO_ROTATEUTIL_H
+#define CRYPTO_ROTATEUTIL_H
+
+#include <inttypes.h>
+
+// Rotation functions that are optimised for best performance on AVR.
+// The most efficient rotations are where the number of bits is 1 or a
+// multiple of 8, so we compose the efficient rotations to produce all
+// other rotation counts of interest.
+
+#if defined(__AVR__)
+#define CRYPTO_ROTATE32_COMPOSED 1
+#define CRYPTO_ROTATE64_COMPOSED 0
+#else
+#define CRYPTO_ROTATE32_COMPOSED 0
+#define CRYPTO_ROTATE64_COMPOSED 0
+#endif
+
+#if CRYPTO_ROTATE32_COMPOSED
+
+// Rotation macros for 32-bit arguments.
+
+// Generic left rotate - best performance when "bits" is 1 or a multiple of 8.
+#define leftRotate(a, bits) \
+    (__extension__ ({ \
+        uint32_t _temp = (a); \
+        (_temp << (bits)) | (_temp >> (32 - (bits))); \
+    }))
+
+// Generic right rotate - best performance when "bits" is 1 or a multiple of 8.
+#define rightRotate(a, bits) \
+    (__extension__ ({ \
+        uint32_t _temp = (a); \
+        (_temp >> (bits)) | (_temp << (32 - (bits))); \
+    }))
+
+// Left rotate by 1.
+#define leftRotate1(a)  (leftRotate((a), 1))
+
+// Left rotate by 2.
+#define leftRotate2(a)  (leftRotate(leftRotate((a), 1), 1))
+
+// Left rotate by 3.
+#define leftRotate3(a)  (leftRotate(leftRotate(leftRotate((a), 1), 1), 1))
+
+// Left rotate by 4.
+#define leftRotate4(a)  (leftRotate(leftRotate(leftRotate(leftRotate((a), 1), 1), 1), 1))
+
+// Left rotate by 5: Rotate left by 8, then right by 3.
+#define leftRotate5(a)  (rightRotate(rightRotate(rightRotate(leftRotate((a), 8), 1), 1), 1))
+
+// Left rotate by 6: Rotate left by 8, then right by 2.
+#define leftRotate6(a)  (rightRotate(rightRotate(leftRotate((a), 8), 1), 1))
+
+// Left rotate by 7: Rotate left by 8, then right by 1.
+#define leftRotate7(a)  (rightRotate(leftRotate((a), 8), 1))
+
+// Left rotate by 8.
+#define leftRotate8(a)  (leftRotate((a), 8))
+
+// Left rotate by 9: Rotate left by 8, then left by 1.
+#define leftRotate9(a)  (leftRotate(leftRotate((a), 8), 1))
+
+// Left rotate by 10: Rotate left by 8, then left by 2.
+#define leftRotate10(a) (leftRotate(leftRotate(leftRotate((a), 8), 1), 1))
+
+// Left rotate by 11: Rotate left by 8, then left by 3.
+#define leftRotate11(a) (leftRotate(leftRotate(leftRotate(leftRotate((a), 8), 1), 1), 1))
+
+// Left rotate by 12: Rotate left by 16, then right by 4.
+#define leftRotate12(a) (rightRotate(rightRotate(rightRotate(rightRotate(leftRotate((a), 16), 1), 1), 1), 1))
+
+// Left rotate by 13: Rotate left by 16, then right by 3.
+#define leftRotate13(a) (rightRotate(rightRotate(rightRotate(leftRotate((a), 16), 1), 1), 1))
+
+// Left rotate by 14: Rotate left by 16, then right by 2.
+#define leftRotate14(a) (rightRotate(rightRotate(leftRotate((a), 16), 1), 1))
+
+// Left rotate by 15: Rotate left by 16, then right by 1.
+#define leftRotate15(a) (rightRotate(leftRotate((a), 16), 1))
+
+// Left rotate by 16.
+#define leftRotate16(a) (leftRotate((a), 16))
+
+// Left rotate by 17: Rotate left by 16, then left by 1.
+#define leftRotate17(a) (leftRotate(leftRotate((a), 16), 1))
+
+// Left rotate by 18: Rotate left by 16, then left by 2.
+#define leftRotate18(a) (leftRotate(leftRotate(leftRotate((a), 16), 1), 1))
+
+// Left rotate by 19: Rotate left by 16, then left by 3.
+#define leftRotate19(a) (leftRotate(leftRotate(leftRotate(leftRotate((a), 16), 1), 1), 1))
+
+// Left rotate by 20: Rotate left by 16, then left by 4.
+#define leftRotate20(a) (leftRotate(leftRotate(leftRotate(leftRotate(leftRotate((a), 16), 1), 1), 1), 1))
+
+// Left rotate by 21: Rotate left by 24, then right by 3.
+#define leftRotate21(a) (rightRotate(rightRotate(rightRotate(leftRotate((a), 24), 1), 1), 1))
+
+// Left rotate by 22: Rotate left by 24, then right by 2.
+#define leftRotate22(a) (rightRotate(rightRotate(leftRotate((a), 24), 1), 1))
+
+// Left rotate by 23: Rotate left by 24, then right by 1.
+#define leftRotate23(a) (rightRotate(leftRotate((a), 24), 1))
+
+// Left rotate by 24.
+#define leftRotate24(a) (leftRotate((a), 24))
+
+// Left rotate by 25: Rotate left by 24, then left by 1.
+#define leftRotate25(a) (leftRotate(leftRotate((a), 24), 1))
+
+// Left rotate by 26: Rotate left by 24, then left by 2.
+#define leftRotate26(a) (leftRotate(leftRotate(leftRotate((a), 24), 1), 1))
+
+// Left rotate by 27: Rotate left by 24, then left by 3.
+#define leftRotate27(a) (leftRotate(leftRotate(leftRotate(leftRotate((a), 24), 1), 1), 1))
+
+// Left rotate by 28: Rotate right by 4.
+#define leftRotate28(a) (rightRotate(rightRotate(rightRotate(rightRotate((a), 1), 1), 1), 1))
+
+// Left rotate by 29: Rotate right by 3.
+#define leftRotate29(a) (rightRotate(rightRotate(rightRotate((a), 1), 1), 1))
+
+// Left rotate by 30: Rotate right by 2.
+#define leftRotate30(a) (rightRotate(rightRotate((a), 1), 1))
+
+// Left rotate by 31: Rotate right by 1.
+#define leftRotate31(a) (rightRotate((a), 1))
+
+// Define the 32-bit right rotations in terms of left rotations.
+#define rightRotate1(a)  (leftRotate31((a)))
+#define rightRotate2(a)  (leftRotate30((a)))
+#define rightRotate3(a)  (leftRotate29((a)))
+#define rightRotate4(a)  (leftRotate28((a)))
+#define rightRotate5(a)  (leftRotate27((a)))
+#define rightRotate6(a)  (leftRotate26((a)))
+#define rightRotate7(a)  (leftRotate25((a)))
+#define rightRotate8(a)  (leftRotate24((a)))
+#define rightRotate9(a)  (leftRotate23((a)))
+#define rightRotate10(a) (leftRotate22((a)))
+#define rightRotate11(a) (leftRotate21((a)))
+#define rightRotate12(a) (leftRotate20((a)))
+#define rightRotate13(a) (leftRotate19((a)))
+#define rightRotate14(a) (leftRotate18((a)))
+#define rightRotate15(a) (leftRotate17((a)))
+#define rightRotate16(a) (leftRotate16((a)))
+#define rightRotate17(a) (leftRotate15((a)))
+#define rightRotate18(a) (leftRotate14((a)))
+#define rightRotate19(a) (leftRotate13((a)))
+#define rightRotate20(a) (leftRotate12((a)))
+#define rightRotate21(a) (leftRotate11((a)))
+#define rightRotate22(a) (leftRotate10((a)))
+#define rightRotate23(a) (leftRotate9((a)))
+#define rightRotate24(a) (leftRotate8((a)))
+#define rightRotate25(a) (leftRotate7((a)))
+#define rightRotate26(a) (leftRotate6((a)))
+#define rightRotate27(a) (leftRotate5((a)))
+#define rightRotate28(a) (leftRotate4((a)))
+#define rightRotate29(a) (leftRotate3((a)))
+#define rightRotate30(a) (leftRotate2((a)))
+#define rightRotate31(a) (leftRotate1((a)))
+
+#else // !CRYPTO_ROTATE32_COMPOSED
+
+// Generic rotation functions.  All bit shifts are considered to have
+// similar performance.  Usually true of 32-bit and higher platforms.
+
+// Rotation macros for 32-bit arguments.
+
+// Generic left rotate.
+#define leftRotate(a, bits) \
+    (__extension__ ({ \
+        uint32_t _temp = (a); \
+        (_temp << (bits)) | (_temp >> (32 - (bits))); \
+    }))
+
+// Generic right rotate.
+#define rightRotate(a, bits) \
+    (__extension__ ({ \
+        uint32_t _temp = (a); \
+        (_temp >> (bits)) | (_temp << (32 - (bits))); \
+    }))
+
+// Left rotate by a specific number of bits.
+#define leftRotate1(a)  (leftRotate((a), 1))
+#define leftRotate2(a)  (leftRotate((a), 2))
+#define leftRotate3(a)  (leftRotate((a), 3))
+#define leftRotate4(a)  (leftRotate((a), 4))
+#define leftRotate5(a)  (leftRotate((a), 5))
+#define leftRotate6(a)  (leftRotate((a), 6))
+#define leftRotate7(a)  (leftRotate((a), 7))
+#define leftRotate8(a)  (leftRotate((a), 8))
+#define leftRotate9(a)  (leftRotate((a), 9))
+#define leftRotate10(a) (leftRotate((a), 10))
+#define leftRotate11(a) (leftRotate((a), 11))
+#define leftRotate12(a) (leftRotate((a), 12))
+#define leftRotate13(a) (leftRotate((a), 13))
+#define leftRotate14(a) (leftRotate((a), 14))
+#define leftRotate15(a) (leftRotate((a), 15))
+#define leftRotate16(a) (leftRotate((a), 16))
+#define leftRotate17(a) (leftRotate((a), 17))
+#define leftRotate18(a) (leftRotate((a), 18))
+#define leftRotate19(a) (leftRotate((a), 19))
+#define leftRotate20(a) (leftRotate((a), 20))
+#define leftRotate21(a) (leftRotate((a), 21))
+#define leftRotate22(a) (leftRotate((a), 22))
+#define leftRotate23(a) (leftRotate((a), 23))
+#define leftRotate24(a) (leftRotate((a), 24))
+#define leftRotate25(a) (leftRotate((a), 25))
+#define leftRotate26(a) (leftRotate((a), 26))
+#define leftRotate27(a) (leftRotate((a), 27))
+#define leftRotate28(a) (leftRotate((a), 28))
+#define leftRotate29(a) (leftRotate((a), 29))
+#define leftRotate30(a) (leftRotate((a), 30))
+#define leftRotate31(a) (leftRotate((a), 31))
+
+// Right rotate by a specific number of bits.
+#define rightRotate1(a)  (rightRotate((a), 1))
+#define rightRotate2(a)  (rightRotate((a), 2))
+#define rightRotate3(a)  (rightRotate((a), 3))
+#define rightRotate4(a)  (rightRotate((a), 4))
+#define rightRotate5(a)  (rightRotate((a), 5))
+#define rightRotate6(a)  (rightRotate((a), 6))
+#define rightRotate7(a)  (rightRotate((a), 7))
+#define rightRotate8(a)  (rightRotate((a), 8))
+#define rightRotate9(a)  (rightRotate((a), 9))
+#define rightRotate10(a) (rightRotate((a), 10))
+#define rightRotate11(a) (rightRotate((a), 11))
+#define rightRotate12(a) (rightRotate((a), 12))
+#define rightRotate13(a) (rightRotate((a), 13))
+#define rightRotate14(a) (rightRotate((a), 14))
+#define rightRotate15(a) (rightRotate((a), 15))
+#define rightRotate16(a) (rightRotate((a), 16))
+#define rightRotate17(a) (rightRotate((a), 17))
+#define rightRotate18(a) (rightRotate((a), 18))
+#define rightRotate19(a) (rightRotate((a), 19))
+#define rightRotate20(a) (rightRotate((a), 20))
+#define rightRotate21(a) (rightRotate((a), 21))
+#define rightRotate22(a) (rightRotate((a), 22))
+#define rightRotate23(a) (rightRotate((a), 23))
+#define rightRotate24(a) (rightRotate((a), 24))
+#define rightRotate25(a) (rightRotate((a), 25))
+#define rightRotate26(a) (rightRotate((a), 26))
+#define rightRotate27(a) (rightRotate((a), 27))
+#define rightRotate28(a) (rightRotate((a), 28))
+#define rightRotate29(a) (rightRotate((a), 29))
+#define rightRotate30(a) (rightRotate((a), 30))
+#define rightRotate31(a) (rightRotate((a), 31))
+
+#endif // !CRYPTO_ROTATE32_COMPOSED
+
+#if CRYPTO_ROTATE64_COMPOSED
+
+// Rotation macros for 64-bit arguments.
+
+// Generic left rotate - best performance when "bits" is 1 or a multiple of 8.
+#define leftRotate_64(a, bits) \
+    (__extension__ ({ \
+        uint64_t _temp = (a); \
+        (_temp << (bits)) | (_temp >> (64 - (bits))); \
+    }))
+
+// Generic right rotate - best performance when "bits" is 1 or a multiple of 8.
+#define rightRotate_64(a, bits) \
+    (__extension__ ({ \
+        uint64_t _temp = (a); \
+        (_temp >> (bits)) | (_temp << (64 - (bits))); \
+    }))
+
+// Left rotate by 1.
+#define leftRotate1_64(a)  (leftRotate_64((a), 1))
+
+// Left rotate by 2.
+#define leftRotate2_64(a)  (leftRotate_64(leftRotate_64((a), 1), 1))
+
+// Left rotate by 3.
+#define leftRotate3_64(a)  (leftRotate_64(leftRotate_64(leftRotate_64((a), 1), 1), 1))
+
+// Left rotate by 4.
+#define leftRotate4_64(a)  (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 1), 1), 1), 1))
+
+// Left rotate by 5: Rotate left by 8, then right by 3.
+#define leftRotate5_64(a)  (rightRotate_64(rightRotate_64(rightRotate_64(leftRotate_64((a), 8), 1), 1), 1))
+
+// Left rotate by 6: Rotate left by 8, then right by 2.
+#define leftRotate6_64(a)  (rightRotate_64(rightRotate_64(leftRotate_64((a), 8), 1), 1))
+
+// Left rotate by 7: Rotate left by 8, then right by 1.
+#define leftRotate7_64(a)  (rightRotate_64(leftRotate_64((a), 8), 1))
+
+// Left rotate by 8.
+#define leftRotate8_64(a)  (leftRotate_64((a), 8))
+
+// Left rotate by 9: Rotate left by 8, then left by 1.
+#define leftRotate9_64(a)  (leftRotate_64(leftRotate_64((a), 8), 1))
+
+// Left rotate by 10: Rotate left by 8, then left by 2.
+#define leftRotate10_64(a) (leftRotate_64(leftRotate_64(leftRotate_64((a), 8), 1), 1))
+
+// Left rotate by 11: Rotate left by 8, then left by 3.
+#define leftRotate11_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 8), 1), 1), 1))
+
+// Left rotate by 12: Rotate left by 16, then right by 4.
+#define leftRotate12_64(a) (rightRotate_64(rightRotate_64(rightRotate_64(rightRotate_64(leftRotate_64((a), 16), 1), 1), 1), 1))
+
+// Left rotate by 13: Rotate left by 16, then right by 3.
+#define leftRotate13_64(a) (rightRotate_64(rightRotate_64(rightRotate_64(leftRotate_64((a), 16), 1), 1), 1))
+
+// Left rotate by 14: Rotate left by 16, then right by 2.
+#define leftRotate14_64(a) (rightRotate_64(rightRotate_64(leftRotate_64((a), 16), 1), 1))
+
+// Left rotate by 15: Rotate left by 16, then right by 1.
+#define leftRotate15_64(a) (rightRotate_64(leftRotate_64((a), 16), 1))
+
+// Left rotate by 16.
+#define leftRotate16_64(a) (leftRotate_64((a), 16))
+
+// Left rotate by 17: Rotate left by 16, then left by 1.
+#define leftRotate17_64(a) (leftRotate_64(leftRotate_64((a), 16), 1))
+
+// Left rotate by 18: Rotate left by 16, then left by 2.
+#define leftRotate18_64(a) (leftRotate_64(leftRotate_64(leftRotate_64((a), 16), 1), 1))
+
+// Left rotate by 19: Rotate left by 16, then left by 3.
+#define leftRotate19_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 16), 1), 1), 1))
+
+// Left rotate by 20: Rotate left by 16, then left by 4.
+#define leftRotate20_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 16), 1), 1), 1), 1))
+
+// Left rotate by 21: Rotate left by 24, then right by 3.
+#define leftRotate21_64(a) (rightRotate_64(rightRotate_64(rightRotate_64(leftRotate_64((a), 24), 1), 1), 1))
+
+// Left rotate by 22: Rotate left by 24, then right by 2.
+#define leftRotate22_64(a) (rightRotate_64(rightRotate_64(leftRotate_64((a), 24), 1), 1))
+
+// Left rotate by 23: Rotate left by 24, then right by 1.
+#define leftRotate23_64(a) (rightRotate_64(leftRotate_64((a), 24), 1))
+
+// Left rotate by 24.
+#define leftRotate24_64(a) (leftRotate_64((a), 24))
+
+// Left rotate by 25: Rotate left by 24, then left by 1.
+#define leftRotate25_64(a) (leftRotate_64(leftRotate_64((a), 24), 1))
+
+// Left rotate by 26: Rotate left by 24, then left by 2.
+#define leftRotate26_64(a) (leftRotate_64(leftRotate_64(leftRotate_64((a), 24), 1), 1))
+
+// Left rotate by 27: Rotate left by 24, then left by 3.
+#define leftRotate27_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 24), 1), 1), 1))
+
+// Left rotate by 28: Rotate left by 24, then left by 4.
+#define leftRotate28_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 24), 1), 1), 1), 1))
+
+// Left rotate by 29: Rotate left by 32, then right by 3.
+#define leftRotate29_64(a) (rightRotate_64(rightRotate_64(rightRotate_64(leftRotate_64((a), 32), 1), 1), 1))
+
+// Left rotate by 30: Rotate left by 32, then right by 2.
+#define leftRotate30_64(a) (rightRotate_64(rightRotate_64(leftRotate_64((a), 32), 1), 1))
+
+// Left rotate by 31: Rotate left by 32, then right by 1.
+#define leftRotate31_64(a) (rightRotate_64(leftRotate_64((a), 32), 1))
+
+// Left rotate by 32.
+#define leftRotate32_64(a) (leftRotate_64((a), 32))
+
+// Left rotate by 33: Rotate left by 32, then left by 1.
+#define leftRotate33_64(a) (leftRotate_64(leftRotate_64((a), 32), 1))
+
+// Left rotate by 34: Rotate left by 32, then left by 2.
+#define leftRotate34_64(a) (leftRotate_64(leftRotate_64(leftRotate_64((a), 32), 1), 1))
+
+// Left rotate by 35: Rotate left by 32, then left by 3.
+#define leftRotate35_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 32), 1), 1), 1))
+
+// Left rotate by 36: Rotate left by 32, then left by 4.
+#define leftRotate36_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 32), 1), 1), 1), 1))
+
+// Left rotate by 37: Rotate left by 40, then right by 3.
+#define leftRotate37_64(a) (rightRotate_64(rightRotate_64(rightRotate_64(leftRotate_64((a), 40), 1), 1), 1))
+
+// Left rotate by 38: Rotate left by 40, then right by 2.
+#define leftRotate38_64(a) (rightRotate_64(rightRotate_64(leftRotate_64((a), 40), 1), 1))
+
+// Left rotate by 39: Rotate left by 40, then right by 1.
+#define leftRotate39_64(a) (rightRotate_64(leftRotate_64((a), 40), 1))
+
+// Left rotate by 40.
+#define leftRotate40_64(a) (leftRotate_64((a), 40))
+
+// Left rotate by 41: Rotate left by 40, then left by 1.
+#define leftRotate41_64(a) (leftRotate_64(leftRotate_64((a), 40), 1))
+
+// Left rotate by 42: Rotate left by 40, then left by 2.
+#define leftRotate42_64(a) (leftRotate_64(leftRotate_64(leftRotate_64((a), 40), 1), 1))
+
+// Left rotate by 43: Rotate left by 40, then left by 3.
+#define leftRotate43_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 40), 1), 1), 1))
+
+// Left rotate by 44: Rotate left by 40, then left by 4.
+#define leftRotate44_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 40), 1), 1), 1), 1))
+
+// Left rotate by 45: Rotate left by 48, then right by 3.
+#define leftRotate45_64(a) (rightRotate_64(rightRotate_64(rightRotate_64(leftRotate_64((a), 48), 1), 1), 1))
+
+// Left rotate by 46: Rotate left by 48, then right by 2.
+#define leftRotate46_64(a) (rightRotate_64(rightRotate_64(leftRotate_64((a), 48), 1), 1))
+
+// Left rotate by 47: Rotate left by 48, then right by 1.
+#define leftRotate47_64(a) (rightRotate_64(leftRotate_64((a), 48), 1))
+
+// Left rotate by 48.
+#define leftRotate48_64(a) (leftRotate_64((a), 48))
+
+// Left rotate by 49: Rotate left by 48, then left by 1.
+#define leftRotate49_64(a) (leftRotate_64(leftRotate_64((a), 48), 1))
+
+// Left rotate by 50: Rotate left by 48, then left by 2.
+#define leftRotate50_64(a) (leftRotate_64(leftRotate_64(leftRotate_64((a), 48), 1), 1))
+
+// Left rotate by 51: Rotate left by 48, then left by 3.
+#define leftRotate51_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 48), 1), 1), 1))
+
+// Left rotate by 52: Rotate left by 48, then left by 4.
+#define leftRotate52_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 48), 1), 1), 1), 1))
+
+// Left rotate by 53: Rotate left by 56, then right by 3.
+#define leftRotate53_64(a) (rightRotate_64(rightRotate_64(rightRotate_64(leftRotate_64((a), 56), 1), 1), 1))
+
+// Left rotate by 54: Rotate left by 56, then right by 2.
+#define leftRotate54_64(a) (rightRotate_64(rightRotate_64(leftRotate_64((a), 56), 1), 1))
+
+// Left rotate by 55: Rotate left by 56, then right by 1.
+#define leftRotate55_64(a) (rightRotate_64(leftRotate_64((a), 56), 1))
+
+// Left rotate by 56.
+#define leftRotate56_64(a) (leftRotate_64((a), 56))
+
+// Left rotate by 57: Rotate left by 56, then left by 1.
+#define leftRotate57_64(a) (leftRotate_64(leftRotate_64((a), 56), 1))
+
+// Left rotate by 58: Rotate left by 56, then left by 2.
+#define leftRotate58_64(a) (leftRotate_64(leftRotate_64(leftRotate_64((a), 56), 1), 1))
+
+// Left rotate by 59: Rotate left by 56, then left by 3.
+#define leftRotate59_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 56), 1), 1), 1))
+
+// Left rotate by 60: Rotate left by 60, then left by 4.
+#define leftRotate60_64(a) (leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64(leftRotate_64((a), 56), 1), 1), 1), 1))
+
+// Left rotate by 61: Rotate right by 3.
+#define leftRotate61_64(a) (rightRotate_64(rightRotate_64(rightRotate_64((a), 1), 1), 1))
+
+// Left rotate by 62: Rotate right by 2.
+#define leftRotate62_64(a) (rightRotate_64(rightRotate_64((a), 1), 1))
+
+// Left rotate by 63: Rotate right by 1.
+#define leftRotate63_64(a) (rightRotate_64((a), 1))
+
+// Define the 64-bit right rotations in terms of left rotations.
+#define rightRotate1_64(a)  (leftRotate63_64((a)))
+#define rightRotate2_64(a)  (leftRotate62_64((a)))
+#define rightRotate3_64(a)  (leftRotate61_64((a)))
+#define rightRotate4_64(a)  (leftRotate60_64((a)))
+#define rightRotate5_64(a)  (leftRotate59_64((a)))
+#define rightRotate6_64(a)  (leftRotate58_64((a)))
+#define rightRotate7_64(a)  (leftRotate57_64((a)))
+#define rightRotate8_64(a)  (leftRotate56_64((a)))
+#define rightRotate9_64(a)  (leftRotate55_64((a)))
+#define rightRotate10_64(a) (leftRotate54_64((a)))
+#define rightRotate11_64(a) (leftRotate53_64((a)))
+#define rightRotate12_64(a) (leftRotate52_64((a)))
+#define rightRotate13_64(a) (leftRotate51_64((a)))
+#define rightRotate14_64(a) (leftRotate50_64((a)))
+#define rightRotate15_64(a) (leftRotate49_64((a)))
+#define rightRotate16_64(a) (leftRotate48_64((a)))
+#define rightRotate17_64(a) (leftRotate47_64((a)))
+#define rightRotate18_64(a) (leftRotate46_64((a)))
+#define rightRotate19_64(a) (leftRotate45_64((a)))
+#define rightRotate20_64(a) (leftRotate44_64((a)))
+#define rightRotate21_64(a) (leftRotate43_64((a)))
+#define rightRotate22_64(a) (leftRotate42_64((a)))
+#define rightRotate23_64(a) (leftRotate41_64((a)))
+#define rightRotate24_64(a) (leftRotate40_64((a)))
+#define rightRotate25_64(a) (leftRotate39_64((a)))
+#define rightRotate26_64(a) (leftRotate38_64((a)))
+#define rightRotate27_64(a) (leftRotate37_64((a)))
+#define rightRotate28_64(a) (leftRotate36_64((a)))
+#define rightRotate29_64(a) (leftRotate35_64((a)))
+#define rightRotate30_64(a) (leftRotate34_64((a)))
+#define rightRotate31_64(a) (leftRotate33_64((a)))
+#define rightRotate32_64(a) (leftRotate32_64((a)))
+#define rightRotate33_64(a) (leftRotate31_64((a)))
+#define rightRotate34_64(a) (leftRotate30_64((a)))
+#define rightRotate35_64(a) (leftRotate29_64((a)))
+#define rightRotate36_64(a) (leftRotate28_64((a)))
+#define rightRotate37_64(a) (leftRotate27_64((a)))
+#define rightRotate38_64(a) (leftRotate26_64((a)))
+#define rightRotate39_64(a) (leftRotate25_64((a)))
+#define rightRotate40_64(a) (leftRotate24_64((a)))
+#define rightRotate41_64(a) (leftRotate23_64((a)))
+#define rightRotate42_64(a) (leftRotate22_64((a)))
+#define rightRotate43_64(a) (leftRotate21_64((a)))
+#define rightRotate44_64(a) (leftRotate20_64((a)))
+#define rightRotate45_64(a) (leftRotate19_64((a)))
+#define rightRotate46_64(a) (leftRotate18_64((a)))
+#define rightRotate47_64(a) (leftRotate17_64((a)))
+#define rightRotate48_64(a) (leftRotate16_64((a)))
+#define rightRotate49_64(a) (leftRotate15_64((a)))
+#define rightRotate50_64(a) (leftRotate14_64((a)))
+#define rightRotate51_64(a) (leftRotate13_64((a)))
+#define rightRotate52_64(a) (leftRotate12_64((a)))
+#define rightRotate53_64(a) (leftRotate11_64((a)))
+#define rightRotate54_64(a) (leftRotate10_64((a)))
+#define rightRotate55_64(a) (leftRotate9_64((a)))
+#define rightRotate56_64(a) (leftRotate8_64((a)))
+#define rightRotate57_64(a) (leftRotate7_64((a)))
+#define rightRotate58_64(a) (leftRotate6_64((a)))
+#define rightRotate59_64(a) (leftRotate5_64((a)))
+#define rightRotate60_64(a) (leftRotate4_64((a)))
+#define rightRotate61_64(a) (leftRotate3_64((a)))
+#define rightRotate62_64(a) (leftRotate2_64((a)))
+#define rightRotate63_64(a) (leftRotate1_64((a)))
+
+#else // !CRYPTO_ROTATE64_COMPOSED
+
+// Rotation macros for 64-bit arguments.
+
+// Generic left rotate.
+#define leftRotate_64(a, bits) \
+    (__extension__ ({ \
+        uint64_t _temp = (a); \
+        (_temp << (bits)) | (_temp >> (64 - (bits))); \
+    }))
+
+// Generic right rotate.
+#define rightRotate_64(a, bits) \
+    (__extension__ ({ \
+        uint64_t _temp = (a); \
+        (_temp >> (bits)) | (_temp << (64 - (bits))); \
+    }))
+
+// Left rotate by a specific number of bits.
+#define leftRotate1_64(a)  (leftRotate_64((a), 1))
+#define leftRotate2_64(a)  (leftRotate_64((a), 2))
+#define leftRotate3_64(a)  (leftRotate_64((a), 3))
+#define leftRotate4_64(a)  (leftRotate_64((a), 4))
+#define leftRotate5_64(a)  (leftRotate_64((a), 5))
+#define leftRotate6_64(a)  (leftRotate_64((a), 6))
+#define leftRotate7_64(a)  (leftRotate_64((a), 7))
+#define leftRotate8_64(a)  (leftRotate_64((a), 8))
+#define leftRotate9_64(a)  (leftRotate_64((a), 9))
+#define leftRotate10_64(a) (leftRotate_64((a), 10))
+#define leftRotate11_64(a) (leftRotate_64((a), 11))
+#define leftRotate12_64(a) (leftRotate_64((a), 12))
+#define leftRotate13_64(a) (leftRotate_64((a), 13))
+#define leftRotate14_64(a) (leftRotate_64((a), 14))
+#define leftRotate15_64(a) (leftRotate_64((a), 15))
+#define leftRotate16_64(a) (leftRotate_64((a), 16))
+#define leftRotate17_64(a) (leftRotate_64((a), 17))
+#define leftRotate18_64(a) (leftRotate_64((a), 18))
+#define leftRotate19_64(a) (leftRotate_64((a), 19))
+#define leftRotate20_64(a) (leftRotate_64((a), 20))
+#define leftRotate21_64(a) (leftRotate_64((a), 21))
+#define leftRotate22_64(a) (leftRotate_64((a), 22))
+#define leftRotate23_64(a) (leftRotate_64((a), 23))
+#define leftRotate24_64(a) (leftRotate_64((a), 24))
+#define leftRotate25_64(a) (leftRotate_64((a), 25))
+#define leftRotate26_64(a) (leftRotate_64((a), 26))
+#define leftRotate27_64(a) (leftRotate_64((a), 27))
+#define leftRotate28_64(a) (leftRotate_64((a), 28))
+#define leftRotate29_64(a) (leftRotate_64((a), 29))
+#define leftRotate30_64(a) (leftRotate_64((a), 30))
+#define leftRotate31_64(a) (leftRotate_64((a), 31))
+#define leftRotate32_64(a) (leftRotate_64((a), 32))
+#define leftRotate33_64(a) (leftRotate_64((a), 33))
+#define leftRotate34_64(a) (leftRotate_64((a), 34))
+#define leftRotate35_64(a) (leftRotate_64((a), 35))
+#define leftRotate36_64(a) (leftRotate_64((a), 36))
+#define leftRotate37_64(a) (leftRotate_64((a), 37))
+#define leftRotate38_64(a) (leftRotate_64((a), 38))
+#define leftRotate39_64(a) (leftRotate_64((a), 39))
+#define leftRotate40_64(a) (leftRotate_64((a), 40))
+#define leftRotate41_64(a) (leftRotate_64((a), 41))
+#define leftRotate42_64(a) (leftRotate_64((a), 42))
+#define leftRotate43_64(a) (leftRotate_64((a), 43))
+#define leftRotate44_64(a) (leftRotate_64((a), 44))
+#define leftRotate45_64(a) (leftRotate_64((a), 45))
+#define leftRotate46_64(a) (leftRotate_64((a), 46))
+#define leftRotate47_64(a) (leftRotate_64((a), 47))
+#define leftRotate48_64(a) (leftRotate_64((a), 48))
+#define leftRotate49_64(a) (leftRotate_64((a), 49))
+#define leftRotate50_64(a) (leftRotate_64((a), 50))
+#define leftRotate51_64(a) (leftRotate_64((a), 51))
+#define leftRotate52_64(a) (leftRotate_64((a), 52))
+#define leftRotate53_64(a) (leftRotate_64((a), 53))
+#define leftRotate54_64(a) (leftRotate_64((a), 54))
+#define leftRotate55_64(a) (leftRotate_64((a), 55))
+#define leftRotate56_64(a) (leftRotate_64((a), 56))
+#define leftRotate57_64(a) (leftRotate_64((a), 57))
+#define leftRotate58_64(a) (leftRotate_64((a), 58))
+#define leftRotate59_64(a) (leftRotate_64((a), 59))
+#define leftRotate60_64(a) (leftRotate_64((a), 60))
+#define leftRotate61_64(a) (leftRotate_64((a), 61))
+#define leftRotate62_64(a) (leftRotate_64((a), 62))
+#define leftRotate63_64(a) (leftRotate_64((a), 63))
+
+// Right rotate by a specific number of bits.
+#define rightRotate1_64(a)  (rightRotate_64((a), 1))
+#define rightRotate2_64(a)  (rightRotate_64((a), 2))
+#define rightRotate3_64(a)  (rightRotate_64((a), 3))
+#define rightRotate4_64(a)  (rightRotate_64((a), 4))
+#define rightRotate5_64(a)  (rightRotate_64((a), 5))
+#define rightRotate6_64(a)  (rightRotate_64((a), 6))
+#define rightRotate7_64(a)  (rightRotate_64((a), 7))
+#define rightRotate8_64(a)  (rightRotate_64((a), 8))
+#define rightRotate9_64(a)  (rightRotate_64((a), 9))
+#define rightRotate10_64(a) (rightRotate_64((a), 10))
+#define rightRotate11_64(a) (rightRotate_64((a), 11))
+#define rightRotate12_64(a) (rightRotate_64((a), 12))
+#define rightRotate13_64(a) (rightRotate_64((a), 13))
+#define rightRotate14_64(a) (rightRotate_64((a), 14))
+#define rightRotate15_64(a) (rightRotate_64((a), 15))
+#define rightRotate16_64(a) (rightRotate_64((a), 16))
+#define rightRotate17_64(a) (rightRotate_64((a), 17))
+#define rightRotate18_64(a) (rightRotate_64((a), 18))
+#define rightRotate19_64(a) (rightRotate_64((a), 19))
+#define rightRotate20_64(a) (rightRotate_64((a), 20))
+#define rightRotate21_64(a) (rightRotate_64((a), 21))
+#define rightRotate22_64(a) (rightRotate_64((a), 22))
+#define rightRotate23_64(a) (rightRotate_64((a), 23))
+#define rightRotate24_64(a) (rightRotate_64((a), 24))
+#define rightRotate25_64(a) (rightRotate_64((a), 25))
+#define rightRotate26_64(a) (rightRotate_64((a), 26))
+#define rightRotate27_64(a) (rightRotate_64((a), 27))
+#define rightRotate28_64(a) (rightRotate_64((a), 28))
+#define rightRotate29_64(a) (rightRotate_64((a), 29))
+#define rightRotate30_64(a) (rightRotate_64((a), 30))
+#define rightRotate31_64(a) (rightRotate_64((a), 31))
+#define rightRotate32_64(a) (rightRotate_64((a), 32))
+#define rightRotate33_64(a) (rightRotate_64((a), 33))
+#define rightRotate34_64(a) (rightRotate_64((a), 34))
+#define rightRotate35_64(a) (rightRotate_64((a), 35))
+#define rightRotate36_64(a) (rightRotate_64((a), 36))
+#define rightRotate37_64(a) (rightRotate_64((a), 37))
+#define rightRotate38_64(a) (rightRotate_64((a), 38))
+#define rightRotate39_64(a) (rightRotate_64((a), 39))
+#define rightRotate40_64(a) (rightRotate_64((a), 40))
+#define rightRotate41_64(a) (rightRotate_64((a), 41))
+#define rightRotate42_64(a) (rightRotate_64((a), 42))
+#define rightRotate43_64(a) (rightRotate_64((a), 43))
+#define rightRotate44_64(a) (rightRotate_64((a), 44))
+#define rightRotate45_64(a) (rightRotate_64((a), 45))
+#define rightRotate46_64(a) (rightRotate_64((a), 46))
+#define rightRotate47_64(a) (rightRotate_64((a), 47))
+#define rightRotate48_64(a) (rightRotate_64((a), 48))
+#define rightRotate49_64(a) (rightRotate_64((a), 49))
+#define rightRotate50_64(a) (rightRotate_64((a), 50))
+#define rightRotate51_64(a) (rightRotate_64((a), 51))
+#define rightRotate52_64(a) (rightRotate_64((a), 52))
+#define rightRotate53_64(a) (rightRotate_64((a), 53))
+#define rightRotate54_64(a) (rightRotate_64((a), 54))
+#define rightRotate55_64(a) (rightRotate_64((a), 55))
+#define rightRotate56_64(a) (rightRotate_64((a), 56))
+#define rightRotate57_64(a) (rightRotate_64((a), 57))
+#define rightRotate58_64(a) (rightRotate_64((a), 58))
+#define rightRotate59_64(a) (rightRotate_64((a), 59))
+#define rightRotate60_64(a) (rightRotate_64((a), 60))
+#define rightRotate61_64(a) (rightRotate_64((a), 61))
+#define rightRotate62_64(a) (rightRotate_64((a), 62))
+#define rightRotate63_64(a) (rightRotate_64((a), 63))
+
+#endif // !CRYPTO_ROTATE64_COMPOSED
+
+#endif
diff --git a/src/modules/mqtt/PubSubClient.cpp b/src/modules/mqtt/PubSubClient.cpp
new file mode 100644
index 0000000..982f12b
--- /dev/null
+++ b/src/modules/mqtt/PubSubClient.cpp
@@ -0,0 +1,607 @@
+/*
+  PubSubClient.cpp - A simple client for MQTT.
+  Nick O'Leary
+  http://knolleary.net
+*/
+
+#include "PubSubClient.h"
+#include "Arduino.h"
+
+PubSubClient::PubSubClient() {
+    this->_state = MQTT_DISCONNECTED;
+    this->_client = NULL;
+    this->stream = NULL;
+    setCallback(NULL);
+}
+
+PubSubClient::PubSubClient(Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setClient(client);
+    this->stream = NULL;
+}
+
+PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(addr, port);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(addr,port);
+    setClient(client);
+    setStream(stream);
+}
+PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(addr, port);
+    setCallback(callback);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(addr,port);
+    setCallback(callback);
+    setClient(client);
+    setStream(stream);
+}
+
+PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(ip, port);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(ip,port);
+    setClient(client);
+    setStream(stream);
+}
+PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(ip, port);
+    setCallback(callback);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(ip,port);
+    setCallback(callback);
+    setClient(client);
+    setStream(stream);
+}
+
+PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(domain,port);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(domain,port);
+    setClient(client);
+    setStream(stream);
+}
+PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(domain,port);
+    setCallback(callback);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(domain,port);
+    setCallback(callback);
+    setClient(client);
+    setStream(stream);
+}
+
+boolean PubSubClient::connect(const char *id) {
+    return connect(id,NULL,NULL,0,0,0,0);
+}
+
+boolean PubSubClient::connect(const char *id, const char *user, const char *pass) {
+    return connect(id,user,pass,0,0,0,0);
+}
+
+boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
+    return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage);
+}
+
+boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
+    if (!connected()) {
+        int result = 0;
+
+        if (domain != NULL) {
+            result = _client->connect(this->domain, this->port);
+        } else {
+            result = _client->connect(this->ip, this->port);
+        }
+        if (result == 1) {
+            nextMsgId = 1;
+            // Leave room in the buffer for header and variable length field
+            uint16_t length = 5;
+            unsigned int j;
+
+#if MQTT_VERSION == MQTT_VERSION_3_1
+            uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION};
+#define MQTT_HEADER_VERSION_LENGTH 9
+#elif MQTT_VERSION == MQTT_VERSION_3_1_1
+            uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION};
+#define MQTT_HEADER_VERSION_LENGTH 7
+#endif
+            for (j = 0;j<MQTT_HEADER_VERSION_LENGTH;j++) {
+                buffer[length++] = d[j];
+				//SerialUSB.println(buffer[j]);   //调试代码
+            }
+
+            uint8_t v;
+            if (willTopic) {
+                v = 0x06|(willQos<<3)|(willRetain<<5);
+            } else {
+                v = 0x02;
+            }
+
+            if(user != NULL) {
+                v = v|0x80;
+
+                if(pass != NULL) {
+                    v = v|(0x80>>1);
+                }
+            }
+
+            buffer[length++] = v;
+
+            buffer[length++] = ((MQTT_KEEPALIVE) >> 8);
+            buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF);
+            length = writeString(id,buffer,length);
+            if (willTopic) {
+                length = writeString(willTopic,buffer,length);
+                length = writeString(willMessage,buffer,length);
+            }
+
+            if(user != NULL) {
+                length = writeString(user,buffer,length);
+                if(pass != NULL) {
+                    length = writeString(pass,buffer,length);
+                }
+            }
+
+            write(MQTTCONNECT,buffer,length-5);
+			//write(MQTTCONNECT,buffer,length);
+			/*
+			//调试代码
+			for(int s =0;s<length;s++)
+			{
+				SerialUSB.print(buffer[s],HEX);
+				SerialUSB.print(",");
+			}
+			SerialUSB.println();
+			//=============================调试结束=============================
+			*/
+            lastInActivity = lastOutActivity = millis();
+
+            while (!_client->available()) {
+                unsigned long t = millis();
+                if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) {
+                    _state = MQTT_CONNECTION_TIMEOUT;
+                    _client->stop();
+                    return false;
+                }
+            }
+            uint8_t llen;
+            uint16_t len = readPacket(&llen);
+
+            if (len == 4) {
+                if (buffer[3] == 0) {
+                    lastInActivity = millis();
+                    pingOutstanding = false;
+                    _state = MQTT_CONNECTED;
+                    return true;
+                } else {
+                    _state = buffer[3];
+                }
+            }
+            _client->stop();
+        } else {
+            _state = MQTT_CONNECT_FAILED;
+        }
+        return false;
+    }
+    return true;
+}
+
+// reads a byte into result
+boolean PubSubClient::readByte(uint8_t * result) {
+   uint32_t previousMillis = millis();
+   while(!_client->available()) {
+     uint32_t currentMillis = millis();
+     if(currentMillis - previousMillis >= ((int32_t) MQTT_SOCKET_TIMEOUT * 1000)){
+       return false;
+     }
+   }
+   *result = _client->read();
+   return true;
+}
+
+// reads a byte into result[*index] and increments index
+boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){
+  uint16_t current_index = *index;
+  uint8_t * write_address = &(result[current_index]);
+  if(readByte(write_address)){
+    *index = current_index + 1;
+    return true;
+  }
+  return false;
+}
+
+uint16_t PubSubClient::readPacket(uint8_t* lengthLength) {
+    uint16_t len = 0;
+    if(!readByte(buffer, &len)) return 0;
+    bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH;
+    uint32_t multiplier = 1;
+    uint16_t length = 0;
+    uint8_t digit = 0;
+    uint16_t skip = 0;
+    uint8_t start = 0;
+
+    do {
+        if(!readByte(&digit)) return 0;
+        buffer[len++] = digit;
+        length += (digit & 127) * multiplier;
+        multiplier *= 128;
+    } while ((digit & 128) != 0);
+    *lengthLength = len-1;
+
+    if (isPublish) {
+        // Read in topic length to calculate bytes to skip over for Stream writing
+        if(!readByte(buffer, &len)) return 0;
+        if(!readByte(buffer, &len)) return 0;
+        skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2];
+        start = 2;
+        if (buffer[0]&MQTTQOS1) {
+            // skip message id
+            skip += 2;
+        }
+    }
+
+    for (uint16_t i = start;i<length;i++) {
+        if(!readByte(&digit)) return 0;
+        if (this->stream) {
+            if (isPublish && len-*lengthLength-2>skip) {
+                this->stream->write(digit);
+            }
+        }
+        if (len < MQTT_MAX_PACKET_SIZE) {
+            buffer[len] = digit;
+        }
+        len++;
+    }
+
+    if (!this->stream && len > MQTT_MAX_PACKET_SIZE) {
+        len = 0; // This will cause the packet to be ignored.
+    }
+
+    return len;
+}
+
+boolean PubSubClient::loop() {
+    if (connected()) {
+        unsigned long t = millis();
+        if ((t - lastInActivity > MQTT_KEEPALIVE*1000UL) || (t - lastOutActivity > MQTT_KEEPALIVE*1000UL)) {
+            if (pingOutstanding) {
+                this->_state = MQTT_CONNECTION_TIMEOUT;
+                _client->stop();
+                return false;
+            } else {
+                buffer[0] = MQTTPINGREQ;
+                buffer[1] = 0;
+                _client->write(buffer,2);
+                lastOutActivity = t;
+                lastInActivity = t;
+                pingOutstanding = true;
+            }
+        }
+        if (_client->available()) {
+            uint8_t llen;
+            uint16_t len = readPacket(&llen);
+            uint16_t msgId = 0;
+            uint8_t *payload;
+            if (len > 0) {
+                lastInActivity = t;
+                uint8_t type = buffer[0]&0xF0;
+                if (type == MQTTPUBLISH) {
+                    if (callback) {
+                        uint16_t tl = (buffer[llen+1]<<8)+buffer[llen+2]; /* topic length in bytes */
+                        memmove(buffer+llen+2,buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */
+                        buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */
+                        char *topic = (char*) buffer+llen+2;
+                        // msgId only present for QOS>0
+                        if ((buffer[0]&0x06) == MQTTQOS1) {
+                            msgId = (buffer[llen+3+tl]<<8)+buffer[llen+3+tl+1];
+                            payload = buffer+llen+3+tl+2;
+                            callback(topic,payload,len-llen-3-tl-2);
+
+                            buffer[0] = MQTTPUBACK;
+                            buffer[1] = 2;
+                            buffer[2] = (msgId >> 8);
+                            buffer[3] = (msgId & 0xFF);
+                            _client->write(buffer,4);
+                            lastOutActivity = t;
+
+                        } else {
+                            payload = buffer+llen+3+tl;
+                            callback(topic,payload,len-llen-3-tl);
+                        }
+                    }
+                } else if (type == MQTTPINGREQ) {
+                    buffer[0] = MQTTPINGRESP;
+                    buffer[1] = 0;
+                    _client->write(buffer,2);
+                } else if (type == MQTTPINGRESP) {
+                    pingOutstanding = false;
+                }
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+boolean PubSubClient::publish(const char* topic, const char* payload) {
+    return publish(topic,(const uint8_t*)payload,strlen(payload),false);
+}
+
+boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) {
+    return publish(topic,(const uint8_t*)payload,strlen(payload),retained);
+}
+
+boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) {
+    return publish(topic, payload, plength, false);
+}
+
+boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
+    if (connected()) {
+        if (MQTT_MAX_PACKET_SIZE < 5 + 2+strlen(topic) + plength) {
+            // Too long
+            return false;
+        }
+        // Leave room in the buffer for header and variable length field
+        uint16_t length = 5;
+        length = writeString(topic,buffer,length);
+        uint16_t i;
+        for (i=0;i<plength;i++) {
+            buffer[length++] = payload[i];
+        }
+        uint8_t header = MQTTPUBLISH;
+        if (retained) {
+            header |= 1;
+        }
+        return write(header,buffer,length-5);
+    }
+    return false;
+}
+
+boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
+    uint8_t llen = 0;
+    uint8_t digit;
+    unsigned int rc = 0;
+    uint16_t tlen;
+    unsigned int pos = 0;
+    unsigned int i;
+    uint8_t header;
+    unsigned int len;
+
+    if (!connected()) {
+        return false;
+    }
+
+    tlen = strlen(topic);
+
+    header = MQTTPUBLISH;
+    if (retained) {
+        header |= 1;
+    }
+    buffer[pos++] = header;
+    len = plength + 2 + tlen;
+    do {
+        digit = len % 128;
+        len = len / 128;
+        if (len > 0) {
+            digit |= 0x80;
+        }
+        buffer[pos++] = digit;
+        llen++;
+    } while(len>0);
+
+    pos = writeString(topic,buffer,pos);
+
+    rc += _client->write(buffer,pos);
+
+    for (i=0;i<plength;i++) {
+        rc += _client->write((char)pgm_read_byte_near(payload + i));
+    }
+
+    lastOutActivity = millis();
+
+    return rc == tlen + 4 + plength;
+}
+
+boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) 
+{
+    uint8_t lenBuf[4];
+    uint8_t llen = 0;
+    uint8_t digit;
+    uint8_t pos = 0;
+    uint16_t rc;
+    uint16_t len = length;
+    do {
+        digit = len % 128;
+		//SerialUSB.println(digit);   //调试
+        len = len / 128;
+		
+        if (len > 0) {
+            digit |= 0x80;
+			//SerialUSB.print("digit");   //调试
+			//SerialUSB.println(digit);
+        }
+        lenBuf[pos++] = digit;
+        llen++;
+    } while(len>0);
+
+    buf[4-llen] = header;
+	//SerialUSB.println();
+    for (int i=0;i<llen;i++) {
+        buf[5-llen+i] = lenBuf[i];
+		//SerialUSB.print(lenBuf[i]);
+		//SerialUSB.print(";");
+    }
+
+#ifdef MQTT_MAX_TRANSFER_SIZE
+    uint8_t* writeBuf = buf+(4-llen);
+    uint16_t bytesRemaining = length+1+llen;  //Match the length type
+    uint8_t bytesToWrite;
+    boolean result = true;
+    while((bytesRemaining > 0) && result) {
+        bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining;
+        rc = _client->write(writeBuf,bytesToWrite);
+        result = (rc == bytesToWrite);
+        bytesRemaining -= rc;
+        writeBuf += rc;
+    }
+    return result;
+#else
+    rc = _client->write(buf+(4-llen),length+1+llen);
+    lastOutActivity = millis();
+    return (rc == 1+llen+length);
+#endif
+}
+
+boolean PubSubClient::subscribe(const char* topic) {
+    return subscribe(topic, 0);
+}
+
+boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
+    if (qos > 1) {
+        return false;
+    }
+    if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
+        // Too long
+        return false;
+    }
+    if (connected()) {
+        // Leave room in the buffer for header and variable length field
+        uint16_t length = 5;
+        nextMsgId++;
+        if (nextMsgId == 0) {
+            nextMsgId = 1;
+        }
+        buffer[length++] = (nextMsgId >> 8);
+        buffer[length++] = (nextMsgId & 0xFF);
+        length = writeString((char*)topic, buffer,length);
+        buffer[length++] = qos;
+        return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-5);
+    }
+    return false;
+}
+
+boolean PubSubClient::unsubscribe(const char* topic) {
+    if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
+        // Too long
+        return false;
+    }
+    if (connected()) {
+        uint16_t length = 5;
+        nextMsgId++;
+        if (nextMsgId == 0) {
+            nextMsgId = 1;
+        }
+        buffer[length++] = (nextMsgId >> 8);
+        buffer[length++] = (nextMsgId & 0xFF);
+        length = writeString(topic, buffer,length);
+        return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-5);
+    }
+    return false;
+}
+
+void PubSubClient::disconnect() {
+    buffer[0] = MQTTDISCONNECT;
+    buffer[1] = 0;
+    _client->write(buffer,2);
+    _state = MQTT_DISCONNECTED;
+    _client->stop();
+    lastInActivity = lastOutActivity = millis();
+}
+
+uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) {
+    const char* idp = string;
+    uint16_t i = 0;
+    pos += 2;
+    while (*idp) {
+        buf[pos++] = *idp++;
+        i++;
+    }
+    buf[pos-i-2] = (i >> 8);
+    buf[pos-i-1] = (i & 0xFF);
+    return pos;
+}
+
+
+boolean PubSubClient::connected() {
+    boolean rc;
+    if (_client == NULL ) {
+        rc = false;
+    } else {
+        rc = (int)_client->connected();
+        if (!rc) {
+            if (this->_state == MQTT_CONNECTED) {
+                this->_state = MQTT_CONNECTION_LOST;
+                _client->flush();
+                _client->stop();
+            }
+        }
+    }
+    return rc;
+}
+
+PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) {
+    IPAddress addr(ip[0],ip[1],ip[2],ip[3]);
+    return setServer(addr,port);
+}
+
+PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) {
+    this->ip = ip;
+    this->port = port;
+    this->domain = NULL;
+    return *this;
+}
+
+PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) {
+    this->domain = domain;
+    this->port = port;
+    return *this;
+}
+
+PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) {
+    this->callback = callback;
+    return *this;
+}
+
+PubSubClient& PubSubClient::setClient(Client& client){
+    this->_client = &client;
+    return *this;
+}
+
+PubSubClient& PubSubClient::setStream(Stream& stream){
+    this->stream = &stream;
+    return *this;
+}
+
+int PubSubClient::state() {
+    return this->_state;
+}
diff --git a/src/modules/mqtt/PubSubClient.h b/src/modules/mqtt/PubSubClient.h
new file mode 100644
index 0000000..474cb77
--- /dev/null
+++ b/src/modules/mqtt/PubSubClient.h
@@ -0,0 +1,145 @@
+/*
+ PubSubClient.h - A simple client for MQTT.
+  Nick O'Leary
+  http://knolleary.net
+*/
+
+#ifndef PubSubClient_h
+#define PubSubClient_h
+
+#include <Arduino.h>
+#include "IPAddress.h"
+#include "Client.h"
+#include "Stream.h"
+
+#define MQTT_VERSION_3_1      3
+#define MQTT_VERSION_3_1_1    4
+
+// MQTT_VERSION : Pick the version
+//#define MQTT_VERSION MQTT_VERSION_3_1
+#ifndef MQTT_VERSION
+#define MQTT_VERSION MQTT_VERSION_3_1_1
+#endif
+
+// MQTT_MAX_PACKET_SIZE : Maximum packet size
+#ifndef MQTT_MAX_PACKET_SIZE
+#define MQTT_MAX_PACKET_SIZE 400
+#endif
+
+// MQTT_KEEPALIVE : keepAlive interval in Seconds
+#ifndef MQTT_KEEPALIVE
+#define MQTT_KEEPALIVE 60   //阿里云要求不得小于60秒
+//#define MQTT_KEEPALIVE 300  //最大300秒
+#endif
+
+// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds
+#ifndef MQTT_SOCKET_TIMEOUT
+#define MQTT_SOCKET_TIMEOUT 15
+#endif
+
+// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client
+//  in each write call. Needed for the Arduino Wifi Shield. Leave undefined to
+//  pass the entire MQTT packet in each write call.
+//#define MQTT_MAX_TRANSFER_SIZE 60
+
+// Possible values for client.state()
+#define MQTT_CONNECTION_TIMEOUT     -4
+#define MQTT_CONNECTION_LOST        -3
+#define MQTT_CONNECT_FAILED         -2
+#define MQTT_DISCONNECTED           -1
+#define MQTT_CONNECTED               0
+#define MQTT_CONNECT_BAD_PROTOCOL    1
+#define MQTT_CONNECT_BAD_CLIENT_ID   2
+#define MQTT_CONNECT_UNAVAILABLE     3
+#define MQTT_CONNECT_BAD_CREDENTIALS 4
+#define MQTT_CONNECT_UNAUTHORIZED    5
+
+#define MQTTCONNECT     1 << 4  // Client request to connect to Server
+#define MQTTCONNACK     2 << 4  // Connect Acknowledgment
+#define MQTTPUBLISH     3 << 4  // Publish message
+#define MQTTPUBACK      4 << 4  // Publish Acknowledgment
+#define MQTTPUBREC      5 << 4  // Publish Received (assured delivery part 1)
+#define MQTTPUBREL      6 << 4  // Publish Release (assured delivery part 2)
+#define MQTTPUBCOMP     7 << 4  // Publish Complete (assured delivery part 3)
+#define MQTTSUBSCRIBE   8 << 4  // Client Subscribe request
+#define MQTTSUBACK      9 << 4  // Subscribe Acknowledgment
+#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request
+#define MQTTUNSUBACK    11 << 4 // Unsubscribe Acknowledgment
+#define MQTTPINGREQ     12 << 4 // PING Request
+#define MQTTPINGRESP    13 << 4 // PING Response
+#define MQTTDISCONNECT  14 << 4 // Client is Disconnecting
+#define MQTTReserved    15 << 4 // Reserved
+
+#define MQTTQOS0        (0 << 1)
+#define MQTTQOS1        (1 << 1)
+#define MQTTQOS2        (2 << 1)
+
+#ifdef ESP8266
+#include <functional>
+#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
+#else
+#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
+#endif
+
+class PubSubClient {
+private:
+   Client* _client;
+   uint8_t buffer[MQTT_MAX_PACKET_SIZE];
+   uint16_t nextMsgId;
+   unsigned long lastOutActivity;
+   unsigned long lastInActivity;
+   bool pingOutstanding;
+   MQTT_CALLBACK_SIGNATURE;
+   uint16_t readPacket(uint8_t*);
+   boolean readByte(uint8_t * result);
+   boolean readByte(uint8_t * result, uint16_t * index);
+   boolean write(uint8_t header, uint8_t* buf, uint16_t length);
+   uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos);
+   IPAddress ip;
+   const char* domain;
+   uint16_t port;
+   Stream* stream;
+   int _state;
+public:
+   PubSubClient();
+   PubSubClient(Client& client);
+   PubSubClient(IPAddress, uint16_t, Client& client);
+   PubSubClient(IPAddress, uint16_t, Client& client, Stream&);
+   PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
+   PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
+   PubSubClient(uint8_t *, uint16_t, Client& client);
+   PubSubClient(uint8_t *, uint16_t, Client& client, Stream&);
+   PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
+   PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
+   PubSubClient(const char*, uint16_t, Client& client);
+   PubSubClient(const char*, uint16_t, Client& client, Stream&);
+   PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
+   PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
+
+   PubSubClient& setServer(IPAddress ip, uint16_t port);
+   PubSubClient& setServer(uint8_t * ip, uint16_t port);
+   PubSubClient& setServer(const char * domain, uint16_t port);
+   PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE);
+   PubSubClient& setClient(Client& client);
+   PubSubClient& setStream(Stream& stream);
+
+   boolean connect(const char* id);
+   boolean connect(const char* id, const char* user, const char* pass);
+   boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
+   boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
+   void disconnect();
+   boolean publish(const char* topic, const char* payload);
+   boolean publish(const char* topic, const char* payload, boolean retained);
+   boolean publish(const char* topic, const uint8_t * payload, unsigned int plength);
+   boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
+   boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
+   boolean subscribe(const char* topic);
+   boolean subscribe(const char* topic, uint8_t qos);
+   boolean unsubscribe(const char* topic);
+   boolean loop();
+   boolean connected();
+   int state();
+};
+
+
+#endif

--
Gitblit v1.8.0