summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Schanzenbach <mschanzenbach@posteo.de>2021-05-01 22:05:15 +0200
committerMartin Schanzenbach <mschanzenbach@posteo.de>2021-05-02 10:39:55 +0200
commit572f4d6f7b19dec42d571829384ac9cd356bb092 (patch)
treecbe8bf3ae413a1aa3b71beffcbe7ce9bb83fe03f
parentca3ebf1e59eb00ad101ba8b26e5185db4d785610 (diff)
GNS: Add EDKEY support.
GNS and GNSRECORD can now handle EdDSA keys in addition to the existing ECDSA scheme. See also LSD0001.
-rw-r--r--po/POTFILES.in2
-rw-r--r--src/gnsrecord/gnsrecord_crypto.c426
-rw-r--r--src/gnsrecord/gnsrecord_misc.c17
-rw-r--r--src/gnsrecord/gnsrecord_serialization.c3
-rw-r--r--src/gnsrecord/test_gnsrecord_crypto.c36
-rw-r--r--src/include/gnunet_crypto_lib.h78
-rw-r--r--src/include/gnunet_gnsrecord_lib.h33
-rw-r--r--src/util/Makefile.am1
-rw-r--r--src/util/crypto_ecc.c129
-rw-r--r--src/util/crypto_ecc_gnsrecord.c351
10 files changed, 891 insertions, 185 deletions
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 3416e6c84..996b8bda2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -313,6 +313,7 @@ src/reclaim/plugin_rest_reclaim.c
src/reclaim/reclaim_api.c
src/reclaim/reclaim_attribute.c
src/reclaim/reclaim_credential.c
+src/reclaim/test.c
src/regex/gnunet-daemon-regexprofiler.c
src/regex/gnunet-regex-profiler.c
src/regex/gnunet-regex-simulation-profiler.c
@@ -517,6 +518,7 @@ src/util/crypto_abe.c
src/util/crypto_crc.c
src/util/crypto_ecc.c
src/util/crypto_ecc_dlog.c
+src/util/crypto_ecc_gnsrecord.c
src/util/crypto_ecc_setup.c
src/util/crypto_hash.c
src/util/crypto_hash_file.c
diff --git a/src/gnsrecord/gnsrecord_crypto.c b/src/gnsrecord/gnsrecord_crypto.c
index 9c551a936..289f0e885 100644
--- a/src/gnsrecord/gnsrecord_crypto.c
+++ b/src/gnsrecord/gnsrecord_crypto.c
@@ -64,7 +64,6 @@ ecdsa_symmetric_decrypt (
}
-
ssize_t
ecdsa_symmetric_encrypt (
const void *block,
@@ -92,6 +91,34 @@ ecdsa_symmetric_encrypt (
}
+enum GNUNET_GenericReturnValue
+eddsa_symmetric_decrypt (
+ const void *block,
+ size_t size,
+ const unsigned char *key,
+ const unsigned char *nonce,
+ void *result)
+{
+ if (0 != crypto_secretbox_open_easy (result, block, size, nonce, key))
+ {
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+eddsa_symmetric_encrypt (
+ const void *block,
+ size_t size,
+ const unsigned char *key,
+ const unsigned char *nonce,
+ void *result)
+{
+ crypto_secretbox_easy (result, block, size, nonce, key);
+ return GNUNET_OK;
+}
+
/**
* Derive session key and iv from label and public key.
@@ -131,6 +158,42 @@ derive_block_aes_key (unsigned char *ctr,
/**
+ * Derive session key and iv from label and public key.
+ *
+ * @param nonce initialization vector to initialize
+ * @param skey session key to initialize
+ * @param label label to use for KDF
+ * @param pub public key to use for KDF
+ */
+static void
+derive_block_xsalsa_key (unsigned char *nonce,
+ unsigned char *key,
+ const char *label,
+ uint64_t exp,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *pub)
+{
+ static const char ctx_key[] = "gns-aes-ctx-key";
+ static const char ctx_iv[] = "gns-aes-ctx-iv";
+
+ GNUNET_CRYPTO_kdf (key, crypto_secretbox_KEYBYTES,
+ ctx_key, strlen (ctx_key),
+ pub, sizeof(struct GNUNET_CRYPTO_EddsaPublicKey),
+ label, strlen (label),
+ NULL, 0);
+ memset (nonce, 0, crypto_secretbox_NONCEBYTES);
+ /** 16 byte nonce **/
+ GNUNET_CRYPTO_kdf (nonce, (crypto_secretbox_NONCEBYTES - sizeof (exp)),
+ ctx_iv, strlen (ctx_iv),
+ pub, sizeof(struct GNUNET_CRYPTO_EddsaPublicKey),
+ label, strlen (label),
+ NULL, 0);
+ /** Expiration time 64 bit. **/
+ memcpy (nonce + (crypto_secretbox_NONCEBYTES - sizeof (exp)),
+ &exp, sizeof (exp));
+}
+
+
+/**
* Sign name and records
*
* @param key the private key
@@ -246,6 +309,119 @@ block_create_ecdsa (const struct GNUNET_CRYPTO_EcdsaPrivateKey *key,
/**
+ * Sign name and records (EDDSA version)
+ *
+ * @param key the private key
+ * @param pkey associated public key
+ * @param expire block expiration
+ * @param label the name for the records
+ * @param rd record data
+ * @param rd_count number of records
+ * @return NULL on error (block too large)
+ */
+static struct GNUNET_GNSRECORD_Block *
+block_create_eddsa (const struct GNUNET_CRYPTO_EddsaPrivateKey *key,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *pkey,
+ struct GNUNET_TIME_Absolute expire,
+ const char *label,
+ const struct GNUNET_GNSRECORD_Data *rd,
+ unsigned int rd_count)
+{
+ ssize_t payload_len = GNUNET_GNSRECORD_records_get_size (rd_count,
+ rd);
+ struct GNUNET_GNSRECORD_Block *block;
+ struct GNUNET_GNSRECORD_EddsaBlock *edblock;
+ struct GNUNET_CRYPTO_EddsaPrivateScalar dkey;
+ unsigned char nonce[crypto_secretbox_NONCEBYTES];
+ unsigned char skey[crypto_secretbox_KEYBYTES];
+ struct GNUNET_GNSRECORD_Data rdc[GNUNET_NZL (rd_count)];
+ uint32_t rd_count_nbo;
+ struct GNUNET_TIME_Absolute now;
+
+ if (payload_len < 0)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (payload_len > GNUNET_GNSRECORD_MAX_BLOCK_SIZE)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ /* convert relative to absolute times */
+ now = GNUNET_TIME_absolute_get ();
+ for (unsigned int i = 0; i < rd_count; i++)
+ {
+ rdc[i] = rd[i];
+ if (0 != (rd[i].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION))
+ {
+ struct GNUNET_TIME_Relative t;
+
+ /* encrypted blocks must never have relative expiration times, convert! */
+ rdc[i].flags &= ~GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
+ t.rel_value_us = rdc[i].expiration_time;
+ rdc[i].expiration_time = GNUNET_TIME_absolute_add (now, t).abs_value_us;
+ }
+ }
+ /* serialize */
+ rd_count_nbo = htonl (rd_count);
+ {
+ char payload[sizeof(uint32_t) + payload_len];
+
+ GNUNET_memcpy (payload,
+ &rd_count_nbo,
+ sizeof(uint32_t));
+ GNUNET_assert (payload_len ==
+ GNUNET_GNSRECORD_records_serialize (rd_count,
+ rdc,
+ payload_len,
+ &payload[sizeof(uint32_t)
+ ]));
+ block = GNUNET_malloc (sizeof(struct GNUNET_GNSRECORD_Block)
+ + sizeof(uint32_t)
+ + payload_len
+ + crypto_secretbox_MACBYTES);
+ edblock = &block->eddsa_block;
+ block->type = htonl (GNUNET_GNSRECORD_TYPE_EDKEY);
+ edblock->purpose.size = htonl (sizeof(uint32_t)
+ + payload_len
+ + sizeof(struct
+ GNUNET_CRYPTO_EccSignaturePurpose)
+ + sizeof(struct GNUNET_TIME_AbsoluteNBO)
+ + crypto_secretbox_MACBYTES);
+ edblock->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN);
+ edblock->expiration_time = GNUNET_TIME_absolute_hton (expire);
+ /* encrypt and sign */
+ GNUNET_CRYPTO_eddsa_private_key_derive (key,
+ label,
+ "gns",
+ &dkey);
+ // FIXME: We may want a key_get_public_from_private_scalar function
+ struct GNUNET_CRYPTO_EddsaPublicKey test;
+ crypto_scalarmult_ed25519_base_noclamp (test.q_y,
+ dkey.s);
+ edblock->derived_key = test;
+ derive_block_xsalsa_key (nonce,
+ skey,
+ label,
+ edblock->expiration_time.abs_value_us__,
+ pkey);
+ GNUNET_break (GNUNET_OK ==
+ eddsa_symmetric_encrypt (payload,
+ payload_len
+ + sizeof(uint32_t),
+ skey,
+ nonce,
+ &edblock[1]));
+ }
+ GNUNET_CRYPTO_eddsa_sign_with_scalar (&dkey,
+ &edblock->purpose,
+ &edblock->signature);
+ return block;
+}
+
+
+/**
* Sign name and records
*
* @param key the private key
@@ -263,6 +439,7 @@ GNUNET_GNSRECORD_block_create (const struct GNUNET_IDENTITY_PrivateKey *key,
unsigned int rd_count)
{
struct GNUNET_CRYPTO_EcdsaPublicKey pkey;
+ struct GNUNET_CRYPTO_EddsaPublicKey edkey;
switch (ntohl (key->type))
{
@@ -275,6 +452,15 @@ GNUNET_GNSRECORD_block_create (const struct GNUNET_IDENTITY_PrivateKey *key,
label,
rd,
rd_count);
+ case GNUNET_GNSRECORD_TYPE_EDKEY:
+ GNUNET_CRYPTO_eddsa_key_get_public (&key->eddsa_key,
+ &edkey);
+ return block_create_eddsa (&key->eddsa_key,
+ &edkey,
+ expire,
+ label,
+ rd,
+ rd_count);
default:
GNUNET_assert (0);
}
@@ -319,33 +505,45 @@ GNUNET_GNSRECORD_block_create2 (const struct GNUNET_IDENTITY_PrivateKey *pkey,
unsigned int rd_count)
{
const struct GNUNET_CRYPTO_EcdsaPrivateKey *key;
+ struct GNUNET_CRYPTO_EddsaPublicKey edpubkey;
- if (GNUNET_IDENTITY_TYPE_ECDSA != ntohl (pkey->type))
+ if (GNUNET_IDENTITY_TYPE_ECDSA == ntohl (pkey->type))
{
- return NULL; // FIXME
- }
- key = &pkey->ecdsa_key;
+ key = &pkey->ecdsa_key;
#define CSIZE 64
- static struct KeyCacheLine cache[CSIZE];
- struct KeyCacheLine *line;
+ static struct KeyCacheLine cache[CSIZE];
+ struct KeyCacheLine *line;
- line = &cache[(*(unsigned int *) key) % CSIZE];
- if (0 != memcmp (&line->key,
- key,
- sizeof(*key)))
+ line = &cache[(*(unsigned int *) key) % CSIZE];
+ if (0 != memcmp (&line->key,
+ key,
+ sizeof(*key)))
+ {
+ /* cache miss, recompute */
+ line->key = *key;
+ GNUNET_CRYPTO_ecdsa_key_get_public (key,
+ &line->pkey);
+ }
+#undef CSIZE
+ return block_create_ecdsa (key,
+ &line->pkey,
+ expire,
+ label,
+ rd,
+ rd_count);
+ }
+ else if (GNUNET_IDENTITY_TYPE_EDDSA == ntohl (pkey->type))
{
- /* cache miss, recompute */
- line->key = *key;
- GNUNET_CRYPTO_ecdsa_key_get_public (key,
- &line->pkey);
+ GNUNET_CRYPTO_eddsa_key_get_public (&pkey->eddsa_key,
+ &edpubkey);
+ return block_create_eddsa (&pkey->eddsa_key,
+ &edpubkey,
+ expire,
+ label,
+ rd,
+ rd_count);
}
-#undef CSIZE
- return block_create_ecdsa (key,
- &line->pkey,
- expire,
- label,
- rd,
- rd_count);
+ return NULL;
}
@@ -359,21 +557,23 @@ GNUNET_GNSRECORD_block_create2 (const struct GNUNET_IDENTITY_PrivateKey *pkey,
enum GNUNET_GenericReturnValue
GNUNET_GNSRECORD_block_verify (const struct GNUNET_GNSRECORD_Block *block)
{
- const struct GNUNET_CRYPTO_EcdsaPublicKey *key;
- const struct GNUNET_GNSRECORD_EcdsaBlock *ecblock;
-
- if (GNUNET_GNSRECORD_TYPE_PKEY != ntohl (block->type))
+ switch (ntohl (block->type))
{
- GNUNET_break (0);
+ case GNUNET_GNSRECORD_TYPE_PKEY:
+ return GNUNET_CRYPTO_ecdsa_verify_ (
+ GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN,
+ &block->ecdsa_block.purpose,
+ &block->ecdsa_block.signature,
+ &block->ecdsa_block.derived_key);
+ case GNUNET_GNSRECORD_TYPE_EDKEY:
+ return GNUNET_CRYPTO_eddsa_verify_ (
+ GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN,
+ &block->eddsa_block.purpose,
+ &block->eddsa_block.signature,
+ &block->eddsa_block.derived_key);
+ default:
return GNUNET_NO;
}
- ecblock = &block->ecdsa_block;
- key = &ecblock->derived_key;
-
- return GNUNET_CRYPTO_ecdsa_verify_ (GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN,
- &ecblock->purpose,
- &ecblock->signature,
- key);
}
@@ -505,6 +705,134 @@ block_decrypt_ecdsa (const struct GNUNET_GNSRECORD_EcdsaBlock *block,
}
+enum GNUNET_GenericReturnValue
+block_decrypt_eddsa (const struct GNUNET_GNSRECORD_EddsaBlock *block,
+ const struct
+ GNUNET_CRYPTO_EddsaPublicKey *zone_key,
+ const char *label,
+ GNUNET_GNSRECORD_RecordCallback proc,
+ void *proc_cls)
+{
+ size_t payload_len = ntohl (block->purpose.size)
+ - sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose)
+ - sizeof(struct GNUNET_TIME_AbsoluteNBO);
+ unsigned char nonce[crypto_secretbox_NONCEBYTES];
+ unsigned char key[crypto_secretbox_KEYBYTES];
+
+ if (ntohl (block->purpose.size) <
+ sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose)
+ + sizeof(struct GNUNET_TIME_AbsoluteNBO))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ derive_block_xsalsa_key (nonce,
+ key,
+ label,
+ block->expiration_time.abs_value_us__,
+ zone_key);
+ {
+ char payload[payload_len];
+ uint32_t rd_count;
+
+ GNUNET_break (GNUNET_OK ==
+ eddsa_symmetric_decrypt (&block[1], payload_len,
+ key, nonce,
+ payload));
+ GNUNET_memcpy (&rd_count,
+ payload,
+ sizeof(uint32_t));
+ rd_count = ntohl (rd_count);
+ if (rd_count > 2048)
+ {
+ /* limit to sane value */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ struct GNUNET_GNSRECORD_Data rd[GNUNET_NZL (rd_count)];
+ unsigned int j;
+ struct GNUNET_TIME_Absolute now;
+
+ if (GNUNET_OK !=
+ GNUNET_GNSRECORD_records_deserialize (payload_len - sizeof(uint32_t),
+ &payload[sizeof(uint32_t)],
+ rd_count,
+ rd))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* hide expired records */
+ now = GNUNET_TIME_absolute_get ();
+ j = 0;
+ for (unsigned int i = 0; i < rd_count; i++)
+ {
+ if (0 != (rd[i].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION))
+ {
+ /* encrypted blocks must never have relative expiration times, skip! */
+ GNUNET_break_op (0);
+ continue;
+ }
+
+ if (0 != (rd[i].flags & GNUNET_GNSRECORD_RF_SHADOW_RECORD))
+ {
+ int include_record = GNUNET_YES;
+ /* Shadow record, figure out if we have a not expired active record */
+ for (unsigned int k = 0; k < rd_count; k++)
+ {
+ if (k == i)
+ continue;
+ if (rd[i].expiration_time < now.abs_value_us)
+ include_record = GNUNET_NO; /* Shadow record is expired */
+ if ((rd[k].record_type == rd[i].record_type) &&
+ (rd[k].expiration_time >= now.abs_value_us) &&
+ (0 == (rd[k].flags & GNUNET_GNSRECORD_RF_SHADOW_RECORD)))
+ {
+ include_record = GNUNET_NO; /* We have a non-expired, non-shadow record of the same type */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Ignoring shadow record\n");
+ break;
+ }
+ }
+ if (GNUNET_YES == include_record)
+ {
+ rd[i].flags ^= GNUNET_GNSRECORD_RF_SHADOW_RECORD; /* Remove Flag */
+ if (j != i)
+ rd[j] = rd[i];
+ j++;
+ }
+ }
+ else if (rd[i].expiration_time >= now.abs_value_us)
+ {
+ /* Include this record */
+ if (j != i)
+ rd[j] = rd[i];
+ j++;
+ }
+ else
+ {
+ struct GNUNET_TIME_Absolute at;
+
+ at.abs_value_us = rd[i].expiration_time;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Excluding record that expired %s (%llu ago)\n",
+ GNUNET_STRINGS_absolute_time_to_string (at),
+ (unsigned long long) rd[i].expiration_time
+ - now.abs_value_us);
+ }
+ }
+ rd_count = j;
+ if (NULL != proc)
+ proc (proc_cls,
+ rd_count,
+ (0 != rd_count) ? rd : NULL);
+ }
+ }
+ return GNUNET_OK;
+}
+
+
/**
* Decrypt block.
*
@@ -524,17 +852,17 @@ GNUNET_GNSRECORD_block_decrypt (const struct GNUNET_GNSRECORD_Block *block,
GNUNET_GNSRECORD_RecordCallback proc,
void *proc_cls)
{
- const struct GNUNET_CRYPTO_EcdsaPublicKey *key;
-
- if (GNUNET_IDENTITY_TYPE_ECDSA != ntohl (zone_key->type))
+ switch (ntohl (zone_key->type))
{
- return GNUNET_NO;
+ case GNUNET_IDENTITY_TYPE_ECDSA:
+ return block_decrypt_ecdsa (&block->ecdsa_block,
+ &zone_key->ecdsa_key, label, proc, proc_cls);
+ case GNUNET_IDENTITY_TYPE_EDDSA:
+ return block_decrypt_eddsa (&block->eddsa_block,
+ &zone_key->eddsa_key, label, proc, proc_cls);
+ default:
+ return GNUNET_SYSERR;
}
- key = &zone_key->ecdsa_key;
-
- return block_decrypt_ecdsa (&block->ecdsa_block,
- key, label, proc, proc_cls);
-
}
@@ -555,6 +883,7 @@ GNUNET_GNSRECORD_query_from_private_key (const struct
switch (ntohl (zone->type))
{
case GNUNET_GNSRECORD_TYPE_PKEY:
+ case GNUNET_GNSRECORD_TYPE_EDKEY:
GNUNET_IDENTITY_key_get_public (zone,
&pub);
@@ -570,6 +899,7 @@ GNUNET_GNSRECORD_query_from_private_key (const struct
/**
* Calculate the DHT query for a given @a label in a given @a zone.
+ * FIXME: We may want to plugin-ize this at some point.
*
* @param pub public key of the zone
* @param label label of the record
@@ -595,6 +925,16 @@ GNUNET_GNSRECORD_query_from_public_key (const struct
sizeof (pd.ecdsa_key),
query);
break;
+ case GNUNET_GNSRECORD_TYPE_EDKEY:
+ pd.type = pub->type;
+ GNUNET_CRYPTO_eddsa_public_key_derive (&pub->eddsa_key,
+ label,
+ "gns",
+ &pd.eddsa_key);
+ GNUNET_CRYPTO_hash (&pd.eddsa_key,
+ sizeof (pd.eddsa_key),
+ query);
+ break;
default:
GNUNET_assert (0);
}
diff --git a/src/gnsrecord/gnsrecord_misc.c b/src/gnsrecord/gnsrecord_misc.c
index b907eed27..2fe315bd8 100644
--- a/src/gnsrecord/gnsrecord_misc.c
+++ b/src/gnsrecord/gnsrecord_misc.c
@@ -338,6 +338,12 @@ GNUNET_GNSRECORD_block_get_size (const struct GNUNET_GNSRECORD_Block *block)
+ ntohl (block->ecdsa_block.purpose.size) /* Length of signed data */
- sizeof (block->ecdsa_block.purpose); /* Purpose already in EcdsaBlock */
break;
+ case GNUNET_GNSRECORD_TYPE_EDKEY:
+ return sizeof (uint32_t) /* zone type */
+ + sizeof (block->eddsa_block) /* EddsaBlock */
+ + ntohl (block->eddsa_block.purpose.size) /* Length of signed data */
+ - sizeof (block->ecdsa_block.purpose); /* Purpose already in EcdsaBlock */
+
default:
return 0;
}
@@ -354,6 +360,8 @@ GNUNET_GNSRECORD_block_get_expiration (const struct
{
case GNUNET_GNSRECORD_TYPE_PKEY:
return GNUNET_TIME_absolute_ntoh (block->ecdsa_block.expiration_time);
+ case GNUNET_GNSRECORD_TYPE_EDKEY:
+ return GNUNET_TIME_absolute_ntoh (block->eddsa_block.expiration_time);
default:
GNUNET_break (0); /* Hopefully we never get here, but we might */
}
@@ -373,6 +381,11 @@ GNUNET_GNSRECORD_query_from_block (const struct GNUNET_GNSRECORD_Block *block,
sizeof (block->ecdsa_block.derived_key),
query);
return GNUNET_OK;
+ case GNUNET_GNSRECORD_TYPE_EDKEY:
+ GNUNET_CRYPTO_hash (&block->eddsa_block.derived_key,
+ sizeof (block->eddsa_block.derived_key),
+ query);
+ return GNUNET_OK;
default:
return GNUNET_SYSERR;
}
@@ -394,6 +407,10 @@ GNUNET_GNSRECORD_record_to_identity_key (const struct GNUNET_GNSRECORD_Data *rd,
key->type = htonl (rd->record_type);
memcpy (&key->ecdsa_key, rd->data, sizeof (key->ecdsa_key));
return GNUNET_OK;
+ case GNUNET_GNSRECORD_TYPE_EDKEY:
+ key->type = htonl (rd->record_type);
+ memcpy (&key->eddsa_key, rd->data, sizeof (key->eddsa_key));
+ return GNUNET_OK;
default:
return GNUNET_SYSERR;
}
diff --git a/src/gnsrecord/gnsrecord_serialization.c b/src/gnsrecord/gnsrecord_serialization.c
index 42665e662..cb6957605 100644
--- a/src/gnsrecord/gnsrecord_serialization.c
+++ b/src/gnsrecord/gnsrecord_serialization.c
@@ -124,7 +124,8 @@ GNUNET_GNSRECORD_records_get_size (unsigned int rd_count,
return -1;
}
// Do not pad PKEY
- if (GNUNET_GNSRECORD_TYPE_PKEY == rd->record_type)
+ if ((GNUNET_GNSRECORD_TYPE_PKEY == rd->record_type) ||
+ (GNUNET_GNSRECORD_TYPE_EDKEY == rd->record_type))
return ret;
/**
* Efficiently round up to the next
diff --git a/src/gnsrecord/test_gnsrecord_crypto.c b/src/gnsrecord/test_gnsrecord_crypto.c
index d541f3076..9394f562d 100644
--- a/src/gnsrecord/test_gnsrecord_crypto.c
+++ b/src/gnsrecord/test_gnsrecord_crypto.c
@@ -92,29 +92,22 @@ rd_decrypt_cb (void *cls,
res = 0;
}
-
static void
-run (void *cls,
- char *const *args,
- const char *cfgfile,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
+test_with_type (struct GNUNET_IDENTITY_PrivateKey *privkey)
{
struct GNUNET_GNSRECORD_Block *block;
struct GNUNET_IDENTITY_PublicKey pubkey;
struct GNUNET_HashCode query_pub;
struct GNUNET_HashCode query_priv;
struct GNUNET_TIME_Absolute expire = GNUNET_TIME_absolute_get ();
- struct GNUNET_IDENTITY_PrivateKey privkey;
- privkey.type = htonl (GNUNET_GNSRECORD_TYPE_PKEY);
- GNUNET_CRYPTO_ecdsa_key_create (&privkey.ecdsa_key);
/* get public key */
- GNUNET_IDENTITY_key_get_public (&privkey,
+ GNUNET_IDENTITY_key_get_public (privkey,
&pubkey);
/* test query derivation */
- GNUNET_GNSRECORD_query_from_private_key (&privkey,
+ GNUNET_GNSRECORD_query_from_private_key (privkey,
"testlabel",
&query_priv);
GNUNET_GNSRECORD_query_from_public_key (&pubkey,
@@ -129,7 +122,7 @@ run (void *cls,
/* Create block */
GNUNET_assert (NULL != (block =
- GNUNET_GNSRECORD_block_create (&privkey,
+ GNUNET_GNSRECORD_block_create (privkey,
expire,
s_name,
s_rd,
@@ -146,6 +139,27 @@ run (void *cls,
}
+
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct GNUNET_IDENTITY_PrivateKey privkey;
+ struct GNUNET_IDENTITY_PrivateKey privkey_ed;
+
+
+ privkey.type = htonl (GNUNET_GNSRECORD_TYPE_PKEY);
+ GNUNET_CRYPTO_ecdsa_key_create (&privkey.ecdsa_key);
+ test_with_type (&privkey);
+
+ privkey_ed.type = htonl (GNUNET_GNSRECORD_TYPE_EDKEY);
+ GNUNET_CRYPTO_eddsa_key_create (&privkey_ed.eddsa_key);
+ test_with_type(&privkey_ed);
+}
+
+
int
main (int argc, char *argv[])
{
diff --git a/src/include/gnunet_crypto_lib.h b/src/include/gnunet_crypto_lib.h
index 43cdfdfac..a334b50d0 100644
--- a/src/include/gnunet_crypto_lib.h
+++ b/src/include/gnunet_crypto_lib.h
@@ -275,6 +275,19 @@ struct GNUNET_CRYPTO_EddsaPrivateKey
/**
+ * Private ECC scalar encoded for transmission. To be used only for EdDSA
+ * signatures.
+ */
+struct GNUNET_CRYPTO_EddsaPrivateScalar
+{
+ /**
+ * s is the expandedprivate 512-bit scalar of a private key.
+ */
+ unsigned char s[512 / 8];
+};
+
+
+/**
* @brief type for session keys
*/
struct GNUNET_CRYPTO_SymmetricSessionKey
@@ -1907,6 +1920,71 @@ GNUNET_CRYPTO_ecdsa_public_key_derive (
/**
+ * @ingroup crypto
+ * Derive a private scalar from a given private key and a label.
+ * Essentially calculates a private key 'h = H(l,P) * d mod n'
+ * where n is the size of the ECC group and P is the public
+ * key associated with the private key 'd'.
+ * The result is the derived private _scalar_, not the private
+ * key as for EdDSA we cannot derive before we hash the
+ * private key.
+ *
+ * @param priv original private key
+ * @param label label to use for key deriviation
+ * @param context additional context to use for HKDF of 'h';
+ * typically the name of the subsystem/application
+ * @param result derived private scalar
+ */
+void
+GNUNET_CRYPTO_eddsa_private_key_derive (
+ const struct GNUNET_CRYPTO_EddsaPrivateKey *priv,
+ const char *label,
+ const char *context,
+ struct GNUNET_CRYPTO_EddsaPrivateScalar *result);
+
+
+/**
+ * @ingroup crypto
+ * Derive a public key from a given public key and a label.
+ * Essentially calculates a public key 'V = H(l,P) * P'.
+ *
+ * @param pub original public key
+ * @param label label to use for key deriviation
+ * @param context additional context to use for HKDF of 'h'.
+ * typically the name of the subsystem/application
+ * @param result where to write the derived public key
+ */
+void
+GNUNET_CRYPTO_eddsa_public_key_derive (
+ const struct GNUNET_CRYPTO_EddsaPublicKey *pub,
+ const char *label,
+ const char *context,
+ struct GNUNET_CRYPTO_EddsaPublicKey *result);
+
+
+/**
+ * This is a signature function for EdDSA which takes the
+ * secret scalar sk instead of the private seed which is
+ * usually the case for crypto APIs. We require this functionality
+ * in order to use derived private keys for signatures we
+ * cannot calculate the inverse of a sk to find the seed
+ * efficiently.
+ *
+ * The resulting signature is a standard EdDSA signature
+ * which can be verified using the usual APIs.
+ *
+ * @param sk the secret scalar
+ * @param purp the signature purpose
+ * @param sig the resulting signature
+ */
+void
+GNUNET_CRYPTO_eddsa_sign_with_scalar (
+ const struct GNUNET_CRYPTO_EddsaPrivateScalar *priv,
+ const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+ struct GNUNET_CRYPTO_EddsaSignature *sig);
+
+
+/**
* Output the given MPI value to the given buffer in network
* byte order. The MPI @a val may not be negative.
*
diff --git a/src/include/gnunet_gnsrecord_lib.h b/src/include/gnunet_gnsrecord_lib.h
index 61cbac2ca..5afb3f253 100644
--- a/src/include/gnunet_gnsrecord_lib.h
+++ b/src/include/gnunet_gnsrecord_lib.h
@@ -197,6 +197,37 @@ struct GNUNET_GNSRECORD_EcdsaBlock
/* followed by encrypted data */
};
+
+/**
+ * Information we have in an encrypted block with record data (i.e. in the DHT).
+ */
+struct GNUNET_GNSRECORD_EddsaBlock
+{
+ /**
+ * Derived key used for signing; hash of this is the query.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey derived_key;
+
+ /**
+ * Signature of the block.
+ */
+ struct GNUNET_CRYPTO_EddsaSignature signature;
+
+ /**
+ * Number of bytes signed; also specifies the number of bytes
+ * of encrypted data that follow.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Expiration time of the block.
+ */
+ struct GNUNET_TIME_AbsoluteNBO expiration_time;
+
+ /* followed by encrypted data */
+};
+
+
struct GNUNET_GNSRECORD_Block
{
uint32_t type;
@@ -204,7 +235,7 @@ struct GNUNET_GNSRECORD_Block
union
{
struct GNUNET_GNSRECORD_EcdsaBlock ecdsa_block;
- //struct GNUNET_GNSRECORD_EddsaBlock eddsa_block;
+ struct GNUNET_GNSRECORD_EddsaBlock eddsa_block;
};
};
diff --git a/src/util/Makefile.am b/src/util/Makefile.am
index 72fd37bde..09620aee0 100644
--- a/src/util/Makefile.am
+++ b/src/util/Makefile.am
@@ -61,6 +61,7 @@ libgnunetutil_la_SOURCES = \
crypto_symmetric.c \
crypto_crc.c \
crypto_ecc.c \
+ crypto_ecc_gnsrecord.c \
$(DLOG) \
crypto_ecc_setup.c \
crypto_hash.c \
diff --git a/src/util/crypto_ecc.c b/src/util/crypto_ecc.c
index efbf2ee17..5b1b579ec 100644
--- a/src/util/crypto_ecc.c
+++ b/src/util/crypto_ecc.c
@@ -714,135 +714,6 @@ GNUNET_CRYPTO_ecc_ecdh (const struct GNUNET_CRYPTO_EcdhePrivateKey *priv,
}
-/**
- * Derive the 'h' value for key derivation, where
- * 'h = H(l,P)'.
- *
- * @param pub public key for deriviation
- * @param label label for deriviation
- * @param context additional context to use for HKDF of 'h';
- * typically the name of the subsystem/application
- * @return h value
- */
-static gcry_mpi_t
-derive_h (const struct GNUNET_CRYPTO_EcdsaPublicKey *pub,
- const char *label,
- const char *context)
-{
- gcry_mpi_t h;
- struct GNUNET_HashCode hc;
- static const char *const salt = "key-derivation";
-
- GNUNET_CRYPTO_kdf (&hc,
- sizeof(hc),
- salt,
- strlen (salt),
- pub,
- sizeof(*pub),
- label,
- strlen (label),
- context,
- strlen (context),
- NULL,
- 0);
- GNUNET_CRYPTO_mpi_scan_unsigned (&h, (unsigned char *) &hc, sizeof(hc));
- return h;
-}
-
-
-struct GNUNET_CRYPTO_EcdsaPrivateKey *
-GNUNET_CRYPTO_ecdsa_private_key_derive (
- const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv,
- const char *label,
- const char *context)
-{
- struct GNUNET_CRYPTO_EcdsaPublicKey pub;
- struct GNUNET_CRYPTO_EcdsaPrivateKey *ret;
- uint8_t dc[32];
- gcry_mpi_t h;
- gcry_mpi_t x;
- gcry_mpi_t d;
- gcry_mpi_t n;
- gcry_ctx_t ctx;
-
- GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, CURVE));
-
- n = gcry_mpi_ec_get_mpi ("n", ctx, 1);
- GNUNET_CRYPTO_ecdsa_key_get_public (priv, &pub);
-
- h = derive_h (&pub, label, context);
- /* Convert to big endian for libgcrypt */
- for (size_t i = 0; i < 32; i++)
- dc[i] = priv->d[31 - i];
- GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc));
- d = gcry_mpi_new (256);
- gcry_mpi_mulm (d, h, x, n);
- gcry_mpi_release (h);
- gcry_mpi_release (x);
- gcry_mpi_release (n);
- gcry_ctx_release (ctx);
- ret = GNUNET_new (struct GNUNET_CRYPTO_EcdsaPrivateKey);
- GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d);
- /* Convert to big endian for libgcrypt */
- for (size_t i = 0; i < 32; i++)
- ret->d[i] = dc[31 - i];
- sodium_memzero (dc, sizeof(dc));
- gcry_mpi_release (d);
- return ret;
-}
-
-
-void
-GNUNET_CRYPTO_ecdsa_public_key_derive (
- const struct GNUNET_CRYPTO_EcdsaPublicKey *pub,
- const char *label,
- const char *context,
- struct GNUNET_CRYPTO_EcdsaPublicKey *result)
-{
- gcry_ctx_t ctx;
- gcry_mpi_t q_y;
- gcry_mpi_t h;
- gcry_mpi_t n;
- gcry_mpi_t h_mod_n;
- gcry_mpi_point_t q;
- gcry_mpi_point_t v;
-
- GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, CURVE));
-
- /* obtain point 'q' from original public key. The provided 'q' is
- compressed thus we first store it in the context and then get it
- back as a (decompresssed) point. */
- q_y = gcry_mpi_set_opaque_copy (NULL, pub->q_y, 8 * sizeof(pub->q_y));
- GNUNET_assert (NULL != q_y);
- GNUNET_assert (0 == gcry_mpi_ec_set_mpi ("q", q_y, ctx));
- gcry_mpi_release (q_y);
- q = gcry_mpi_ec_get_point ("q", ctx, 0);
- GNUNET_assert (q);
-
- /* calculate h_mod_n = h % n */
- h = derive_h (pub, label, context);
- n = gcry_mpi_ec_get_mpi ("n", ctx, 1);
- h_mod_n = gcry_mpi_new (256);
- gcry_mpi_mod (h_mod_n, h, n);
- /* calculate v = h_mod_n * q */
- v = gcry_mpi_point_new (0);
- gcry_mpi_ec_mul (v, h_mod_n, q, ctx);
- gcry_mpi_release (h_mod_n);
- gcry_mpi_release (h);
- gcry_mpi_release (n);
- gcry_mpi_point_release (q);
-
- /* convert point 'v' to public key that we return */
- GNUNET_assert (0 == gcry_mpi_ec_set_point ("q", v, ctx));
- gcry_mpi_point_release (v);
- q_y = gcry_mpi_ec_get_mpi ("q@eddsa", ctx, 0);
- GNUNET_assert (q_y);
- GNUNET_CRYPTO_mpi_print_unsigned (result->q_y, sizeof(result->q_y), q_y);
- gcry_mpi_release (q_y);
- gcry_ctx_release (ctx);
-}
-
-
enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_eddsa_ecdh (const struct GNUNET_CRYPTO_EddsaPrivateKey *priv,
const struct GNUNET_CRYPTO_EcdhePublicKey *pub,
diff --git a/src/util/crypto_ecc_gnsrecord.c b/src/util/crypto_ecc_gnsrecord.c
new file mode 100644
index 000000000..6689a21f1
--- /dev/null
+++ b/src/util/crypto_ecc_gnsrecord.c
@@ -0,0 +1,351 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2012, 2013, 2015 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+
+/**
+ * @file util/crypto_ecc_gnsrecord.c
+ * @brief public key cryptography (ECC) for GNS records (LSD0001)
+ * @author Christian Grothoff
+ * @author Florian Dold
+ * @author Martin Schanzenbach
+ */
+#include "platform.h"
+#include <gcrypt.h>
+#include <sodium.h>
+#include "gnunet_crypto_lib.h"
+#include "gnunet_strings_lib.h"
+
+#define CURVE "Ed25519"
+
+/**
+ * Derive the 'h' value for key derivation, where
+ * 'h = H(l,P)'.
+ *
+ * @param pub public key for deriviation
+ * @param pubsize the size of the public key
+ * @param label label for deriviation
+ * @param context additional context to use for HKDF of 'h';
+ * typically the name of the subsystem/application
+ * @return h value
+ */
+static gcry_mpi_t
+derive_h (const void *pub,
+ size_t pubsize,
+ const char *label,
+ const char *context)
+{
+ gcry_mpi_t h;
+ struct GNUNET_HashCode hc;
+ static const char *const salt = "key-derivation";
+
+ GNUNET_CRYPTO_kdf (&hc,
+ sizeof(hc),
+ salt,
+ strlen (salt),
+ pub,
+ pubsize,
+ label,
+ strlen (label),
+ context,
+ strlen (context),
+ NULL,
+ 0);
+ GNUNET_CRYPTO_mpi_scan_unsigned (&h, (unsigned char *) &hc, sizeof(hc));
+ return h;
+}
+
+
+/**
+ * This is a signature function for EdDSA which takes the
+ * secret scalar sk instead of the private seed which is
+ * usually the case for crypto APIs. We require this functionality
+ * in order to use derived private keys for signatures we
+ * cannot calculate the inverse of a sk to find the seed
+ * efficiently.
+ *
+ * The resulting signature is a standard EdDSA signature
+ * which can be verified using the usual APIs.
+ *
+ * @param sk the secret scalar
+ * @param purp the signature purpose
+ * @param sig the resulting signature
+ */
+void
+GNUNET_CRYPTO_eddsa_sign_with_scalar (
+ const struct GNUNET_CRYPTO_EddsaPrivateScalar *priv,
+ const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+ struct GNUNET_CRYPTO_EddsaSignature *sig)
+{
+
+ crypto_hash_sha512_state hs;
+ unsigned char az[64];
+ unsigned char nonce[64];
+ unsigned char hram[64];
+ unsigned char R[32];
+ unsigned char pk[32];
+
+ crypto_hash_sha512_init (&hs);
+
+ // crypto_hash_sha512 (az, sk, 32); DO NOT EXPAND, WE HAVE A KEY
+ memcpy (az, priv->s, 64);
+ crypto_scalarmult_ed25519_base_noclamp (pk,
+ priv->s);
+ crypto_hash_sha512_update (&hs, az + 32, 32);
+
+ crypto_hash_sha512_update (&hs, (uint8_t*) purpose, ntohl (purpose->size));
+ crypto_hash_sha512_final (&hs, nonce);
+
+ // This effectively creates R || A in sig
+ memcpy (sig->s, pk, 32);
+
+ unsigned char nonce_mod[64];
+ crypto_core_ed25519_scalar_reduce (nonce_mod, nonce);
+ // nonce == r; r * G == R
+ crypto_scalarmult_ed25519_base_noclamp (R, nonce_mod);
+ memcpy (sig->r, R, sizeof (R));
+
+ // SHA512 (R | A | M) == k
+ crypto_hash_sha512_init (&hs);
+ crypto_hash_sha512_update (&hs, (uint8_t*) sig, 64);
+ crypto_hash_sha512_update (&hs, (uint8_t*) purpose,
+ ntohl (purpose->size));
+ crypto_hash_sha512_final (&hs, hram);
+
+ unsigned char hram_mod[64];
+ crypto_core_ed25519_scalar_reduce (hram_mod, hram);
+ az[0] &= 248;
+ az[31] &= 127;
+ az[31] |= 64;
+
+ unsigned char tmp[32];
+ // r + k * s mod L == S
+ crypto_core_ed25519_scalar_mul (tmp, hram_mod, az);
+ crypto_core_ed25519_scalar_add (sig->s, tmp, nonce_mod);
+
+ sodium_memzero (az, sizeof az);
+ sodium_memzero (nonce, sizeof nonce);
+}
+
+
+struct GNUNET_CRYPTO_EcdsaPrivateKey *
+GNUNET_CRYPTO_ecdsa_private_key_derive (
+ const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv,
+ const char *label,
+ const char *context)
+{
+ struct GNUNET_CRYPTO_EcdsaPublicKey pub;
+ struct GNUNET_CRYPTO_EcdsaPrivateKey *ret;
+ uint8_t dc[32];
+ gcry_mpi_t h;
+ gcry_mpi_t x;
+ gcry_mpi_t d;
+ gcry_mpi_t n;
+ gcry_ctx_t ctx;
+
+ GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, CURVE));
+
+ n = gcry_mpi_ec_get_mpi ("n", ctx, 1);
+ GNUNET_CRYPTO_ecdsa_key_get_public (priv, &pub);
+
+ h = derive_h (&pub, sizeof (pub), label, context);
+ /* Convert to big endian for libgcrypt */
+ for (size_t i = 0; i < 32; i++)
+ dc[i] = priv->d[31 - i];
+ GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc));
+ d = gcry_mpi_new (256);
+ gcry_mpi_mulm (d, h, x, n);
+ gcry_mpi_release (h);
+ gcry_mpi_release (x);
+ gcry_mpi_release (n);
+ gcry_ctx_release (ctx);
+ ret = GNUNET_new (struct GNUNET_CRYPTO_EcdsaPrivateKey);
+ GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d);
+ /* Convert to big endian for libgcrypt */
+ for (size_t i = 0; i < 32; i++)
+ ret->d[i] = dc[31 - i];
+ sodium_memzero (dc, sizeof(dc));
+ gcry_mpi_release (d);
+ return ret;
+}
+
+
+void
+GNUNET_CRYPTO_ecdsa_public_key_derive (
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *pub,
+ const char *label,
+ const char *context,
+ struct GNUNET_CRYPTO_EcdsaPublicKey *result)
+{
+ gcry_ctx_t ctx;
+ gcry_mpi_t q_y;
+ gcry_mpi_t h;
+ gcry_mpi_t n;
+ gcry_mpi_t h_mod_n;
+ gcry_mpi_point_t q;
+ gcry_mpi_point_t v;
+
+ GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, CURVE));
+
+ /* obtain point 'q' from original public key. The provided 'q' is
+ compressed thus we first store it in the context and then get it
+ back as a (decompresssed) point. */
+ q_y = gcry_mpi_set_opaque_copy (NULL, pub->q_y, 8 * sizeof(pub->q_y));
+ GNUNET_assert (NULL != q_y);
+ GNUNET_assert (0 == gcry_mpi_ec_set_mpi ("q", q_y, ctx));
+ gcry_mpi_release (q_y);
+ q = gcry_mpi_ec_get_point ("q", ctx, 0);
+ GNUNET_assert (q);
+
+ /* calculate h_mod_n = h % n */
+ h = derive_h (pub, sizeof (pub), label, context);
+ n = gcry_mpi_ec_get_mpi ("n", ctx, 1);
+ h_mod_n = gcry_mpi_new (256);
+ gcry_mpi_mod (h_mod_n, h, n);
+ /* calculate v = h_mod_n * q */
+ v = gcry_mpi_point_new (0);
+ gcry_mpi_ec_mul (v, h_mod_n, q, ctx);
+ gcry_mpi_release (h_mod_n);
+ gcry_mpi_release (h);
+ gcry_mpi_release (n);
+ gcry_mpi_point_release (q);
+
+ /* convert point 'v' to public key that we return */
+ GNUNET_assert (0 == gcry_mpi_ec_set_point ("q", v, ctx));
+ gcry_mpi_point_release (v);
+ q_y = gcry_mpi_ec_get_mpi ("q@eddsa", ctx, 0);
+ GNUNET_assert (q_y);
+ GNUNET_CRYPTO_mpi_print_unsigned (result->q_y, sizeof(result->q_y), q_y);
+ gcry_mpi_release (q_y);
+ gcry_ctx_release (ctx);
+}
+
+
+void
+GNUNET_CRYPTO_eddsa_private_key_derive (
+ const struct GNUNET_CRYPTO_EddsaPrivateKey *priv,
+ const char *label,
+ const char *context,
+ struct GNUNET_CRYPTO_EddsaPrivateScalar *result)
+{
+ struct GNUNET_CRYPTO_EddsaPublicKey pub;
+ uint8_t dc[32];
+ unsigned char sk[64];
+ gcry_mpi_t h;
+ gcry_mpi_t h_mod_n;
+ gcry_mpi_t x;
+ gcry_mpi_t d;
+ gcry_mpi_t n;
+ gcry_mpi_t a1;
+ gcry_mpi_t a2;
+ gcry_ctx_t ctx;
+
+ GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, "Ed25519"));
+
+ n = gcry_mpi_ec_get_mpi ("n", ctx, 1);
+ GNUNET_CRYPTO_eddsa_key_get_public (priv, &pub);
+ crypto_hash_sha512 (sk, priv->d, 32);
+ sk[0] &= 248;
+ sk[31] &= 127;
+ sk[31] |= 64;
+ h = derive_h (&pub, sizeof (pub), label, context);
+ h_mod_n = gcry_mpi_new (256);
+ gcry_mpi_mod (h_mod_n, h, n);
+ /* Convert to big endian for libgcrypt */
+ for (size_t i = 0; i < 32; i++)
+ dc[i] = sk[31 - i];
+ GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc)); // a
+ a1 = gcry_mpi_new (256);
+ gcry_mpi_t eight = gcry_mpi_set_ui (NULL, 8);
+ gcry_mpi_div (a1, NULL, x, eight, 0); // a1 := a / 8
+ a2 = gcry_mpi_new (256);
+ gcry_mpi_mulm (a2, h_mod_n, a1, n); // a2 := h * a1 mod n
+ d = gcry_mpi_new (256);
+ // gcry_mpi_mulm (d, a2, eight, n); // a' := a2 * 8 mod n
+ gcry_mpi_mul (d, a2, eight); // a' := a2 * 8
+ gcry_mpi_release (h);
+ gcry_mpi_release (x);
+ gcry_mpi_release (n);
+ gcry_mpi_release (a1);
+ gcry_mpi_release (a2);
+ gcry_ctx_release (ctx);
+ GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d);
+ memcpy (result->s, sk, sizeof (sk));
+ /* Convert to little endian for libsodium */
+ for (size_t i = 0; i < 32; i++)
+ result->s[i] = dc[31 - i];
+ result->s[0] &= 248;
+ result->s[31] &= 127;
+ result->s[31] |= 64;
+
+ sodium_memzero (dc, sizeof(dc));
+ gcry_mpi_release (d);
+}
+
+
+void
+GNUNET_CRYPTO_eddsa_public_key_derive (
+ const struct GNUNET_CRYPTO_EddsaPublicKey *pub,
+ const char *label,
+ const char *context,
+ struct GNUNET_CRYPTO_EddsaPublicKey *result)
+{
+ gcry_ctx_t ctx;
+ gcry_mpi_t q_y;
+ gcry_mpi_t h;
+ gcry_mpi_t n;
+ gcry_mpi_t h_mod_n;
+ gcry_mpi_point_t q;
+ gcry_mpi_point_t v;
+
+ GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, "Ed25519"));
+
+ /* obtain point 'q' from original public key. The provided 'q' is
+ compressed thus we first store it in the context and then get it
+ back as a (decompresssed) point. */
+ q_y = gcry_mpi_set_opaque_copy (NULL, pub->q_y, 8 * sizeof(pub->q_y));
+ GNUNET_assert (NULL != q_y);
+ GNUNET_assert (0 == gcry_mpi_ec_set_mpi ("q", q_y, ctx));
+ gcry_mpi_release (q_y);
+ q = gcry_mpi_ec_get_point ("q", ctx, 0);
+ GNUNET_assert (q);
+
+ /* calculate h_mod_n = h % n */
+ h = derive_h (pub, sizeof (*pub), label, context);
+ n = gcry_mpi_ec_get_mpi ("n", ctx, 1);
+ h_mod_n = gcry_mpi_new (256);
+ gcry_mpi_mod (h_mod_n, h, n);
+
+ /* calculate v = h_mod_n * q */
+ v = gcry_mpi_point_new (0);
+ gcry_mpi_ec_mul (v, h_mod_n, q, ctx);
+ gcry_mpi_release (h_mod_n);
+ gcry_mpi_release (h);
+ gcry_mpi_release (n);
+ gcry_mpi_point_release (q);
+
+ /* convert point 'v' to public key that we return */
+ GNUNET_assert (0 == gcry_mpi_ec_set_point ("q", v, ctx));
+ gcry_mpi_point_release (v);
+ q_y = gcry_mpi_ec_get_mpi ("q@eddsa", ctx, 0);
+ GNUNET_assert (q_y);
+ GNUNET_CRYPTO_mpi_print_unsigned (result->q_y, sizeof(result->q_y), q_y);
+ gcry_mpi_release (q_y);
+ gcry_ctx_release (ctx);
+
+}