V1 identities now use a VDF (verifiable delay function)

This commit is contained in:
Adam Ierymenko 2020-02-26 11:08:03 -08:00
parent c6a7b5774c
commit 83e79e1a1b
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
4 changed files with 72 additions and 79 deletions

View file

@ -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;
}
}

View file

@ -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;

View file

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

View file

@ -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());