Browse Source

Some work on delay function... which is delaying us... but need to get it right because its hard as hell to change later.

Adam Ierymenko 5 years ago
parent
commit
a20aebaaf8
4 changed files with 183 additions and 56 deletions
  1. 1 0
      node/CMakeLists.txt
  2. 32 54
      node/MIMC52.cpp
  3. 148 0
      node/Speck128.hpp
  4. 2 2
      node/Tests.cpp

+ 1 - 0
node/CMakeLists.txt

@@ -41,6 +41,7 @@ set(core_headers
 	SelfAwareness.hpp
 	SHA512.hpp
 	SharedPtr.hpp
+	Speck128.hpp
 	Tag.hpp
 	Topology.hpp
 	Trace.hpp

+ 32 - 54
node/MIMC52.cpp

@@ -14,6 +14,9 @@
 #include "MIMC52.hpp"
 #include "SHA512.hpp"
 #include "Utils.hpp"
+#include "Speck128.hpp"
+
+#include <cstdio>
 
 // This gets defined on any architecture whose FPU is not capable of doing the mulmod52() FPU trick.
 //#define ZT_MIMC52_NO_FPU
@@ -96,67 +99,31 @@ ZT_INLINE uint64_t modpow52(uint64_t a,uint64_t e,const uint64_t p) noexcept
 	}
 }
 
-// See: https://en.wikipedia.org/wiki/Speck_(cipher)
-#define SPECK_ROR(x,r) ((x >> r) | (x << (64U - r)))
-#define SPECK_ROL(x,r) ((x << r) | (x >> (64U - r)))
-#define SPECK_R(x,y,k) (x = SPECK_ROR(x,8U), x += y, x ^= k, y = SPECK_ROL(y,3U), y ^= x)
+} // anonymous namespace
 
-ZT_INLINE void mimc52Init(uint64_t k[128],const void *const salt,const unsigned int saltSize,uint64_t &p,uint64_t &x) noexcept
+uint64_t mimc52Delay(const void *const salt,const unsigned int saltSize,const unsigned long rounds)
 {
 	uint64_t hash[6];
 	SHA384(hash,salt,saltSize);
 
-	// Choose a prime and a delay starting point / verification end point.
-#if __BYTE_ORDER == __LITTLE_ENDIAN
-	p = s_mimc52Primes[hash[0] & 511U];
-	x = hash[1] % p;
-#else
-	p = s_mimc52Primes[Utils::swapBytes(hash[0]) & 511U];
-	x = Utils::swapBytes(hash[1]) % p;
-#endif
-
-	// Use the Speck128 round function as a fast PRNG to generate MIMC round constants. Speck128 initial inputs are
-	// from the last 256 bits of the salt hash. MIMC round constants could be static, but generating them from the
-	// salt may improve the overall randomness and non-reversibility of the delay function.
-#if __BYTE_ORDER == __LITTLE_ENDIAN
-	uint64_t ka = hash[2],kb = hash[3];
-#else
-	uint64_t ka = Utils::swapBytes(hash[2]),kb = Utils::swapBytes(hash[3]);
-#endif
-	uint64_t sy = hash[4],sx = hash[5];
-	for(unsigned long i=0;i<128;i+=2) {
-		ka += i;
-		kb += i;
-		SPECK_R(sx,sy,kb);
-		SPECK_R(ka,kb,0U);
-		SPECK_R(sx,sy,kb);
-		SPECK_R(ka,kb,1U);
-		SPECK_R(sx,sy,kb);
-		SPECK_R(ka,kb,2U);
-		SPECK_R(sx,sy,kb);
-		SPECK_R(ka,kb,3U);
-		SPECK_R(sx,sy,kb);
 #if __BYTE_ORDER == __LITTLE_ENDIAN
-		k[i] = sy;
-		k[i+1] = sx;
+	uint64_t p = s_mimc52Primes[hash[0] & 511U];
+	uint64_t x = hash[1] % p;
 #else
-		k[i] = Utils::swapBytes(sy);
-		k[i+1] = Utils::swapBytes(sx);
+	uint64_t p = s_mimc52Primes[Utils::swapBytes(hash[0]) & 511U];
+	uint64_t x = Utils::swapBytes(hash[1]) % p;
 #endif
-	}
-}
-
-} // anonymous namespace
-
-uint64_t mimc52Delay(const void *const salt,const unsigned int saltSize,const unsigned long rounds)
-{
-	uint64_t k[128],x,p;
-	mimc52Init(k,salt,saltSize,p,x);
 
+	Speck128<8> roundConstantGenerator(hash + 2);
 	const uint64_t e = ((p * 2) - 1) / 3;
 	const uint64_t m52 = 0xfffffffffffffULL;
-	for(unsigned long r=0,kn=rounds;r<rounds;++r) {
-		x = (x - k[--kn & 127U]) & m52;
+	const uint64_t rmin1 = rounds - 1;
+	const uint64_t sxx = hash[4];
+#pragma unroll 16
+	for(unsigned long r=0;r<rounds;++r) {
+		uint64_t sx = sxx,sy = rmin1 - r;
+		roundConstantGenerator.encryptXY(sx,sy);
+		x = (x - sy) & m52;
 		x = modpow52(x,e,p);
 	}
 
@@ -165,18 +132,29 @@ uint64_t mimc52Delay(const void *const salt,const unsigned int saltSize,const un
 
 bool mimc52Verify(const void *const salt,const unsigned int saltSize,unsigned long rounds,const uint64_t proof)
 {
-	uint64_t k[128],x,p;
-	mimc52Init(k,salt,saltSize,p,x);
+	uint64_t hash[6];
+	SHA384(hash,salt,saltSize);
 
-	// Note: x64_cubemod() seems slower even here on all tested cores.
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	uint64_t p = s_mimc52Primes[hash[0] & 511U];
+	uint64_t x = hash[1] % p;
+#else
+	uint64_t p = s_mimc52Primes[Utils::swapBytes(hash[0]) & 511U];
+	uint64_t x = Utils::swapBytes(hash[1]) % p;
+#endif
 
+	Speck128<8> roundConstantGenerator(hash + 2);
 	const uint64_t m52 = 0xfffffffffffffULL;
 	uint64_t y = proof & m52;
+	const uint64_t sxx = hash[4];
 #if !defined(ZT_MIMC52_NO_FPU)
 	double ii,of,pp = (double)p;
 	uint64_t oi,one = 1;
 #endif
+#pragma unroll 16
 	for(unsigned long r=0;r<rounds;++r) {
+		uint64_t sx = sxx,sy = r;
+		roundConstantGenerator.encryptXY(sx,sy);
 #ifdef ZT_MIMC52_NO_FPU
 #ifdef x64_cubemod
 		x64_cubemod(y,p);
@@ -196,7 +174,7 @@ bool mimc52Verify(const void *const salt,const unsigned int saltSize,unsigned lo
 		y -= ((uint64_t)ii - one) * p;
 		y %= p;
 #endif
-		y = (y + k[r & 127U]) & m52;
+		y = (y + sy) & m52;
 	}
 
 	return (y % p) == x;

+ 148 - 0
node/Speck128.hpp

@@ -0,0 +1,148 @@
+/*
+ * Copyright (c)2013-2020 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2024-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+#ifndef ZT_SPECK128_HPP
+#define ZT_SPECK128_HPP
+
+#include "Constants.hpp"
+#include "Utils.hpp"
+
+namespace ZeroTier {
+
+/**
+ * Tiny and simple 128-bit ARX block cipher
+ *
+ * Speck does not specify a mandatory endian-ness. This implementation is
+ * little-endian for higher performance on the majority of platforms.
+ *
+ * @tparam R Number of rounds (default: 32)
+ */
+template<int R = 32>
+class Speck128
+{
+public:
+	/**
+	 * Create an uninitialized instance, init() must be called to set up.
+	 */
+	ZT_INLINE Speck128() noexcept {}
+
+	/**
+	 * Initialize Speck from a 128-bit key
+	 *
+	 * @param k 128-bit / 16 byte key
+	 */
+	ZT_INLINE Speck128(const void *k) noexcept { this->init(k); }
+
+	ZT_INLINE ~Speck128() { Utils::burn(_k,sizeof(_k)); }
+
+	/**
+	 * Initialize Speck from a 128-bit key
+	 *
+	 * @param k 128-bit / 16 byte key
+	 */
+	ZT_INLINE void init(const void *k) noexcept
+	{
+		uint64_t x = Utils::loadLittleEndian<uint64_t>(k);
+		uint64_t y = Utils::loadLittleEndian<uint64_t>(reinterpret_cast<const uint8_t *>(k) + 8);
+		_k[0] = x;
+		for(uint64_t i=0;i<(R-1);++i) {
+			x = x >> 8U | x << 56U;
+			x += y;
+			x ^= i;
+			y = y << 3U | y >> 61U;
+			y ^= x;
+			_k[i + 1] = y;
+		}
+	}
+
+	/**
+	 * Encrypt a 128-bit block as two 64-bit words
+	 *
+	 * These should be in host byte order. If read or written to/from data
+	 * they should be stored in little-endian byte order.
+	 *
+	 * @param x Least significant 64 bits
+	 * @param y Most significant 64 bits
+	 */
+	ZT_INLINE void encryptXY(uint64_t &x,uint64_t &y) const noexcept
+	{
+#pragma unroll
+		for (int i=0;i<R;++i) {
+			const uint64_t kk = _k[i];
+			x = x >> 8U | x << 56U;
+			x += y;
+			x ^= kk;
+			y = y << 3U | y >> 61U;
+			y ^= x;
+		}
+	}
+
+	/**
+	 * Decrypt a 128-bit block as two 64-bit words
+	 *
+	 * These should be in host byte order. If read or written to/from data
+	 * they should be stored in little-endian byte order.
+	 *
+	 * @param x Least significant 64 bits
+	 * @param y Most significant 64 bits
+	 */
+	ZT_INLINE void decryptXY(uint64_t &x,uint64_t &y) const noexcept
+	{
+#pragma unroll
+		for (int i=(R-1);i>=0;--i) {
+			const uint64_t kk = _k[i];
+			y ^= x;
+			y = y >> 3U | y << 61U;
+			x ^= kk;
+			x -= y;
+			x = x << 8U | x >> 56U;
+		}
+	}
+
+	/**
+	 * Encrypt a block
+	 *
+	 * @param in 128-bit / 16 byte input
+	 * @param out 128-bit / 16 byte output
+	 */
+	ZT_INLINE void encrypt(const void *const in,void *const out) const noexcept
+	{
+		uint64_t x = Utils::loadLittleEndian<uint64_t>(in);
+		uint64_t y = Utils::loadLittleEndian<uint64_t>(reinterpret_cast<const uint8_t *>(in) + 8);
+		encryptXY(x,y);
+		Utils::storeLittleEndian<uint64_t>(out,x);
+		Utils::storeLittleEndian<uint64_t>(reinterpret_cast<uint8_t *>(out) + 8,y);
+	}
+
+	/**
+	 * Decrypt a block
+	 *
+	 * @param in 128-bit / 16 byte input
+	 * @param out 128-bit / 16 byte output
+	 */
+	ZT_INLINE void decrypt(const void *const in,void *const out) const noexcept
+	{
+		uint64_t x = Utils::loadLittleEndian<uint64_t>(in);
+		uint64_t y = Utils::loadLittleEndian<uint64_t>(reinterpret_cast<const uint8_t *>(in) + 8);
+		decryptXY(x,y);
+		Utils::storeLittleEndian<uint64_t>(out,x);
+		Utils::storeLittleEndian<uint64_t>(reinterpret_cast<uint8_t *>(out) + 8,y);
+	}
+
+private:
+	uint64_t _k[R];
+};
+
+} // namespace ZeroTier
+
+#endif

+ 2 - 2
node/Tests.cpp

@@ -777,8 +777,8 @@ extern "C" const char *ZTT_crypto()
 
 		{
 			ZT_T_PRINTF("[crypto] Testing MIMC52 VDF... ");
-			const uint64_t proof = mimc52Delay("testing",7,1000);
-			if ((!mimc52Verify("testing",7,1000,proof))||(proof != 0x000b501115c73369)) {
+			const uint64_t proof = mimc52Delay("",1,1000);
+			if ((!mimc52Verify("",1,1000,proof))||(proof != 0x00036030471c2aec)) {
 				ZT_T_PRINTF("FAILED (%.16llx)" ZT_EOL_S,proof);
 				return "MIMC52 failed simple delay/verify test";
 			}