/* This file is part of GNUnet Copyright (C) 2014,2016,2019 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 . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @file util/crypto_cs.c * @brief Clause Blind Schnorr signatures using Curve25519 * @author Lucien Heuzeveldt * @author Gian Demarmels */ #include "platform.h" #include "gnunet_crypto_lib.h" #include #include /** * IMPLEMENTATION NOTICE: * * This is an implementation of the Clause Blind Schnorr Signature Scheme using Curve25519. * Further details about the Clause Blind Schnorr Signature Scheme can be found here: * https://eprint.iacr.org/2019/877.pdf * * We use libsodium wherever possible. */ void GNUNET_CRYPTO_cs_private_key_generate (struct GNUNET_CRYPTO_CsPrivateKey *priv) { crypto_core_ed25519_scalar_random (priv->scalar.d); } void GNUNET_CRYPTO_cs_private_key_get_public ( const struct GNUNET_CRYPTO_CsPrivateKey *priv, struct GNUNET_CRYPTO_CsPublicKey *pub) { GNUNET_assert (0 == crypto_scalarmult_ed25519_base_noclamp (pub->point.y, priv->scalar.d)); } /** * Maps 32 random bytes to a scalar. This is necessary because libsodium * expects scalar to be in the prime order subgroup. * * @param[in,out] scalar containing 32 byte char array, is modified to be in prime order subgroup */ static void map_to_scalar_subgroup (struct GNUNET_CRYPTO_Cs25519Scalar *scalar) { /* perform clamping as described in RFC7748 */ scalar->d[0] &= 248; scalar->d[31] &= 127; scalar->d[31] |= 64; } void GNUNET_CRYPTO_cs_r_derive (const struct GNUNET_CRYPTO_CsNonce *nonce, const char *seed, const struct GNUNET_CRYPTO_CsPrivateKey *lts, struct GNUNET_CRYPTO_CsRSecret r[2]) { GNUNET_assert ( GNUNET_YES == GNUNET_CRYPTO_kdf ( r, sizeof (struct GNUNET_CRYPTO_CsRSecret) * 2, seed, strlen (seed), lts, sizeof (*lts), nonce, sizeof (*nonce), NULL, 0)); map_to_scalar_subgroup (&r[0].scalar); map_to_scalar_subgroup (&r[1].scalar); } void GNUNET_CRYPTO_cs_r_get_public (const struct GNUNET_CRYPTO_CsRSecret *r_priv, struct GNUNET_CRYPTO_CsRPublic *r_pub) { GNUNET_assert (0 == crypto_scalarmult_ed25519_base_noclamp (r_pub->point.y, r_priv->scalar.d)); } void GNUNET_CRYPTO_cs_blinding_secrets_derive ( const struct GNUNET_CRYPTO_CsNonce *blind_seed, struct GNUNET_CRYPTO_CsBlindingSecret bs[2]) { GNUNET_assert ( GNUNET_YES == GNUNET_CRYPTO_hkdf (bs, sizeof (struct GNUNET_CRYPTO_CsBlindingSecret) * 2, GCRY_MD_SHA512, GCRY_MD_SHA256, "alphabeta", strlen ("alphabeta"), blind_seed, sizeof(*blind_seed), NULL, 0)); map_to_scalar_subgroup (&bs[0].alpha); map_to_scalar_subgroup (&bs[0].beta); map_to_scalar_subgroup (&bs[1].alpha); map_to_scalar_subgroup (&bs[1].beta); } /* order of subgroup required for scalars by libsodium 2^252 + 27742317777372353535851937790883648493 copied from https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_core/ed25519/ref10/ed25519_ref10.c and converted to big endian */ static const unsigned char L_BIG_ENDIAN[32] = { 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xde, 0xf9, 0xde, 0xa2, 0xf7, 0x9c, 0xd6, 0x58, 0x12, 0x63, 0x1a, 0x5c, 0xf5, 0xd3, 0xed }; /** * Computes a Hash of (R', m) mapped to a Curve25519 scalar * * @param hash initial hash of the message to be signed * @param pub denomination public key (used as salt) * @param[out] c C containing scalar */ static void cs_full_domain_hash (const struct GNUNET_CRYPTO_CsRPublic *r_dash, const void *msg, size_t msg_len, const struct GNUNET_CRYPTO_CsPublicKey *pub, struct GNUNET_CRYPTO_CsC *c) { // SHA-512 hash of R' and message size_t r_m_concat_len = sizeof(struct GNUNET_CRYPTO_CsRPublic) + msg_len; char r_m_concat[r_m_concat_len]; memcpy (r_m_concat, r_dash, sizeof(struct GNUNET_CRYPTO_CsRPublic)); memcpy (r_m_concat + sizeof(struct GNUNET_CRYPTO_CsRPublic), msg, msg_len); struct GNUNET_HashCode prehash; GNUNET_CRYPTO_hash (r_m_concat, r_m_concat_len, &prehash); // modulus converted to MPI representation gcry_mpi_t l_mpi; GNUNET_CRYPTO_mpi_scan_unsigned (&l_mpi, L_BIG_ENDIAN, sizeof(L_BIG_ENDIAN)); // calculate full domain hash gcry_mpi_t c_mpi; GNUNET_CRYPTO_kdf_mod_mpi (&c_mpi, l_mpi, pub, sizeof(struct GNUNET_CRYPTO_CsPublicKey), &prehash, sizeof(struct GNUNET_HashCode), "Curve25519FDH"); gcry_mpi_release (l_mpi); // convert c from mpi unsigned char c_big_endian[256 / 8]; GNUNET_CRYPTO_mpi_print_unsigned (c_big_endian, sizeof(c_big_endian), c_mpi); gcry_mpi_release (c_mpi); for (size_t i = 0; i<32; i++) c->scalar.d[i] = c_big_endian[31 - i]; } /** * calculate R' * * @param bs blinding secret * @param r_pub R * @param pub public key * @param[out] blinded_r_pub R' */ static void calc_r_dash (const struct GNUNET_CRYPTO_CsBlindingSecret *bs, const struct GNUNET_CRYPTO_CsRPublic *r_pub, const struct GNUNET_CRYPTO_CsPublicKey *pub, struct GNUNET_CRYPTO_CsRPublic *blinded_r_pub) { // R'i = Ri + alpha i*G + beta i*pub struct GNUNET_CRYPTO_Cs25519Point alpha_mul_base; GNUNET_assert (0 == crypto_scalarmult_ed25519_base_noclamp ( alpha_mul_base.y, bs->alpha.d)); struct GNUNET_CRYPTO_Cs25519Point beta_mul_pub; GNUNET_assert (0 == crypto_scalarmult_ed25519_noclamp (beta_mul_pub.y, bs->beta.d, pub->point.y)); struct GNUNET_CRYPTO_Cs25519Point alpha_mul_base_plus_beta_mul_pub; GNUNET_assert (0 == crypto_core_ed25519_add ( alpha_mul_base_plus_beta_mul_pub.y, alpha_mul_base.y, beta_mul_pub.y)); GNUNET_assert (0 == crypto_core_ed25519_add (blinded_r_pub->point.y, r_pub->point.y, alpha_mul_base_plus_beta_mul_pub. y)); } void GNUNET_CRYPTO_cs_calc_blinded_c ( const struct GNUNET_CRYPTO_CsBlindingSecret bs[2], const struct GNUNET_CRYPTO_CsRPublic r_pub[2], const struct GNUNET_CRYPTO_CsPublicKey *pub, const void *msg, size_t msg_len, struct GNUNET_CRYPTO_CsC blinded_c[2], struct GNUNET_CRYPTO_CsRPublic blinded_r_pub[2]) { // for i 0/1: R'i = Ri + alpha i*G + beta i*pub calc_r_dash (&bs[0], &r_pub[0], pub, &blinded_r_pub[0]); calc_r_dash (&bs[1], &r_pub[1], pub, &blinded_r_pub[1]); // for i 0/1: c'i = H(R'i, msg) struct GNUNET_CRYPTO_CsC c_dash_0; struct GNUNET_CRYPTO_CsC c_dash_1; cs_full_domain_hash (&blinded_r_pub[0], msg, msg_len, pub, &c_dash_0); cs_full_domain_hash (&blinded_r_pub[1], msg, msg_len, pub, &c_dash_1); // for i 0/1: ci = c'i + beta i mod p crypto_core_ed25519_scalar_add (blinded_c[0].scalar.d, c_dash_0.scalar.d, bs[0].beta.d); crypto_core_ed25519_scalar_add (blinded_c[1].scalar.d, c_dash_1.scalar.d, bs[1].beta.d); } unsigned int GNUNET_CRYPTO_cs_sign_derive ( const struct GNUNET_CRYPTO_CsPrivateKey *priv, const struct GNUNET_CRYPTO_CsRSecret r[2], const struct GNUNET_CRYPTO_CsC c[2], const struct GNUNET_CRYPTO_CsNonce *nonce, struct GNUNET_CRYPTO_CsBlindS *blinded_signature_scalar) { uint32_t hkdf_out; // derive clause session identifier b (random bit) GNUNET_assert (GNUNET_YES == GNUNET_CRYPTO_hkdf (&hkdf_out, sizeof (hkdf_out), GCRY_MD_SHA512, GCRY_MD_SHA256, "b", strlen ("b"), priv, sizeof (*priv), nonce, sizeof (*nonce), NULL, 0)); unsigned int b = hkdf_out % 2; // s = r_b + c_b priv struct GNUNET_CRYPTO_Cs25519Scalar c_b_mul_priv; crypto_core_ed25519_scalar_mul (c_b_mul_priv.d, c[b].scalar.d, priv->scalar.d); crypto_core_ed25519_scalar_add (blinded_signature_scalar->scalar.d, r[b].scalar.d, c_b_mul_priv.d); return b; } void GNUNET_CRYPTO_cs_unblind ( const struct GNUNET_CRYPTO_CsBlindS *blinded_signature_scalar, const struct GNUNET_CRYPTO_CsBlindingSecret *bs, struct GNUNET_CRYPTO_CsS *signature_scalar) { crypto_core_ed25519_scalar_add (signature_scalar->scalar.d, blinded_signature_scalar->scalar.d, bs->alpha.d); } enum GNUNET_GenericReturnValue GNUNET_CRYPTO_cs_verify (const struct GNUNET_CRYPTO_CsSignature *sig, const struct GNUNET_CRYPTO_CsPublicKey *pub, const void *msg, size_t msg_len) { // calculate c' = H(R, m) struct GNUNET_CRYPTO_CsC c_dash; cs_full_domain_hash (&sig->r_point, msg, msg_len, pub, &c_dash); // s'G ?= R' + c' pub struct GNUNET_CRYPTO_Cs25519Point sig_scal_mul_base; GNUNET_assert (0 == crypto_scalarmult_ed25519_base_noclamp ( sig_scal_mul_base.y, sig->s_scalar.scalar.d)); struct GNUNET_CRYPTO_Cs25519Point c_dash_mul_pub; GNUNET_assert (0 == crypto_scalarmult_ed25519_noclamp (c_dash_mul_pub.y, c_dash.scalar.d, pub->point.y)); struct GNUNET_CRYPTO_Cs25519Point R_add_c_dash_mul_pub; GNUNET_assert (0 == crypto_core_ed25519_add (R_add_c_dash_mul_pub.y, sig->r_point.point.y, c_dash_mul_pub.y)); return 0 == GNUNET_memcmp (&sig_scal_mul_base, &R_add_c_dash_mul_pub) ? GNUNET_OK : GNUNET_SYSERR; }