Browse Source

V1 identities now use a VDF (verifiable delay function)

Adam Ierymenko 5 years ago
parent
commit
83e79e1a1b
4 changed files with 72 additions and 79 deletions
  1. 22 31
      node/Identity.cpp
  2. 12 26
      node/Identity.hpp
  3. 1 0
      node/SHA512.cpp
  4. 37 22
      node/Tests.cpp

+ 22 - 31
node/Identity.cpp

@@ -17,11 +17,14 @@
 #include "Salsa20.hpp"
 #include "AES.hpp"
 #include "Utils.hpp"
+#include "MIMC52.hpp"
 
 #include <cstring>
 #include <cstdint>
 #include <algorithm>
 
+#define ZT_V1_IDENTITY_MIMC52_VDF_ROUNDS_BASE 250000
+
 namespace ZeroTier {
 
 namespace {
@@ -75,31 +78,18 @@ struct _v0_identity_generate_cond
 	char *genmem;
 };
 
-ZT_ALWAYS_INLINE void _v1_hash(uint8_t *const digest,const void *const in,const unsigned int len) noexcept
-{
-	SHA384(digest,in,len);
-	Utils::storeBigEndian(digest,Utils::loadBigEndian<uint64_t>(digest)           % 18446744073709549811ULL);
-	Utils::storeBigEndian(digest + 8,Utils::loadBigEndian<uint64_t>(digest + 8)   % 18446744073709549757ULL);
-	Utils::storeBigEndian(digest + 16,Utils::loadBigEndian<uint64_t>(digest + 16) % 18446744073709549733ULL);
-	Utils::storeBigEndian(digest + 24,Utils::loadBigEndian<uint64_t>(digest + 24) % 18446744073709549667ULL);
-	Utils::storeBigEndian(digest + 32,Utils::loadBigEndian<uint64_t>(digest + 32) % 18446744073709549613ULL);
-	Utils::storeBigEndian(digest + 40,Utils::loadBigEndian<uint64_t>(digest + 40) % 18446744073709549583ULL);
-	SHA384(digest,in,len,digest,48);
-}
-
 } // anonymous namespace
 
 const Identity Identity::NIL;
 
 bool Identity::generate(const Type t)
 {
-	uint8_t digest[64];
-
 	_type = t;
 	_hasPrivate = true;
 
 	switch(t) {
 		case C25519: {
+			uint8_t digest[64];
 			char *const genmem = new char[ZT_V0_IDENTITY_GEN_MEMORY];
 			do {
 				C25519::generateSatisfying(_v0_identity_generate_cond(digest,genmem),_pub.c25519,_priv.c25519);
@@ -110,18 +100,15 @@ bool Identity::generate(const Type t)
 		} break;
 
 		case P384: {
-			AES c;
 			for(;;) {
 				C25519::generate(_pub.c25519,_priv.c25519);
 				ECC384GenerateKey(_pub.p384,_priv.p384);
-				_v1_hash(digest,&_pub,sizeof(_pub));
-				if (((digest[46] & 1U)|digest[47]) == 0) { // right-most 9 bits must be zero
-					_address.setTo(digest);
-					if (!_address.isReserved())
-						break;
-				}
+				Utils::storeBigEndian(_pub.t1mimc52,mimc52Delay(&_pub,sizeof(_pub) - sizeof(_pub.t1mimc52),ZT_V1_IDENTITY_MIMC52_VDF_ROUNDS_BASE));
+				_computeHash();
+				_address.setTo(_hash.data());
+				if (!_address.isReserved())
+					break;
 			}
-			_hash.set(digest); // P384 uses the same hash for hash() and address generation
 		} break;
 
 		default:
@@ -145,10 +132,19 @@ bool Identity::locallyValidate() const
 				delete [] genmem;
 				return ((_address == Address(digest + 59))&&(!_address.isReserved())&&(digest[0] < 17));
 			} catch ( ... ) {}
-			return false;
+			break;
 
 		case P384:
-			return ( (Address(_hash.data()) == _address) && (((_hash[46] & 1U)|_hash[47]) == 0) );
+			if ((_address == Address(_hash.data()))&&(!_address.isReserved())) {
+				// The most significant 8 bits of the MIMC proof included with v1 identities can be used to store a multiplier
+				// that can indicate that more work than the required minimum has been performed. Right now this is never done
+				// but it could have some use in the future. There is no harm in doing it, and we'll accept any round count
+				// that is at least ZT_V1_IDENTITY_MIMC52_VDF_ROUNDS_BASE.
+				const unsigned long rounds = ZT_V1_IDENTITY_MIMC52_VDF_ROUNDS_BASE * ((unsigned long)_pub.t1mimc52[0] + 1U);
+				if (mimc52Verify(&_pub,sizeof(_pub) - sizeof(_pub.t1mimc52),rounds,Utils::loadBigEndian<uint64_t>(_pub.t1mimc52)))
+					return true;
+			}
+			break;
 
 	}
 	return false;
@@ -488,18 +484,13 @@ int Identity::unmarshal(const uint8_t *data,const int len) noexcept
 
 				_hasPrivate = true;
 				memcpy(&_priv,data + ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1,ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE);
-				_computeHash();
-				if (!this->locallyValidate()) // for P384 we do this always
-					return -1;
 
+				_computeHash();
 				return ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1 + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE;
 			} else if (privlen == 0) {
 				_hasPrivate = false;
 
 				_computeHash();
-				if (!this->locallyValidate()) // for P384 we do this always
-					return -1;
-
 				return ZT_ADDRESS_LENGTH + 1 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + 1;
 			}
 			break;
@@ -521,7 +512,7 @@ void Identity::_computeHash()
 			break;
 
 		case P384:
-			_v1_hash(_hash.data(),&_pub,sizeof(_pub));
+			SHA384(_hash.data(),&_pub,sizeof(_pub));
 			break;
 	}
 }

+ 12 - 26
node/Identity.hpp

@@ -14,9 +14,6 @@
 #ifndef ZT_IDENTITY_HPP
 #define ZT_IDENTITY_HPP
 
-#include <cstdio>
-#include <cstdlib>
-
 #include "Constants.hpp"
 #include "Utils.hpp"
 #include "Address.hpp"
@@ -26,8 +23,12 @@
 #include "TriviallyCopyable.hpp"
 #include "Hash.hpp"
 
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
 #define ZT_IDENTITY_STRING_BUFFER_LENGTH 1024
-#define ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE (ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE)
+#define ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE (ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE + 8)
 #define ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE (ZT_C25519_PRIVATE_KEY_LEN + ZT_ECC384_PRIVATE_KEY_SIZE)
 #define ZT_IDENTITY_MARSHAL_SIZE_MAX (ZT_ADDRESS_LENGTH + 4 + ZT_IDENTITY_P384_COMPOUND_PUBLIC_KEY_SIZE + ZT_IDENTITY_P384_COMPOUND_PRIVATE_KEY_SIZE)
 
@@ -41,10 +42,6 @@ namespace ZeroTier {
  * key pair. Type 1 identities use P-384 for signatures but use both key pairs at once
  * (hashing both keys together) for key agreement with other type 1 identities, and can
  * agree with type 0 identities by only using the Curve25519 component.
- *
- * Type 1 identities also use a simpler mechanism to rate limit identity generation (as
- * a defense in depth against intentional collision) that makes local identity validation
- * faster, allowing full identity validation on all unmarshal() operations.
  */
 class Identity : public TriviallyCopyable
 {
@@ -210,14 +207,7 @@ public:
 
 	ZT_ALWAYS_INLINE bool operator==(const Identity &id) const noexcept
 	{
-		if ((_address == id._address)&&(_type == id._type)) {
-			switch(_type) {
-				case C25519: return (memcmp(_pub.c25519,id._pub.c25519,ZT_C25519_PUBLIC_KEY_LEN) == 0);
-				// case P384:
-				default: return (memcmp(&_pub,&id._pub,sizeof(_pub)) == 0);
-			}
-		}
-		return false;
+		return ((_address == id._address)&&(_type == id._type)&&(memcmp(_hash.data(),id._hash.data(),ZT_SHA384_DIGEST_LEN) == 0));
 	}
 	ZT_ALWAYS_INLINE bool operator!=(const Identity &id) const noexcept { return !(*this == id); }
 	ZT_ALWAYS_INLINE bool operator<(const Identity &id) const noexcept
@@ -227,13 +217,8 @@ public:
 		if (_address == id._address) {
 			if ((int)_type < (int)id._type)
 				return true;
-			if (_type == id._type) {
-				switch(_type) {
-					case C25519: return (memcmp(_pub.c25519,id._pub.c25519,ZT_C25519_PUBLIC_KEY_LEN) < 0);
-					// case P384:
-					default: return (memcmp(&_pub,&id._pub,sizeof(_pub)) < 0);
-				}
-			}
+			if (_type == id._type)
+				return memcmp(_hash.data(),id._hash.data(),ZT_SHA384_DIGEST_LEN) < 0;
 		}
 		return false;
 	}
@@ -249,14 +234,15 @@ private:
 	void _computeHash();
 
 	Address _address;
-	Hash<384> _hash;
-	ZT_PACKED_STRUCT(struct { // don't re-order these
+	Fingerprint _hash;
+	ZT_PACKED_STRUCT(struct { // do not re-order these fields
 		uint8_t c25519[ZT_C25519_PRIVATE_KEY_LEN];
 		uint8_t p384[ZT_ECC384_PRIVATE_KEY_SIZE];
 	}) _priv;
-	ZT_PACKED_STRUCT(struct { // don't re-order these
+	ZT_PACKED_STRUCT(struct { // do not re-order these fields
 		uint8_t c25519[ZT_C25519_PUBLIC_KEY_LEN]; // Curve25519 and Ed25519 public keys
 		uint8_t p384[ZT_ECC384_PUBLIC_KEY_SIZE];  // NIST P-384 public key
+		uint8_t t1mimc52[8];                      // Type 1 MIMC52 proof and work amount in big-endian byte order
 	}) _pub;
 	Type _type; // _type determines which fields in _priv and _pub are used
 	bool _hasPrivate;

+ 1 - 0
node/SHA512.cpp

@@ -6,6 +6,7 @@
 #include <cstdint>
 #include <cstdlib>
 #include <cstring>
+#include <algorithm>
 
 namespace ZeroTier {
 

+ 37 - 22
node/Tests.cpp

@@ -179,8 +179,8 @@ static const C25519TestVector C25519_TEST_VECTORS[ZT_NUM_C25519_TEST_VECTORS] =
 #define IDENTITY_V0_KNOWN_GOOD_0 "8e4df28b72:0:ac3d46abe0c21f3cfe7a6c8d6a85cfcffcb82fbd55af6a4d6350657c68200843fa2e16f9418bbd9702cae365f2af5fb4c420908b803a681d4daef6114d78a2d7:bd8dd6e4ce7022d2f812797a80c6ee8ad180dc4ebf301dec8b06d1be08832bddd63a2f1cfa7b2c504474c75bdc8898ba476ef92e8e2d0509f8441985171ff16e"
 #define IDENTITY_V0_KNOWN_BAD_0 "9e4df28b72:0:ac3d46abe0c21f3cfe7a6c8d6a85cfcffcb82fbd55af6a4d6350657c68200843fa2e16f9418bbd9702cae365f2af5fb4c420908b803a681d4daef6114d78a2d7:bd8dd6e4ce7022d2f812797a80c6ee8ad180dc4ebf301dec8b06d1be08832bddd63a2f1cfa7b2c504474c75bdc8898ba476ef92e8e2d0509f8441985171ff16e"
 
-#define IDENTITY_V1_KNOWN_GOOD_0 "bc72fb58e4:1:fya26hekqeromqdtpzq3mzj26zecwf7pkjahictpreapv4sw5vjcdkf6tbwaajzw6cq2ro6usrtzerccr37n52hiydogi2boaxk4tjidnhctgsbk4i4g34madrxihraurflyoe3xgeqkbpj2zrlsivscvbygzd3zfqs3qihoi6e24xy2jridq:tqaxnh3pucstd2xuwylgjfapyug7zdxorfwv37ted66qic6fu5g3pveodg7so4vt7cil7ptoht6msn6m2tsrfyd52a5f3b3g5wbd5ljjds2sftrjjw3qcb645eg4iizbqv5mlphgpa2uznonoo77qblbx6fdjh2nbt3ksooebj377rgu6qmq"
-#define IDENTITY_V1_KNOWN_BAD_0 "bc82fb58e4:1:fya26hekqeromqdtpzq3mzj26zecwf7pkjahictpreapv4sw5vjcdkf6tbwaajzw6cq2ro6usrtzerccr37n52hiydogi2boaxk4tjidnhctgsbk4i4g34madrxihraurflyoe3xgeqkbpj2zrlsivscvbygzd3zfqs3qihoi6e24xy2jridq:tqaxnh3pucstd2xuwylgjfapyug7zdxorfwv37ted66qic6fu5g3pveodg7so4vt7cil7ptoht6msn6m2tsrfyd52a5f3b3g5wbd5ljjds2sftrjjw3qcb645eg4iizbqv5mlphgpa2uznonoo77qblbx6fdjh2nbt3ksooebj377rgu6qmq"
+#define IDENTITY_V1_KNOWN_GOOD_0 "237ce8d8e2:1:5w3rj6am3sa7f5vtwm535iswob6ngmkpdidijz5ormqrfwkj55lhwyyszruu4rkbjycmlxzzoiuwtyw5s2mybknqx5j2cwxnaflqbwycoio2hqzcro5afrpcncnxlemzs6bt5linlib5flsej3f3r3bbzclxk733ei7tdrtm5uruiwpmyi4vgaafze42sx6hpe:mwjavgvhxz75ow2fhgq3zu4qfou5kce4wzegpjjd6545fpjnhjxb26e5unuutv7k3c6sm6umpyvatgpufwehi4wqmyudvq724h2klbiem6txs2h5iit5crgg3e6se5xeomuqhircv7zhkylrtnlgh57il742pwkrdgt4lz5fstetmiw7y3rq"
+#define IDENTITY_V1_KNOWN_BAD_0 "238ce8d8e2:1:5w3rj6am3sa7f5vtwm535iswob6ngmkpdidijz5ormqrfwkj55lhwyyszruu4rkbjycmlxzzoiuwtyw5s2mybknqx5j2cwxnaflqbwycoio2hqzcro5afrpcncnxlemzs6bt5linlib5flsej3f3r3bbzclxk733ei7tdrtm5uruiwpmyi4vgaafze42sx6hpe:mwjavgvhxz75ow2fhgq3zu4qfou5kce4wzegpjjd6545fpjnhjxb26e5unuutv7k3c6sm6umpyvatgpufwehi4wqmyudvq724h2klbiem6txs2h5iit5crgg3e6se5xeomuqhircv7zhkylrtnlgh57il742pwkrdgt4lz5fstetmiw7y3rq"
 
 // --------------------------------------------------------------------------------------------------------------------
 
@@ -353,16 +353,6 @@ extern "C" const char *ZTT_general()
 			ZT_T_PRINTF("OK" ZT_EOL_S);
 		}
 
-		{
-			ZT_T_PRINTF("[general] Testing MIMC52... ");
-			const uint64_t proof = mimc52Delay("testing",7,1000);
-			if ((!mimc52Verify("testing",7,1000,proof))||(proof != 0x0007a1a0a1b0fe32)) {
-				ZT_T_PRINTF("FAILED (%.16llx)" ZT_EOL_S,proof);
-				return "MIMC52 failed simple delay/verify test";
-			}
-			ZT_T_PRINTF("OK (%.16llx)" ZT_EOL_S,proof);
-		}
-
 		{
 			ZT_T_PRINTF("[general] Testing FCV (fixed capacity vector)... ");
 			long cnt = 0;
@@ -646,6 +636,16 @@ extern "C" const char *ZTT_crypto()
 			ZT_T_PRINTF("OK" ZT_EOL_S);
 		}
 
+		{
+			ZT_T_PRINTF("[crypto] Testing MIMC52 VDF... ");
+			const uint64_t proof = mimc52Delay("testing",7,1000);
+			if ((!mimc52Verify("testing",7,1000,proof))||(proof != 0x0007a1a0a1b0fe32)) {
+				ZT_T_PRINTF("FAILED (%.16llx)" ZT_EOL_S,proof);
+				return "MIMC52 failed simple delay/verify test";
+			}
+			ZT_T_PRINTF("OK (%.16llx)" ZT_EOL_S,proof);
+		}
+
 		{
 			uint8_t agree0[32],agree1[32],kh[64],sig[96];
 			ZT_T_PRINTF("[crypto] Testing C25519/Ed25519... ");
@@ -862,18 +862,18 @@ extern "C" const char *ZTT_benchmarkCrypto()
 		memset(tag,0,sizeof(tag));
 
 		{
-			ZT_T_PRINTF("[crypto] Benchmarking MIMC52 delay... ");
+			ZT_T_PRINTF("[crypto] Benchmarking SHA384... ");
 			int64_t start = now();
-			const uint64_t proof = mimc52Delay("testing",7,250000);
+			for(int i=0;i<10000;++i)
+				SHA384(tmp,tmp,sizeof(tmp));
 			int64_t end = now();
-			int64_t dtime = end - start;
-			ZT_T_PRINTF("%.4f μs/round" ZT_EOL_S,((double)dtime * 1000.0) / 250000.0);
-			ZT_T_PRINTF("[crypto] Benchmarking MIMC52 verify... ");
+			ZT_T_PRINTF("%.4f MiB/sec" ZT_EOL_S,((16384.0 * 10000.0) / 1048576.0) / ((double)(end - start) / 1000.0));
+			ZT_T_PRINTF("[crypto] Benchmarking SHA512... ");
 			start = now();
-			foo = (uint8_t)mimc52Verify("testing",7,1000000,proof); // doesn't matter if return is true or false here
+			for(int i=0;i<10000;++i)
+				SHA512(tmp,tmp,sizeof(tmp));
 			end = now();
-			int64_t vtime = end - start;
-			ZT_T_PRINTF("%.8f μs/round, %.4fX faster than delay" ZT_EOL_S,((double)vtime * 1000.0) / 1000000.0,(double)(dtime / 250000.0) / (double)(vtime / 1000000.0));
+			ZT_T_PRINTF("%.4f MiB/sec" ZT_EOL_S,((16384.0 * 10000.0) / 1048576.0) / ((double)(end - start) / 1000.0));
 		}
 
 		{
@@ -999,6 +999,21 @@ extern "C" const char *ZTT_benchmarkCrypto()
 			ZT_T_PRINTF("%.4f μs/verify" ZT_EOL_S,((double)(end - start) * 1000.0) / (double)(500 * ZT_NUM_C25519_TEST_VECTORS));
 		}
 
+		{
+			ZT_T_PRINTF("[crypto] Benchmarking MIMC52 VDF delay... ");
+			int64_t start = now();
+			const uint64_t proof = mimc52Delay("testing",7,250000);
+			int64_t end = now();
+			int64_t dtime = end - start;
+			ZT_T_PRINTF("%.4f μs/round" ZT_EOL_S,((double)dtime * 1000.0) / 250000.0);
+			ZT_T_PRINTF("[crypto] Benchmarking MIMC52 VDF verify... ");
+			start = now();
+			foo = (uint8_t)mimc52Verify("testing",7,1000000,proof); // doesn't matter if return is true or false here
+			end = now();
+			int64_t vtime = end - start;
+			ZT_T_PRINTF("%.8f μs/round, %.4fX faster than delay" ZT_EOL_S,((double)vtime * 1000.0) / 1000000.0,(double)(dtime / 250000.0) / (double)(vtime / 1000000.0));
+		}
+
 		{
 			ZT_T_PRINTF("[crypto] Benchmarking V0 Identity generation... ");
 			Identity id;
@@ -1008,7 +1023,7 @@ extern "C" const char *ZTT_benchmarkCrypto()
 				foo = (uint8_t)id.address().toInt();
 			}
 			int64_t end = now();
-			ZT_T_PRINTF("%.4f ms/generation" ZT_EOL_S,(double)(end - start) / 5.0);
+			ZT_T_PRINTF("%.4f ms/generation (average, can vary quite a bit)" ZT_EOL_S,(double)(end - start) / 5.0);
 			ZT_T_PRINTF("[crypto] Benchmarking V1 Identity generation... ");
 			start = now();
 			for(long i=0;i<5;++i) {
@@ -1016,7 +1031,7 @@ extern "C" const char *ZTT_benchmarkCrypto()
 				foo = (uint8_t)id.address().toInt();
 			}
 			end = now();
-			ZT_T_PRINTF("%.4f ms/generation" ZT_EOL_S,(double)(end - start) / 5.0);
+			ZT_T_PRINTF("%.4f ms/generation (relatively constant time)" ZT_EOL_S,(double)(end - start) / 5.0);
 		}
 	} catch (std::exception &e) {
 		ZT_T_PRINTF(ZT_EOL_S "[crypto] Unexpected exception: %s" ZT_EOL_S,e.what());