This commit is contained in:
josie 2024-04-29 04:31:07 +02:00 committed by GitHub
commit 771fce0bd8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 9820 additions and 29 deletions

View File

@ -14,7 +14,7 @@
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>ENABLE_MODULE_RECOVERY;ENABLE_MODULE_EXTRAKEYS;ENABLE_MODULE_SCHNORRSIG;ENABLE_MODULE_ELLSWIFT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>ENABLE_MODULE_ECDH;ENABLE_MODULE_RECOVERY;ENABLE_MODULE_EXTRAKEYS;ENABLE_MODULE_SCHNORRSIG;ENABLE_MODULE_ELLSWIFT;ENABLE_MODULE_SILENTPAYMENTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UndefinePreprocessorDefinitions>USE_ASM_X86_64;%(UndefinePreprocessorDefinitions)</UndefinePreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\src\secp256k1;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<DisableSpecificWarnings>4146;4244;4267</DisableSpecificWarnings>

View File

@ -1818,7 +1818,7 @@ CPPFLAGS="$CPPFLAGS_TEMP"
if test -n "$use_sanitizers"; then
export SECP_CFLAGS="$SECP_CFLAGS $SANITIZER_CFLAGS"
fi
ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --disable-module-ecdh"
ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --enable-module-ecdh --enable-module-silentpayments"
AC_CONFIG_SUBDIRS([src/secp256k1])
AC_OUTPUT

View File

@ -124,6 +124,7 @@ BITCOIN_CORE_H = \
base58.h \
bech32.h \
bip324.h \
bip352.h \
blockencodings.h \
blockfilter.h \
chain.h \
@ -384,6 +385,7 @@ libbitcoin_node_a_SOURCES = \
addrman.cpp \
banman.cpp \
bip324.cpp \
bip352.cpp \
blockencodings.cpp \
blockfilter.cpp \
chain.cpp \
@ -669,6 +671,7 @@ libbitcoin_common_a_SOURCES = \
addresstype.cpp \
base58.cpp \
bech32.cpp \
bip352.cpp \
chainparams.cpp \
coins.cpp \
common/args.cpp \

View File

@ -17,6 +17,7 @@ FUZZ_BINARY=test/fuzz/fuzz$(EXEEXT)
JSON_TEST_FILES = \
test/data/script_tests.json \
test/data/bip341_wallet_vectors.json \
test/data/bip352_send_and_receive_vectors.json \
test/data/base58_encode_decode.json \
test/data/blockfilters.json \
test/data/key_io_valid.json \
@ -75,6 +76,7 @@ BITCOIN_TESTS =\
test/bech32_tests.cpp \
test/bip32_tests.cpp \
test/bip324_tests.cpp \
test/bip352_tests.cpp \
test/blockchain_tests.cpp \
test/blockencodings_tests.cpp \
test/blockfilter_index_tests.cpp \

View File

@ -104,6 +104,10 @@ namespace {
class CScriptVisitor
{
public:
CScript operator()(const V0SilentPaymentDestination& dest) const
{
return CScript();
}
CScript operator()(const CNoDestination& dest) const
{
return dest.GetScript();
@ -152,6 +156,9 @@ public:
bool operator()(const PubKeyDestination& dest) const { return false; }
bool operator()(const PKHash& dest) const { return true; }
bool operator()(const ScriptHash& dest) const { return true; }
// silent payment addresses are not valid until sending support has been implemented
// TODO: set this to true once sending is implemented
bool operator()(const V0SilentPaymentDestination& dest) const { return false; }
bool operator()(const WitnessV0KeyHash& dest) const { return true; }
bool operator()(const WitnessV0ScriptHash& dest) const { return true; }
bool operator()(const WitnessV1Taproot& dest) const { return true; }

View File

@ -116,6 +116,25 @@ public:
}
};
struct V0SilentPaymentDestination
{
CPubKey m_scan_pubkey;
CPubKey m_spend_pubkey;
friend bool operator==(const V0SilentPaymentDestination& a, const V0SilentPaymentDestination& b) {
if (a.m_scan_pubkey != b.m_scan_pubkey) return false;
if (a.m_spend_pubkey != b.m_spend_pubkey) return false;
return true;
}
friend bool operator<(const V0SilentPaymentDestination& a, const V0SilentPaymentDestination& b) {
if (a.m_scan_pubkey < b.m_scan_pubkey) return true;
if (a.m_scan_pubkey > b.m_scan_pubkey) return false;
if (a.m_spend_pubkey < b.m_spend_pubkey) return true;
return false;
}
};
/**
* A txout script categorized into standard templates.
* * CNoDestination: Optionally a script, no corresponding address.
@ -128,7 +147,7 @@ public:
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? address)
* A CTxDestination is the internal data type encoded in a bitcoin address
*/
using CTxDestination = std::variant<CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown>;
using CTxDestination = std::variant<CNoDestination, V0SilentPaymentDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown>;
/** Check whether a CTxDestination corresponds to one with an address. */
bool IsValidDestination(const CTxDestination& dest);

View File

@ -370,11 +370,11 @@ std::string Encode(Encoding encoding, const std::string& hrp, const data& values
}
/** Decode a Bech32 or Bech32m string. */
DecodeResult Decode(const std::string& str) {
DecodeResult Decode(const std::string& str, CharLimit limit) {
std::vector<int> errors;
if (!CheckCharacters(str, errors)) return {};
size_t pos = str.rfind('1');
if (str.size() > 90 || pos == str.npos || pos == 0 || pos + 7 > str.size()) {
if (str.size() > limit || pos == str.npos || pos == 0 || pos + 7 > str.size()) {
return {};
}
data values(str.size() - 1 - pos);
@ -397,12 +397,12 @@ DecodeResult Decode(const std::string& str) {
}
/** Find index of an incorrect character in a Bech32 string. */
std::pair<std::string, std::vector<int>> LocateErrors(const std::string& str) {
std::pair<std::string, std::vector<int>> LocateErrors(const std::string& str, CharLimit limit) {
std::vector<int> error_locations{};
if (str.size() > 90) {
error_locations.resize(str.size() - 90);
std::iota(error_locations.begin(), error_locations.end(), 90);
if (str.size() > limit) {
error_locations.resize(str.size() - limit);
std::iota(error_locations.begin(), error_locations.end(), static_cast<int>(limit));
return std::make_pair("Bech32 string too long", std::move(error_locations));
}

View File

@ -28,6 +28,17 @@ enum class Encoding {
BECH32M, //!< Bech32m encoding as defined in BIP350
};
/** Character limits for bech32(m) encoded strings. Character limits are how we provide error location guarantees.
* These values should never exceed 2^31 - 1 (max value for a 32-bit int), since there are places where we may need to
* convert the CharLimit::VALUE to an int. In practice, this should never happen since this CharLimit applies to an address encoding
* and we would never encode an address with such a massive value */
enum CharLimit : size_t {
NO_LIMIT = 0, //!< Allows for Bech32(m) encoded strings of arbitrary size. No guarantees on the number of errors detected
SEGWIT = 90, //!< BIP173/350 imposed 90 character limit on Bech32(m) encoded addresses. This guarantees finding up to 4 errors
SILENT_PAYMENTS = 1024, //!< BIP352 imposed 1024 character limit, which guarantees finding up to 3 errors
};
/** Encode a Bech32 or Bech32m string. If hrp contains uppercase characters, this will cause an
* assertion error. Encoding must be one of BECH32 or BECH32M. */
std::string Encode(Encoding encoding, const std::string& hrp, const std::vector<uint8_t>& values);
@ -43,10 +54,10 @@ struct DecodeResult
};
/** Decode a Bech32 or Bech32m string. */
DecodeResult Decode(const std::string& str);
DecodeResult Decode(const std::string& str, CharLimit limit = CharLimit::SEGWIT);
/** Return the positions of errors in a Bech32 string. */
std::pair<std::string, std::vector<int>> LocateErrors(const std::string& str);
std::pair<std::string, std::vector<int>> LocateErrors(const std::string& str, CharLimit limit = CharLimit::SEGWIT);
} // namespace bech32

352
src/bip352.cpp Normal file
View File

@ -0,0 +1,352 @@
// Copyright (c) 2023 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <addresstype.h>
#include <primitives/transaction.h>
#include <secp256k1_extrakeys.h>
#include <pubkey.h>
#include <secp256k1.h>
#include <span.h>
#include <bip352.h>
#include <secp256k1_silentpayments.h>
#include <streams.h>
#include <uint256.h>
#include <logging.h>
#include <policy/policy.h>
#include <script/sign.h>
#include <script/solver.h>
#include <script/script_error.h>
#include <util/check.h>
#include <util/strencodings.h>
extern secp256k1_context* secp256k1_context_sign; // TODO: this is hacky, is there a better solution?
namespace BIP352 {
std::optional<PubKey> GetPubKeyFromInput(const CTxIn& txin, const CScript& spk)
{
std::vector<std::vector<unsigned char>> solutions;
TxoutType type = Solver(spk, solutions);
PubKey pubkey;
if (type == TxoutType::WITNESS_V1_TAPROOT) {
// Check for H point in script path spend
if (txin.scriptWitness.stack.size() > 1) {
// Check for annex
bool has_annex = txin.scriptWitness.stack.back()[0] == ANNEX_TAG;
size_t post_annex_size = txin.scriptWitness.stack.size() - (has_annex ? 1 : 0);
if (post_annex_size > 1) {
// Actually a script path spend
const std::vector<unsigned char>& control = txin.scriptWitness.stack.at(post_annex_size - 1);
Assert(control.size() >= 33);
if (std::equal(NUMS_H.begin(), NUMS_H.end(), control.begin() + 1)) {
// Skip script path with H internal key
return std::nullopt;
}
}
}
pubkey = XOnlyPubKey{solutions[0]};
} else if (type == TxoutType::WITNESS_V0_KEYHASH) {
pubkey = CPubKey{txin.scriptWitness.stack.back()};
} else if (type == TxoutType::PUBKEYHASH || type == TxoutType::SCRIPTHASH) {
// Use the script interpreter to get the stack after executing the scriptSig
std::vector<std::vector<unsigned char>> stack;
ScriptError serror;
Assert(EvalScript(stack, txin.scriptSig, MANDATORY_SCRIPT_VERIFY_FLAGS, DUMMY_CHECKER, SigVersion::BASE, &serror));
if (type == TxoutType::PUBKEYHASH) {
pubkey = CPubKey{stack.back()};
} else if (type == TxoutType::SCRIPTHASH) {
// Check if the redeemScript is P2WPKH
CScript redeem_script{stack.back().begin(), stack.back().end()};
TxoutType rs_type = Solver(redeem_script, solutions);
if (rs_type == TxoutType::WITNESS_V0_KEYHASH) {
pubkey = CPubKey{txin.scriptWitness.stack.back()};
}
}
}
if (std::holds_alternative<XOnlyPubKey>(pubkey)) return pubkey;
if (auto compressed_pubkey = std::get_if<CPubKey>(&pubkey)) {
if (compressed_pubkey->IsCompressed() && compressed_pubkey->IsValid()) return *compressed_pubkey;
}
return std::nullopt;
}
PubTweakData CreateInputPubkeysTweak(
const std::vector<CPubKey>& plain_pubkeys,
const std::vector<XOnlyPubKey>& taproot_pubkeys,
const COutPoint& smallest_outpoint)
{
PubTweakData public_data;
// TODO: see if there is a way to clean this up
std::vector<secp256k1_pubkey> plain_pubkey_objs;
std::vector<secp256k1_xonly_pubkey> taproot_pubkey_objs;
std::vector<secp256k1_pubkey *> plain_pubkey_ptrs;
std::vector<secp256k1_xonly_pubkey *> taproot_pubkey_ptrs;
plain_pubkey_objs.reserve(plain_pubkeys.size());
plain_pubkey_ptrs.reserve(plain_pubkeys.size());
taproot_pubkey_objs.reserve(taproot_pubkeys.size());
taproot_pubkey_ptrs.reserve(taproot_pubkeys.size());
for (const CPubKey& pubkey : plain_pubkeys) {
secp256k1_pubkey parsed_pubkey;
bool ret = secp256k1_ec_pubkey_parse(secp256k1_context_static, &parsed_pubkey,
pubkey.data(), pubkey.size());
assert(ret);
plain_pubkey_objs.emplace_back(parsed_pubkey);
}
for (const XOnlyPubKey& pubkey : taproot_pubkeys) {
secp256k1_xonly_pubkey parsed_xonly_pubkey;
bool ret = secp256k1_xonly_pubkey_parse(secp256k1_context_static, &parsed_xonly_pubkey, pubkey.data());
assert(ret);
taproot_pubkey_objs.emplace_back(parsed_xonly_pubkey);
}
for (auto &p : plain_pubkey_objs) {
plain_pubkey_ptrs.push_back(&p);
}
for (auto &t : taproot_pubkey_objs) {
taproot_pubkey_ptrs.push_back(&t);
}
std::vector<unsigned char> smallest_outpoint_ser;
VectorWriter stream{smallest_outpoint_ser, 0};
stream << smallest_outpoint;
bool ret = secp256k1_silentpayments_recipient_public_data_create(secp256k1_context_static,
&public_data,
smallest_outpoint_ser.data(),
taproot_pubkey_ptrs.data(), taproot_pubkey_ptrs.size(),
plain_pubkey_ptrs.data(), plain_pubkey_ptrs.size()
);
assert(ret);
return public_data;
}
std::optional<PubTweakData> GetSilentPaymentTweakDataFromTxInputs(const std::vector<CTxIn>& vin, const std::map<COutPoint, Coin>& coins)
{
// Extract the keys from the inputs
// or skip if no valid inputs
std::vector<CPubKey> pubkeys;
std::vector<XOnlyPubKey> xonly_pubkeys;
std::vector<COutPoint> tx_outpoints;
for (const CTxIn& txin : vin) {
const Coin& coin = coins.at(txin.prevout);
Assert(!coin.IsSpent());
tx_outpoints.emplace_back(txin.prevout);
auto pubkey = GetPubKeyFromInput(txin, coin.out.scriptPubKey);
if (pubkey.has_value()) {
std::visit([&pubkeys, &xonly_pubkeys](auto&& pubkey) {
using T = std::decay_t<decltype(pubkey)>;
if constexpr (std::is_same_v<T, CPubKey>) {
pubkeys.push_back(pubkey);
} else if constexpr (std::is_same_v<T, XOnlyPubKey>) {
xonly_pubkeys.push_back(pubkey);
}
}, *pubkey);
}
}
if (pubkeys.size() + xonly_pubkeys.size() == 0) return std::nullopt;
auto smallest_outpoint = std::min_element(tx_outpoints.begin(), tx_outpoints.end());
return CreateInputPubkeysTweak(pubkeys, xonly_pubkeys, *smallest_outpoint);
}
std::optional<CPubKey> GetSerializedSilentPaymentsPublicData(const std::vector<CTxIn>& vin, const std::map<COutPoint, Coin>& coins)
{
CPubKey serialized_public_data;
bool ret;
const auto& result = GetSilentPaymentTweakDataFromTxInputs(vin, coins);
if (!result.has_value()) return std::nullopt;
secp256k1_silentpayments_public_data public_data = *result;
ret = secp256k1_silentpayments_recipient_public_data_serialize(secp256k1_context_static, (unsigned char *)serialized_public_data.begin(), &public_data);
assert(ret);
return serialized_public_data;
}
std::vector<secp256k1_xonly_pubkey> CreateOutputs(
const std::vector<V0SilentPaymentDestination> recipients,
const std::vector<CKey>& plain_keys,
const std::vector<CKey>& taproot_keys,
const COutPoint& smallest_outpoint
) {
bool ret;
std::vector<secp256k1_keypair> taproot_keypairs;
std::vector<const secp256k1_keypair *> taproot_keypair_ptrs;
std::vector<const unsigned char *> plain_key_ptrs;
taproot_keypairs.reserve(taproot_keys.size());
taproot_keypair_ptrs.reserve(taproot_keys.size());
plain_key_ptrs.reserve(plain_keys.size());
std::vector<secp256k1_silentpayments_recipient> recipient_objs;
std::vector<const secp256k1_silentpayments_recipient *> recipient_ptrs;
recipient_objs.reserve(recipients.size());
recipient_ptrs.reserve(recipients.size());
std::vector<secp256k1_xonly_pubkey> generated_outputs;
std::vector<secp256k1_xonly_pubkey *> generated_output_ptrs;
generated_outputs.reserve(recipients.size());
generated_output_ptrs.reserve(recipients.size());
for (size_t i = 0; i < recipients.size(); i++) {
secp256k1_silentpayments_recipient recipient_obj;
ret = secp256k1_ec_pubkey_parse(secp256k1_context_static, &recipient_obj.scan_pubkey, recipients[i].m_scan_pubkey.data(), recipients[i].m_scan_pubkey.size());
ret = secp256k1_ec_pubkey_parse(secp256k1_context_static, &recipient_obj.spend_pubkey, recipients[i].m_spend_pubkey.data(), recipients[i].m_spend_pubkey.size());
assert(ret);
recipient_obj.index = i;
recipient_objs.push_back(recipient_obj);
recipient_ptrs.push_back(&recipient_objs[i]);
secp256k1_xonly_pubkey generated_output;
generated_outputs.push_back(generated_output);
generated_output_ptrs.push_back(&generated_outputs[i]);
}
for (const auto& key : plain_keys) {
plain_key_ptrs.push_back(UCharCast(key.begin()));
}
for (size_t i = 0; i < taproot_keys.size(); i++) {
secp256k1_keypair keypair;
ret = secp256k1_keypair_create(secp256k1_context_sign, &keypair, UCharCast(taproot_keys[i].begin()));
taproot_keypairs.push_back(keypair);
taproot_keypair_ptrs.push_back(&taproot_keypairs[i]);
}
// Serialize the outpoint
std::vector<unsigned char> smallest_outpoint_ser;
VectorWriter stream{smallest_outpoint_ser, 0};
stream << smallest_outpoint;
ret = secp256k1_silentpayments_sender_create_outputs(secp256k1_context_sign,
generated_output_ptrs.data(),
recipient_ptrs.data(), recipient_ptrs.size(),
smallest_outpoint_ser.data(),
taproot_keypair_ptrs.data(), taproot_keypair_ptrs.size(),
plain_key_ptrs.data(), plain_key_ptrs.size()
);
assert(ret);
return generated_outputs;
}
std::map<size_t, WitnessV1Taproot> GenerateSilentPaymentTaprootDestinations(const std::map<size_t, V0SilentPaymentDestination>& sp_dests, const std::vector<CKey>& plain_keys, const std::vector<CKey>& taproot_keys, const COutPoint& smallest_outpoint)
{
bool ret;
std::map<size_t, WitnessV1Taproot> tr_dests;
std::vector<V0SilentPaymentDestination> recipients;
recipients.reserve(sp_dests.size());
for (const auto& [_, addr] : sp_dests) {
recipients.push_back(addr);
}
std::vector<secp256k1_xonly_pubkey> outputs = CreateOutputs(recipients, plain_keys, taproot_keys, smallest_outpoint);
for (size_t i = 0; i < outputs.size(); i++) {
unsigned char xonly_pubkey_bytes[32];
ret = secp256k1_xonly_pubkey_serialize(secp256k1_context_static, xonly_pubkey_bytes, &outputs[i]);
assert(ret);
tr_dests[i] = WitnessV1Taproot{XOnlyPubKey{xonly_pubkey_bytes}};
}
return tr_dests;
}
const unsigned char* LabelLookupCallback(const secp256k1_pubkey* key, const void* context) {
auto label_context = static_cast<const std::map<CPubKey, uint256>*>(context);
CPubKey label;
size_t pubkeylen = CPubKey::COMPRESSED_SIZE;
bool ret = secp256k1_ec_pubkey_serialize(secp256k1_context_static, (unsigned char*)label.begin(), &pubkeylen, key, SECP256K1_EC_COMPRESSED);
assert(ret);
// Find the pubkey in the map
auto it = label_context->find(label);
if (it != label_context->end()) {
// Return a pointer to the uint256 label tweak if found
// so it can be added to t_k
return it->second.begin();
}
return nullptr;
}
std::optional<std::vector<SilentPaymentOutput>> GetTxOutputTweaks(
const CKey& scan_key,
const PubTweakData& public_data,
const CPubKey& receiver_spend_pubkey,
const std::vector<XOnlyPubKey>& tx_outputs,
const std::map<CPubKey, uint256>& labels
) {
bool ret;
secp256k1_pubkey spend_pubkey_obj;
std::vector<secp256k1_silentpayments_found_output> found_output_objs;
std::vector<secp256k1_silentpayments_found_output *> found_output_ptrs;
std::vector<secp256k1_xonly_pubkey> tx_output_objs;
std::vector<secp256k1_xonly_pubkey *> tx_output_ptrs;
found_output_objs.reserve(tx_outputs.size());
found_output_ptrs.reserve(tx_outputs.size());
tx_output_objs.reserve(tx_outputs.size());
tx_output_ptrs.reserve(tx_outputs.size());
for (size_t i = 0; i < tx_outputs.size(); i++) {
secp256k1_silentpayments_found_output found_output;
secp256k1_xonly_pubkey tx_output_obj;
found_output_objs.push_back(found_output);
found_output_ptrs.push_back(&found_output_objs[i]);
ret = secp256k1_xonly_pubkey_parse(secp256k1_context_static, &tx_output_obj, tx_outputs[i].data());
assert(ret);
tx_output_objs.push_back(tx_output_obj);
tx_output_ptrs.push_back(&tx_output_objs[i]);
}
// Parse the pubkeys into secp pubkey and xonly_pubkey objects
ret = secp256k1_ec_pubkey_parse(secp256k1_context_static, &spend_pubkey_obj, receiver_spend_pubkey.data(), receiver_spend_pubkey.size());
assert(ret);
// Scan the outputs!
size_t n_found_outputs = 0;
ret = secp256k1_silentpayments_recipient_scan_outputs(secp256k1_context_static,
found_output_ptrs.data(), &n_found_outputs,
tx_output_ptrs.data(), tx_output_ptrs.size(),
UCharCast(scan_key.begin()),
&public_data,
&spend_pubkey_obj,
LabelLookupCallback,
&labels
);
assert(ret);
if (n_found_outputs == 0) return {};
std::vector<SilentPaymentOutput> outputs;
for (size_t i = 0; i < n_found_outputs; i++) {
SilentPaymentOutput sp_output;
ret = secp256k1_xonly_pubkey_serialize(secp256k1_context_static, sp_output.output.begin(), &found_output_objs[i].output);
assert(ret);
sp_output.tweak = uint256{found_output_objs[i].tweak};
if (found_output_objs[i].found_with_label) {
CPubKey label;
size_t pubkeylen = CPubKey::COMPRESSED_SIZE;
ret = secp256k1_ec_pubkey_serialize(secp256k1_context_static, (unsigned char *)label.begin(), &pubkeylen, &found_output_objs[i].label, SECP256K1_EC_COMPRESSED);
sp_output.label = label;
}
outputs.push_back(sp_output);
}
return outputs;
}
std::pair<CPubKey, uint256> CreateLabelTweak(const CKey& scan_key, const int m) {
secp256k1_pubkey label_obj;
unsigned char label_tweak[32];
bool ret = secp256k1_silentpayments_recipient_create_label_tweak(secp256k1_context_sign, &label_obj, label_tweak, UCharCast(scan_key.data()), m);
assert(ret);
CPubKey label;
size_t pubkeylen = CPubKey::COMPRESSED_SIZE;
ret = secp256k1_ec_pubkey_serialize(secp256k1_context_static, (unsigned char*)label.begin(), &pubkeylen, &label_obj, SECP256K1_EC_COMPRESSED);
assert(ret);
return {label, uint256{label_tweak}};
}
CPubKey CreateLabeledSpendPubKey(const CPubKey& spend_pubkey, const CPubKey& label) {
secp256k1_pubkey spend_obj, label_obj, labelled_spend_obj;
bool ret = secp256k1_ec_pubkey_parse(secp256k1_context_static, &spend_obj, spend_pubkey.data(), spend_pubkey.size());
assert(ret);
ret = secp256k1_ec_pubkey_parse(secp256k1_context_static, &label_obj, label.data(), label.size());
assert(ret);
ret = secp256k1_silentpayments_recipient_create_labelled_spend_pubkey(secp256k1_context_static, &labelled_spend_obj, &spend_obj, &label_obj);
assert(ret);
size_t pubkeylen = CPubKey::COMPRESSED_SIZE;
CPubKey labelled_spend_pubkey;
ret = secp256k1_ec_pubkey_serialize(secp256k1_context_static, (unsigned char*)labelled_spend_pubkey.begin(), &pubkeylen, &labelled_spend_obj, SECP256K1_EC_COMPRESSED);
return labelled_spend_pubkey;
}
}; // namespace BIP352

113
src/bip352.h Normal file
View File

@ -0,0 +1,113 @@
// Copyright (c) 2023 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_BIP352_H
#define BITCOIN_BIP352_H
#include <key.h>
#include <pubkey.h>
#include <primitives/transaction.h>
#include <secp256k1.h>
#include <secp256k1_silentpayments.h>
#include <uint256.h>
#include <addresstype.h>
#include <coins.h>
#include <vector>
#include <variant>
namespace BIP352 {
using PubKey = std::variant<CPubKey, XOnlyPubKey>;
using PubTweakData = secp256k1_silentpayments_public_data;
struct SilentPaymentOutput {
XOnlyPubKey output;
uint256 tweak;
std::optional<CPubKey> label;
};
/**
* @brief Get the public key from an input.
*
* Get the public key from a silent payment eligible input. This requires knowledge of the prevout
* scriptPubKey to determine the type of input and whether or not it is eligible for silent payments.
*
* If the input is not eligible for silent payments, the input is skipped (indicated by returning a nullopt).
*
* @param txin The transaction input.
* @param spk The scriptPubKey of the prevout.
* @return std::optional<CPubKey> The public key, or nullopt if not found.
*/
std::optional<PubKey> GetPubKeyFromInput(const CTxIn& txin, const CScript& spk);
/**
* @brief Generate a silent payment labeled address.
*
* @param receiver The receiver's silent payment destination (i.e. scan and spend public keys).
* @param label The label tweak
* @return V0SilentPaymentDestination The silent payment destination, with `B_spend -> B_spend + label * G`.
*
* @see CreateLabelTweak(const CKey& scan_key, const int m);
*/
V0SilentPaymentDestination GenerateSilentPaymentLabeledAddress(const V0SilentPaymentDestination& receiver, const uint256& label);
std::pair<CPubKey, uint256> CreateLabelTweak(const CKey& scan_key, const int m);
/**
* @brief Generate silent payment taproot destinations.
*
* Given a set of silent payment destinations, generate the requested number of outputs. If a silent payment
* destination is repeated, this indicates multiple outputs are requested for the same recipient. The silent payment
* desintaions are passed in map where the key indicates their desired position in the final tx.vout array.
*
* @param sp_dests The silent payment destinations.
* @param plain_keys The private keys for non-taproot inputs.
* @param taproot_keys The private keys for taproot inputs.
* @param smallest_outpoint The smallest_outpoint from the transaction inputs.
* @return std::map<size_t, WitnessV1Taproot> The generated silent payment taproot destinations.
*
*/
std::map<size_t, WitnessV1Taproot> GenerateSilentPaymentTaprootDestinations(const std::map<size_t, V0SilentPaymentDestination>& sp_dests, const std::vector<CKey>& plain_keys, const std::vector<CKey>& taproot_keys, const COutPoint& smallest_outpoint);
/**
* @brief Get silent payment tweak data from transaction inputs.
*
* Get the necessary data from the transaction inputs to be able to scan the transaction outputs for silent payment outputs.
* This requires knowledge of the prevout scriptPubKey, which is passed via `coins`.
*
* This function returns the public key sum and the input hash separately and is intended to be used by the wallet when scanning
* a transaction. To get the silent payments public data for sending to light clients or saving in an index, use
* `GetSerializedSilentPaymentsPublicData`.
*
* If there are no eligible inputs, nullopt is returned, indicating that this transaction does not
* contain silent payment outputs.
*
* @param vin The transaction inputs.
* @param coins The coins (potentially) spent in this transaction.
* @return std::optional<PubTweakData> The silent payment tweak data, or nullopt if not found.
*/
std::optional<PubTweakData> GetSilentPaymentTweakDataFromTxInputs(const std::vector<CTxIn>& vin, const std::map<COutPoint, Coin>& coins);
std::optional<CPubKey> GetSerializedSilentPaymentsPublicData(const std::vector<CTxIn>& vin, const std::map<COutPoint, Coin>& coins);
/**
* @brief Get the silent payment output tweaks from a transaction.
*
* Scan the transaction for silent payment outputs intendended for us. The shared secret tweak is returned
* for each output found. This tweak data is needed to spend the output, by adding it to the spend secret key.
* If no tweak data is returned, this transaction does not contain silent payment outputs intended for us.
*
* While labels are optional for the receiver, it is strongly recommended that the change label is always checked
* when scanning.
*
* @param scan_key The ECDH public key.
* @param spend_pubkey The receiver's spend public key.
* @param output_pub_keys The taproot output public keys.
* @param labels The receiver's labels.
* @return std::<optional<std::vector<SilentPaymentOutput>> The transaction output tweaks.
*
*/
std::optional<std::vector<SilentPaymentOutput>> GetTxOutputTweaks(const CKey& scan_key, const PubTweakData& public_data, const CPubKey& spend_pubkey, const std::vector<XOnlyPubKey>& output_pub_keys, const std::map<CPubKey, uint256>& labels);
}; // namespace BIP352
#endif // BITCOIN_BIP352_H

View File

@ -148,6 +148,7 @@ public:
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4};
bech32_hrp = "bc";
silent_payment_hrp = "sp";
vFixedSeeds = std::vector<uint8_t>(std::begin(chainparams_seed_main), std::end(chainparams_seed_main));
@ -254,6 +255,7 @@ public:
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
bech32_hrp = "tb";
silent_payment_hrp = "tsp";
vFixedSeeds = std::vector<uint8_t>(std::begin(chainparams_seed_test), std::end(chainparams_seed_test));
@ -391,6 +393,7 @@ public:
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
bech32_hrp = "tb";
silent_payment_hrp = "tsp";
fDefaultConsistencyChecks = false;
m_is_mockable_chain = false;
@ -520,6 +523,7 @@ public:
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
bech32_hrp = "bcrt";
silent_payment_hrp = "sprt";
}
};

View File

@ -116,6 +116,7 @@ public:
const std::vector<std::string>& DNSSeeds() const { return vSeeds; }
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; }
const std::string& Bech32HRP() const { return bech32_hrp; }
const std::string& SilentPaymentHRP() const { return silent_payment_hrp; }
const std::vector<uint8_t>& FixedSeeds() const { return vFixedSeeds; }
const CCheckpointData& Checkpoints() const { return checkpointData; }
@ -173,6 +174,7 @@ protected:
std::vector<std::string> vSeeds;
std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];
std::string bech32_hrp;
std::string silent_payment_hrp;
ChainType m_chain_type;
CBlock genesis;
std::vector<uint8_t> vFixedSeeds;

View File

@ -16,7 +16,7 @@
#include <secp256k1_recovery.h>
#include <secp256k1_schnorrsig.h>
static secp256k1_context* secp256k1_context_sign = nullptr;
secp256k1_context* secp256k1_context_sign = nullptr;
/** These functions are taken from the libsecp256k1 distribution and are very ugly. */

View File

@ -17,6 +17,8 @@
/// Maximum witness length for Bech32 addresses.
static constexpr std::size_t BECH32_WITNESS_PROG_MAX_LEN = 40;
/// Data size for a BIP352 v0 address
static constexpr std::size_t SILENT_PAYMENT_V0_DATA_SIZE = 66;
namespace {
class DestinationEncoder
@ -65,6 +67,23 @@ public:
return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data);
}
std::string operator()(const V0SilentPaymentDestination& sp) const
{
// The data_in is scan_pubkey + spend_pubkey
std::vector<unsigned char> data_in = {};
data_in.reserve(66);
// Set 0 as the silent payments version
std::vector<unsigned char> data_out = {0};
// ConvertBits will expand each 8-bit byte into 5-bit chunks,
// i.e. (67 * 8 / 5) = 107.2 -> so we reserve 108
data_out.reserve(108);
data_in.insert(data_in.end(), sp.m_scan_pubkey.begin(), sp.m_scan_pubkey.end());
data_in.insert(data_in.end(), sp.m_spend_pubkey.begin(), sp.m_spend_pubkey.end());
ConvertBits<8, 5, true>([&](unsigned char c) { data_out.push_back(c); }, data_in.begin(), data_in.end());
return bech32::Encode(bech32::Encoding::BECH32M, m_params.SilentPaymentHRP(), data_out);
}
std::string operator()(const WitnessUnknown& id) const
{
const std::vector<unsigned char>& program = id.GetWitnessProgram();
@ -88,7 +107,9 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
error_str = "";
// Note this will be false if it is a valid Bech32 address for a different network
bool is_bech32 = (ToLower(str.substr(0, params.Bech32HRP().size())) == params.Bech32HRP());
// BIP352 addresses are encoded using bech32m but with a higher character limit, so also check if it's a silent payment address
bool is_silent_payment = (ToLower(str.substr(0, params.SilentPaymentHRP().size())) == params.SilentPaymentHRP());
bool is_bech32 = is_silent_payment ? true : (ToLower(str.substr(0, params.Bech32HRP().size())) == params.Bech32HRP());
if (!is_bech32 && DecodeBase58Check(str, data, 21)) {
// base58-encoded Bitcoin addresses.
@ -128,12 +149,38 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
}
data.clear();
const auto dec = bech32::Decode(str);
const auto dec = bech32::Decode(str, is_silent_payment ? bech32::CharLimit::SILENT_PAYMENTS : bech32::CharLimit::SEGWIT);
if (dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) {
if (dec.data.empty()) {
error_str = "Empty Bech32 data section";
return CNoDestination();
}
if (is_silent_payment) {
if (!ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin() + 1, dec.data.end())) {
return CNoDestination();
}
if (data.size() < SILENT_PAYMENT_V0_DATA_SIZE) {
error_str = strprintf("Silent payment data payload is too small (expected at least %d, got %d).", SILENT_PAYMENT_V0_DATA_SIZE, data.size());
return CNoDestination();
}
auto version = dec.data[0]; // retrieve the version
if (version >= 31) {
error_str = strprintf("This implementation only supports sending to Silent payment addresses v0 through v30 (got %d).", version);
return CNoDestination();
}
if (version == 0 && data.size() != SILENT_PAYMENT_V0_DATA_SIZE) {
error_str = strprintf("Silent payment version is v0 but data is not the correct size (expected %d, got %d).", SILENT_PAYMENT_V0_DATA_SIZE, data.size());
return CNoDestination();
}
CPubKey scan_pubkey{data.begin(), data.begin() + CPubKey::COMPRESSED_SIZE};
CPubKey spend_pubkey{data.begin() + CPubKey::COMPRESSED_SIZE, data.begin() + 2*CPubKey::COMPRESSED_SIZE};
// This is a bit of a hack to disable silent payments until sending is implemented. The reason we return a V0SilentPaymentDestination
// while also setting an error message is so that we can use DecodeDestination in the unit tests, but also have `validateaddress` fail
// when passed a silent payment address
// TODO: remove this error_str once sending support is implemented
error_str = strprintf("This is a valid Silent Payments v0 address, but sending support is not yet implemented.");
return V0SilentPaymentDestination{scan_pubkey, spend_pubkey};
}
// Bech32 decoding
if (dec.hrp != params.Bech32HRP()) {
error_str = strprintf("Invalid or unsupported prefix for Segwit (Bech32) address (expected %s, got %s).", params.Bech32HRP(), dec.hrp);
@ -199,7 +246,7 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
}
// Perform Bech32 error location
auto res = bech32::LocateErrors(str);
auto res = bech32::LocateErrors(str, is_silent_payment ? bech32::CharLimit::SILENT_PAYMENTS : bech32::CharLimit::SEGWIT);
error_str = res.first;
if (error_locations) *error_locations = std::move(res.second);
return CNoDestination();

View File

@ -10,6 +10,7 @@
#include <consensus/amount.h>
#include <script/script.h>
#include <serialize.h>
#include <streams.h>
#include <uint256.h>
#include <util/transaction_identifier.h> // IWYU pragma: export
@ -43,7 +44,10 @@ public:
friend bool operator<(const COutPoint& a, const COutPoint& b)
{
return std::tie(a.hash, a.n) < std::tie(b.hash, b.n);
DataStream ser_a, ser_b;
ser_a << a;
ser_b << b;
return Span{ser_a} < Span{ser_b};
}
friend bool operator==(const COutPoint& a, const COutPoint& b)

View File

@ -181,6 +181,9 @@ int ecdsa_signature_parse_der_lax(secp256k1_ecdsa_signature* sig, const unsigned
return 1;
}
static const std::vector<unsigned char> NUMS_H_DATA = {0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a, 0x5e, 0x07, 0x8a, 0x5a, 0x0f, 0x28, 0xec, 0x96, 0xd5, 0x47, 0xbf, 0xee, 0x9a, 0xce, 0x80, 0x3a, 0xc0};
const XOnlyPubKey NUMS_H{NUMS_H_DATA};
XOnlyPubKey::XOnlyPubKey(Span<const unsigned char> bytes)
{
assert(bytes.size() == 32);

View File

@ -300,6 +300,8 @@ public:
SERIALIZE_METHODS(XOnlyPubKey, obj) { READWRITE(obj.m_keydata); }
};
extern const XOnlyPubKey NUMS_H;
/** An ElligatorSwift-encoded public key. */
struct EllSwiftPubKey
{

View File

@ -265,6 +265,11 @@ public:
return UniValue(UniValue::VOBJ);
}
UniValue operator()(const V0SilentPaymentDestination& dest) const
{
return UniValue(UniValue::VOBJ);
}
UniValue operator()(const PubKeyDestination& dest) const
{
return UniValue(UniValue::VOBJ);

View File

@ -22,6 +22,7 @@ env:
RECOVERY: no
SCHNORRSIG: no
ELLSWIFT: no
SILENTPAYMENTS: no
### test options
SECP256K1_TEST_ITERS:
BENCH: yes
@ -68,6 +69,7 @@ task:
RECOVERY: yes
SCHNORRSIG: yes
ELLSWIFT: yes
SILENTPAYMENTS: yes
matrix:
# Currently only gcc-snapshot, the other compilers are tested on GHA with QEMU
- env: { CC: 'gcc-snapshot' }
@ -84,6 +86,7 @@ task:
RECOVERY: yes
SCHNORRSIG: yes
ELLSWIFT: yes
SILENTPAYMENTS: yes
WRAPPER_CMD: 'valgrind --error-exitcode=42'
SECP256K1_TEST_ITERS: 2
matrix:

View File

@ -33,6 +33,7 @@ env:
RECOVERY: 'no'
SCHNORRSIG: 'no'
ELLSWIFT: 'no'
SILENTPAYMENTS: 'no'
### test options
SECP256K1_TEST_ITERS:
BENCH: 'yes'
@ -71,18 +72,18 @@ jobs:
matrix:
configuration:
- env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' }
- env_vars: { WIDEMUL: 'int64', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
- env_vars: { WIDEMUL: 'int64', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
- env_vars: { WIDEMUL: 'int128' }
- env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' }
- env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
- env_vars: { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes' }
- env_vars: { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', SILENTPAYMENTS: 'yes' }
- env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' }
- env_vars: { RECOVERY: 'yes', SCHNORRSIG: 'yes' }
- env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', CPPFLAGS: '-DVERIFY' }
- env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', SILENTPAYMENTS: 'yes', CPPFLAGS: '-DVERIFY' }
- env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' }
- env_vars: { CPPFLAGS: '-DDETERMINISTIC' }
- env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' }
- env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
- env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
- env_vars: { ECMULTGENPRECISION: 2, ECMULTWINDOW: 2 }
- env_vars: { ECMULTGENPRECISION: 8, ECMULTWINDOW: 4 }
cc:
@ -141,6 +142,7 @@ jobs:
RECOVERY: 'yes'
SCHNORRSIG: 'yes'
ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CC: ${{ matrix.cc }}
steps:
@ -185,6 +187,7 @@ jobs:
RECOVERY: 'yes'
SCHNORRSIG: 'yes'
ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no'
steps:
@ -236,6 +239,7 @@ jobs:
RECOVERY: 'yes'
SCHNORRSIG: 'yes'
ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no'
steps:
@ -281,6 +285,7 @@ jobs:
RECOVERY: 'yes'
SCHNORRSIG: 'yes'
ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no'
strategy:
@ -336,6 +341,7 @@ jobs:
RECOVERY: 'yes'
SCHNORRSIG: 'yes'
ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no'
steps:
@ -388,6 +394,7 @@ jobs:
RECOVERY: 'yes'
SCHNORRSIG: 'yes'
ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no'
SECP256K1_TEST_ITERS: 2
@ -439,6 +446,7 @@ jobs:
RECOVERY: 'yes'
SCHNORRSIG: 'yes'
ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no'
CFLAGS: '-fsanitize=undefined,address -g'
UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1'
@ -496,6 +504,7 @@ jobs:
RECOVERY: 'yes'
SCHNORRSIG: 'yes'
ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'yes'
CC: 'clang'
SECP256K1_TEST_ITERS: 32
@ -543,6 +552,7 @@ jobs:
RECOVERY: 'yes'
SCHNORRSIG: 'yes'
ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no'
strategy:
@ -599,14 +609,14 @@ jobs:
fail-fast: false
matrix:
env_vars:
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
- { WIDEMUL: 'int128_struct', ECMULTGENPRECISION: 2, ECMULTWINDOW: 4 }
- { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
- { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', CC: 'gcc' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' }
- BUILD: 'distcheck'
@ -718,6 +728,7 @@ jobs:
RECOVERY: 'yes'
SCHNORRSIG: 'yes'
ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
steps:
- name: Checkout

View File

@ -10,6 +10,7 @@ ctime_tests
ecdh_example
ecdsa_example
schnorr_example
silentpayments_example
*.exe
*.so
*.a

View File

@ -60,9 +60,14 @@ option(SECP256K1_ENABLE_MODULE_RECOVERY "Enable ECDSA pubkey recovery module." O
option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON)
option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON)
option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON)
option(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS "Enable Silent Payments module." OFF)
# Processing must be done in a topological sorting of the dependency graph
# (dependent module first).
if(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS)
add_compile_definitions(ENABLE_MODULE_SILENTPAYMENTS=1)
endif()
if(SECP256K1_ENABLE_MODULE_ELLSWIFT)
add_compile_definitions(ENABLE_MODULE_ELLSWIFT=1)
endif()
@ -292,6 +297,7 @@ message(" ECDSA pubkey recovery ............... ${SECP256K1_ENABLE_MODULE_RECOV
message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRAKEYS}")
message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}")
message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}")
message(" Silent Payments ..................... ${SECP256K1_ENABLE_MODULE_SILENTPAYMENTS}")
message("Parameters:")
message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}")
message(" ecmult gen precision bits ........... ${SECP256K1_ECMULT_GEN_PREC_BITS}")

View File

@ -64,6 +64,8 @@ noinst_HEADERS += src/field.h
noinst_HEADERS += src/field_impl.h
noinst_HEADERS += src/bench.h
noinst_HEADERS += src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.h
noinst_HEADERS += src/hsort.h
noinst_HEADERS += src/hsort_impl.h
noinst_HEADERS += contrib/lax_der_parsing.h
noinst_HEADERS += contrib/lax_der_parsing.c
noinst_HEADERS += contrib/lax_der_privatekey_parsing.h
@ -182,6 +184,17 @@ schnorr_example_LDFLAGS += -lbcrypt
endif
TESTS += schnorr_example
endif
if ENABLE_MODULE_SILENTPAYMENTS
noinst_PROGRAMS += silentpayments_example
silentpayments_example_SOURCES = examples/silentpayments.c
silentpayments_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
silentpayments_example_LDADD = libsecp256k1.la
silentpayments_example_LDFLAGS = -static
if BUILD_WINDOWS
silentpayments_example_LDFLAGS += -lbcrypt
endif
TESTS += silentpayments_example
endif
endif
### Precomputed tables
@ -271,3 +284,7 @@ endif
if ENABLE_MODULE_ELLSWIFT
include src/modules/ellswift/Makefile.am.include
endif
if ENABLE_MODULE_SILENTPAYMENTS
include src/modules/silentpayments/Makefile.am.include
endif

View File

@ -13,7 +13,7 @@ print_environment() {
# does not rely on bash.
for var in WERROR_CFLAGS MAKEFLAGS BUILD \
ECMULTWINDOW ECMULTGENPRECISION ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \
EXPERIMENTAL ECDH RECOVERY SCHNORRSIG ELLSWIFT \
EXPERIMENTAL ECDH RECOVERY SCHNORRSIG ELLSWIFT SILENTPAYMENTS \
SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\
EXAMPLES \
HOST WRAPPER_CMD \
@ -77,6 +77,7 @@ esac
--with-ecmult-gen-precision="$ECMULTGENPRECISION" \
--enable-module-ecdh="$ECDH" --enable-module-recovery="$RECOVERY" \
--enable-module-ellswift="$ELLSWIFT" \
--enable-module-silentpayments="$SILENTPAYMENTS" \
--enable-module-schnorrsig="$SCHNORRSIG" \
--enable-examples="$EXAMPLES" \
--enable-ctime-tests="$CTIMETESTS" \

View File

@ -188,6 +188,10 @@ AC_ARG_ENABLE(module_ellswift,
AS_HELP_STRING([--enable-module-ellswift],[enable ElligatorSwift module [default=yes]]), [],
[SECP_SET_DEFAULT([enable_module_ellswift], [yes], [yes])])
AC_ARG_ENABLE(module_silentpayments,
AS_HELP_STRING([--enable-module-silentpayments],[enable Silent Payments module [default=no]]), [],
[SECP_SET_DEFAULT([enable_module_silentpayments], [no], [yes])])
AC_ARG_ENABLE(external_default_callbacks,
AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [],
[SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])])
@ -389,6 +393,10 @@ SECP_CFLAGS="$SECP_CFLAGS $WERROR_CFLAGS"
# Processing must be done in a reverse topological sorting of the dependency graph
# (dependent module first).
if test x"$enable_module_silentpayments" = x"yes"; then
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_SILENTPAYMENTS=1"
fi
if test x"$enable_module_ellswift" = x"yes"; then
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1"
fi
@ -450,6 +458,7 @@ AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"ye
AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_ELLSWIFT], [test x"$enable_module_ellswift" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_SILENTPAYMENTS], [test x"$enable_module_silentpayments" = x"yes"])
AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"])
AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"])
AM_CONDITIONAL([BUILD_WINDOWS], [test "$build_windows" = "yes"])
@ -472,6 +481,7 @@ echo " module recovery = $enable_module_recovery"
echo " module extrakeys = $enable_module_extrakeys"
echo " module schnorrsig = $enable_module_schnorrsig"
echo " module ellswift = $enable_module_ellswift"
echo " module silentpayments = $enable_module_silentpayments"
echo
echo " asm = $set_asm"
echo " ecmult window size = $set_ecmult_window"

View File

@ -0,0 +1,417 @@
/*************************************************************************
* Written in 2024 by josibake *
* To the extent possible under law, the author(s) have dedicated all *
* copyright and related and neighboring rights to the software in this *
* file to the public domain worldwide. This software is distributed *
* without any warranty. For the CC0 Public Domain Dedication, see *
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
*************************************************************************/
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <secp256k1.h>
#include <secp256k1_extrakeys.h>
#include <secp256k1_silentpayments.h>
#include "examples_util.h"
/* Static data for Bob and Carol's silent payment addresses.
* This consists of a scan key for each and the addresse data for each
*/
static unsigned char smallest_outpoint[36] = {
0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91,
0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe,
0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40,
0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00
};
static unsigned char bob_scan_key[32] = {
0xa8,0x90,0x54,0xc9,0x5b,0xe3,0xc3,0x01,
0x56,0x65,0x74,0xf2,0xaa,0x93,0xad,0xe0,
0x51,0x85,0x09,0x03,0xa6,0x9c,0xbd,0xd1,
0xd4,0x7e,0xae,0x26,0x3d,0x7b,0xc0,0x31
};
static unsigned char bob_spend_pubkey[33] = {
0x02,0xee,0x97,0xdf,0x83,0xb2,0x54,0x6a,
0xf5,0xa7,0xd0,0x62,0x15,0xd9,0x8b,0xcb,
0x63,0x7f,0xe0,0x5d,0xd0,0xfa,0x37,0x3b,
0xd8,0x20,0xe6,0x64,0xd3,0x72,0xde,0x9a,0x01
};
static unsigned char bob_address[2][33] = {
{
0x02,0x15,0x40,0xae,0xa8,0x97,0x54,0x7a,
0xd4,0x39,0xb4,0xe0,0xf6,0x09,0xe5,0xf0,
0xfa,0x63,0xde,0x89,0xab,0x11,0xed,0xe3,
0x1e,0x8c,0xde,0x4b,0xe2,0x19,0x42,0x5f,0x23
},
{
0x02,0x3e,0xff,0xf8,0x18,0x51,0x65,0xea,
0x63,0xa9,0x92,0xb3,0x9f,0x31,0xd8,0xfd,
0x8e,0x0e,0x64,0xae,0xf9,0xd3,0x88,0x07,
0x34,0x97,0x37,0x14,0xa5,0x3d,0x83,0x11,0x8d
}
};
static unsigned char carol_scan_key[32] = {
0x04,0xb2,0xa4,0x11,0x63,0x5c,0x09,0x77,
0x59,0xaa,0xcd,0x0f,0x00,0x5a,0x4c,0x82,
0xc8,0xc9,0x28,0x62,0xc6,0xfc,0x28,0x4b,
0x80,0xb8,0xef,0xeb,0xc2,0x0c,0x3d,0x17
};
static unsigned char carol_address[2][33] = {
{
0x03,0xbb,0xc6,0x3f,0x12,0x74,0x5d,0x3b,
0x9e,0x9d,0x24,0xc6,0xcd,0x7a,0x1e,0xfe,
0xba,0xd0,0xa7,0xf4,0x69,0x23,0x2f,0xbe,
0xcf,0x31,0xfb,0xa7,0xb4,0xf7,0xdd,0xed,0xa8
},
{
0x03,0x81,0xeb,0x9a,0x9a,0x9e,0xc7,0x39,
0xd5,0x27,0xc1,0x63,0x1b,0x31,0xb4,0x21,
0x56,0x6f,0x5c,0x2a,0x47,0xb4,0xab,0x5b,
0x1f,0x6a,0x68,0x6d,0xfb,0x68,0xea,0xb7,0x16
}
};
/* Labels
* The structs and call back function are for demonstration only and not optimized.
* In a production usecase, it is expected that the caller will be using a much more performant
* method for storing and querying labels.
*/
struct label_cache_entry {
secp256k1_pubkey label;
unsigned char label_tweak[32];
};
struct labels_cache {
const secp256k1_context *ctx;
size_t entries_used;
struct label_cache_entry entries[5];
};
const unsigned char* label_lookup(const secp256k1_pubkey* key, const void* cache_ptr) {
const struct labels_cache* cache = (const struct labels_cache*)cache_ptr;
size_t i;
for (i = 0; i < cache->entries_used; i++) {
if (secp256k1_ec_pubkey_cmp(cache->ctx, &cache->entries[i].label, key) == 0) {
return cache->entries[i].label_tweak;
}
}
return NULL;
}
int main(void) {
enum { N_TX_INPUTS = 2, N_TX_OUTPUTS = 3 };
unsigned char randomize[32];
unsigned char xonly_print[32];
secp256k1_xonly_pubkey tx_inputs[N_TX_INPUTS];
secp256k1_xonly_pubkey tx_outputs[N_TX_OUTPUTS];
int ret;
size_t i;
/* Before we can call actual API functions, we need to create a "context". */
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
if (!fill_random(randomize, sizeof(randomize))) {
printf("Failed to generate randomness\n");
return 1;
}
/* Randomizing the context is recommended to protect against side-channel
* leakage See `secp256k1_context_randomize` in secp256k1.h for more
* information about it. This should never fail. */
ret = secp256k1_context_randomize(ctx, randomize);
assert(ret);
/*** Sending ***/
{
secp256k1_keypair sender_seckeys[N_TX_INPUTS];
const secp256k1_keypair *sender_seckey_ptrs[N_TX_INPUTS];
secp256k1_silentpayments_recipient recipients[N_TX_OUTPUTS];
const secp256k1_silentpayments_recipient *recipient_ptrs[N_TX_OUTPUTS];
secp256k1_xonly_pubkey *generated_output_ptrs[N_TX_OUTPUTS];
char* address_amounts[N_TX_OUTPUTS] = {"1.0 BTC", "2.0 BTC", "3.0 BTC"};
unsigned char (*sp_addresses[N_TX_OUTPUTS])[2][33];
/*** Generate private keys for the sender ***
*
* In this example, only taproot inputs are used but the function can be called with
* a mix of taproot seckeys and plain seckeys. Taproot seckeys are passed as keypairs
* to allow the sending function to check if the private keys need to be negated without needing
* to do an expensive pubkey generation. This is not needed for plain seckeys since there is no need
* for negation.
*
* The public key from each input keypair is saved in the `tx_inputs` array. This array will be used
* later in the example to represent the public keys the recipient will extracte from the
* transaction inputs.
*/
for (i = 0; i < 2; i++) {
/* If the secret key is zero or out of range (bigger than secp256k1's
* order), we try to sample a new key. Note that the probability of this
* happening is negligible. */
while (1) {
unsigned char seckey[32];
if (!fill_random(seckey, sizeof(seckey))) {
printf("Failed to generate randomness\n");
return 1;
}
/* Try to create a keypair with a valid context, it should only fail if
* the secret key is zero or out of range. */
if (secp256k1_keypair_create(ctx, &sender_seckeys[i], seckey)) {
sender_seckey_ptrs[i] = &sender_seckeys[i];
ret = secp256k1_keypair_xonly_pub(ctx, &tx_inputs[i], NULL, &sender_seckeys[i]);
assert(ret);
break;
} else {
printf("Failed to create keypair\n");
return 1;
}
}
}
/*** Create the recipient objects ***/
/* Alice is sending to Bob and Carol in this transaction:
*
* 1. One output to Bob's labelled address
* 2. Two outputs for Carol (1.0 and 3.0 bitcoin)
*
* Alice creates the recipient objects and adds the index of the original ordering (i.e. the ordering
* of the `sp_addresses` array) to each object. This index is used to return the generated outputs in
* the original ordering so that Alice can match up the generated outputs with the correct amounts.
*
* Note: to create multiple outputs for Carol, Alice simply passes her silent payment
* address mutltiple times.
*/
sp_addresses[0] = &carol_address; /* : 1.0 BTC */
sp_addresses[1] = &bob_address; /* : 2.0 BTC */
sp_addresses[2] = &carol_address; /* : 3.0 BTC */
for (i = 0; i < N_TX_OUTPUTS; i++) {
ret = secp256k1_ec_pubkey_parse(ctx, &recipients[i].scan_pubkey, (*(sp_addresses[i]))[0], 33);
assert(ret);
ret = secp256k1_ec_pubkey_parse(ctx, &recipients[i].spend_pubkey, (*(sp_addresses[i]))[1], 33);
assert(ret);
recipients[i].index = i;
recipient_ptrs[i] = &recipients[i];
}
for (i = 0; i < N_TX_OUTPUTS; i++) {
generated_output_ptrs[i] = &tx_outputs[i];
}
ret = secp256k1_silentpayments_sender_create_outputs(ctx,
generated_output_ptrs,
recipient_ptrs, N_TX_OUTPUTS,
smallest_outpoint,
sender_seckey_ptrs, N_TX_INPUTS,
NULL, 0
);
assert(ret);
printf("Alice created the following outputs for Bob and Carol: \n");
for (i = 0; i < N_TX_OUTPUTS; i++) {
printf(" ");
printf("%s : ", address_amounts[i]);
secp256k1_xonly_pubkey_serialize(ctx, xonly_print, &tx_outputs[i]);
print_hex(xonly_print, sizeof(xonly_print));
}
/* It's best practice to try to clear secrets from memory after using them.
* This is done because some bugs can allow an attacker to leak memory, for
* example through "out of bounds" array access (see Heartbleed), Or the OS
* swapping them to disk. Hence, we overwrite the secret key buffer with zeros.
*
* Here we are preventing these writes from being optimized out, as any good compiler
* will remove any writes that aren't used. */
for (i = 0; i < N_TX_INPUTS; i++) {
secure_erase(&sender_seckeys[i], sizeof(sender_seckeys[i]));
}
}
/*** Receiving ***/
{
/*** Transaction data ***
*
* Here we create a few global variables to represent the transaction data:
*
* 1. The transaction inputs, `tx_input_ptrs`
* 2. The transaction outputs, `tx_output_ptrs`
*
* These will be used to demonstrate scanning as a full node and scanning as a light client.
*/
const secp256k1_xonly_pubkey *tx_input_ptrs[N_TX_INPUTS];
const secp256k1_xonly_pubkey *tx_output_ptrs[N_TX_OUTPUTS];
unsigned char light_client_data33[33];
for (i = 0; i < N_TX_INPUTS; i++) {
tx_input_ptrs[i] = &tx_inputs[i];
}
for (i = 0; i < N_TX_OUTPUTS; i++) {
tx_output_ptrs[i] = &tx_outputs[i];
}
/*** Scanning with labels as a full node (Bob) ***
*
* Since Bob has access to the full transaction, scanning is simple:
*
* 1. Collect the relevant data from the transaction inputs and call
* `secp256k1_silentpayments_recipient_public_data_create`
* 2. Call `secp256k1_silentpayments_recipient_scan_outputs`
*
* Additionally, since Bob has access to the full transaction outputs when scanning its easy for him
* to scan with labels, as demonstrated below. For efficient scanning, Bob keeps a cache of
* every label he has previously used and uses a callback to check if a potential label exists
* in his cache. Since the labels are created using an incremental integer `m`, if Bob ever
* forgets how many labels he has previously used, he can pregenerate a large number of
* labels (e.g. 0..100_000) and use that while scanning.
*/
{
secp256k1_silentpayments_found_output found_outputs[N_TX_OUTPUTS];
secp256k1_silentpayments_found_output *found_output_ptrs[N_TX_OUTPUTS];
secp256k1_silentpayments_public_data public_data;
secp256k1_pubkey spend_pubkey;
size_t n_found_outputs;
unsigned int m = 1;
struct labels_cache labels_cache;
for (i = 0; i < N_TX_OUTPUTS; i++) {
found_output_ptrs[i] = &found_outputs[i];
}
/* In this contrived example, our label context needs the secp256k1 context because our lookup function
* is calling `secp256k1_ec_pubkey_cmp`. In practice, this context can be anything the lookup function needs.
*/
labels_cache.ctx = ctx;
/* Load Bob's spend public key */
ret = secp256k1_ec_pubkey_parse(ctx, &spend_pubkey, bob_spend_pubkey, 33);
/* Add an entry to the cache. This implies Bob has previously called `secp256k1_silentpayments_recipient_create_labelled_spend_pubkey`
* and used the labelled spend pubkey to encode a labelled silent payments address.
*/
ret = secp256k1_silentpayments_recipient_create_label_tweak(ctx,
&labels_cache.entries[0].label,
labels_cache.entries[0].label_tweak,
bob_scan_key,
m
);
assert(ret);
labels_cache.entries_used = 1;
/* Bob collects the data from the transaction inputs and creates a `secp256k1_silentpayments_public_data` object.
* He uses this for his own scanning and also serializes the `public_data` object to send to light clients. We will
* use this later for Carol, who is scanning as a light client. Note, anyone can create and vend these `public_data`
* objecs, i.e. you don't need to be a silent payments wallet, just someone interested in vending this data to light
* clients, e.g. a wallet service provider. In our example, Bob is scanning for himself but also sharing this data
* with light clients.
*/
ret = secp256k1_silentpayments_recipient_public_data_create(ctx,
&public_data,
smallest_outpoint,
tx_input_ptrs, N_TX_INPUTS,
NULL, 0 /* null because no eligible plain pubkey inputs were found in the tx */
);
assert(ret);
/* Save the `public_data` output. This combines the `input_hash` scalar and public key sum by multiplying `input_hash * A_sum`.
* The output is then saved as a 33 byte compressed key. Storing it this way saves 32 bytes for the light client because
* now it can be send as a 33 byte compressed public key instead of 33 bytes for A_sum and 32 bytes for input_hash.
*/
ret = secp256k1_silentpayments_recipient_public_data_serialize(ctx, light_client_data33, &public_data);
assert(ret);
/* Scan the transaction */
n_found_outputs = 0;
ret = secp256k1_silentpayments_recipient_scan_outputs(ctx,
found_output_ptrs, &n_found_outputs,
tx_output_ptrs, N_TX_OUTPUTS,
bob_scan_key,
&public_data,
&spend_pubkey,
label_lookup, &labels_cache /* NULL, NULL if scanning without labels */
);
assert(n_found_outputs == 1);
printf("\n");
printf("Bob found the following outputs: \n");
for (i = 0; i < n_found_outputs; i++) {
printf(" ");
secp256k1_xonly_pubkey_serialize(ctx, xonly_print, &found_outputs[i].output);
print_hex(xonly_print, sizeof(xonly_print));
}
}
/*** Scanning as a light client (Carol) ***
*
* Being a light client, Carol likely does not have access to the transaction outputs. This
* means she will need to first generate an output, check if it exists in the UTXO set (e.g.
* BIP158 or some other means of querying) and only proceed to check the next output (by
* incrementing `k`) if the first output exists.
*
* For the transaction inputs, she needs the 33 byte compressed public key which is `input_hash * A_sum`.
*/
{
/* In practice, Carol wouldn't know the number of outputs ahead of time but we are cheating here
* to keep the example simple.
*/
unsigned char ser_found_outputs[2][32];
unsigned char shared_secret[33];
secp256k1_pubkey spend_pubkey;
secp256k1_silentpayments_public_data public_data;
size_t n_found_outputs;
/* Load Carol's spend public key */
ret = secp256k1_ec_pubkey_parse(ctx, &spend_pubkey, carol_address[1], 33);
assert(ret);
/* Scan, one output at a time, using the light client data from earlier */
ret = secp256k1_silentpayments_recipient_public_data_parse(ctx, &public_data, light_client_data33);
assert(ret);
ret = secp256k1_silentpayments_recipient_create_shared_secret(ctx,
shared_secret,
carol_scan_key,
&public_data
);
assert(ret);
n_found_outputs = 0;
{
int found = 0;
size_t k = 0;
secp256k1_xonly_pubkey potential_output;
while(1) {
ret = secp256k1_silentpayments_recipient_create_output_pubkey(ctx,
&potential_output,
shared_secret,
&spend_pubkey,
k
);
assert(ret);
/* At this point, we check that the utxo exists with a light client protocol.
* For this example, we'll just iterate through the list of transaction outputs
*/
found = 0;
for (i = 0; i < N_TX_OUTPUTS; i++) {
if (secp256k1_xonly_pubkey_cmp(ctx, &potential_output, &tx_outputs[i]) == 0) {
secp256k1_xonly_pubkey_serialize(ctx, ser_found_outputs[n_found_outputs], &potential_output);
/* If found, create a new output with k++ and check again */
found = 1;
n_found_outputs++;
k++;
break;
}
}
/* If we generate an output and it does not exist in the UTXO set,
* we are done scanning this transaction */
if (!found) {
break;
}
}
}
printf("\n");
printf("Carol found the following outputs: \n");
for (i = 0; i < n_found_outputs; i++) {
printf(" ");
print_hex(ser_found_outputs[i], 32);
}
}
}
/* This will clear everything from the context and free the memory */
secp256k1_context_destroy(ctx);
return 0;
}

View File

@ -474,6 +474,20 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_cmp(
const secp256k1_pubkey *pubkey2
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Sort public keys keys using lexicographic (of compressed serialization) order
*
* Returns: 0 if the arguments are invalid. 1 otherwise.
*
* Args: ctx: pointer to a context object
* In: pubkeys: array of pointers to pubkeys to sort
* n_pubkeys: number of elements in the pubkeys array
*/
SECP256K1_API int secp256k1_ec_pubkey_sort(
const secp256k1_context *ctx,
const secp256k1_pubkey **pubkeys,
size_t n_pubkeys
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
/** Parse an ECDSA signature in compact (64 bytes) format.
*
* Returns: 1 when the signature could be parsed, 0 otherwise.

View File

@ -0,0 +1,332 @@
#ifndef SECP256K1_SILENTPAYMENTS_H
#define SECP256K1_SILENTPAYMENTS_H
#include "secp256k1.h"
#include "secp256k1_extrakeys.h"
#ifdef __cplusplus
extern "C" {
#endif
/* This module provides an implementation for Silent Payments, as specified in BIP352.
* This particularly involves the creation of input tweak data by summing up private
* or public keys and the derivation of a shared secret using Elliptic Curve Diffie-Hellman.
* Combined are either:
* - spender's private keys and receiver's public key (a * B, sender side)
* - spender's public keys and receiver's private key (A * b, receiver side)
* With this result, the necessary key material for ultimately creating/scanning
* or spending Silent Payment outputs can be determined.
*
* Note that this module is _not_ a full implementation of BIP352, as it
* inherently doesn't deal with higher-level concepts like addresses, output
* script types or transactions. The intent is to provide a module for abstracting away
* the elliptic-curve operations required for the protocol. For any wallet software already
* using libsecp256k1, this API should provide all the functions needed for a Silent Payments
* implementation without requiring any further elliptic-curve operations from the wallet.
*/
/* This struct serves as an In param for passing the silent payment address data.
* The index field is for when more than one address is being sent to in a transaction. Index is
* set based on the original ordering of the addresses and used to return the generated outputs
* matching the original ordering. When more than one recipient is used the recipient array
* will be sorted in place as part of generating the outputs, but the generated outputs will be
* outputs will be returned original ordering specified by the index to ensure the caller is able
* to match up the generated outputs to the correct silent payment address (e.g. to be able to
* assign the correct amounts to the correct generatetd outputs in the final transaction).
*/
typedef struct {
secp256k1_pubkey scan_pubkey;
secp256k1_pubkey spend_pubkey;
size_t index;
} secp256k1_silentpayments_recipient;
/** Create Silent Payment outputs for recipient(s).
*
* Given a list of n private keys a_1...a_n (one for each silent payment
* eligible input to spend), a serialized outpoint, and a list of recipients,
* create the taproot outputs:
*
* a_sum = a_1 + a_2 + ... + a_n
* input_hash = hash(outpoint_smallest || (a_sum * G))
* taproot_output = B_spend + hash(a_sum * input_hash * B_scan || k) * G
*
* If necessary, the private keys are negated to enforce the right y-parity.
* For that reason, the private keys have to be passed in via two different parameter
* pairs, depending on whether they seckeys correspond to x-only outputs or not.
*
* Returns: 1 if shared secret creation was successful. 0 if an error occured.
* Args: ctx: pointer to a context object
* Out: generated_outputs: pointer to an array of pointers to xonly pubkeys, one per recipient.
* The order of outputs here matches the original ordering of the
* recipients array.
* In: recipients: pointer to an array of pointers to silent payment recipients,
* where each recipient is a scan public key, a spend public key, and
* an index indicating its position in the original ordering.
* The recipient array will be sorted in place, but generated outputs
* are saved in the `generated_outputs` array to match the ordering
* from the index field. This ensures the caller is able to match the
* generated outputs to the correct silent payment addresses. The same
* recipient can be passed multiple times to create multiple
* outputs for the same recipient.
* n_recipients: the number of recipients. This is equal to the total
* number of outputs to be generated as each recipient may passed
* multiple times to generate multiple outputs for the same recipient
* outpoint_smallest36: serialized smallest outpoint
* taproot_seckeys: pointer to an array of pointers to 32-byte private keys
* of taproot inputs (can be NULL if no private keys of
* taproot inputs are used)
* n_taproot_seckeys: the number of sender's taproot input private keys
* plain_seckeys: pointer to an array of pointers to 32-byte private keys
* of non-taproot inputs (can be NULL if no private keys of
* non-taproot inputs are used)
* n_plain_seckeys: the number of sender's non-taproot input private keys
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_sender_create_outputs(
const secp256k1_context *ctx,
secp256k1_xonly_pubkey **generated_outputs,
const secp256k1_silentpayments_recipient **recipients,
size_t n_recipients,
const unsigned char *outpoint_smallest36,
const secp256k1_keypair * const *taproot_seckeys,
size_t n_taproot_seckeys,
const unsigned char * const *plain_seckeys,
size_t n_plain_seckeys
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5);
/** Create Silent Payment label tweak and label.
*
* Given a recipient's scan private key b_scan and a label integer m, calculate
* the corresponding label tweak and label:
*
* label_tweak = hash(b_scan || m)
* label = label_tweak * G
*
* Returns: 1 if label tweak and label creation was successful. 0 if an error occured.
* Args: ctx: pointer to a context object
* Out: label_tweak: pointer to the resulting label tweak
* In: receiver_scan_seckey: pointer to the receiver's scan private key
* m: label integer (0 is used for change outputs)
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_label_tweak(
const secp256k1_context *ctx,
secp256k1_pubkey *label,
unsigned char *label_tweak32,
const unsigned char *receiver_scan_seckey,
unsigned int m
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
/** Create Silent Payment labelled spend public key.
*
* Given a recipient's spend public key B_spend and a label, calculate
* the corresponding serialized labelled spend public key:
*
* B_m = B_spend + label
*
* The result is used by the recipient to create a Silent Payment address, consisting
* of the serialized and concatenated scan public key and (labelled) spend public key each.
*
* Returns: 1 if labelled spend public key creation was successful. 0 if an error occured.
* Args: ctx: pointer to a context object
* Out: l_addr_spend_pubkey33: pointer to the resulting labelled spend public key
* In: receiver_spend_pubkey: pointer to the receiver's spend pubkey
* label: pointer to the the receiver's label
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_labelled_spend_pubkey(
const secp256k1_context *ctx,
secp256k1_pubkey *labeled_spend_pubkey,
const secp256k1_pubkey *receiver_spend_pubkey,
const secp256k1_pubkey *label
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
/** Opaque data structure that holds silent payments public input data.
*
* This structure does not contain secret data. Guaranteed to be 98 bytes in size. It can be safely
* copied/moved. Created with `secp256k1_silentpayments_compute_public_data`. Can be serialized as
* a compressed public key using `secp256k1_silentpayments_public_data_serialize`. The serialization
* is intended for sending the public input data to light clients. Light clients can use this
* serialization with `secp256k1_silentpayments_public_data_parse`.
*/
typedef struct {
unsigned char data[98];
} secp256k1_silentpayments_public_data;
/** Parse a 33-byte sequence into a silent_payments_public_data object.
*
* Returns: 1 if the data was able to be parsed.
* 0 if the sequence is invalid (e.g. does not represnt a valid public key).
*
* Args: ctx: pointer to a context object.
* Out: public_data: pointer to a silentpayments_public_data object. If 1 is returned, it is set to a
* parsed version of input33.
* In: input33: pointer to a serialized silentpayments_public_data.
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_public_data_parse(
const secp256k1_context *ctx,
secp256k1_silentpayments_public_data *public_data,
const unsigned char *input33
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Serialize a silentpayments_public_data object into a 33-byte sequence.
*
* Returns: 1 always.
*
* Args: ctx: pointer to a context object.
* Out: output33: pointer to a 32-byte array to place the serialized key in.
* In: public_data: pointer to an initialized silentpayments_public_data object.
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_public_data_serialize(
const secp256k1_context *ctx,
unsigned char *output33,
const secp256k1_silentpayments_public_data *public_data
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Compute Silent Payment public data from input public keys and transaction inputs.
*
* Given a list of n public keys A_1...A_n (one for each silent payment
* eligible input to spend) and a serialized outpoint_smallest, compute
* the corresponding input public tweak data:
*
* A_sum = A_1 + A_2 + ... + A_n
* input_hash = hash(outpoint_lowest || A_sum)
*
* The public keys have to be passed in via two different parameter pairs,
* one for regular and one for x-only public keys, in order to avoid the need
* of users converting to a common pubkey format before calling this function.
* The resulting data is can be used for scanning on the recipient side, or stored
* in an index for late use (e.g. wallet rescanning, vending data to light clients).
*
* If calling this function for scanning, the reciever must provide an output param
* for the `input_hash`. If calling this function for simply aggregating the inputs
* for later use, the caller can save the result with `silentpayments_public_data_serialize`.
*
* Returns: 1 if tweak data creation was successful. 0 if an error occured.
* Args: ctx: pointer to a context object
* Out: public_data: pointer to public_data object containing the summed public key and
* input_hash.
* In: outpoint_smallest36: serialized smallest outpoint
* xonly_pubkeys: pointer to an array of pointers to taproot x-only
* public keys (can be NULL if no taproot inputs are used)
* n_xonly_pubkeys: the number of taproot input public keys
* plain_pubkeys: pointer to an array of pointers to non-taproot
* public keys (can be NULL if no non-taproot inputs are used)
* n_plain_pubkeys: the number of non-taproot input public keys
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_public_data_create(
const secp256k1_context *ctx,
secp256k1_silentpayments_public_data *public_data,
const unsigned char *outpoint_smallest36,
const secp256k1_xonly_pubkey * const *xonly_pubkeys,
size_t n_xonly_pubkeys,
const secp256k1_pubkey * const *plain_pubkeys,
size_t n_plain_pubkeys
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Scan for Silent Payment transaction outputs.
*
* Given a input public sum, an input_hash, a recipient's spend public key B_spend, and the relevant transaction
* outputs, scan for outputs belong to the recipient and return the tweak(s) needed for spending
* the output(s). An optional label_lookup callback function and label_context can be passed if the
* recipient uses labels. This allows for checking if a label exists in the recipients label cache
* and retrieving the label tweak during scanning.
*
* Returns: 1 if output scanning was successful. 0 if an error occured.
* Args: ctx: pointer to a context object
* Out: found_outputs: pointer to an array of pointers to found output objects. The found outputs
* array MUST be initialized to be the same length as the tx_outputs array
* n_found_outputs: pointer to an integer indicating the final size of the found outputs array.
* This number represents the number of outputs found while scanning (0 if
* none are found)
* In: tx_outputs: pointer to the tx's x-only public key outputs
* n_tx_outputs: the number of tx_outputs being scanned
* scan_key: pointer to the recipient's scan key
* public_tweak_data: pointer to the input public key sum (optionaly, with the `input_hash`
* multiplied in, see `_recipient_compute_public_data`).
* recipient_spend_pubkey: pointer to the receiver's spend pubkey
* input_hash: pointer to the input_hash. MUST be NULL if the input_hash is already
* multipled into the input public key sum (see `_recipient_compute_public_data`)
* label_lookup: pointer to a callback function for looking up a label value. This fucntion
* takes a label pubkey as an argument and returns a pointer to the label tweak
* if the label exists, otherwise returns a nullptr (NULL if labels are not used)
* label_context: pointer to a label context object (NULL if labels are not used)
*/
typedef const unsigned char* (*secp256k1_silentpayments_label_lookup)(const secp256k1_pubkey*, const void*);
typedef struct {
secp256k1_xonly_pubkey output;
unsigned char tweak[32];
int found_with_label;
secp256k1_pubkey label;
} secp256k1_silentpayments_found_output;
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_scan_outputs(
const secp256k1_context *ctx,
secp256k1_silentpayments_found_output **found_outputs,
size_t *n_found_outputs,
const secp256k1_xonly_pubkey * const *tx_outputs,
size_t n_tx_outputs,
const unsigned char *scan_key,
const secp256k1_silentpayments_public_data *public_data,
const secp256k1_pubkey *receiver_spend_pubkey,
const secp256k1_silentpayments_label_lookup label_lookup,
const void *label_context
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4)
SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
/** Create Silent Payment shared secret.
*
* Given the public input data (A_tweaked = input_hash * A_sum), calculate the shared secret using ECDH:
*
* shared_secret = b_scan * A_tweaked [Recipient, Light client scenario]
*
* The resulting shared secret is needed as input for creating silent payments
* outputs belonging to the same recipient scan public key.
*
* Returns: 1 if shared secret creation was successful. 0 if an error occured.
* Args: ctx: pointer to a context object
* Out: shared_secret33: pointer to the resulting 33-byte shared secret
* In: recipient_scan_key: pointer to the recipient's scan key
* public_data: pointer to the input public key sum, tweaked with the input_hash
* (see `_recipient_compute_public_data`)
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_shared_secret(
const secp256k1_context *ctx,
unsigned char *shared_secret33,
const unsigned char *recipient_scan_key,
const secp256k1_silentpayments_public_data *public_data
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
/** Create Silent Payment output public key.
*
* Given a shared_secret, a public key B_spend, and an output counter k,
* calculate the corresponding output public key:
*
* P_output_xonly = B_spend + hash(shared_secret || ser_32(k)) * G
*
* This function is used by the recipient when scanning for outputs without access to the
* transaction outputs (e.g. using BIP158 block filters).
* When scanning with this function, it is the scanners responsibility to determine if the generated
* output exists in a block before proceeding to the next value of `k`.
*
* Returns: 1 if output creation was successful. 0 if an error occured.
* Args: ctx: pointer to a context object
* Out: P_output_xonly: pointer to the resulting output x-only pubkey
* In: shared_secret33: shared secret, derived from either sender's
* or receiver's perspective with routines from above
* receiver_spend_pubkey: pointer to the receiver's spend pubkey (labelled or unlabelled)
* k: output counter (initially set to 0, must be incremented for each
* additional output created or after each output found when scanning)
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_output_pubkey(
const secp256k1_context *ctx,
secp256k1_xonly_pubkey *P_output_xonly,
const unsigned char *shared_secret33,
const secp256k1_pubkey *receiver_spend_pubkey,
unsigned int k
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
#ifdef __cplusplus
}
#endif
#endif /* SECP256K1_SILENTPAYMENTS_H */

22
src/secp256k1/src/hsort.h Normal file
View File

@ -0,0 +1,22 @@
/***********************************************************************
* Copyright (c) 2021 Russell O'Connor, Jonas Nick *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
#ifndef SECP256K1_HSORT_H
#define SECP256K1_HSORT_H
#include <stddef.h>
#include <string.h>
/* In-place, iterative heapsort with an interface matching glibc's qsort_r. This
* is preferred over standard library implementations because they generally
* make no guarantee about being fast for malicious inputs.
*
* See the qsort_r manpage for a description of the interface.
*/
static void secp256k1_hsort(void *ptr, size_t count, size_t size,
int (*cmp)(const void *, const void *, void *),
void *cmp_data);
#endif

View File

@ -0,0 +1,116 @@
/***********************************************************************
* Copyright (c) 2021 Russell O'Connor, Jonas Nick *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
#ifndef SECP256K1_HSORT_IMPL_H
#define SECP256K1_HSORT_IMPL_H
#include "hsort.h"
/* An array is a heap when, for all non-zero indexes i, the element at index i
* compares as less than or equal to the element at index parent(i) = (i-1)/2.
*/
static SECP256K1_INLINE size_t child1(size_t i) {
VERIFY_CHECK(i <= (SIZE_MAX - 1)/2);
return 2*i + 1;
}
static SECP256K1_INLINE size_t child2(size_t i) {
VERIFY_CHECK(i <= SIZE_MAX/2 - 1);
return child1(i)+1;
}
static SECP256K1_INLINE void heap_swap64(unsigned char *a, size_t i, size_t j, size_t stride) {
unsigned char tmp[64];
VERIFY_CHECK(stride <= 64);
memcpy(tmp, a + i*stride, stride);
memmove(a + i*stride, a + j*stride, stride);
memcpy(a + j*stride, tmp, stride);
}
static SECP256K1_INLINE void heap_swap(unsigned char *a, size_t i, size_t j, size_t stride) {
while (64 < stride) {
heap_swap64(a + (stride - 64), i, j, 64);
stride -= 64;
}
heap_swap64(a, i, j, stride);
}
static SECP256K1_INLINE void heap_down(unsigned char *a, size_t i, size_t heap_size, size_t stride,
int (*cmp)(const void *, const void *, void *), void *cmp_data) {
while (i < heap_size/2) {
VERIFY_CHECK(i <= SIZE_MAX/2 - 1);
/* Proof:
* i < heap_size/2
* i + 1 <= heap_size/2
* 2*i + 2 <= heap_size <= SIZE_MAX
* 2*i <= SIZE_MAX - 2
*/
VERIFY_CHECK(child1(i) < heap_size);
/* Proof:
* i < heap_size/2
* i + 1 <= heap_size/2
* 2*i + 2 <= heap_size
* 2*i + 1 < heap_size
* child1(i) < heap_size
*/
/* Let [x] be notation for the contents at a[x*stride].
*
* If [child1(i)] > [i] and [child2(i)] > [i],
* swap [i] with the larger child to ensure the new parent is larger
* than both children. When [child1(i)] == [child2(i)], swap [i] with
* [child2(i)].
* Else if [child1(i)] > [i], swap [i] with [child1(i)].
* Else if [child2(i)] > [i], swap [i] with [child2(i)].
*/
if (child2(i) < heap_size
&& 0 <= cmp(a + child2(i)*stride, a + child1(i)*stride, cmp_data)) {
if (0 < cmp(a + child2(i)*stride, a + i*stride, cmp_data)) {
heap_swap(a, i, child2(i), stride);
i = child2(i);
} else {
/* At this point we have [child2(i)] >= [child1(i)] and we have
* [child2(i)] <= [i], and thus [child1(i)] <= [i] which means
* that the next comparison can be skipped. */
return;
}
} else if (0 < cmp(a + child1(i)*stride, a + i*stride, cmp_data)) {
heap_swap(a, i, child1(i), stride);
i = child1(i);
} else {
return;
}
}
/* heap_size/2 <= i
* heap_size/2 < i + 1
* heap_size < 2*i + 2
* heap_size <= 2*i + 1
* heap_size <= child1(i)
* Thus child1(i) and child2(i) are now out of bounds and we are at a leaf.
*/
}
/* In-place heap sort. */
static void secp256k1_hsort(void *ptr, size_t count, size_t size,
int (*cmp)(const void *, const void *, void *),
void *cmp_data ) {
size_t i;
for(i = count/2; 0 < i; --i) {
heap_down(ptr, i-1, count, size, cmp, cmp_data);
}
for(i = count; 1 < i; --i) {
/* Extract the largest value from the heap */
heap_swap(ptr, 0, i-1, size);
/* Repair the heap condition */
heap_down(ptr, 0, i-1, size, cmp, cmp_data);
}
}
#endif

View File

@ -1,4 +1,4 @@
include_HEADERS += include/secp256k1_extrakeys.h
noinst_HEADERS += src/modules/extrakeys/tests_impl.h
noinst_HEADERS += src/modules/extrakeys/tests_exhaustive_impl.h
noinst_HEADERS += src/modules/extrakeys/main_impl.h
noinst_HEADERS += src/modules/extrakeys/main_impl.h

View File

@ -0,0 +1,4 @@
include_HEADERS += include/secp256k1_silentpayments.h
noinst_HEADERS += src/modules/silentpayments/main_impl.h
noinst_HEADERS += src/modules/silentpayments/tests_impl.h
noinst_HEADERS += src/modules/silentpayments/vectors.h

View File

@ -0,0 +1,590 @@
/***********************************************************************
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
#ifndef SECP256K1_MODULE_SILENTPAYMENTS_MAIN_H
#define SECP256K1_MODULE_SILENTPAYMENTS_MAIN_H
#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_ecdh.h"
#include "../../../include/secp256k1_extrakeys.h"
#include "../../../include/secp256k1_silentpayments.h"
/** Sort an array of silent payment recipients. This is used to group recipients by scan pubkey to
* ensure the correct values of k are used when creating multiple outputs for a recipient. */
static int secp256k1_silentpayments_recipient_sort_cmp(const void* pk1, const void* pk2, void *cmp_data) {
return secp256k1_ec_pubkey_cmp(
((secp256k1_ec_pubkey_sort_cmp_data*)cmp_data)->ctx,
&(*(const secp256k1_silentpayments_recipient **)pk1)->scan_pubkey,
&(*(const secp256k1_silentpayments_recipient **)pk2)->scan_pubkey
);
}
int secp256k1_silentpayments_recipient_sort(const secp256k1_context* ctx, const secp256k1_silentpayments_recipient **recipients, size_t n_recipients) {
secp256k1_ec_pubkey_sort_cmp_data cmp_data;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(recipients != NULL);
cmp_data.ctx = ctx;
/* Suppress wrong warning (fixed in MSVC 19.33) */
#if defined(_MSC_VER) && (_MSC_VER < 1933)
#pragma warning(push)
#pragma warning(disable: 4090)
#endif
secp256k1_hsort(recipients, n_recipients, sizeof(*recipients), secp256k1_silentpayments_recipient_sort_cmp, &cmp_data);
#if defined(_MSC_VER) && (_MSC_VER < 1933)
#pragma warning(pop)
#endif
return 1;
}
/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/Inputs". */
static void secp256k1_silentpayments_sha256_init_inputs(secp256k1_sha256* hash) {
secp256k1_sha256_initialize(hash);
hash->s[0] = 0xd4143ffcul;
hash->s[1] = 0x012ea4b5ul;
hash->s[2] = 0x36e21c8ful;
hash->s[3] = 0xf7ec7b54ul;
hash->s[4] = 0x4dd4e2acul;
hash->s[5] = 0x9bcaa0a4ul;
hash->s[6] = 0xe244899bul;
hash->s[7] = 0xcd06903eul;
hash->bytes = 64;
}
static void secp256k1_silentpayments_calculate_input_hash(unsigned char *input_hash, const unsigned char *outpoint_smallest36, secp256k1_ge *pubkey_sum) {
secp256k1_sha256 hash;
unsigned char pubkey_sum_ser[33];
size_t ser_size;
int ser_ret;
secp256k1_silentpayments_sha256_init_inputs(&hash);
secp256k1_sha256_write(&hash, outpoint_smallest36, 36);
ser_ret = secp256k1_eckey_pubkey_serialize(pubkey_sum, pubkey_sum_ser, &ser_size, 1);
VERIFY_CHECK(ser_ret && ser_size == sizeof(pubkey_sum_ser));
(void)ser_ret;
secp256k1_sha256_write(&hash, pubkey_sum_ser, sizeof(pubkey_sum_ser));
secp256k1_sha256_finalize(&hash, input_hash);
}
/* secp256k1_ecdh expects a hash function to be passed in or uses its default
* hashing function. We don't want to hash the ECDH result yet (it will be
* hashed later with a counter `k`), so we define a custom function which simply
* returns the pubkey without hashing.
*/
static int secp256k1_silentpayments_ecdh_return_pubkey(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data) {
secp256k1_ge point;
secp256k1_fe x, y;
size_t ser_size;
int ser_ret;
(void)data;
/* Parse point as group element */
if (!secp256k1_fe_set_b32_limit(&x, x32) || !secp256k1_fe_set_b32_limit(&y, y32)) {
return 0;
}
secp256k1_ge_set_xy(&point, &x, &y);
/* Serialize as compressed pubkey */
ser_ret = secp256k1_eckey_pubkey_serialize(&point, output, &ser_size, 1);
VERIFY_CHECK(ser_ret && ser_size == 33);
(void)ser_ret;
return 1;
}
int secp256k1_silentpayments_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, const unsigned char *secret_component, const secp256k1_pubkey *public_component, unsigned char *input_hash) {
unsigned char tweaked_secret_component[32];
/* Sanity check inputs */
ARG_CHECK(shared_secret33 != NULL);
memset(shared_secret33, 0, 33);
ARG_CHECK(public_component != NULL);
ARG_CHECK(secret_component != NULL);
/* Tweak secret component with input hash, if available */
memcpy(tweaked_secret_component, secret_component, 32);
if (input_hash != NULL) {
if (!secp256k1_ec_seckey_tweak_mul(ctx, tweaked_secret_component, input_hash)) {
return 0;
}
}
/* Compute shared_secret = tweaked_secret_component * Public_component */
if (!secp256k1_ecdh(ctx, shared_secret33, public_component, tweaked_secret_component, secp256k1_silentpayments_ecdh_return_pubkey, NULL)) {
return 0;
}
return 1;
}
/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/SharedSecret". */
static void secp256k1_silentpayments_sha256_init_sharedsecret(secp256k1_sha256* hash) {
secp256k1_sha256_initialize(hash);
hash->s[0] = 0x88831537ul;
hash->s[1] = 0x5127079bul;
hash->s[2] = 0x69c2137bul;
hash->s[3] = 0xab0303e6ul;
hash->s[4] = 0x98fa21faul;
hash->s[5] = 0x4a888523ul;
hash->s[6] = 0xbd99daabul;
hash->s[7] = 0xf25e5e0aul;
hash->bytes = 64;
}
static void secp256k1_silentpayments_create_t_k(secp256k1_scalar *t_k_scalar, const unsigned char *shared_secret33, unsigned int k) {
secp256k1_sha256 hash;
unsigned char hash_ser[32];
unsigned char k_serialized[4];
/* Compute t_k = hash(shared_secret || ser_32(k)) [sha256 with tag "BIP0352/SharedSecret"] */
secp256k1_silentpayments_sha256_init_sharedsecret(&hash);
secp256k1_sha256_write(&hash, shared_secret33, 33);
secp256k1_write_be32(k_serialized, k);
secp256k1_sha256_write(&hash, k_serialized, sizeof(k_serialized));
secp256k1_sha256_finalize(&hash, hash_ser);
secp256k1_scalar_set_b32(t_k_scalar, hash_ser, NULL);
}
int secp256k1_silentpayments_create_output_pubkey(const secp256k1_context *ctx, secp256k1_xonly_pubkey *P_output_xonly, const unsigned char *shared_secret33, const secp256k1_pubkey *receiver_spend_pubkey, unsigned int k) {
secp256k1_ge P_output_ge;
secp256k1_scalar t_k_scalar;
/* Sanity check inputs */
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(P_output_xonly != NULL);
ARG_CHECK(shared_secret33 != NULL);
ARG_CHECK(receiver_spend_pubkey != NULL);
/* Calculate and return P_output_xonly = B_spend + t_k * G */
secp256k1_silentpayments_create_t_k(&t_k_scalar, shared_secret33, k);
secp256k1_pubkey_load(ctx, &P_output_ge, receiver_spend_pubkey);
if (!secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar)) {
return 0;
}
secp256k1_xonly_pubkey_save(P_output_xonly, &P_output_ge);
return 1;
}
int secp256k1_silentpayments_sender_create_outputs(
const secp256k1_context *ctx,
secp256k1_xonly_pubkey **generated_outputs,
const secp256k1_silentpayments_recipient **recipients,
size_t n_recipients,
const unsigned char *outpoint_smallest36,
const secp256k1_keypair * const *taproot_seckeys,
size_t n_taproot_seckeys,
const unsigned char * const *plain_seckeys,
size_t n_plain_seckeys
) {
size_t i, k;
secp256k1_scalar a_sum_scalar, addend;
secp256k1_ge A_sum_ge;
secp256k1_gej A_sum_gej;
unsigned char input_hash[32];
unsigned char a_sum[32];
unsigned char shared_secret[33];
secp256k1_silentpayments_recipient last_recipient;
/* Sanity check inputs. */
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(recipients != NULL);
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
ARG_CHECK(plain_seckeys == NULL || n_plain_seckeys >= 1);
ARG_CHECK(taproot_seckeys == NULL || n_taproot_seckeys >= 1);
ARG_CHECK((plain_seckeys != NULL) || (taproot_seckeys != NULL));
ARG_CHECK((n_plain_seckeys + n_taproot_seckeys) >= 1);
ARG_CHECK(outpoint_smallest36 != NULL);
/* ensure the index field is set correctly */
for (i = 0; i < n_recipients; i++) {
ARG_CHECK(recipients[i]->index == i);
}
/* Compute input private keys sum: a_sum = a_1 + a_2 + ... + a_n */
a_sum_scalar = secp256k1_scalar_zero;
for (i = 0; i < n_plain_seckeys; i++) {
int ret = secp256k1_scalar_set_b32_seckey(&addend, plain_seckeys[i]);
VERIFY_CHECK(ret);
(void)ret;
secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend);
VERIFY_CHECK(!secp256k1_scalar_is_zero(&a_sum_scalar));
}
/* private keys used for taproot outputs have to be negated if they resulted in an odd point */
for (i = 0; i < n_taproot_seckeys; i++) {
secp256k1_ge addend_point;
int ret = secp256k1_keypair_load(ctx, &addend, &addend_point, taproot_seckeys[i]);
VERIFY_CHECK(ret);
(void)ret;
/* declassify addend_point to allow using it as a branch point (this is fine because addend_point is not a secret) */
secp256k1_declassify(ctx, &addend_point, sizeof(addend_point));
secp256k1_fe_normalize_var(&addend_point.y);
if (secp256k1_fe_is_odd(&addend_point.y)) {
secp256k1_scalar_negate(&addend, &addend);
}
secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend);
VERIFY_CHECK(!secp256k1_scalar_is_zero(&a_sum_scalar));
}
if (secp256k1_scalar_is_zero(&a_sum_scalar)) {
/* TODO: do we need a special error return code for this case? */
return 0;
}
secp256k1_scalar_get_b32(a_sum, &a_sum_scalar);
/* Compute input_hash = hash(outpoint_L || (a_sum * G)) */
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &A_sum_gej, &a_sum_scalar);
secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej);
secp256k1_silentpayments_calculate_input_hash(input_hash, outpoint_smallest36, &A_sum_ge);
secp256k1_silentpayments_recipient_sort(ctx, recipients, n_recipients);
last_recipient = *recipients[0];
k = 0;
for (i = 0; i < n_recipients; i++) {
if ((secp256k1_ec_pubkey_cmp(ctx, &last_recipient.scan_pubkey, &recipients[i]->scan_pubkey) != 0) || (i == 0)) {
/* if we are on a different scan pubkey, its time to recreate the the shared secret and reset k to 0 */
if (!secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, a_sum, &recipients[i]->scan_pubkey, input_hash)) {
return 0;
}
k = 0;
}
if (!secp256k1_silentpayments_create_output_pubkey(ctx, generated_outputs[recipients[i]->index], shared_secret, &recipients[i]->spend_pubkey, k)) {
return 0;
}
k++;
last_recipient = *recipients[i];
}
return 1;
}
/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/Label". */
static void secp256k1_silentpayments_sha256_init_label(secp256k1_sha256* hash) {
secp256k1_sha256_initialize(hash);
hash->s[0] = 0x26b95d63ul;
hash->s[1] = 0x8bf1b740ul;
hash->s[2] = 0x10a5986ful;
hash->s[3] = 0x06a387a5ul;
hash->s[4] = 0x2d1c1c30ul;
hash->s[5] = 0xd035951aul;
hash->s[6] = 0x2d7f0f96ul;
hash->s[7] = 0x29e3e0dbul;
hash->bytes = 64;
}
int secp256k1_silentpayments_recipient_create_label_tweak(const secp256k1_context *ctx, secp256k1_pubkey *label, unsigned char *label_tweak32, const unsigned char *receiver_scan_seckey, unsigned int m) {
secp256k1_sha256 hash;
unsigned char m_serialized[4];
/* Sanity check inputs. */
VERIFY_CHECK(ctx != NULL);
(void)ctx;
VERIFY_CHECK(label != NULL);
VERIFY_CHECK(label_tweak32 != NULL);
VERIFY_CHECK(receiver_scan_seckey != NULL);
/* Compute label_tweak = hash(ser_256(b_scan) || ser_32(m)) [sha256 with tag "BIP0352/Label"] */
secp256k1_silentpayments_sha256_init_label(&hash);
secp256k1_sha256_write(&hash, receiver_scan_seckey, 32);
secp256k1_write_be32(m_serialized, m);
secp256k1_sha256_write(&hash, m_serialized, sizeof(m_serialized));
secp256k1_sha256_finalize(&hash, label_tweak32);
/* Compute label = label_tweak * G */
if (!secp256k1_ec_pubkey_create(ctx, label, label_tweak32)) {
return 0;
}
return 1;
}
int secp256k1_silentpayments_recipient_create_labelled_spend_pubkey(const secp256k1_context *ctx, secp256k1_pubkey *labeled_spend_pubkey, const secp256k1_pubkey *receiver_spend_pubkey, const secp256k1_pubkey *label) {
secp256k1_ge B_m, label_addend;
secp256k1_gej result_gej;
secp256k1_ge result_ge;
/* Sanity check inputs. */
VERIFY_CHECK(ctx != NULL);
VERIFY_CHECK(labeled_spend_pubkey != NULL);
VERIFY_CHECK(receiver_spend_pubkey != NULL);
VERIFY_CHECK(label != NULL);
/* Calculate B_m = B_spend + label */
secp256k1_pubkey_load(ctx, &B_m, receiver_spend_pubkey);
secp256k1_pubkey_load(ctx, &label_addend, label);
secp256k1_gej_set_ge(&result_gej, &B_m);
secp256k1_gej_add_ge_var(&result_gej, &result_gej, &label_addend, NULL);
/* Serialize B_m */
secp256k1_ge_set_gej(&result_ge, &result_gej);
secp256k1_pubkey_save(labeled_spend_pubkey, &result_ge);
return 1;
}
int secp256k1_silentpayments_recipient_public_data_create(
const secp256k1_context *ctx,
secp256k1_silentpayments_public_data *public_data,
const unsigned char *outpoint_smallest36,
const secp256k1_xonly_pubkey * const *xonly_pubkeys,
size_t n_xonly_pubkeys,
const secp256k1_pubkey * const *plain_pubkeys,
size_t n_plain_pubkeys
) {
size_t i;
size_t pubkeylen = 65;
secp256k1_pubkey A_sum;
secp256k1_ge A_sum_ge, addend;
secp256k1_gej A_sum_gej;
unsigned char input_hash_local[32];
/* Sanity check inputs */
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(public_data != NULL);
ARG_CHECK(plain_pubkeys == NULL || n_plain_pubkeys >= 1);
ARG_CHECK(xonly_pubkeys == NULL || n_xonly_pubkeys >= 1);
ARG_CHECK((plain_pubkeys != NULL) || (xonly_pubkeys != NULL));
ARG_CHECK((n_plain_pubkeys + n_xonly_pubkeys) >= 1);
ARG_CHECK(outpoint_smallest36 != NULL);
memset(input_hash_local, 0, 32);
/* Compute input public keys sum: A_sum = A_1 + A_2 + ... + A_n */
secp256k1_gej_set_infinity(&A_sum_gej);
for (i = 0; i < n_plain_pubkeys; i++) {
secp256k1_pubkey_load(ctx, &addend, plain_pubkeys[i]);
secp256k1_gej_add_ge(&A_sum_gej, &A_sum_gej, &addend);
}
for (i = 0; i < n_xonly_pubkeys; i++) {
secp256k1_xonly_pubkey_load(ctx, &addend, xonly_pubkeys[i]);
secp256k1_gej_add_ge(&A_sum_gej, &A_sum_gej, &addend);
}
if (secp256k1_gej_is_infinity(&A_sum_gej)) {
/* TODO: do we need a special error return code for this case? */
return 0;
}
secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej);
/* Compute input_hash = hash(outpoint_L || A_sum) */
secp256k1_silentpayments_calculate_input_hash(input_hash_local, outpoint_smallest36, &A_sum_ge);
secp256k1_pubkey_save(&A_sum, &A_sum_ge);
/* serialize the public_data struct */
public_data->data[0] = 0;
secp256k1_ec_pubkey_serialize(ctx, &public_data->data[1], &pubkeylen, &A_sum, SECP256K1_EC_UNCOMPRESSED);
memcpy(&public_data->data[1 + pubkeylen], input_hash_local, 32);
return 1;
}
int secp256k1_silentpayments_recipient_public_data_load(const secp256k1_context *ctx, secp256k1_pubkey *pubkey, unsigned char *input_hash, const secp256k1_silentpayments_public_data *public_data) {
int combined;
size_t pubkeylen = 65;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(pubkey != NULL);
ARG_CHECK(public_data != NULL);
combined = (int)public_data->data[0];
ARG_CHECK(combined == 0 || combined == 1);
if (combined) {
ARG_CHECK(combined == 1 && input_hash == NULL);
} else {
ARG_CHECK(combined == 0 && input_hash != NULL);
memcpy(input_hash, &public_data->data[1 + pubkeylen], 32);
}
if (!secp256k1_ec_pubkey_parse(ctx, pubkey, &public_data->data[1], pubkeylen)) {
return 0;
}
return 1;
}
int secp256k1_silentpayments_recipient_public_data_serialize(const secp256k1_context *ctx, unsigned char *output33, const secp256k1_silentpayments_public_data *public_data) {
secp256k1_pubkey pubkey;
unsigned char input_hash[32];
size_t pubkeylen = 33;
ARG_CHECK(public_data->data[0] == 0);
if (!secp256k1_silentpayments_recipient_public_data_load(ctx, &pubkey, input_hash, public_data)) {
return 0;
}
if (!secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, input_hash)) {
return 0;
}
secp256k1_ec_pubkey_serialize(ctx, output33, &pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED);
return 1;
}
int secp256k1_silentpayments_recipient_public_data_parse(const secp256k1_context *ctx, secp256k1_silentpayments_public_data *public_data, const unsigned char *input33) {
size_t inputlen = 33;
size_t pubkeylen = 65;
secp256k1_pubkey pubkey;
ARG_CHECK(public_data != NULL);
ARG_CHECK(input33 != NULL);
if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, input33, inputlen)) {
return 0;
}
public_data->data[0] = 1;
secp256k1_ec_pubkey_serialize(ctx, &public_data->data[1], &pubkeylen, &pubkey, SECP256K1_EC_UNCOMPRESSED);
memset(&public_data->data[1 + pubkeylen], 0, 32);
return 1;
}
int secp256k1_silentpayments_recipient_scan_outputs(
const secp256k1_context *ctx,
secp256k1_silentpayments_found_output **found_outputs, size_t *n_found_outputs,
const secp256k1_xonly_pubkey * const *tx_outputs, size_t n_tx_outputs,
const unsigned char *scan_key,
const secp256k1_silentpayments_public_data *public_data,
const secp256k1_pubkey *receiver_spend_pubkey,
const secp256k1_silentpayments_label_lookup label_lookup,
const void *label_context
) {
secp256k1_scalar t_k_scalar;
secp256k1_ge receiver_spend_pubkey_ge;
secp256k1_xonly_pubkey P_output_xonly;
secp256k1_pubkey A_sum;
unsigned char shared_secret[33];
size_t i, k, n_found;
int found, combined;
/* Sanity check inputs */
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(found_outputs != NULL);
ARG_CHECK(tx_outputs != NULL);
ARG_CHECK(scan_key != NULL);
ARG_CHECK(public_data != NULL);
combined = (int)public_data->data[0];
{
unsigned char input_hash[32];
unsigned char *input_hash_ptr;
if (combined) {
input_hash_ptr = NULL;
} else {
memset(input_hash, 0, 32);
input_hash_ptr = input_hash;
}
if (!secp256k1_silentpayments_recipient_public_data_load(ctx, &A_sum, input_hash_ptr, public_data)) {
return 0;
}
secp256k1_pubkey_load(ctx, &receiver_spend_pubkey_ge, receiver_spend_pubkey);
if (!secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, scan_key, &A_sum, input_hash_ptr)) {
return 0;
}
}
n_found = 0;
k = 0;
while (1) {
secp256k1_ge P_output_ge = receiver_spend_pubkey_ge;
/* Calculate t_k = hash(shared_secret || ser_32(k)) */
secp256k1_silentpayments_create_t_k(&t_k_scalar, shared_secret, k);
/* Calculate P_output = B_spend + t_k * G */
if (!secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar)) {
return 0;
}
/* If the calculated output matches the one from the tx, we have a direct match and can
* return without labels calculation (one of the two would result in point of infinity) */
secp256k1_xonly_pubkey_save(&P_output_xonly, &P_output_ge);
found = 0;
for (i = 0; i < n_tx_outputs; i++) {
if (secp256k1_xonly_pubkey_cmp(ctx, &P_output_xonly, tx_outputs[i]) == 0) {
secp256k1_xonly_pubkey_save(&found_outputs[n_found]->output, &P_output_ge);
secp256k1_scalar_get_b32(found_outputs[n_found]->tweak, &t_k_scalar);
found_outputs[n_found]->found_with_label = 0;
secp256k1_pubkey_save(&found_outputs[n_found]->label, &P_output_ge);
found = 1;
n_found++;
k++;
break;
}
/* If desired, also calculate label candidates */
if (label_lookup != NULL) {
secp256k1_pubkey label_pubkey;
secp256k1_ge P_output_negated_ge, tx_output_ge;
secp256k1_ge label_ge;
secp256k1_gej label_gej;
const unsigned char *label_tweak;
/* Calculate negated P_output (common addend) first */
secp256k1_ge_neg(&P_output_negated_ge, &P_output_ge);
/* Calculate first scan label candidate: label1 = tx_output - P_output */
secp256k1_xonly_pubkey_load(ctx, &tx_output_ge, tx_outputs[i]);
secp256k1_gej_set_ge(&label_gej, &tx_output_ge);
secp256k1_gej_add_ge_var(&label_gej, &label_gej, &P_output_negated_ge, NULL);
secp256k1_ge_set_gej(&label_ge, &label_gej);
secp256k1_pubkey_save(&label_pubkey, &label_ge);
label_tweak = label_lookup(&label_pubkey, label_context);
if (label_tweak != NULL) {
secp256k1_xonly_pubkey_save(&found_outputs[n_found]->output, &tx_output_ge);
found_outputs[n_found]->found_with_label = 1;
secp256k1_pubkey_save(&found_outputs[n_found]->label, &label_ge);
secp256k1_scalar_get_b32(found_outputs[n_found]->tweak, &t_k_scalar);
if (!secp256k1_ec_seckey_tweak_add(ctx, found_outputs[n_found]->tweak, label_tweak)) {
return 0;
}
found = 1;
n_found++;
k++;
break;
}
/* Calculate second scan label candidate: label2 = -tx_output - P_output */
secp256k1_gej_set_ge(&label_gej, &tx_output_ge);
secp256k1_gej_neg(&label_gej, &label_gej);
secp256k1_gej_add_ge_var(&label_gej, &label_gej, &P_output_negated_ge, NULL);
secp256k1_ge_set_gej(&label_ge, &label_gej);
secp256k1_pubkey_save(&label_pubkey, &label_ge);
label_tweak = label_lookup(&label_pubkey, label_context);
if (label_tweak != NULL) {
secp256k1_xonly_pubkey_save(&found_outputs[n_found]->output, &tx_output_ge);
found_outputs[n_found]->found_with_label = 1;
secp256k1_pubkey_save(&found_outputs[n_found]->label, &label_ge);
secp256k1_scalar_get_b32(found_outputs[n_found]->tweak, &t_k_scalar);
if (!secp256k1_ec_seckey_tweak_add(ctx, found_outputs[n_found]->tweak, label_tweak)) {
return 0;
}
found = 1;
n_found++;
k++;
break;
}
}
}
if (!found) {
break;
}
}
*n_found_outputs = n_found;
return 1;
}
int secp256k1_silentpayments_recipient_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, const unsigned char *recipient_scan_key, const secp256k1_silentpayments_public_data *public_data) {
secp256k1_pubkey A_tweaked;
/* Sanity check inputs */
ARG_CHECK(shared_secret33 != NULL);
ARG_CHECK(recipient_scan_key != NULL);
ARG_CHECK(public_data != NULL);
ARG_CHECK(public_data->data[0] == 1);
if (!secp256k1_silentpayments_recipient_public_data_load(ctx, &A_tweaked, NULL, public_data)) {
return 0;
}
return secp256k1_silentpayments_create_shared_secret(ctx, shared_secret33, recipient_scan_key, &A_tweaked, NULL);
}
int secp256k1_silentpayments_recipient_create_output_pubkey(const secp256k1_context *ctx, secp256k1_xonly_pubkey *P_output_xonly, const unsigned char *shared_secret33, const secp256k1_pubkey *receiver_spend_pubkey, unsigned int k)
{
return secp256k1_silentpayments_create_output_pubkey(ctx, P_output_xonly, shared_secret33, receiver_spend_pubkey, k);
}
#endif

View File

@ -0,0 +1,260 @@
/***********************************************************************
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
#ifndef SECP256K1_MODULE_SILENTPAYMENTS_TESTS_H
#define SECP256K1_MODULE_SILENTPAYMENTS_TESTS_H
#include "../../../include/secp256k1_silentpayments.h"
#include "../../../src/modules/silentpayments/vectors.h"
#include "../../../examples/examples_util.h"
#include "assert.h"
struct label_cache_entry {
secp256k1_pubkey label;
unsigned char label_tweak[32];
};
struct labels_cache {
const secp256k1_context *ctx;
size_t entries_used;
struct label_cache_entry entries[10];
};
struct labels_cache labels_cache;
const unsigned char* label_lookup(const secp256k1_pubkey* key, const void* cache_ptr) {
const struct labels_cache* cache = (const struct labels_cache*)cache_ptr;
size_t i;
for (i = 0; i < cache->entries_used; i++) {
if (secp256k1_ec_pubkey_cmp(cache->ctx, &cache->entries[i].label, key) == 0) {
return cache->entries[i].label_tweak;
}
}
return NULL;
}
void run_silentpayments_test_vector_send(const struct bip352_test_vector *test) {
secp256k1_silentpayments_recipient recipients[MAX_OUTPUTS_PER_TEST_CASE];
const secp256k1_silentpayments_recipient *recipient_ptrs[MAX_OUTPUTS_PER_TEST_CASE];
secp256k1_xonly_pubkey generated_outputs[MAX_OUTPUTS_PER_TEST_CASE];
secp256k1_xonly_pubkey *generated_output_ptrs[MAX_OUTPUTS_PER_TEST_CASE];
secp256k1_keypair taproot_keypairs[MAX_INPUTS_PER_TEST_CASE];
secp256k1_keypair const *taproot_keypair_ptrs[MAX_INPUTS_PER_TEST_CASE];
unsigned char const *plain_seckeys[MAX_INPUTS_PER_TEST_CASE];
unsigned char created_output[32];
size_t i, j;
int match;
/* Check that sender creates expected outputs */
for (i = 0; i < test->num_outputs; i++) {
CHECK(secp256k1_ec_pubkey_parse(CTX, &recipients[i].scan_pubkey, test->recipient_pubkeys[i].scan_pubkey, 33));
CHECK(secp256k1_ec_pubkey_parse(CTX, &recipients[i].spend_pubkey, test->recipient_pubkeys[i].spend_pubkey, 33));
recipients[i].index = i;
recipient_ptrs[i] = &recipients[i];
generated_output_ptrs[i] = &generated_outputs[i];
}
for (i = 0; i < test->num_plain_inputs; i++) {
plain_seckeys[i] = test->plain_seckeys[i];
}
for (i = 0; i < test->num_taproot_inputs; i++) {
int ret = secp256k1_keypair_create(CTX, &taproot_keypairs[i], test->taproot_seckeys[i]);
assert(ret);
taproot_keypair_ptrs[i] = &taproot_keypairs[i];
}
CHECK(secp256k1_silentpayments_sender_create_outputs(CTX,
generated_output_ptrs,
recipient_ptrs,
test->num_outputs,
test->outpoint_smallest,
test->num_taproot_inputs > 0 ? taproot_keypair_ptrs : NULL, test->num_taproot_inputs,
test->num_plain_inputs > 0 ? plain_seckeys : NULL, test->num_plain_inputs
));
for (i = 0; i < test->num_outputs; i++) {
CHECK(secp256k1_xonly_pubkey_serialize(CTX, created_output, &generated_outputs[i]));
match = 0;
/* Loop over both lists to ensure tests don't fail due to different orderings of outputs */
for (j = 0; j < test->num_recipient_outputs; j++) {
if (secp256k1_memcmp_var(created_output, test->recipient_outputs[j], 32) == 0) {
match = 1;
break;
}
}
CHECK(match);
}
}
void run_silentpayments_test_vector_receive(const struct bip352_test_vector *test) {
secp256k1_pubkey plain_pubkeys_objs[MAX_INPUTS_PER_TEST_CASE];
secp256k1_xonly_pubkey xonly_pubkeys_objs[MAX_INPUTS_PER_TEST_CASE];
secp256k1_xonly_pubkey tx_output_objs[MAX_OUTPUTS_PER_TEST_CASE];
secp256k1_silentpayments_found_output found_output_objs[MAX_OUTPUTS_PER_TEST_CASE];
secp256k1_pubkey const *plain_pubkeys[MAX_INPUTS_PER_TEST_CASE];
secp256k1_xonly_pubkey const *xonly_pubkeys[MAX_INPUTS_PER_TEST_CASE];
secp256k1_xonly_pubkey const *tx_outputs[MAX_OUTPUTS_PER_TEST_CASE];
secp256k1_silentpayments_found_output *found_outputs[MAX_OUTPUTS_PER_TEST_CASE];
unsigned char found_outputs_light_client[MAX_OUTPUTS_PER_TEST_CASE][32];
secp256k1_pubkey receiver_scan_pubkey;
secp256k1_pubkey receiver_spend_pubkey;
size_t i,j;
int match;
size_t n_found = 0;
unsigned char found_output[32];
unsigned char found_signatures[10][64];
secp256k1_silentpayments_public_data public_data, public_data_index;
unsigned char shared_secret_lightclient[33];
unsigned char light_client_data[33];
/* prepare the inputs */
{
for (i = 0; i < test->num_plain_inputs; i++) {
CHECK(secp256k1_ec_pubkey_parse(CTX, &plain_pubkeys_objs[i], test->plain_pubkeys[i], 33));
plain_pubkeys[i] = &plain_pubkeys_objs[i];
}
for (i = 0; i < test->num_taproot_inputs; i++) {
CHECK(secp256k1_xonly_pubkey_parse(CTX, &xonly_pubkeys_objs[i], test->xonly_pubkeys[i]));
xonly_pubkeys[i] = &xonly_pubkeys_objs[i];
}
CHECK(secp256k1_silentpayments_recipient_public_data_create(CTX, &public_data,
test->outpoint_smallest,
test->num_taproot_inputs > 0 ? xonly_pubkeys : NULL, test->num_taproot_inputs,
test->num_plain_inputs > 0 ? plain_pubkeys : NULL, test->num_plain_inputs
));
}
/* prepare the outputs */
{
for (i = 0; i < test->num_to_scan_outputs; i++) {
CHECK(secp256k1_xonly_pubkey_parse(CTX, &tx_output_objs[i], test->to_scan_outputs[i]));
tx_outputs[i] = &tx_output_objs[i];
}
for (i = 0; i < test->num_found_output_pubkeys; i++) {
found_outputs[i] = &found_output_objs[i];
}
}
/* scan / spend pubkeys are not in the given data of the receiver part, so let's compute them */
CHECK(secp256k1_ec_pubkey_create(CTX, &receiver_scan_pubkey, test->scan_seckey));
CHECK(secp256k1_ec_pubkey_create(CTX, &receiver_spend_pubkey, test->spend_seckey));
/* create labels cache */
labels_cache.ctx = CTX;
labels_cache.entries_used = 0;
for (i = 0; i < test->num_labels; i++) {
unsigned int m = test->label_integers[i];
struct label_cache_entry *cache_entry = &labels_cache.entries[labels_cache.entries_used];
CHECK(secp256k1_silentpayments_recipient_create_label_tweak(CTX, &cache_entry->label, cache_entry->label_tweak, test->scan_seckey, m));
labels_cache.entries_used++;
}
CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX,
found_outputs, &n_found,
tx_outputs, test->num_to_scan_outputs,
test->scan_seckey,
&public_data,
&receiver_spend_pubkey,
label_lookup, &labels_cache)
);
for (i = 0; i < n_found; i++) {
unsigned char full_seckey[32];
secp256k1_keypair keypair;
unsigned char signature[64];
const unsigned char msg32[32] = /* sha256("message") */
{0xab,0x53,0x0a,0x13,0xe4,0x59,0x14,0x98,0x2b,0x79,0xf9,0xb7,0xe3,0xfb,0xa9,0x94,
0xcf,0xd1,0xf3,0xfb,0x22,0xf7,0x1c,0xea,0x1a,0xfb,0xf0,0x2b,0x46,0x0c,0x6d,0x1d};
const unsigned char aux32[32] = /* sha256("random auxiliary data") */
{0x0b,0x3f,0xdd,0xfd,0x67,0xbf,0x76,0xae,0x76,0x39,0xee,0x73,0x5b,0x70,0xff,0x15,
0x83,0xfd,0x92,0x48,0xc0,0x57,0xd2,0x86,0x07,0xa2,0x15,0xf4,0x0b,0x0a,0x3e,0xcc};
memcpy(&full_seckey, test->spend_seckey, 32);
CHECK(secp256k1_ec_seckey_tweak_add(CTX, full_seckey, found_outputs[i]->tweak));
CHECK(secp256k1_keypair_create(CTX, &keypair, full_seckey));
CHECK(secp256k1_schnorrsig_sign32(CTX, signature, msg32, &keypair, aux32));
memcpy(found_signatures[i], signature, 64);
}
/* compare expected and scanned outputs (including calculated seckey tweaks and signatures) */
for (i = 0; i < n_found; i++) {
CHECK(secp256k1_xonly_pubkey_serialize(CTX, found_output, &found_outputs[i]->output));
match = 0;
for (j = 0; j < test->num_found_output_pubkeys; j++) {
if (secp256k1_memcmp_var(&found_output, test->found_output_pubkeys[j], 32) == 0) {
match = 1;
CHECK(secp256k1_memcmp_var(found_outputs[i]->tweak, test->found_seckey_tweaks[j], 32) == 0);
CHECK(secp256k1_memcmp_var(found_signatures[i], test->found_signatures[j], 64) == 0);
break;
}
}
CHECK(match);
}
CHECK(n_found == test->num_found_output_pubkeys);
/* Scan as a light client
* it is not recommended to use labels as a light client so here we are only
* running this on tests that do not involve labels. Primarily, this test is to
* ensure that _recipient_created_shared_secret and _create_shared_secret are the same
*/
if (test->num_labels == 0) {
CHECK(secp256k1_silentpayments_recipient_public_data_serialize(CTX, light_client_data, &public_data));
CHECK(secp256k1_silentpayments_recipient_public_data_parse(CTX, &public_data_index, light_client_data));
CHECK(secp256k1_silentpayments_recipient_create_shared_secret(CTX, shared_secret_lightclient, test->scan_seckey, &public_data_index));
n_found = 0;
{
int found = 0;
size_t k = 0;
secp256k1_xonly_pubkey potential_output;
unsigned char xonly_print[32];
while(1) {
CHECK(secp256k1_silentpayments_recipient_create_output_pubkey(CTX,
&potential_output,
shared_secret_lightclient,
&receiver_spend_pubkey,
k
));
/* At this point, we check that the utxo exists with a light client protocol.
* For this example, we'll just iterate through the list of pubkeys */
found = 0;
secp256k1_xonly_pubkey_serialize(CTX, xonly_print, &potential_output);
for (i = 0; i < test->num_to_scan_outputs; i++) {
secp256k1_xonly_pubkey_serialize(CTX, xonly_print, tx_outputs[i]);
if (secp256k1_xonly_pubkey_cmp(CTX, &potential_output, tx_outputs[i]) == 0) {
secp256k1_xonly_pubkey_serialize(CTX, found_outputs_light_client[n_found], &potential_output);
found = 1;
n_found++;
k++;
break;
}
}
if (!found) {
break;
}
}
}
CHECK(n_found == test->num_found_output_pubkeys);
for (i = 0; i < n_found; i++) {
match = 0;
for (j = 0; j < test->num_found_output_pubkeys; j++) {
if (secp256k1_memcmp_var(&found_outputs_light_client[i], test->found_output_pubkeys[j], 32) == 0) {
match = 1;
break;
}
}
CHECK(match);
}
}
}
void run_silentpayments_test_vectors(void) {
size_t i;
for (i = 0; i < sizeof(bip352_test_vectors) / sizeof(bip352_test_vectors[0]); i++) {
const struct bip352_test_vector *test = &bip352_test_vectors[i];
run_silentpayments_test_vector_send(test);
run_silentpayments_test_vector_receive(test);
}
}
void run_silentpayments_tests(void) {
run_silentpayments_test_vectors();
/* TODO: add a few manual tests here, that target the ECC-related parts of silent payments */
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -36,6 +36,7 @@
#include "int128_impl.h"
#include "scratch_impl.h"
#include "selftest.h"
#include "hsort_impl.h"
#ifdef SECP256K1_NO_BUILD
# error "secp256k1.h processed without SECP256K1_BUILD defined while building secp256k1.c"
@ -325,6 +326,40 @@ int secp256k1_ec_pubkey_cmp(const secp256k1_context* ctx, const secp256k1_pubkey
return secp256k1_memcmp_var(out[0], out[1], sizeof(out[0]));
}
/* This struct wraps a const context pointer to satisfy the secp256k1_hsort api
* which expects a non-const cmp_data pointer. */
typedef struct {
const secp256k1_context *ctx;
} secp256k1_ec_pubkey_sort_cmp_data;
static int secp256k1_ec_pubkey_sort_cmp(const void* pk1, const void* pk2, void *cmp_data) {
return secp256k1_ec_pubkey_cmp(((secp256k1_ec_pubkey_sort_cmp_data*)cmp_data)->ctx,
*(secp256k1_pubkey **)pk1,
*(secp256k1_pubkey **)pk2);
}
int secp256k1_ec_pubkey_sort(const secp256k1_context* ctx, const secp256k1_pubkey **pubkeys, size_t n_pubkeys) {
secp256k1_ec_pubkey_sort_cmp_data cmp_data;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(pubkeys != NULL);
cmp_data.ctx = ctx;
/* Suppress wrong warning (fixed in MSVC 19.33) */
#if defined(_MSC_VER) && (_MSC_VER < 1933)
#pragma warning(push)
#pragma warning(disable: 4090)
#endif
secp256k1_hsort(pubkeys, n_pubkeys, sizeof(*pubkeys), secp256k1_ec_pubkey_sort_cmp, &cmp_data);
#if defined(_MSC_VER) && (_MSC_VER < 1933)
#pragma warning(pop)
#endif
return 1;
}
static void secp256k1_ecdsa_signature_load(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, const secp256k1_ecdsa_signature* sig) {
(void)ctx;
if (sizeof(secp256k1_scalar) == 32) {
@ -804,3 +839,7 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32,
#ifdef ENABLE_MODULE_ELLSWIFT
# include "modules/ellswift/main_impl.h"
#endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
# include "modules/silentpayments/main_impl.h"
#endif

View File

@ -6607,6 +6607,203 @@ static void run_pubkey_comparison(void) {
CHECK(secp256k1_ec_pubkey_cmp(CTX, &pk2, &pk1) > 0);
}
static void test_hsort_is_sorted(int *ints, size_t n) {
size_t i;
for (i = 1; i < n; i++) {
CHECK(ints[i-1] <= ints[i]);
}
}
static int test_hsort_cmp(const void *i1, const void *i2, void *counter) {
*(size_t*)counter += 1;
return *(int*)i1 - *(int*)i2;
}
#define NUM 64
static void test_hsort(void) {
int ints[NUM] = { 0 };
size_t counter = 0;
int i, j;
secp256k1_hsort(ints, 0, sizeof(ints[0]), test_hsort_cmp, &counter);
CHECK(counter == 0);
secp256k1_hsort(ints, 1, sizeof(ints[0]), test_hsort_cmp, &counter);
CHECK(counter == 0);
secp256k1_hsort(ints, NUM, sizeof(ints[0]), test_hsort_cmp, &counter);
CHECK(counter > 0);
test_hsort_is_sorted(ints, NUM);
/* Test hsort with length n array and random elements in
* [-interval/2, interval/2] */
for (i = 0; i < COUNT; i++) {
int n = secp256k1_testrand_int(NUM);
int interval = secp256k1_testrand_int(63) + 1;
for (j = 0; j < n; j++) {
ints[j] = secp256k1_testrand_int(interval) - interval/2;
}
secp256k1_hsort(ints, n, sizeof(ints[0]), test_hsort_cmp, &counter);
test_hsort_is_sorted(ints, n);
}
}
#undef NUM
static void test_sort_helper(secp256k1_pubkey *pk, size_t *pk_order, size_t n_pk) {
size_t i;
const secp256k1_pubkey *pk_test[5];
for (i = 0; i < n_pk; i++) {
pk_test[i] = &pk[pk_order[i]];
}
secp256k1_ec_pubkey_sort(CTX, pk_test, n_pk);
for (i = 0; i < n_pk; i++) {
CHECK(secp256k1_memcmp_var(pk_test[i], &pk[i], sizeof(*pk_test[i])) == 0);
}
}
static void permute(size_t *arr, size_t n) {
size_t i;
for (i = n - 1; i >= 1; i--) {
size_t tmp, j;
j = secp256k1_testrand_int(i + 1);
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
static void rand_pk(secp256k1_pubkey *pk) {
unsigned char seckey[32];
secp256k1_keypair keypair;
secp256k1_testrand256(seckey);
CHECK(secp256k1_keypair_create(CTX, &keypair, seckey) == 1);
CHECK(secp256k1_keypair_pub(CTX, pk, &keypair) == 1);
}
static void test_sort_api(void) {
secp256k1_pubkey pks[2];
const secp256k1_pubkey *pks_ptr[2];
pks_ptr[0] = &pks[0];
pks_ptr[1] = &pks[1];
rand_pk(&pks[0]);
rand_pk(&pks[1]);
CHECK(secp256k1_ec_pubkey_sort(CTX, pks_ptr, 2) == 1);
CHECK_ILLEGAL(CTX, secp256k1_ec_pubkey_sort(CTX, NULL, 2));
CHECK(secp256k1_ec_pubkey_sort(CTX, pks_ptr, 0) == 1);
/* Test illegal public keys */
memset(&pks[0], 0, sizeof(pks[0]));
CHECK_ILLEGAL_VOID(CTX, CHECK(secp256k1_ec_pubkey_sort(CTX, pks_ptr, 2) == 1));
memset(&pks[1], 0, sizeof(pks[1]));
{
int32_t ecount = 0;
secp256k1_context_set_illegal_callback(CTX, counting_callback_fn, &ecount);
CHECK(secp256k1_ec_pubkey_sort(CTX, pks_ptr, 2) == 1);
CHECK(ecount == 2);
secp256k1_context_set_illegal_callback(CTX, NULL, NULL);
}
}
static void test_sort(void) {
secp256k1_pubkey pk[5];
unsigned char pk_ser[5][33] = {
{ 0x02, 0x08 },
{ 0x02, 0x0b },
{ 0x02, 0x0c },
{ 0x03, 0x05 },
{ 0x03, 0x0a },
};
int i;
size_t pk_order[5] = { 0, 1, 2, 3, 4 };
for (i = 0; i < 5; i++) {
CHECK(secp256k1_ec_pubkey_parse(CTX, &pk[i], pk_ser[i], sizeof(pk_ser[i])));
}
permute(pk_order, 1);
test_sort_helper(pk, pk_order, 1);
permute(pk_order, 2);
test_sort_helper(pk, pk_order, 2);
permute(pk_order, 3);
test_sort_helper(pk, pk_order, 3);
for (i = 0; i < COUNT; i++) {
permute(pk_order, 4);
test_sort_helper(pk, pk_order, 4);
}
for (i = 0; i < COUNT; i++) {
permute(pk_order, 5);
test_sort_helper(pk, pk_order, 5);
}
/* Check that sorting also works for random pubkeys */
for (i = 0; i < COUNT; i++) {
int j;
const secp256k1_pubkey *pk_ptr[5];
for (j = 0; j < 5; j++) {
rand_pk(&pk[j]);
pk_ptr[j] = &pk[j];
}
secp256k1_ec_pubkey_sort(CTX, pk_ptr, 5);
for (j = 1; j < 5; j++) {
CHECK(secp256k1_ec_pubkey_sort_cmp(&pk_ptr[j - 1], &pk_ptr[j], CTX) <= 0);
}
}
}
/* Test vectors from BIP-MuSig2 */
static void test_sort_vectors(void) {
enum { N_PUBKEYS = 6 };
unsigned char pk_ser[N_PUBKEYS][33] = {
{ 0x02, 0xDD, 0x30, 0x8A, 0xFE, 0xC5, 0x77, 0x7E, 0x13, 0x12, 0x1F,
0xA7, 0x2B, 0x9C, 0xC1, 0xB7, 0xCC, 0x01, 0x39, 0x71, 0x53, 0x09,
0xB0, 0x86, 0xC9, 0x60, 0xE1, 0x8F, 0xD9, 0x69, 0x77, 0x4E, 0xB8 },
{ 0x02, 0xF9, 0x30, 0x8A, 0x01, 0x92, 0x58, 0xC3, 0x10, 0x49, 0x34,
0x4F, 0x85, 0xF8, 0x9D, 0x52, 0x29, 0xB5, 0x31, 0xC8, 0x45, 0x83,
0x6F, 0x99, 0xB0, 0x86, 0x01, 0xF1, 0x13, 0xBC, 0xE0, 0x36, 0xF9 },
{ 0x03, 0xDF, 0xF1, 0xD7, 0x7F, 0x2A, 0x67, 0x1C, 0x5F, 0x36, 0x18,
0x37, 0x26, 0xDB, 0x23, 0x41, 0xBE, 0x58, 0xFE, 0xAE, 0x1D, 0xA2,
0xDE, 0xCE, 0xD8, 0x43, 0x24, 0x0F, 0x7B, 0x50, 0x2B, 0xA6, 0x59 },
{ 0x02, 0x35, 0x90, 0xA9, 0x4E, 0x76, 0x8F, 0x8E, 0x18, 0x15, 0xC2,
0xF2, 0x4B, 0x4D, 0x80, 0xA8, 0xE3, 0x14, 0x93, 0x16, 0xC3, 0x51,
0x8C, 0xE7, 0xB7, 0xAD, 0x33, 0x83, 0x68, 0xD0, 0x38, 0xCA, 0x66 },
{ 0x02, 0xDD, 0x30, 0x8A, 0xFE, 0xC5, 0x77, 0x7E, 0x13, 0x12, 0x1F,
0xA7, 0x2B, 0x9C, 0xC1, 0xB7, 0xCC, 0x01, 0x39, 0x71, 0x53, 0x09,
0xB0, 0x86, 0xC9, 0x60, 0xE1, 0x8F, 0xD9, 0x69, 0x77, 0x4E, 0xFF },
{ 0x02, 0xDD, 0x30, 0x8A, 0xFE, 0xC5, 0x77, 0x7E, 0x13, 0x12, 0x1F,
0xA7, 0x2B, 0x9C, 0xC1, 0xB7, 0xCC, 0x01, 0x39, 0x71, 0x53, 0x09,
0xB0, 0x86, 0xC9, 0x60, 0xE1, 0x8F, 0xD9, 0x69, 0x77, 0x4E, 0xB8 }
};
secp256k1_pubkey pubkeys[N_PUBKEYS];
secp256k1_pubkey *sorted[N_PUBKEYS];
const secp256k1_pubkey *pks_ptr[N_PUBKEYS];
int i;
sorted[0] = &pubkeys[3];
sorted[1] = &pubkeys[0];
sorted[2] = &pubkeys[0];
sorted[3] = &pubkeys[4];
sorted[4] = &pubkeys[1];
sorted[5] = &pubkeys[2];
for (i = 0; i < N_PUBKEYS; i++) {
CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkeys[i], pk_ser[i], sizeof(pk_ser[i])));
pks_ptr[i] = &pubkeys[i];
}
CHECK(secp256k1_ec_pubkey_sort(CTX, pks_ptr, N_PUBKEYS) == 1);
for (i = 0; i < N_PUBKEYS; i++) {
CHECK(secp256k1_memcmp_var(pks_ptr[i], sorted[i], sizeof(secp256k1_pubkey)) == 0);
}
}
static void run_pubkey_sort(void) {
test_hsort();
test_sort_api();
test_sort();
test_sort_vectors();
}
static void run_random_pubkeys(void) {
int i;
for (i = 0; i < 10*COUNT; i++) {
@ -7298,6 +7495,10 @@ static void run_ecdsa_wycheproof(void) {
# include "modules/ellswift/tests_impl.h"
#endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
# include "modules/silentpayments/tests_impl.h"
#endif
static void run_secp256k1_memczero_test(void) {
unsigned char buf1[6] = {1, 2, 3, 4, 5, 6};
unsigned char buf2[sizeof(buf1)];
@ -7622,6 +7823,7 @@ int main(int argc, char **argv) {
/* ecdsa tests */
run_ec_illegal_argument_tests();
run_pubkey_comparison();
run_pubkey_sort();
run_random_pubkeys();
run_ecdsa_der_parse();
run_ecdsa_sign_verify();
@ -7646,6 +7848,10 @@ int main(int argc, char **argv) {
run_ellswift_tests();
#endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
run_silentpayments_tests();
#endif
/* util tests */
run_secp256k1_memczero_test();
run_secp256k1_byteorder_tests();

View File

@ -0,0 +1,135 @@
# Copyright (c) 2017, 2020 Pieter Wuille
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""Reference implementation for Bech32/Bech32m and segwit addresses."""
from enum import Enum
class Encoding(Enum):
"""Enumeration type to list the various supported encodings."""
BECH32 = 1
BECH32M = 2
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
BECH32M_CONST = 0x2bc830a3
def bech32_polymod(values):
"""Internal function that computes the Bech32 checksum."""
generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
chk = 1
for value in values:
top = chk >> 25
chk = (chk & 0x1ffffff) << 5 ^ value
for i in range(5):
chk ^= generator[i] if ((top >> i) & 1) else 0
return chk
def bech32_hrp_expand(hrp):
"""Expand the HRP into values for checksum computation."""
return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
def bech32_verify_checksum(hrp, data):
"""Verify a checksum given HRP and converted data characters."""
const = bech32_polymod(bech32_hrp_expand(hrp) + data)
if const == 1:
return Encoding.BECH32
if const == BECH32M_CONST:
return Encoding.BECH32M
return None
def bech32_create_checksum(hrp, data, spec):
"""Compute the checksum values given HRP and data."""
values = bech32_hrp_expand(hrp) + data
const = BECH32M_CONST if spec == Encoding.BECH32M else 1
polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
def bech32_encode(hrp, data, spec):
"""Compute a Bech32 string given HRP and data values."""
combined = data + bech32_create_checksum(hrp, data, spec)
return hrp + '1' + ''.join([CHARSET[d] for d in combined])
def bech32_decode(bech):
"""Validate a Bech32/Bech32m string, and determine HRP and data."""
if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
(bech.lower() != bech and bech.upper() != bech)):
return (None, None, None)
bech = bech.lower()
pos = bech.rfind('1')
# remove the requirement that bech32m be less than 90 chars
if pos < 1 or pos + 7 > len(bech):
return (None, None, None)
if not all(x in CHARSET for x in bech[pos+1:]):
return (None, None, None)
hrp = bech[:pos]
data = [CHARSET.find(x) for x in bech[pos+1:]]
spec = bech32_verify_checksum(hrp, data)
if spec is None:
return (None, None, None)
return (hrp, data[:-6], spec)
def convertbits(data, frombits, tobits, pad=True):
"""General power-of-2 base conversion."""
acc = 0
bits = 0
ret = []
maxv = (1 << tobits) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1
for value in data:
if value < 0 or (value >> frombits):
return None
acc = ((acc << frombits) | value) & max_acc
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
if pad:
if bits:
ret.append((acc << (tobits - bits)) & maxv)
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
return None
return ret
def decode(hrp, addr):
"""Decode a segwit address."""
hrpgot, data, spec = bech32_decode(addr)
if hrpgot != hrp:
return (None, None)
decoded = convertbits(data[1:], 5, 8, False)
if decoded is None or len(decoded) < 2:
return (None, None)
if data[0] > 16:
return (None, None)
return (data[0], decoded)
def encode(hrp, witver, witprog):
"""Encode a segwit address."""
spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M
ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec)
if decode(hrp, ret) == (None, None):
return None
return ret

View File

@ -0,0 +1,130 @@
# Copyright (c) 2021 Pieter Wuille
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test-only pure Python RIPEMD160 implementation."""
import unittest
# Message schedule indexes for the left path.
ML = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13
]
# Message schedule indexes for the right path.
MR = [
5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11
]
# Rotation counts for the left path.
RL = [
11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6
]
# Rotation counts for the right path.
RR = [
8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11
]
# K constants for the left path.
KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e]
# K constants for the right path.
KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0]
def fi(x, y, z, i):
"""The f1, f2, f3, f4, and f5 functions from the specification."""
if i == 0:
return x ^ y ^ z
elif i == 1:
return (x & y) | (~x & z)
elif i == 2:
return (x | ~y) ^ z
elif i == 3:
return (x & z) | (y & ~z)
elif i == 4:
return x ^ (y | ~z)
else:
assert False
def rol(x, i):
"""Rotate the bottom 32 bits of x left by i bits."""
return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff
def compress(h0, h1, h2, h3, h4, block):
"""Compress state (h0, h1, h2, h3, h4) with block."""
# Left path variables.
al, bl, cl, dl, el = h0, h1, h2, h3, h4
# Right path variables.
ar, br, cr, dr, er = h0, h1, h2, h3, h4
# Message variables.
x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)]
# Iterate over the 80 rounds of the compression.
for j in range(80):
rnd = j >> 4
# Perform left side of the transformation.
al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el
al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl
# Perform right side of the transformation.
ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er
ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr
# Compose old state, left transform, and right transform into new state.
return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr
def ripemd160(data):
"""Compute the RIPEMD-160 hash of data."""
# Initialize state.
state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0)
# Process full 64-byte blocks in the input.
for b in range(len(data) >> 6):
state = compress(*state, data[64*b:64*(b+1)])
# Construct final blocks (with padding and size).
pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63)
fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little')
# Process final blocks.
for b in range(len(fin) >> 6):
state = compress(*state, fin[64*b:64*(b+1)])
# Produce output.
return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state)
class TestFrameworkKey(unittest.TestCase):
def test_ripemd160(self):
"""RIPEMD-160 test vectors."""
# See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
for msg, hexout in [
(b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"),
(b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"),
(b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"),
(b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"),
(b"abcdefghijklmnopqrstuvwxyz",
"f71c27109c692c1b56bbdceb5b9d2865b3708dbc"),
(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"12a053384a9c0c88e405a06c27dcf49ada62eb2b"),
(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
"b0e20b6e3116640286ed3a87a5713079b21f5189"),
(b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"),
(b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528")
]:
self.assertEqual(ripemd160(msg).hex(), hexout)

View File

@ -0,0 +1,282 @@
#!/usr/bin/env python3
import hashlib
import json
import sys
import bech32m
import ripemd160
NUMS_H = bytes.fromhex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")
MAX_INPUTS_PER_TEST_CASE = 3
MAX_OUTPUTS_PER_TEST_CASE = 12
def sha256(s):
return hashlib.sha256(s).digest()
def hash160(s):
return ripemd160.ripemd160(sha256(s))
def smallest_outpoint(outpoints):
serialized_outpoints = [bytes.fromhex(txid)[::-1] + n.to_bytes(4, 'little') for txid, n in outpoints]
return sorted(serialized_outpoints)[0]
def decode_silent_payments_address(address):
_, data = bech32m.decode("sp", address)
data = bytes(data) # convert from list to bytes
assert len(data) == 66
return data[:33], data[33:]
def is_p2tr(s): # OP_1 OP_PUSHBYTES_32 <32 bytes>
return (len(s) == 34) and (s[0] == 0x51) and (s[1] == 0x20)
def is_p2wpkh(s): # OP_0 OP_PUSHBYTES_20 <20 bytes>
return (len(s) == 22) and (s[0] == 0x00) and (s[1] == 0x14)
def is_p2sh(s): # OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUAL
return (len(s) == 23) and (s[0] == 0xA9) and (s[1] == 0x14) and (s[-1] == 0x87)
def is_p2pkh(s): # OP_DUP OP_HASH160 OP_PUSHBYTES_20 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG
return (len(s) == 25) and (s[0] == 0x76) and (s[1] == 0xA9) and (s[2] == 0x14) and \
(s[-2] == 0x88) and (s[-1] == 0xAC)
def get_pubkey_from_input(spk, script_sig, witness):
# build witness stack from raw witness data
witness_stack = []
no_witness_items = 0
if len(witness) > 0:
no_witness_items = witness[0]
witness = witness[1:]
for i in range(no_witness_items):
item_len = witness[0]
witness_stack.append(witness[1:item_len+1])
witness = witness[item_len+1:]
if is_p2pkh(spk):
spk_pkh = spk[3:3 + 20]
for i in range(len(script_sig), 0, -1):
if i - 33 >= 0:
pk = script_sig[i - 33:i]
if hash160(pk) == spk_pkh:
return pk
elif is_p2sh(spk) and is_p2wpkh(script_sig[1:]):
pubkey = witness_stack[-1]
if len(pubkey) == 33:
return pubkey
elif is_p2wpkh(spk):
# the witness must contain two items and the second item is the pubkey
pubkey = witness_stack[-1]
if len(pubkey) == 33:
return pubkey
elif is_p2tr(spk):
if len(witness_stack) > 1 and witness_stack[-1][0] == 0x50:
witness_stack.pop()
if len(witness_stack) > 1: # script-path spend?
control_block = witness_stack[-1]
internal_key = control_block[1:33]
if internal_key == NUMS_H: # skip
return b''
return spk[2:]
return b''
def to_c_array(x):
if x == "":
return ""
s = ',0x'.join(a+b for a,b in zip(x[::2], x[1::2]))
return "0x" + s
def emit_key_material(comment, keys, include_count=False):
global out
if include_count:
out += f" {len(keys)}," + "\n"
out += f" {{ /* {comment} */" + "\n"
for i in range(MAX_INPUTS_PER_TEST_CASE):
out += " "
if i < len(keys):
out += "{"
out += to_c_array(keys[i])
out += "}"
else:
out += '""'
if i != MAX_INPUTS_PER_TEST_CASE - 1:
out += ','
out += "\n"
out += " },\n"
def emit_receiver_addr_material(receiver_addresses):
global out
out += f" {len(receiver_addresses)}," + "\n"
out += " { /* recipient pubkeys (address data) */\n"
for i in range(MAX_OUTPUTS_PER_TEST_CASE):
out += " {\n"
if i < len(receiver_addresses):
B_scan, B_spend = decode_silent_payments_address(receiver_addresses[i])
out += " {"
out += to_c_array(B_scan.hex())
out += "},\n"
out += " {"
out += to_c_array(B_spend.hex())
out += "},\n"
else:
out += ' "",\n'
out += ' "",\n'
out += " }"
if i != MAX_OUTPUTS_PER_TEST_CASE - 1:
out += ','
out += "\n"
out += " },\n"
def emit_outputs(comment, outputs, include_count=False, last=False):
global out
if include_count:
out += f" {len(outputs)}," + "\n"
if comment:
out += f" {{ /* {comment} */" + "\n"
else:
out += " {\n"
for i in range(MAX_OUTPUTS_PER_TEST_CASE):
if i < len(outputs):
out += " {"
out += to_c_array(outputs[i])
out += "}"
else:
out += ' ""'
if i != MAX_OUTPUTS_PER_TEST_CASE - 1:
out += ','
out += "\n"
out += " }"
if not last:
out += ","
out += "\n"
filename_input = sys.argv[1]
with open(filename_input) as f:
test_vectors = json.load(f)
out = ""
num_vectors = 0
for test_nr, test_vector in enumerate(test_vectors):
# determine input private and public keys, grouped into plain and taproot/x-only
input_plain_seckeys = []
input_taproot_seckeys = []
input_plain_pubkeys = []
input_xonly_pubkeys = []
outpoints = []
for i in test_vector['sending'][0]['given']['vin']:
pub_key = get_pubkey_from_input(bytes.fromhex(i['prevout']['scriptPubKey']['hex']),
bytes.fromhex(i['scriptSig']), bytes.fromhex(i['txinwitness']))
if len(pub_key) == 33: # regular input
input_plain_seckeys.append(i['private_key'])
input_plain_pubkeys.append(pub_key.hex())
elif len(pub_key) == 32: # taproot input
input_taproot_seckeys.append(i['private_key'])
input_xonly_pubkeys.append(pub_key.hex())
outpoints.append((i['txid'], i['vout']))
if len(input_plain_pubkeys) == 0 and len(input_xonly_pubkeys) == 0:
continue
num_vectors += 1
out += f" /* ----- {test_vector['comment']} ({num_vectors}) ----- */\n"
out += " {\n"
outpoint_L = smallest_outpoint(outpoints).hex()
emit_key_material("input plain seckeys", input_plain_seckeys, include_count=True)
emit_key_material("input plain pubkeys", input_plain_pubkeys)
emit_key_material("input taproot seckeys", input_taproot_seckeys, include_count=True)
emit_key_material("input x-only pubkeys", input_xonly_pubkeys)
out += " /* smallest outpoint */\n"
out += " {"
out += to_c_array(outpoint_L)
out += "},\n"
# emit recipient pubkeys (address data)
emit_receiver_addr_material(test_vector['sending'][0]['given']['recipients'])
# emit recipient outputs
emit_outputs("recipient outputs", test_vector['sending'][0]['expected']['outputs'], include_count=True)
# emit receiver scan/spend seckeys
recv_test_given = test_vector['receiving'][0]['given']
recv_test_expected = test_vector['receiving'][0]['expected']
out += " /* receiver data (scan and spend seckeys) */\n"
out += " {" + f"{to_c_array(recv_test_given['key_material']['scan_priv_key'])}" + "},\n"
out += " {" + f"{to_c_array(recv_test_given['key_material']['spend_priv_key'])}" + "},\n"
# emit receiver to-scan outputs, labels and expected-found outputs
emit_outputs("outputs to scan", recv_test_given['outputs'], include_count=True)
labels = recv_test_given['labels']
out += f" {len(labels)}, " + "{"
for i in range(4):
if i < len(labels):
out += f"{labels[i]}"
else:
out += "0xffffffff"
if i != 3:
out += ", "
out += "}, /* labels */\n"
expected_pubkeys = [o['pub_key'] for o in recv_test_expected['outputs']]
expected_tweaks = [o['priv_key_tweak'] for o in recv_test_expected['outputs']]
expected_signatures = [o['signature'] for o in recv_test_expected['outputs']]
out += " /* expected output data (pubkeys and seckey tweaks) */\n"
emit_outputs("", expected_pubkeys, include_count=True)
emit_outputs("", expected_tweaks)
emit_outputs("", expected_signatures, last=True)
out += " }"
if test_nr != len(test_vectors)-1:
out += ","
out += "\n\n"
STRUCT_DEFINITIONS = f"""
#define MAX_INPUTS_PER_TEST_CASE {MAX_INPUTS_PER_TEST_CASE}
#define MAX_OUTPUTS_PER_TEST_CASE {MAX_OUTPUTS_PER_TEST_CASE}
struct bip352_receiver_addressdata {{
unsigned char scan_pubkey[33];
unsigned char spend_pubkey[33];
}};
struct bip352_test_vector {{
/* Inputs (private keys / public keys + smallest outpoint) */
size_t num_plain_inputs;
unsigned char plain_seckeys[MAX_INPUTS_PER_TEST_CASE][32];
unsigned char plain_pubkeys[MAX_INPUTS_PER_TEST_CASE][33];
size_t num_taproot_inputs;
unsigned char taproot_seckeys[MAX_INPUTS_PER_TEST_CASE][32];
unsigned char xonly_pubkeys[MAX_INPUTS_PER_TEST_CASE][32];
unsigned char outpoint_smallest[36];
/* Given sender data (pubkeys encoded per output address to send to) */
size_t num_outputs;
struct bip352_receiver_addressdata recipient_pubkeys[MAX_OUTPUTS_PER_TEST_CASE];
/* Expected sender data */
size_t num_recipient_outputs;
unsigned char recipient_outputs[MAX_OUTPUTS_PER_TEST_CASE][32];
/* Given receiver data */
unsigned char scan_seckey[32];
unsigned char spend_seckey[32];
size_t num_to_scan_outputs;
unsigned char to_scan_outputs[MAX_OUTPUTS_PER_TEST_CASE][32];
size_t num_labels;
unsigned int label_integers[MAX_OUTPUTS_PER_TEST_CASE];
/* Expected receiver data */
size_t num_found_output_pubkeys;
unsigned char found_output_pubkeys[MAX_OUTPUTS_PER_TEST_CASE][32];
unsigned char found_seckey_tweaks[MAX_OUTPUTS_PER_TEST_CASE][32];
unsigned char found_signatures[MAX_OUTPUTS_PER_TEST_CASE][64];
}};
"""
print("/* Note: this file was autogenerated using tests_silentpayments_generate.py. Do not edit. */")
print(f"#define SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS ({num_vectors})")
print(STRUCT_DEFINITIONS)
print("static const struct bip352_test_vector bip352_test_vectors[SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS] = {")
print(out, end='')
print("};")

173
src/test/bip352_tests.cpp Normal file
View File

@ -0,0 +1,173 @@
#include <bip352.h>
#include <span.h>
#include <addresstype.h>
#include <policy/policy.h>
#include <script/solver.h>
#include <test/data/bip352_send_and_receive_vectors.json.h>
#include <test/util/setup_common.h>
#include <hash.h>
#include <boost/test/unit_test.hpp>
#include <test/util/json.h>
#include <vector>
#include <util/bip32.h>
#include <util/strencodings.h>
#include <key_io.h>
#include <streams.h>
namespace wallet {
BOOST_FIXTURE_TEST_SUITE(bip352_tests, BasicTestingSetup)
CKey ParseHexToCKey(std::string hex) {
CKey output;
std::vector<unsigned char> hex_data = ParseHex(hex);
output.Set(hex_data.begin(), hex_data.end(), true);
return output;
};
CKey GetKeyFromBIP32Path(std::vector<std::byte> seed, std::vector<uint32_t> path)
{
CExtKey key;
key.SetSeed(seed);
for (auto index : path) {
BOOST_CHECK(key.Derive(key, index));
}
return key.key;
}
BOOST_AUTO_TEST_CASE(bip352_send_and_receive_test_vectors)
{
UniValue tests;
tests.read(json_tests::bip352_send_and_receive_vectors);
for (const auto& vec : tests.getValues()) {
// run sending tests
BOOST_TEST_MESSAGE(vec["comment"].get_str());
for (const auto& sender : vec["sending"].getValues()) {
const UniValue& given = sender["given"];
const UniValue& expected = sender["expected"];
std::vector<COutPoint> outpoints;
std::vector<CKey> keys;
std::vector<CKey> taproot_keys;
for (const auto& input : given["vin"].getValues()) {
COutPoint outpoint{TxidFromString(input["txid"].get_str()), input["vout"].getInt<uint32_t>()};
outpoints.push_back(outpoint);
const auto& spk_bytes = ParseHex(input["prevout"]["scriptPubKey"]["hex"].get_str());
CScript spk = CScript(spk_bytes.begin(), spk_bytes.end());
const auto& script_sig_bytes = ParseHex(input["scriptSig"].get_str());
CScript script_sig = CScript(script_sig_bytes.begin(), script_sig_bytes.end());
CTxIn txin{outpoint, script_sig};
CScriptWitness witness;
// read the field txWitness as a stream and write txWitness >> witness.stack;
auto witness_str = ParseHex(input["txinwitness"].get_str());
if (!witness_str.empty()) {
SpanReader(witness_str) >> witness.stack;
txin.scriptWitness = witness;
}
// check if this is a silent payment input by trying to extract the public key
const auto& pubkey = BIP352::GetPubKeyFromInput(txin, spk);
if (pubkey.has_value()) {
std::vector<std::vector<unsigned char>> solutions;
TxoutType type = Solver(spk, solutions);
if (type == TxoutType::WITNESS_V1_TAPROOT) {
taproot_keys.emplace_back(ParseHexToCKey(input["private_key"].get_str()));
} else {
keys.emplace_back(ParseHexToCKey(input["private_key"].get_str()));
}
}
}
if (taproot_keys.empty() && keys.empty()) continue;
// silent payments logic
auto smallest_outpoint = std::min_element(outpoints.begin(), outpoints.end());
std::map<size_t, V0SilentPaymentDestination> sp_dests;
const std::vector<UniValue>& silent_payment_addresses = given["recipients"].getValues();
for (size_t i = 0; i < silent_payment_addresses.size(); ++i) {
const CTxDestination& tx_dest = DecodeDestination(silent_payment_addresses[i].get_str());
if (const auto* sp = std::get_if<V0SilentPaymentDestination>(&tx_dest)) {
sp_dests[i] = *sp;
}
}
std::map<size_t, WitnessV1Taproot> sp_tr_dests = BIP352::GenerateSilentPaymentTaprootDestinations(sp_dests, keys, taproot_keys, *smallest_outpoint);
std::vector<WitnessV1Taproot> expected_spks;
for (const auto& recipient : expected["outputs"].getValues()) {
const WitnessV1Taproot tap{XOnlyPubKey(ParseHex(recipient.get_str()))};
expected_spks.push_back(tap);
}
BOOST_CHECK(sp_tr_dests.size() == expected["n_outputs"].getInt<size_t>());
for (const auto& [_, spk]: sp_tr_dests) {
BOOST_CHECK(std::find(expected_spks.begin(), expected_spks.end(), spk) != expected_spks.end());
}
}
// Test receiving
for (const auto& recipient : vec["receiving"].getValues()) {
const UniValue& given = recipient["given"];
const UniValue& expected = recipient["expected"];
std::vector<CTxIn> vin;
std::map<COutPoint, Coin> coins;
for (const auto& input : given["vin"].getValues()) {
COutPoint outpoint{TxidFromString(input["txid"].get_str()), input["vout"].getInt<uint32_t>()};
const auto& spk_bytes = ParseHex(input["prevout"]["scriptPubKey"]["hex"].get_str());
CScript spk = CScript(spk_bytes.begin(), spk_bytes.end());
const auto& script_sig_bytes = ParseHex(input["scriptSig"].get_str());
CScript script_sig = CScript(script_sig_bytes.begin(), script_sig_bytes.end());
CTxIn txin{outpoint, script_sig};
CScriptWitness witness;
// read the field txWitness as a stream and write txWitness >> witness.stack;
auto witness_str = ParseHex(input["txinwitness"].get_str());
if (!witness_str.empty()) {
SpanReader(witness_str) >> witness.stack;
txin.scriptWitness = witness;
}
vin.push_back(txin);
coins[outpoint] = Coin{CTxOut{{}, spk}, 0, false};
}
std::vector<XOnlyPubKey> output_pub_keys;
for (const auto& pubkey : given["outputs"].getValues()) {
output_pub_keys.emplace_back(ParseHex(pubkey.get_str()));
}
CKey scan_priv_key = ParseHexToCKey(given["key_material"]["scan_priv_key"].get_str());
CKey spend_priv_key = ParseHexToCKey(given["key_material"]["spend_priv_key"].get_str());
V0SilentPaymentDestination sp_address{scan_priv_key.GetPubKey(), spend_priv_key.GetPubKey()};
std::map<CPubKey, uint256> labels;
// Always calculate the change key, whether we use labels or not
const auto& [change_pubkey, change_tweak] = BIP352::CreateLabelTweak(scan_priv_key, 0);
labels[change_pubkey] = change_tweak;
// If labels are used, add them to the dictionary
for (const auto& label : given["labels"].getValues()) {
const auto& [label_pubkey, label_tweak] = BIP352::CreateLabelTweak(scan_priv_key, label.getInt<int>());
labels[label_pubkey] = label_tweak;
}
// Scanning
const auto& pub_tweak_data = BIP352::GetSilentPaymentTweakDataFromTxInputs(vin, coins);
// If we don't get any tweak data from the transaction inputs, it is not a silent payment
// transaction, so we skip it.
if (!pub_tweak_data.has_value()) continue;
const auto& found_outputs = BIP352::GetTxOutputTweaks(scan_priv_key, *pub_tweak_data, sp_address.m_spend_pubkey, output_pub_keys, labels);
// The transaction may be a silent payment transaction, but it does not contain any outputs for us,
// so we continue to the next transaction.
if (!found_outputs.has_value()) continue;
std::vector<XOnlyPubKey> expected_outputs;
for (const auto& output : expected["outputs"].getValues()) {
std::string pubkey_hex = output["pub_key"].get_str();
expected_outputs.emplace_back(ParseHex(pubkey_hex));
}
BOOST_CHECK(found_outputs->size() == expected["n_outputs"].getInt<size_t>());
for (const auto& output : *found_outputs) {
BOOST_CHECK(std::find(expected_outputs.begin(), expected_outputs.end(), output.output) != expected_outputs.end());
}
}
}
}
BOOST_AUTO_TEST_SUITE_END()
} // namespace wallet

File diff suppressed because it is too large Load Diff

View File

@ -153,9 +153,17 @@ FUZZ_TARGET(script, .init = initialize_script)
const CScript dest{GetScriptForDestination(tx_destination_1)};
const bool valid{IsValidDestination(tx_destination_1)};
if (!std::get_if<V0SilentPaymentDestination>(&tx_destination_1) && !std::get_if<PubKeyDestination>(&tx_destination_1)) {
// For V0SilentPaymentDestination, we skip this check because the address is valid but
// does not have a CScript.
//
// For PubKeyDestination, we skip this check because the destination is no longer valid for
// Bitcoin Core but does have a CScript.
Assert(dest.empty() != valid);
}
if (!std::get_if<PubKeyDestination>(&tx_destination_1)) {
// Only try to round trip non-pubkey destinations since PubKeyDestination has no encoding
Assert(dest.empty() != valid);
Assert(tx_destination_1 == DecodeDestination(encoded_dest));
Assert(valid == IsValidDestinationString(encoded_dest));
}

View File

@ -213,6 +213,11 @@ CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) no
[&] {
tx_destination = WitnessV1Taproot{XOnlyPubKey{ConsumeUInt256(fuzzed_data_provider)}};
},
[&] {
CPubKey scan_pk{ConstructPubKeyBytes(fuzzed_data_provider, ConsumeFixedLengthByteVector(fuzzed_data_provider, CPubKey::COMPRESSED_SIZE), /*compressed=*/true)};
CPubKey spend_pk{ConstructPubKeyBytes(fuzzed_data_provider, ConsumeFixedLengthByteVector(fuzzed_data_provider, CPubKey::COMPRESSED_SIZE), /*compressed=*/true)};
tx_destination = V0SilentPaymentDestination{scan_pk, spend_pk};
},
[&] {
std::vector<unsigned char> program{ConsumeRandomLengthByteVector(fuzzed_data_provider, /*max_length=*/40)};
if (program.size() < 2) {

View File

@ -433,6 +433,7 @@ public:
UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); }
UniValue operator()(const PubKeyDestination& dest) const { return UniValue(UniValue::VOBJ); }
UniValue operator()(const V0SilentPaymentDestination& dest) const { return UniValue(UniValue::VOBJ); }
UniValue operator()(const PKHash& pkhash) const
{

View File

@ -291,6 +291,11 @@ struct CRecipient
CTxDestination dest;
CAmount nAmount;
bool fSubtractFeeFromAmount;
friend bool operator==(const CRecipient& a, const CRecipient& b)
{
return a.dest == b.dest && a.nAmount == b.nAmount;
}
};
class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime