From 83e79e1a1bb3085349168fae6f656d965495f1a2 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 26 Feb 2020 11:08:03 -0800 Subject: [PATCH] V1 identities now use a VDF (verifiable delay function) --- node/Identity.cpp | 53 ++++++++++++++++++------------------------ node/Identity.hpp | 38 ++++++++++-------------------- node/SHA512.cpp | 1 + node/Tests.cpp | 59 +++++++++++++++++++++++++++++------------------ 4 files changed, 72 insertions(+), 79 deletions(-) diff --git a/node/Identity.cpp b/node/Identity.cpp index ca285ef3f..caef80481 100644 --- a/node/Identity.cpp +++ b/node/Identity.cpp @@ -17,11 +17,14 @@ #include "Salsa20.hpp" #include "AES.hpp" #include "Utils.hpp" +#include "MIMC52.hpp" #include #include #include +#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(digest) % 18446744073709549811ULL); - Utils::storeBigEndian(digest + 8,Utils::loadBigEndian(digest + 8) % 18446744073709549757ULL); - Utils::storeBigEndian(digest + 16,Utils::loadBigEndian(digest + 16) % 18446744073709549733ULL); - Utils::storeBigEndian(digest + 24,Utils::loadBigEndian(digest + 24) % 18446744073709549667ULL); - Utils::storeBigEndian(digest + 32,Utils::loadBigEndian(digest + 32) % 18446744073709549613ULL); - Utils::storeBigEndian(digest + 40,Utils::loadBigEndian(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(_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; } } diff --git a/node/Identity.hpp b/node/Identity.hpp index 51d6fe32e..a9cff39cb 100644 --- a/node/Identity.hpp +++ b/node/Identity.hpp @@ -14,9 +14,6 @@ #ifndef ZT_IDENTITY_HPP #define ZT_IDENTITY_HPP -#include -#include - #include "Constants.hpp" #include "Utils.hpp" #include "Address.hpp" @@ -26,8 +23,12 @@ #include "TriviallyCopyable.hpp" #include "Hash.hpp" +#include +#include +#include + #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; diff --git a/node/SHA512.cpp b/node/SHA512.cpp index 0d91fbe54..8bdafb883 100644 --- a/node/SHA512.cpp +++ b/node/SHA512.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace ZeroTier { diff --git a/node/Tests.cpp b/node/Tests.cpp index a089d3397..921f088bc 100644 --- a/node/Tests.cpp +++ b/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());