Skip to content

Commit

Permalink
crypto: update CKey, CPubkey for silent payments
Browse files Browse the repository at this point in the history
* Add methods tweaking a CKey by adding a scalar value or by multiplying
* Add a method for combining multiple CPubKeys via addition
* Add a method for doing ECDH with a CPubKey and scalar

These are the CKey,CPubkey primitives we need to implement
the silent payments protocol.
  • Loading branch information
w0xlt authored and josibake committed Jul 21, 2023
1 parent e23b563 commit 5688262
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 0 deletions.
75 changes: 75 additions & 0 deletions src/key.cpp
Expand Up @@ -3,6 +3,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <array>
#include <key.h>

#include <crypto/common.h>
Expand All @@ -15,6 +16,7 @@
#include <secp256k1_extrakeys.h>
#include <secp256k1_recovery.h>
#include <secp256k1_schnorrsig.h>
#include <secp256k1_ecdh.h>

static secp256k1_context* secp256k1_context_sign = nullptr;

Expand Down Expand Up @@ -172,6 +174,79 @@ bool CKey::Negate()
return secp256k1_ec_seckey_negate(secp256k1_context_sign, keydata.data());
}

CKey CKey::AddTweak(const unsigned char *tweak32) const
{
assert(fValid);

unsigned char tweaked_seckey[32];
memcpy(tweaked_seckey, data(), 32);

int ret = secp256k1_ec_seckey_tweak_add(secp256k1_context_sign, tweaked_seckey, tweak32);
assert(ret);

CKey new_seckey;
new_seckey.Set(std::begin(tweaked_seckey), std::end(tweaked_seckey), true);

return new_seckey;
}

CKey CKey::MultiplyTweak(const unsigned char *tweak32) const
{
assert(fValid);

unsigned char tweaked_seckey[32];
memcpy(tweaked_seckey, data(), 32);

int ret = secp256k1_ec_seckey_tweak_mul(secp256k1_context_sign, tweaked_seckey, tweak32);
assert(ret);

CKey new_seckey;
new_seckey.Set(std::begin(tweaked_seckey), std::end(tweaked_seckey), true);

return new_seckey;
}

// we dont want to hash the output of ECDH yet, so we provide a custom hash function to secp256k1_ecdh which
// returns the serialized public key without hashing it
int custom_ecdh_hash_function(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data) {

secp256k1_pubkey pubkey;
unsigned char uncompressed_pubkey[65];
uncompressed_pubkey[0] = 0x04;
memcpy(uncompressed_pubkey + 1, x32, 32);
memcpy(uncompressed_pubkey + 33, y32, 32);

if (!secp256k1_ec_pubkey_parse(secp256k1_context_sign, &pubkey, uncompressed_pubkey, 65)) {
return 0;
}

size_t outputlen = 33;
if (!secp256k1_ec_pubkey_serialize(secp256k1_context_sign, output, &outputlen, &pubkey, SECP256K1_EC_COMPRESSED)) {
return 0;
}
return 1;
}

// WARNING: DO NOT USE THIS FUNCTION OUTSIDE OF SILENT PAYMENTS
// This function returns the un-hashed ECDH result and is specific to silent payments.
// If ECDH is required for a different use-case, a separate function should be used which does not use
// the `custom_ecdh_hash_function` and instead returns the hashed ECDH result (as is standard practice)
CPubKey CKey::SilentPaymentECDH(const CPubKey& pubkey) const
{
unsigned char shared_secret[33];

secp256k1_pubkey ecdh_pubkey;
int return_val = secp256k1_ec_pubkey_parse(secp256k1_context_sign, &ecdh_pubkey, pubkey.data(), pubkey.size());
assert(return_val);

int ret = secp256k1_ecdh(secp256k1_context_sign, shared_secret, &ecdh_pubkey, begin(), custom_ecdh_hash_function, nullptr);
assert(ret);

CPubKey result(shared_secret);
assert(result.IsValid());

return result;
}
CPrivKey CKey::GetPrivKey() const {
assert(fValid);
CPrivKey seckey;
Expand Down
14 changes: 14 additions & 0 deletions src/key.h
Expand Up @@ -107,6 +107,20 @@ class CKey
//! Negate private key
bool Negate();

//! Tweak a secret key by adding a scalar value to it.
//
CKey AddTweak(const unsigned char *tweak32) const;

//! Tweak a secret key by multiplying it by a scalar value.
CKey MultiplyTweak(const unsigned char *tweak32) const;

/**
* Compute a public key via ECDH for Silent Payments.
* This returns the un-hashed ECDH result and should not be used
* outside of silent payments
*/
CPubKey SilentPaymentECDH(const CPubKey& pubkey) const;

/**
* Convert the private key to a CPrivKey (serialized OpenSSL private key data).
* This is expensive.
Expand Down
45 changes: 45 additions & 0 deletions src/pubkey.cpp
Expand Up @@ -256,6 +256,22 @@ std::optional<std::pair<XOnlyPubKey, bool>> XOnlyPubKey::CreateTapTweak(const ui
return ret;
}

CPubKey CPubKey::AddTweak(const unsigned char *tweak32) const
{
secp256k1_pubkey original_pubkey;
int return_val = secp256k1_ec_pubkey_parse(secp256k1_context_static, &original_pubkey, data(), size());
assert(return_val);

return_val = secp256k1_ec_pubkey_tweak_add(secp256k1_context_static, &original_pubkey, tweak32);
assert(return_val);

unsigned char pubkey_bytes[COMPRESSED_SIZE];
size_t publen = COMPRESSED_SIZE;
return_val = secp256k1_ec_pubkey_serialize(secp256k1_context_static, pubkey_bytes, &publen, &original_pubkey, SECP256K1_EC_COMPRESSED);
assert(return_val);

return CPubKey(pubkey_bytes);
}

bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig) const {
if (!IsValid())
Expand Down Expand Up @@ -396,3 +412,32 @@ bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild) const {
}
return (!secp256k1_ecdsa_signature_normalize(secp256k1_context_static, nullptr, &sig));
}

/* static */ CPubKey CPubKey::Combine(std::vector<CPubKey> pubkeys)
{
std::vector<secp256k1_pubkey> parsed_pubkeys;
std::vector<secp256k1_pubkey *> pointers;
parsed_pubkeys.reserve(pubkeys.size());
pointers.reserve(pubkeys.size());
for (auto& pubkey : pubkeys) {
secp256k1_pubkey parsed_pubkey;
int return_val = secp256k1_ec_pubkey_parse(secp256k1_context_static, &parsed_pubkey, pubkey.data(), pubkey.size());
assert(return_val);

parsed_pubkeys.push_back(parsed_pubkey);
pointers.push_back(&parsed_pubkeys.back());
}

secp256k1_pubkey sum_pubkey;
int return_val = secp256k1_ec_pubkey_combine(secp256k1_context_static, &sum_pubkey, pointers.data(), pointers.size());
assert(return_val);

// Serialize and test the tweaked public key
size_t len;
unsigned char output_pubkey[33];
len = sizeof(output_pubkey);
return_val = secp256k1_ec_pubkey_serialize(secp256k1_context_static, output_pubkey, &len, &sum_pubkey, SECP256K1_EC_COMPRESSED);
assert(return_val);

return CPubKey(output_pubkey);
}
14 changes: 14 additions & 0 deletions src/pubkey.h
Expand Up @@ -211,6 +211,12 @@ class CPubKey
*/
static bool CheckLowS(const std::vector<unsigned char>& vchSig);

//! Add a number of public keys together.
static CPubKey Combine(std::vector<CPubKey> pubkeys);

/** Tweak a public key by adding the generator multiplied with tweak32 to it. */
CPubKey AddTweak(const unsigned char *tweak32) const;

//! Recover a public key from a compact signature.
bool RecoverCompact(const uint256& hash, const std::vector<unsigned char>& vchSig);

Expand Down Expand Up @@ -271,6 +277,7 @@ class XOnlyPubKey
/** Construct a Taproot tweaked output point with this point as internal key. */
std::optional<std::pair<XOnlyPubKey, bool>> CreateTapTweak(const uint256* merkle_root) const;


/** Returns a list of CKeyIDs for the CPubKeys that could have been used to create this XOnlyPubKey.
* This is needed for key lookups since keys are indexed by CKeyID.
*/
Expand All @@ -287,6 +294,13 @@ class XOnlyPubKey
bool operator!=(const XOnlyPubKey& other) const { return m_keydata != other.m_keydata; }
bool operator<(const XOnlyPubKey& other) const { return m_keydata < other.m_keydata; }

CPubKey ConvertToCompressedPubKey(bool even = true) const
{
std::vector<unsigned char> vch(std::begin(m_keydata), std::end(m_keydata));
vch.insert(vch.begin(), even ? 2 : 3);
return CPubKey(vch.begin(), vch.end());
}

//! Implement serialization without length prefixes since it is a fixed length
SERIALIZE_METHODS(XOnlyPubKey, obj) { READWRITE(obj.m_keydata); }
};
Expand Down

0 comments on commit 5688262

Please sign in to comment.