実際に使用するコードの実装はその2までで完了しているのでちょっと遊びでC++のtemplateを使ってkeccakの汎用実装をやってみました。
何度も書いていますが、SHA3として使っているのはkeccak-f[1600]と言う形式です。
が、本来はkeccakにはワード長を変更した形式が存在し、それも内部的には使うことが出来ます。(keccak-f[800]、keccak-f[400]など)
それをtemplateを使うことで簡単に使えるようにしてやろう、と言う物です。
templateバージョンは速度としては専用に組んだ物には確実に劣りますのでSHA3として使いたいときはその1にあるようなルーチンを使うとよいとおもいます。
今回はコードを直接書きながらちょっと実装のテクニックを紹介してみたいと思います。
C++ではこんな書き方も出来るんだ~と言うことを見てみたいと思います。
今回のコードはすべて同じヘッダーファイルに記述することでコードとして意味を持つようになっています。
ヘッダインクルード、定数宣言部
#include <stdlib.h> #include <memory.h> #ifndef _WIN32 #include <stdint.h> #else typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef unsigned __int64 uint64_t; #endif //_WIN32 namespace _keccak_const{ const unsigned int c_anIndexM1M5[5] = { 4, 0, 1, 2, 3 }; const unsigned int c_anIndexP1M5[5] = { 1, 2, 3, 4, 0 }; const unsigned int c_anIndexP2M5[5] = { 2, 3, 4, 0, 1 }; const unsigned int c_aanIndex2XP3YM5[5][5] = { { 0, 2, 4, 1, 3 }, { 3, 0, 2, 4, 1 }, { 1, 3, 0, 2, 4 }, { 4, 1, 3, 0, 2 }, { 2, 4, 1, 3, 0 } }; const unsigned int c_aanRotateCount[5][5] = { { 0, 1, 190, 28, 91 }, { 36, 300, 6, 55, 276 }, { 3, 10, 171, 153, 231 }, { 105, 45, 15, 21, 136 }, { 210, 66, 253, 120, 78 } }; const uint64_t c_anRoundConstant[24] = { 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000, 0x000000000000808B, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 0x000000000000008A, 0x0000000000000088, 0x0000000080008009, 0x000000008000000A, 0x000000008000808B, 0x800000000000008B, 0x8000000000008089, 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, 0x000000000000800A, 0x800000008000000A, 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 }; }
符号なし整数型の定義については先に簡略化した定義を行っています。このファイル内ではほとんどこの表現を使っています。
また、定数系はすべてnamespaceの中で宣言しています。クラス定義により変わるような物でもないことと、
テンプレートクラス内に書くと各テンプレートごとに変数が宣言される、と言う間抜けな事態になるからです。
また、回転シフトのカウントについては64bit用の定義ではなく、本来の定義に従った値を設定しています。そのため、使用するときにはマスクをかけるようにします。
テンプレートクラス宣言部
template <class wordtype,size_t hashlength> class CHashKeccak{ private: enum{ KECCAK_WORD = sizeof(wordtype), KECCAK_BLOCKSIZE = 25 * KECCAK_WORD, KECCAK_SHIFTMASK = 8 * KECCAK_WORD - 1 }; template <class valuetype> static inline valuetype RotateLeft(valuetype nValue,unsigned int nShift) { return (nValue << nShift) | (nValue >> ((sizeof(valuetype) * 8) - nShift)); } #ifdef _BIG_ENDIAN static inline uint8_t SetLittleEndian(uint8_t nValue){ return nValue; } static inline uint16_t SetLittleEndian(uint16_t nValue){ return (nValue << 8) | (nValue >> 8); } static inline uint32_t SetLittleEndian(uint32_t nValue){ return (nValue << 24) | ((nValue & 0x0000ff00) << 8) | ((nValue & 0x00ff0000) >> 8) | (nValue >> 24); } static inline uint64_t SetLittleEndian(uint64_t nValue){ return ((uint64_t)SetLittleEndian((uint32_t)nValue) << 32) | (uint64_t)SetLittleEndian((uint32_t)(nValue >> 32)); } #else template <class valuetype> static inline valuetype SetLittleEndian(valuetype nValue){ return nValue; } #endif //_BIG_ENDIAN public: CHashKeccak(size_t nBlockLength = 0); ~CHashKeccak(); void Init(void); void Load(const void *data,size_t len); void Final(void); void GetDigest(void *buf) const; protected: static void Update(wordtype state[25],size_t rounds); private: wordtype m_anState[25]; wordtype m_anBlock[25]; size_t m_nHashLength; size_t m_nBlockLength; size_t m_nBlockCount; size_t m_nRoundCount; size_t m_nNumChr; };
おそらくこのテンプレートクラスの宣言を見ると、大半の人が「え?」と思うと思います。
これはテンプレートの宣言時に、本来型宣言を行う部分に整数値をとることが出来る、という仕様を使っています。
このように宣言すると、こんな書き方が出来ます。
typedef CHashKeccak<uint64_t,256> CHashSHA3_256; template <size_t hashlength> class CHashKeccak_f_1600 : public CHashKeccak<uint64_t,hashlength> { }; typedef CHashKeccak_f_1600<512> CHashSHA3_512;
まさにC++のテンプレート宣言のマジックですね。
一応説明をすると、wordtypeには扱うべきワードの型を指定します。使えるのは以下の通りです。
型名 | keccak-fの種類 |
---|---|
uint8_t (unsigned char) | keccak-f[200] |
uint16_t (unsinged short) | keccak-f[400] |
uint32_t (unsigned int) | keccak-f[800] |
uint64_t (unsigned __int64,unsigned long long) | keccak-f[1600] |
そして、hashlengthには取得したいハッシュ長(ダイジェスト長)をビット単位で指定します。内部的にはオクテット単位に切りそろえられてしまいますが。
また、内部的に使用している左回転シフトやバイトオーダーの変換ルーチンもここで宣言しています。
テンプレートを使って汎用的に書いていますが、高速化を考えるならテンプレートを特殊化して書いた方がいいですね。
バイトオーダーの変換ルーチンには間違いがあるかも・・・。使っていないので・・・。
コンストラクタ、デストラクタ
template <class wordtype,size_t hashlength> CHashKeccak<wordtype,hashlength>::CHashKeccak(size_t nBlockLength) : m_nHashLength(hashlength / 8), m_nBlockLength(nBlockLength != 0 ? nBlockLength : 25 * KECCAK_WORD - 2 * hashlength / 8), m_nBlockCount(0), m_nRoundCount(0), m_nNumChr(0) { if(m_nBlockLength > KECCAK_BLOCKSIZE) throw -1; m_nBlockCount = (m_nBlockLength + KECCAK_WORD - 1) / KECCAK_WORD; switch(KECCAK_WORD){ case 1: m_nRoundCount = 18; break; case 2: m_nRoundCount = 20; break; case 4: m_nRoundCount = 22; break; case 8: m_nRoundCount = 24; break; default: __assume(0); } Init(); } template <class wordtype,size_t hashlength> CHashKeccak<wordtype,hashlength>::~CHashKeccak() { }
この部分は単純に定義に従って各値の初期化を行っているだけです。
注意点としては、データブロック長を指定して初期化できるのですが、データブロック長が扱えるブロック長より長くなってしまう場合は例外を投げるようになっています。
データブロック長を指定しないでハッシュ長による初期化を行って負の数となった場合も変数が符号なし整数型なのでこの例外にかかります。
本来はコンパイルエラーなどで防ぎたいのですが、ちょっと簡単には思いつきませんでした。
あとは、ハッシュ取得時のラウンド数計算についてはswtich-caseによる固定処理にしてあります。
本来の定義は、ワードビット長をとするとき、ラウンド数
となります。
デストラクタの必要はありませんね。メモリの動的確保などもありませんので。
後の実装は次回に
コードが長くなっているので・・・。