This commit is contained in:
josie 2025-04-29 12:07:52 +02:00 committed by GitHub
commit fb9b4f9b8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
70 changed files with 14041 additions and 417 deletions

View file

@ -140,6 +140,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL
coins.cpp coins.cpp
common/args.cpp common/args.cpp
common/bloom.cpp common/bloom.cpp
common/bip352.cpp
common/config.cpp common/config.cpp
common/init.cpp common/init.cpp
common/interfaces.cpp common/interfaces.cpp

View file

@ -108,6 +108,10 @@ namespace {
class CScriptVisitor class CScriptVisitor
{ {
public: public:
CScript operator()(const V0SilentPaymentDestination& dest) const
{
return CScript();
}
CScript operator()(const CNoDestination& dest) const CScript operator()(const CNoDestination& dest) const
{ {
return dest.GetScript(); return dest.GetScript();
@ -156,6 +160,9 @@ public:
bool operator()(const PubKeyDestination& dest) const { return false; } bool operator()(const PubKeyDestination& dest) const { return false; }
bool operator()(const PKHash& dest) const { return true; } bool operator()(const PKHash& dest) const { return true; }
bool operator()(const ScriptHash& 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 WitnessV0KeyHash& dest) const { return true; }
bool operator()(const WitnessV0ScriptHash& dest) const { return true; } bool operator()(const WitnessV0ScriptHash& dest) const { return true; }
bool operator()(const WitnessV1Taproot& dest) const { return true; } bool operator()(const WitnessV1Taproot& dest) const { return true; }

View file

@ -127,6 +127,25 @@ struct PayToAnchor : public WitnessUnknown
}; };
}; };
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. * A txout script categorized into standard templates.
* * CNoDestination: Optionally a script, no corresponding address. * * CNoDestination: Optionally a script, no corresponding address.
@ -140,7 +159,7 @@ struct PayToAnchor : public WitnessUnknown
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? address) * * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? address)
* A CTxDestination is the internal data type encoded in a bitcoin address * A CTxDestination is the internal data type encoded in a bitcoin address
*/ */
using CTxDestination = std::variant<CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown>; using CTxDestination = std::variant<CNoDestination, V0SilentPaymentDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown>;
/** Check whether a CTxDestination corresponds to one with an address. */ /** Check whether a CTxDestination corresponds to one with an address. */
bool IsValidDestination(const CTxDestination& dest); bool IsValidDestination(const CTxDestination& dest);

View file

@ -37,6 +37,7 @@ enum class Encoding {
* and we would never encode an address with such a massive value */ * and we would never encode an address with such a massive value */
enum CharLimit : size_t { enum CharLimit : size_t {
BECH32 = 90, //!< BIP173/350 imposed character limit for Bech32(m) encoded addresses. This guarantees finding up to 4 errors. BECH32 = 90, //!< BIP173/350 imposed character limit for Bech32(m) encoded addresses. This guarantees finding up to 4 errors.
SILENT_PAYMENTS = 1024, //!< BIP352 imposed 1024 character limit on Bech32m encoded silent payment addresses. This guarantees finding up to 3 errors
}; };
/** Encode a Bech32 or Bech32m string. If hrp contains uppercase characters, this will cause an /** Encode a Bech32 or Bech32m string. If hrp contains uppercase characters, this will cause an

353
src/common/bip352.cpp Normal file
View file

@ -0,0 +1,353 @@
// 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 <common/bip352.h>
#include <optional>
#include <primitives/transaction.h>
#include <secp256k1_extrakeys.h>
#include <pubkey.h>
#include <secp256k1.h>
#include <span.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(WitnessV1Taproot::NUMS_H.begin(), WitnessV1Taproot::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;
}
std::optional<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()
);
if (!ret) return std::nullopt;
return public_data;
}
std::optional<PubTweakData> GetSilentPaymentsPublicData(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 = GetSilentPaymentsPublicData(vin, coins);
if (!result.has_value()) return std::nullopt;
secp256k1_silentpayments_recipient_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<KeyPair>& taproot_keypairs,
const COutPoint& smallest_outpoint
) {
bool ret;
std::vector<const secp256k1_keypair *> taproot_keypair_ptrs;
std::vector<const unsigned char *> plain_key_ptrs;
taproot_keypair_ptrs.reserve(taproot_keypairs.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.labeled_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 (const auto& keypair : taproot_keypairs) {
taproot_keypair_ptrs.push_back(reinterpret_cast<const secp256k1_keypair*>(keypair.data()));
}
// 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()
);
if (!ret) return {};
return generated_outputs;
}
std::optional<std::map<size_t, WitnessV1Taproot>> GenerateSilentPaymentTaprootDestinations(const std::map<size_t, V0SilentPaymentDestination>& sp_dests, const std::vector<CKey>& plain_keys, const std::vector<KeyPair>& 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);
// This will only faily if the inputs were maliciously crafted to sum to zero
if (outputs.empty()) return std::nullopt;
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 unsigned char* key, const void* context) {
auto label_context = static_cast<const std::map<CPubKey, uint256>*>(context);
CPubKey label{key, key + CPubKey::COMPRESSED_SIZE};
// 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::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(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, labeled_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_labeled_spend_pubkey(secp256k1_context_static, &labeled_spend_obj, &spend_obj, &label_obj);
assert(ret);
size_t pubkeylen = CPubKey::COMPRESSED_SIZE;
CPubKey labeled_spend_pubkey;
ret = secp256k1_ec_pubkey_serialize(secp256k1_context_static, (unsigned char*)labeled_spend_pubkey.begin(), &pubkeylen, &labeled_spend_obj, SECP256K1_EC_COMPRESSED);
return labeled_spend_pubkey;
}
V0SilentPaymentDestination GenerateSilentPaymentLabeledAddress(const V0SilentPaymentDestination& recipient, const uint256& label)
{
CKey label_key;
label_key.Set(label.begin(), label.end(), true);
CPubKey labeled_spend_pubkey = CreateLabeledSpendPubKey(recipient.m_spend_pubkey, label_key.GetPubKey());
return V0SilentPaymentDestination{recipient.m_scan_pubkey, labeled_spend_pubkey};
}
std::optional<std::vector<SilentPaymentOutput>> ScanForSilentPaymentOutputs(
const CKey& scan_key,
const PubTweakData& public_data,
const CPubKey& recipient_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, recipient_spend_pubkey.data(), recipient_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;
}
}; // namespace bip352

158
src/common/bip352.h Normal file
View file

@ -0,0 +1,158 @@
// 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_COMMON_BIP352_H
#define BITCOIN_COMMON_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_recipient_public_data;
struct BIP352Comparator {
bool operator()(const COutPoint& a, const COutPoint& b) const {
// BIP352 defines the "smallest outpoint" based on a lexicographic
// sort of the outpoints, using the 36-byte serialization:
// <txid, 32-bytes little-endian>:<vout, 4-bytes little-endian>
if (a.hash != b.hash) {
return a.hash < b.hash;
}
uint8_t a_vout[4], b_vout[4];
WriteLE32(a_vout, a.n);
WriteLE32(b_vout, b.n);
return std::memcmp(a_vout, b_vout, 4) < 0;
}
};
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 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 keypairs 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::optional<std::map<size_t, WitnessV1Taproot>> GenerateSilentPaymentTaprootDestinations(const std::map<size_t, V0SilentPaymentDestination>& sp_dests, const std::vector<CKey>& plain_keys, const std::vector<KeyPair>& taproot_keys, const COutPoint& smallest_outpoint);
/**
* @brief Create a silent payments label pair.
*
* Create a label tweak and the corresponding public key from an integer `m` and the recipient scan key.
* `m = 0` is reserved for the change label.
*
* Label public keys can be stored in a cache, mapping the public key to the label tweak. This cache
* is used during scanning to determine if a label was used and if so to retrieve the label tweak.
*
* @param scan_key The recipient's scan_key, used to salt the hash
* @param m An integer m (only use m = 0 for the change label)
* @return std::<CPubKey, uint156> The label public key and label tweak.
*
* @see GenerateSilentPaymentLabeledAddress
*/
std::pair<CPubKey, uint256> CreateLabelTweak(const CKey& scan_key, const int m);
/**
* @brief Generate a silent payment labeled address.
*
* @param recipient The recipient'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& recipient, const uint256& label);
/**
* @brief Get silent payment public 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<PublicData> The silent payment public data, nullopt if not found.
*/
std::optional<PubTweakData> GetSilentPaymentsPublicData(const std::vector<CTxIn>& vin, const std::map<COutPoint, Coin>& coins);
/**
* @brief Get and serialize public data from the transaction inputs.
*
* This function returns the public key sum multiplied by the input hash and is intended to be used by an index store the
* input data for wallet rescans or for serving the data to light clients. To get the silent payments public data for
* scanning a transaction, use `GetSilentPaymentsPublicData`.
*
* 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 spent in this transaction.
* @return std::optional<PubTweakData> The silent payment tweak data, nullopt if not found.
*/
std::optional<CPubKey> GetSerializedSilentPaymentsPublicData(const std::vector<CTxIn>& vin, const std::map<COutPoint, Coin>& coins);
/**
* @brief Scan a transaction for silent payment outputs.
*
* Scan the transaction for silent payment outputs intendended for the recipient (i.e. the owner of `scan_key`).
* The output, shared secret tweak, and (optionally) label public key is returned for each output found. If the
* output was sent to a labeled address, the label tweak is added to the shared secret tweak.
* The shared secret tweak is needed to spend the output, by adding it to the spend secret key.
* If no outputs are found, this transaction does not contain silent payment outputs for the recipient.
*
* While labels are optional for the recipient, it is strongly recommended that the change label is always checked
* when scanning.
*
* @param scan_key The recipient's scan key.
* @param spend_pubkey The recipient's spend public key.
* @param output_pub_keys The taproot output public keys.
* @param labels The recipient's labels.
* @return std::<optional<std::vector<SilentPaymentOutput>> The found outputs, nullopt if none found.
*/
std::optional<std::vector<SilentPaymentOutput>> ScanForSilentPaymentOutputs(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_COMMON_BIP352_H

View file

@ -161,6 +161,7 @@ public:
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4};
bech32_hrp = "bc"; bech32_hrp = "bc";
silent_payment_hrp = "sp";
vFixedSeeds = std::vector<uint8_t>(std::begin(chainparams_seed_main), std::end(chainparams_seed_main)); vFixedSeeds = std::vector<uint8_t>(std::begin(chainparams_seed_main), std::end(chainparams_seed_main));
@ -262,6 +263,7 @@ public:
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
bech32_hrp = "tb"; bech32_hrp = "tb";
silent_payment_hrp = "tsp";
vFixedSeeds = std::vector<uint8_t>(std::begin(chainparams_seed_test), std::end(chainparams_seed_test)); vFixedSeeds = std::vector<uint8_t>(std::begin(chainparams_seed_test), std::end(chainparams_seed_test));
@ -360,6 +362,7 @@ public:
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
bech32_hrp = "tb"; bech32_hrp = "tb";
silent_payment_hrp = "tsp";
vFixedSeeds = std::vector<uint8_t>(std::begin(chainparams_seed_testnet4), std::end(chainparams_seed_testnet4)); vFixedSeeds = std::vector<uint8_t>(std::begin(chainparams_seed_testnet4), std::end(chainparams_seed_testnet4));
@ -484,6 +487,7 @@ public:
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
bech32_hrp = "tb"; bech32_hrp = "tb";
silent_payment_hrp = "tsp";
fDefaultConsistencyChecks = false; fDefaultConsistencyChecks = false;
m_is_mockable_chain = false; m_is_mockable_chain = false;
@ -615,6 +619,7 @@ public:
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
bech32_hrp = "bcrt"; bech32_hrp = "bcrt";
silent_payment_hrp = "sprt";
} }
}; };

View file

@ -105,6 +105,7 @@ public:
const std::vector<std::string>& DNSSeeds() const { return vSeeds; } const std::vector<std::string>& DNSSeeds() const { return vSeeds; }
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; }
const std::string& Bech32HRP() const { return bech32_hrp; } 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 std::vector<uint8_t>& FixedSeeds() const { return vFixedSeeds; }
std::optional<AssumeutxoData> AssumeutxoForHeight(int height) const std::optional<AssumeutxoData> AssumeutxoForHeight(int height) const
@ -163,6 +164,7 @@ protected:
std::vector<std::string> vSeeds; std::vector<std::string> vSeeds;
std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES]; std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];
std::string bech32_hrp; std::string bech32_hrp;
std::string silent_payment_hrp;
ChainType m_chain_type; ChainType m_chain_type;
CBlock genesis; CBlock genesis;
std::vector<uint8_t> vFixedSeeds; std::vector<uint8_t> vFixedSeeds;

View file

@ -16,7 +16,7 @@
#include <secp256k1_recovery.h> #include <secp256k1_recovery.h>
#include <secp256k1_schnorrsig.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. */ /** These functions are taken from the libsecp256k1 distribution and are very ugly. */

View file

@ -288,6 +288,19 @@ public:
friend KeyPair CKey::ComputeKeyPair(const uint256* merkle_root) const; friend KeyPair CKey::ComputeKeyPair(const uint256* merkle_root) const;
[[nodiscard]] bool SignSchnorr(const uint256& hash, std::span<unsigned char> sig, const uint256& aux) const; [[nodiscard]] bool SignSchnorr(const uint256& hash, std::span<unsigned char> sig, const uint256& aux) const;
/**
* data() is provided as a read-only method for passing a KeyPair object to secp256k1 functions
* expecting a `secp256k1_keypair`. This avoids needing to create a temporary `secp256k1_keypair`
* object by allowing the KeyPair to be passed directly in the following manner:
*
* reinterpret_cast<const secp256k1_keypair*>(keypair.data())
*
* Recall that `secp256k1_keypair` is an opaque data type, so this method should only be used
* for passing a KeyPair object as a secp256k1_keypair and should never be used to access the
* underlying keypair bytes directly.
*/
const unsigned char* data() const { return IsValid() ? m_keypair->data() : nullptr; }
//! Check whether this keypair is valid. //! Check whether this keypair is valid.
bool IsValid() const { return !!m_keypair; } bool IsValid() const { return !!m_keypair; }

View file

@ -17,6 +17,8 @@
/// Maximum witness length for Bech32 addresses. /// Maximum witness length for Bech32 addresses.
static constexpr std::size_t BECH32_WITNESS_PROG_MAX_LEN = 40; 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 { namespace {
class DestinationEncoder class DestinationEncoder
@ -65,6 +67,23 @@ public:
return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data); 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 std::string operator()(const WitnessUnknown& id) const
{ {
const std::vector<unsigned char>& program = id.GetWitnessProgram(); const std::vector<unsigned char>& program = id.GetWitnessProgram();
@ -88,7 +107,9 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
error_str = ""; error_str = "";
// Note this will be false if it is a valid Bech32 address for a different network // 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)) { if (!is_bech32 && DecodeBase58Check(str, data, 21)) {
// base58-encoded Bitcoin addresses. // base58-encoded Bitcoin addresses.
@ -128,12 +149,38 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
} }
data.clear(); data.clear();
const auto dec = bech32::Decode(str); const auto dec = bech32::Decode(str, is_silent_payment ? bech32::CharLimit::SILENT_PAYMENTS : bech32::CharLimit::BECH32);
if (dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) { if (dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) {
if (dec.data.empty()) { if (dec.data.empty()) {
error_str = "Empty Bech32 data section"; error_str = "Empty Bech32 data section";
return CNoDestination(); 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 // Bech32 decoding
if (dec.hrp != params.Bech32HRP()) { if (dec.hrp != params.Bech32HRP()) {
error_str = strprintf("Invalid or unsupported prefix for Segwit (Bech32) address (expected %s, got %s).", params.Bech32HRP(), dec.hrp); error_str = strprintf("Invalid or unsupported prefix for Segwit (Bech32) address (expected %s, got %s).", params.Bech32HRP(), dec.hrp);
@ -203,7 +250,7 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
} }
// Perform Bech32 error location // Perform Bech32 error location
auto res = bech32::LocateErrors(str); auto res = bech32::LocateErrors(str, is_silent_payment ? bech32::CharLimit::SILENT_PAYMENTS : bech32::CharLimit::BECH32);
error_str = res.first; error_str = res.first;
if (error_locations) *error_locations = std::move(res.second); if (error_locations) *error_locations = std::move(res.second);
return CNoDestination(); return CNoDestination();

View file

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

View file

@ -24,11 +24,14 @@ env:
SCHNORRSIG: no SCHNORRSIG: no
MUSIG: no MUSIG: no
ELLSWIFT: no ELLSWIFT: no
SILENTPAYMENTS: no
### test options ### test options
SECP256K1_TEST_ITERS: 64 SECP256K1_TEST_ITERS: 64
BENCH: yes BENCH: yes
SECP256K1_BENCH_ITERS: 2 SECP256K1_BENCH_ITERS: 2
CTIMETESTS: yes CTIMETESTS: yes
SYMBOL_CHECK: yes
VIRTUAL_ENV: /root/venv
# Compile and run the tests # Compile and run the tests
EXAMPLES: yes EXAMPLES: yes
@ -53,6 +56,7 @@ cat_logs_snippet: &CAT_LOGS
linux_arm64_container_snippet: &LINUX_ARM64_CONTAINER linux_arm64_container_snippet: &LINUX_ARM64_CONTAINER
env_script: env_script:
- export PATH="$VIRTUAL_ENV/bin:$PATH"
- env | tee /tmp/env - env | tee /tmp/env
build_script: build_script:
- DOCKER_BUILDKIT=1 docker build --file "ci/linux-debian.Dockerfile" --tag="ci_secp256k1_arm" - DOCKER_BUILDKIT=1 docker build --file "ci/linux-debian.Dockerfile" --tag="ci_secp256k1_arm"
@ -72,6 +76,7 @@ task:
SCHNORRSIG: yes SCHNORRSIG: yes
MUSIG: yes MUSIG: yes
ELLSWIFT: yes ELLSWIFT: yes
SILENTPAYMENTS: yes
matrix: matrix:
# Currently only gcc-snapshot, the other compilers are tested on GHA with QEMU # Currently only gcc-snapshot, the other compilers are tested on GHA with QEMU
- env: { CC: 'gcc-snapshot' } - env: { CC: 'gcc-snapshot' }
@ -90,6 +95,7 @@ task:
SCHNORRSIG: yes SCHNORRSIG: yes
MUSIG: yes MUSIG: yes
ELLSWIFT: yes ELLSWIFT: yes
SILENTPAYMENTS: yes
WRAPPER_CMD: 'valgrind --error-exitcode=42' WRAPPER_CMD: 'valgrind --error-exitcode=42'
SECP256K1_TEST_ITERS: 2 SECP256K1_TEST_ITERS: 2
matrix: matrix:

View file

@ -0,0 +1,34 @@
name: "Print logs"
description: "Print the log files produced by ci/ci.sh"
runs:
using: "composite"
steps:
- shell: bash
run: |
# Print the log files produced by ci/ci.sh
# Helper functions
group() {
title=$1
echo "::group::$title"
}
endgroup() {
echo "::endgroup::"
}
cat_file() {
file=$1
group "$file"
cat "$file"
endgroup
}
# Print all *.log files
shopt -s nullglob
for file in *.log; do
cat_file "$file"
done
# Print environment
group "CI env"
env
endgroup

View file

@ -35,11 +35,13 @@ env:
SCHNORRSIG: 'no' SCHNORRSIG: 'no'
MUSIG: 'no' MUSIG: 'no'
ELLSWIFT: 'no' ELLSWIFT: 'no'
SILENTPAYMENTS: 'no'
### test options ### test options
SECP256K1_TEST_ITERS: 64 SECP256K1_TEST_ITERS: 64
BENCH: 'yes' BENCH: 'yes'
SECP256K1_BENCH_ITERS: 2 SECP256K1_BENCH_ITERS: 2
CTIMETESTS: 'yes' CTIMETESTS: 'yes'
SYMBOL_CHECK: 'yes'
# Compile and run the examples. # Compile and run the examples.
EXAMPLES: 'yes' EXAMPLES: 'yes'
@ -73,18 +75,18 @@ jobs:
matrix: matrix:
configuration: configuration:
- env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' } - env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' }
- env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
- env_vars: { WIDEMUL: 'int128' } - env_vars: { WIDEMUL: 'int128' }
- env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' } - env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' }
- env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
- env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' } - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', SILENTPAYMENTS: 'yes' }
- env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' } - env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' }
- env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' } - env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' }
- env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', CPPFLAGS: '-DVERIFY' } - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', SILENTPAYMENTS: 'yes', CPPFLAGS: '-DVERIFY' }
- env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' } - env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' }
- env_vars: { CPPFLAGS: '-DDETERMINISTIC' } - env_vars: { CPPFLAGS: '-DDETERMINISTIC' }
- env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' } - env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' }
- env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
- env_vars: { ECMULTGENKB: 2, ECMULTWINDOW: 2 } - env_vars: { ECMULTGENKB: 2, ECMULTWINDOW: 2 }
- env_vars: { ECMULTGENKB: 86, ECMULTWINDOW: 4 } - env_vars: { ECMULTGENKB: 86, ECMULTWINDOW: 4 }
cc: cc:
@ -107,23 +109,9 @@ jobs:
dockerfile: ./ci/linux-debian.Dockerfile dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image tag: linux-debian-image
- run: cat tests.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat noverify_tests.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
i686_debian: i686_debian:
name: "i686: Linux (Debian stable)" name: "i686: Linux (Debian stable)"
@ -145,6 +133,7 @@ jobs:
SCHNORRSIG: 'yes' SCHNORRSIG: 'yes'
MUSIG: 'yes' MUSIG: 'yes'
ELLSWIFT: 'yes' ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CC: ${{ matrix.cc }} CC: ${{ matrix.cc }}
steps: steps:
@ -157,23 +146,9 @@ jobs:
dockerfile: ./ci/linux-debian.Dockerfile dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image tag: linux-debian-image
- run: cat tests.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat noverify_tests.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
s390x_debian: s390x_debian:
name: "s390x (big-endian): Linux (Debian stable, QEMU)" name: "s390x (big-endian): Linux (Debian stable, QEMU)"
@ -191,6 +166,7 @@ jobs:
SCHNORRSIG: 'yes' SCHNORRSIG: 'yes'
MUSIG: 'yes' MUSIG: 'yes'
ELLSWIFT: 'yes' ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
steps: steps:
@ -203,23 +179,10 @@ jobs:
dockerfile: ./ci/linux-debian.Dockerfile dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image tag: linux-debian-image
- run: cat tests.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat noverify_tests.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
arm32_debian: arm32_debian:
name: "ARM32: Linux (Debian stable, QEMU)" name: "ARM32: Linux (Debian stable, QEMU)"
@ -244,6 +207,7 @@ jobs:
SCHNORRSIG: 'yes' SCHNORRSIG: 'yes'
MUSIG: 'yes' MUSIG: 'yes'
ELLSWIFT: 'yes' ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
steps: steps:
@ -257,23 +221,9 @@ jobs:
dockerfile: ./ci/linux-debian.Dockerfile dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image tag: linux-debian-image
- run: cat tests.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat noverify_tests.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
arm64_debian: arm64_debian:
name: "ARM64: Linux (Debian stable, QEMU)" name: "ARM64: Linux (Debian stable, QEMU)"
@ -291,6 +241,7 @@ jobs:
SCHNORRSIG: 'yes' SCHNORRSIG: 'yes'
MUSIG: 'yes' MUSIG: 'yes'
ELLSWIFT: 'yes' ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
strategy: strategy:
@ -314,23 +265,9 @@ jobs:
dockerfile: ./ci/linux-debian.Dockerfile dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image tag: linux-debian-image
- run: cat tests.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat noverify_tests.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
ppc64le_debian: ppc64le_debian:
name: "ppc64le: Linux (Debian stable, QEMU)" name: "ppc64le: Linux (Debian stable, QEMU)"
@ -348,6 +285,7 @@ jobs:
SCHNORRSIG: 'yes' SCHNORRSIG: 'yes'
MUSIG: 'yes' MUSIG: 'yes'
ELLSWIFT: 'yes' ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
steps: steps:
@ -360,23 +298,10 @@ jobs:
dockerfile: ./ci/linux-debian.Dockerfile dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image tag: linux-debian-image
- run: cat tests.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat noverify_tests.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
valgrind_debian: valgrind_debian:
name: "Valgrind (memcheck)" name: "Valgrind (memcheck)"
@ -402,6 +327,7 @@ jobs:
SCHNORRSIG: 'yes' SCHNORRSIG: 'yes'
MUSIG: 'yes' MUSIG: 'yes'
ELLSWIFT: 'yes' ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
SECP256K1_TEST_ITERS: 2 SECP256K1_TEST_ITERS: 2
@ -416,23 +342,9 @@ jobs:
dockerfile: ./ci/linux-debian.Dockerfile dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image tag: linux-debian-image
- run: cat tests.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat noverify_tests.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
sanitizers_debian: sanitizers_debian:
name: "UBSan, ASan, LSan" name: "UBSan, ASan, LSan"
@ -455,12 +367,14 @@ jobs:
SCHNORRSIG: 'yes' SCHNORRSIG: 'yes'
MUSIG: 'yes' MUSIG: 'yes'
ELLSWIFT: 'yes' ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
CFLAGS: '-fsanitize=undefined,address -g' CFLAGS: '-fsanitize=undefined,address -g'
UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1' UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1'
ASAN_OPTIONS: 'strict_string_checks=1:detect_stack_use_after_return=1:detect_leaks=1' ASAN_OPTIONS: 'strict_string_checks=1:detect_stack_use_after_return=1:detect_leaks=1'
LSAN_OPTIONS: 'use_unaligned=1' LSAN_OPTIONS: 'use_unaligned=1'
SECP256K1_TEST_ITERS: 32 SECP256K1_TEST_ITERS: 32
SYMBOL_CHECK: 'no'
steps: steps:
- name: Checkout - name: Checkout
@ -473,23 +387,9 @@ jobs:
dockerfile: ./ci/linux-debian.Dockerfile dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image tag: linux-debian-image
- run: cat tests.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat noverify_tests.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
msan_debian: msan_debian:
name: "MSan" name: "MSan"
@ -521,10 +421,12 @@ jobs:
SCHNORRSIG: 'yes' SCHNORRSIG: 'yes'
MUSIG: 'yes' MUSIG: 'yes'
ELLSWIFT: 'yes' ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CC: 'clang' CC: 'clang'
SECP256K1_TEST_ITERS: 32 SECP256K1_TEST_ITERS: 32
ASM: 'no' ASM: 'no'
WITH_VALGRIND: 'no' WITH_VALGRIND: 'no'
SYMBOL_CHECK: 'no'
steps: steps:
- name: Checkout - name: Checkout
@ -537,23 +439,10 @@ jobs:
dockerfile: ./ci/linux-debian.Dockerfile dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image tag: linux-debian-image
- run: cat tests.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat noverify_tests.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
mingw_debian: mingw_debian:
name: ${{ matrix.configuration.job_name }} name: ${{ matrix.configuration.job_name }}
@ -569,6 +458,7 @@ jobs:
SCHNORRSIG: 'yes' SCHNORRSIG: 'yes'
MUSIG: 'yes' MUSIG: 'yes'
ELLSWIFT: 'yes' ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
strategy: strategy:
@ -593,23 +483,9 @@ jobs:
dockerfile: ./ci/linux-debian.Dockerfile dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image tag: linux-debian-image
- run: cat tests.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat noverify_tests.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
x86_64-macos-native: x86_64-macos-native:
name: "x86_64: macOS Ventura, Valgrind" name: "x86_64: macOS Ventura, Valgrind"
@ -620,19 +496,20 @@ jobs:
CC: 'clang' CC: 'clang'
HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1
SYMBOL_CHECK: 'no'
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
env_vars: env_vars:
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
- { WIDEMUL: 'int128_struct', ECMULTGENKB: 2, ECMULTWINDOW: 4 } - { WIDEMUL: 'int128_struct', ECMULTGENKB: 2, ECMULTWINDOW: 4 }
- { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes' } - { WIDEMUL: 'int128', RECOVERY: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', CC: 'gcc' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', SILENTPAYMENTS: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' }
- BUILD: 'distcheck' - BUILD: 'distcheck'
@ -652,23 +529,15 @@ jobs:
env: ${{ matrix.env_vars }} env: ${{ matrix.env_vars }}
run: ./ci/ci.sh run: ./ci/ci.sh
- run: cat tests.log || true - name: Symbol check
if: ${{ always() }} run: |
- run: cat noverify_tests.log || true python3 --version
if: ${{ always() }} python3 -m pip install lief
- run: cat exhaustive_tests.log || true python3 ./tools/symbol-check.py .libs/libsecp256k1.dylib
if: ${{ always() }}
- run: cat ctime_tests.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat bench.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
arm64-macos-native: arm64-macos-native:
name: "ARM64: macOS Sonoma" name: "ARM64: macOS Sonoma"
@ -681,6 +550,7 @@ jobs:
HOMEBREW_NO_INSTALL_CLEANUP: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1
WITH_VALGRIND: 'no' WITH_VALGRIND: 'no'
CTIMETESTS: 'no' CTIMETESTS: 'no'
SYMBOL_CHECK: 'no'
strategy: strategy:
fail-fast: false fail-fast: false
@ -708,23 +578,20 @@ jobs:
env: ${{ matrix.env_vars }} env: ${{ matrix.env_vars }}
run: ./ci/ci.sh run: ./ci/ci.sh
- run: cat tests.log || true - name: Symbol check
if: ${{ always() }} env:
- run: cat noverify_tests.log || true VIRTUAL_ENV: '${{ github.workspace }}/venv'
if: ${{ always() }} run: |
- run: cat exhaustive_tests.log || true python3 --version
if: ${{ always() }} python3 -m venv $VIRTUAL_ENV
- run: cat ctime_tests.log || true export PATH="$VIRTUAL_ENV/bin:$PATH"
if: ${{ always() }} python3 -m pip install lief
- run: cat bench.log || true python3 ./tools/symbol-check.py .libs/libsecp256k1.dylib
if: ${{ always() }}
- run: cat config.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat test_env.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
win64-native: win64-native:
name: ${{ matrix.configuration.job_name }} name: ${{ matrix.configuration.job_name }}
@ -737,6 +604,7 @@ jobs:
configuration: configuration:
- job_name: 'x64 (MSVC): Windows (VS 2022, shared)' - job_name: 'x64 (MSVC): Windows (VS 2022, shared)'
cmake_options: '-A x64 -DBUILD_SHARED_LIBS=ON' cmake_options: '-A x64 -DBUILD_SHARED_LIBS=ON'
symbol_check: 'true'
- job_name: 'x64 (MSVC): Windows (VS 2022, static)' - job_name: 'x64 (MSVC): Windows (VS 2022, static)'
cmake_options: '-A x64 -DBUILD_SHARED_LIBS=OFF' cmake_options: '-A x64 -DBUILD_SHARED_LIBS=OFF'
- job_name: 'x64 (MSVC): Windows (VS 2022, int128_struct)' - job_name: 'x64 (MSVC): Windows (VS 2022, int128_struct)'
@ -746,6 +614,8 @@ jobs:
cpp_flags: '/DSECP256K1_MSVC_MULH_TEST_OVERRIDE' cpp_flags: '/DSECP256K1_MSVC_MULH_TEST_OVERRIDE'
- job_name: 'x86 (MSVC): Windows (VS 2022)' - job_name: 'x86 (MSVC): Windows (VS 2022)'
cmake_options: '-A Win32' cmake_options: '-A Win32'
- job_name: 'x64 (MSVC): Windows (clang-cl)'
cmake_options: '-T ClangCL'
steps: steps:
- name: Checkout - name: Checkout
@ -763,6 +633,13 @@ jobs:
run: | run: |
cd build/bin/RelWithDebInfo && file *tests.exe bench*.exe libsecp256k1-*.dll || true cd build/bin/RelWithDebInfo && file *tests.exe bench*.exe libsecp256k1-*.dll || true
- name: Symbol check
if: ${{ matrix.configuration.symbol_check }}
run: |
py -3 --version
py -3 -m pip install lief
py -3 .\tools\symbol-check.py build\bin\RelWithDebInfo\libsecp256k1-5.dll
- name: Check - name: Check
run: | run: |
ctest -C RelWithDebInfo --test-dir build -j ([int]$env:NUMBER_OF_PROCESSORS + 1) ctest -C RelWithDebInfo --test-dir build -j ([int]$env:NUMBER_OF_PROCESSORS + 1)
@ -802,6 +679,7 @@ jobs:
SCHNORRSIG: 'yes' SCHNORRSIG: 'yes'
MUSIG: 'yes' MUSIG: 'yes'
ELLSWIFT: 'yes' ELLSWIFT: 'yes'
SILENTPAYMENTS: 'yes'
steps: steps:
- name: Checkout - name: Checkout
@ -813,23 +691,9 @@ jobs:
dockerfile: ./ci/linux-debian.Dockerfile dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image tag: linux-debian-image
- run: cat tests.log || true - name: Print logs
if: ${{ always() }} uses: ./.github/actions/print-logs
- run: cat noverify_tests.log || true if: ${{ !cancelled() }}
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
cxx_headers_debian: cxx_headers_debian:
name: "C++ (public headers)" name: "C++ (public headers)"

View file

@ -12,6 +12,7 @@ ecdsa_example
schnorr_example schnorr_example
ellswift_example ellswift_example
musig_example musig_example
silentpayments_example
*.exe *.exe
*.so *.so
*.a *.a

View file

@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
#### Removed
- Removed previously deprecated function aliases `secp256k1_ec_privkey_negate`, `secp256k1_ec_privkey_tweak_add` and
`secp256k1_ec_privkey_tweak_mul`. Use `secp256k1_ec_seckey_negate`, `secp256k1_ec_seckey_tweak_add` and
`secp256k1_ec_seckey_tweak_mul` instead.
## [0.6.0] - 2024-11-04 ## [0.6.0] - 2024-11-04
#### Added #### Added
@ -162,6 +169,7 @@ This version was in fact never released.
The number was given by the build system since the introduction of autotools in Jan 2014 (ea0fe5a5bf0c04f9cc955b2966b614f5f378c6f6). The number was given by the build system since the introduction of autotools in Jan 2014 (ea0fe5a5bf0c04f9cc955b2966b614f5f378c6f6).
Therefore, this version number does not uniquely identify a set of source files. Therefore, this version number does not uniquely identify a set of source files.
[unreleased]: https://github.com/bitcoin-core/secp256k1/compare/v0.6.0...HEAD
[0.6.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.1...v0.6.0 [0.6.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.1...v0.6.0
[0.5.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.0...v0.5.1 [0.5.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.0...v0.5.1
[0.5.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.4.1...v0.5.0 [0.5.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.4.1...v0.5.0

View file

@ -7,7 +7,7 @@ project(libsecp256k1
# The package (a.k.a. release) version is based on semantic versioning 2.0.0 of # The package (a.k.a. release) version is based on semantic versioning 2.0.0 of
# the API. All changes in experimental modules are treated as # the API. All changes in experimental modules are treated as
# backwards-compatible and therefore at most increase the minor version. # backwards-compatible and therefore at most increase the minor version.
VERSION 0.6.0 VERSION 0.6.1
DESCRIPTION "Optimized C library for ECDSA signatures and secret/public key operations on curve secp256k1." DESCRIPTION "Optimized C library for ECDSA signatures and secret/public key operations on curve secp256k1."
HOMEPAGE_URL "https://github.com/bitcoin-core/secp256k1" HOMEPAGE_URL "https://github.com/bitcoin-core/secp256k1"
LANGUAGES C LANGUAGES C
@ -32,7 +32,7 @@ endif()
# All changes in experimental modules are treated as if they don't affect the # All changes in experimental modules are treated as if they don't affect the
# interface and therefore only increase the revision. # interface and therefore only increase the revision.
set(${PROJECT_NAME}_LIB_VERSION_CURRENT 5) set(${PROJECT_NAME}_LIB_VERSION_CURRENT 5)
set(${PROJECT_NAME}_LIB_VERSION_REVISION 0) set(${PROJECT_NAME}_LIB_VERSION_REVISION 1)
set(${PROJECT_NAME}_LIB_VERSION_AGE 0) set(${PROJECT_NAME}_LIB_VERSION_AGE 0)
#============================= #=============================
@ -55,16 +55,29 @@ option(SECP256K1_INSTALL "Enable installation." ${PROJECT_IS_TOP_LEVEL})
## Modules ## Modules
# We declare all options before processing them, to make sure we can express # We declare all options before processing them, to make sure we can express
# dependendencies while processing. # dependencies while processing.
option(SECP256K1_ENABLE_MODULE_ECDH "Enable ECDH module." ON) option(SECP256K1_ENABLE_MODULE_ECDH "Enable ECDH module." ON)
option(SECP256K1_ENABLE_MODULE_RECOVERY "Enable ECDSA pubkey recovery module." OFF) option(SECP256K1_ENABLE_MODULE_RECOVERY "Enable ECDSA pubkey recovery module." OFF)
option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON) option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON)
option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON) option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON)
option(SECP256K1_ENABLE_MODULE_MUSIG "Enable musig module." ON) option(SECP256K1_ENABLE_MODULE_MUSIG "Enable musig module." ON)
option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON) option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON)
option(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS "Enable Silent Payments module." ON)
# Processing must be done in a topological sorting of the dependency graph # Processing must be done in a topological sorting of the dependency graph
# (dependent module first). # (dependent module first).
if(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS)
if(DEFINED SECP256K1_ENABLE_MODULE_SCHNORRSIG AND NOT SECP256K1_ENABLE_MODULE_SCHNORRSIG)
message(FATAL_ERROR "Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the silentpayments module.")
endif()
if(DEFINED SECP256K1_ENABLE_MODULE_EXTRAKEYS AND NOT SECP256K1_ENABLE_MODULE_EXTRAKEYS)
message(FATAL_ERROR "Module dependency error: You have disabled the extrakeys module explicitly, but it is required by the silentpayments module.")
endif()
set(SECP256K1_ENABLE_MODULE_EXTRAKEYS ON)
set(SECP256K1_ENABLE_MODULE_SCHNORRSIG ON)
add_compile_definitions(ENABLE_MODULE_SILENTPAYMENTS=1)
endif()
if(SECP256K1_ENABLE_MODULE_ELLSWIFT) if(SECP256K1_ENABLE_MODULE_ELLSWIFT)
add_compile_definitions(ENABLE_MODULE_ELLSWIFT=1) add_compile_definitions(ENABLE_MODULE_ELLSWIFT=1)
endif() endif()
@ -242,17 +255,21 @@ endif()
include(TryAppendCFlags) include(TryAppendCFlags)
if(MSVC) if(MSVC)
# Keep the following commands ordered lexicographically. # For both cl and clang-cl compilers.
try_append_c_flags(/W3) # Production quality warning level. try_append_c_flags(/W3) # Production quality warning level.
try_append_c_flags(/wd4146) # Disable warning C4146 "unary minus operator applied to unsigned type, result still unsigned".
try_append_c_flags(/wd4244) # Disable warning C4244 "'conversion' conversion from 'type1' to 'type2', possible loss of data".
try_append_c_flags(/wd4267) # Disable warning C4267 "'var' : conversion from 'size_t' to 'type', possible loss of data".
# Eliminate deprecation warnings for the older, less secure functions. # Eliminate deprecation warnings for the older, less secure functions.
add_compile_definitions(_CRT_SECURE_NO_WARNINGS) add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
else()
try_append_c_flags(-Wall) # GCC >= 2.95 and probably many other compilers.
endif()
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
# Keep the following commands ordered lexicographically.
try_append_c_flags(/wd4146) # Disable warning C4146 "unary minus operator applied to unsigned type, result still unsigned".
try_append_c_flags(/wd4244) # Disable warning C4244 "'conversion' conversion from 'type1' to 'type2', possible loss of data".
try_append_c_flags(/wd4267) # Disable warning C4267 "'var' : conversion from 'size_t' to 'type', possible loss of data".
else() else()
# Keep the following commands ordered lexicographically. # Keep the following commands ordered lexicographically.
try_append_c_flags(-pedantic) try_append_c_flags(-pedantic)
try_append_c_flags(-Wall) # GCC >= 2.95 and probably many other compilers.
try_append_c_flags(-Wcast-align) # GCC >= 2.95. try_append_c_flags(-Wcast-align) # GCC >= 2.95.
try_append_c_flags(-Wcast-align=strict) # GCC >= 8.0. try_append_c_flags(-Wcast-align=strict) # GCC >= 8.0.
try_append_c_flags(-Wconditional-uninitialized) # Clang >= 3.0 only. try_append_c_flags(-Wconditional-uninitialized) # Clang >= 3.0 only.
@ -267,8 +284,6 @@ else()
try_append_c_flags(-Wundef) try_append_c_flags(-Wundef)
endif() endif()
set(CMAKE_C_VISIBILITY_PRESET hidden)
set(print_msan_notice) set(print_msan_notice)
if(SECP256K1_BUILD_CTIME_TESTS) if(SECP256K1_BUILD_CTIME_TESTS)
include(CheckMemorySanitizer) include(CheckMemorySanitizer)
@ -327,6 +342,7 @@ message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRA
message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}") message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}")
message(" musig ............................... ${SECP256K1_ENABLE_MODULE_MUSIG}") message(" musig ............................... ${SECP256K1_ENABLE_MODULE_MUSIG}")
message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}") message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}")
message(" Silent Payments ..................... ${SECP256K1_ENABLE_MODULE_SILENTPAYMENTS}")
message("Parameters:") message("Parameters:")
message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}") message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}")
message(" ecmult gen table size ............... ${SECP256K1_ECMULT_GEN_KB} KiB") message(" ecmult gen table size ............... ${SECP256K1_ECMULT_GEN_KB} KiB")

View file

@ -74,6 +74,7 @@ In addition, libsecp256k1 tries to maintain the following coding conventions:
* User-facing comment lines in headers should be limited to 80 chars if possible. * User-facing comment lines in headers should be limited to 80 chars if possible.
* All identifiers in file scope should start with `secp256k1_`. * All identifiers in file scope should start with `secp256k1_`.
* Avoid trailing whitespace. * Avoid trailing whitespace.
* Use the constants `EXIT_SUCCESS`/`EXIT_FAILURE` (defined in `stdlib.h`) to indicate program execution status for examples and other binaries.
### Tests ### Tests

View file

@ -47,6 +47,7 @@ noinst_HEADERS += src/assumptions.h
noinst_HEADERS += src/checkmem.h noinst_HEADERS += src/checkmem.h
noinst_HEADERS += src/testutil.h noinst_HEADERS += src/testutil.h
noinst_HEADERS += src/util.h noinst_HEADERS += src/util.h
noinst_HEADERS += src/util_local_visibility.h
noinst_HEADERS += src/int128.h noinst_HEADERS += src/int128.h
noinst_HEADERS += src/int128_impl.h noinst_HEADERS += src/int128_impl.h
noinst_HEADERS += src/int128_native.h noinst_HEADERS += src/int128_native.h
@ -206,6 +207,17 @@ musig_example_LDFLAGS += -lbcrypt
endif endif
TESTS += musig_example TESTS += musig_example
endif 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 endif
### Precomputed tables ### Precomputed tables
@ -248,11 +260,16 @@ maintainer-clean-local: clean-precomp
### Pregenerated test vectors ### Pregenerated test vectors
### (see the comments in the previous section for detailed rationale) ### (see the comments in the previous section for detailed rationale)
TESTVECTORS = src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.h TESTVECTORS = src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.h
TESTVECTORS += src/modules/silentpayments/vectors.h
src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.h: src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.h:
mkdir -p $(@D) mkdir -p $(@D)
python3 $(top_srcdir)/tools/tests_wycheproof_generate.py $(top_srcdir)/src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.json > $@ python3 $(top_srcdir)/tools/tests_wycheproof_generate.py $(top_srcdir)/src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.json > $@
src/modules/silentpayments/vectors.h:
mkdir -p $(@D)
python3 $(top_srcdir)/tools/tests_silentpayments_generate.py $(top_srcdir)/src/modules/silentpayments/bip352_send_and_receive_test_vectors.json > $@
testvectors: $(TESTVECTORS) testvectors: $(TESTVECTORS)
BUILT_SOURCES += $(TESTVECTORS) BUILT_SOURCES += $(TESTVECTORS)
@ -300,3 +317,7 @@ endif
if ENABLE_MODULE_ELLSWIFT if ENABLE_MODULE_ELLSWIFT
include src/modules/ellswift/Makefile.am.include include src/modules/ellswift/Makefile.am.include
endif endif
if ENABLE_MODULE_SILENTPAYMENTS
include src/modules/silentpayments/Makefile.am.include
endif

View file

@ -22,6 +22,7 @@ Features:
* Optional module for Schnorr signatures according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). * Optional module for Schnorr signatures according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
* Optional module for ElligatorSwift key exchange according to [BIP-324](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki). * Optional module for ElligatorSwift key exchange according to [BIP-324](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki).
* Optional module for MuSig2 Schnorr multi-signatures according to [BIP-327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki). * Optional module for MuSig2 Schnorr multi-signatures according to [BIP-327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki).
* Optional module for Silent Payments send and receive according to [BIP-352](https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki).
Implementation details Implementation details
---------------------- ----------------------
@ -61,14 +62,50 @@ Implementation details
* Optional runtime blinding which attempts to frustrate differential power analysis. * Optional runtime blinding which attempts to frustrate differential power analysis.
* The precomputed tables add and eventually subtract points for which no known scalar (secret key) is known, preventing even an attacker with control over the secret key used to control the data internally. * The precomputed tables add and eventually subtract points for which no known scalar (secret key) is known, preventing even an attacker with control over the secret key used to control the data internally.
Obtaining and verifying
-----------------------
The git tag for each release (e.g. `v0.6.0`) is GPG-signed by one of the maintainers.
For a fully verified build of this project, it is recommended to obtain this repository
via git, obtain the GPG keys of the signing maintainer(s), and then verify the release
tag's signature using git.
This can be done with the following steps:
1. Obtain the GPG keys listed in [SECURITY.md](./SECURITY.md).
2. If possible, cross-reference these key IDs with another source controlled by its owner (e.g.
social media, personal website). This is to mitigate the unlikely case that incorrect
content is being presented by this repository.
3. Clone the repository:
```
git clone https://github.com/bitcoin-core/secp256k1
```
4. Check out the latest release tag, e.g.
```
git checkout v0.6.0
```
5. Use git to verify the GPG signature:
```
% git tag -v v0.6.0 | grep -C 3 'Good signature'
gpg: Signature made Mon 04 Nov 2024 12:14:44 PM EST
gpg: using RSA key 4BBB845A6F5A65A69DFAEC234861DBF262123605
gpg: Good signature from "Jonas Nick <jonas@n-ck.net>" [unknown]
gpg: aka "Jonas Nick <jonasd.nick@gmail.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 36C7 1A37 C9D9 88BD E825 08D9 B1A7 0E4F 8DCD 0366
Subkey fingerprint: 4BBB 845A 6F5A 65A6 9DFA EC23 4861 DBF2 6212 3605
```
Building with Autotools Building with Autotools
----------------------- -----------------------
$ ./autogen.sh $ ./autogen.sh # Generate a ./configure script
$ ./configure $ ./configure # Generate a build system
$ make $ make # Run the actual build process
$ make check # run the test suite $ make check # Run the test suite
$ sudo make install # optional $ sudo make install # Install the library into the system (optional)
To compile optional modules (such as Schnorr signatures), you need to run `./configure` with additional flags (such as `--enable-module-schnorrsig`). Run `./configure --help` to see the full list of available flags. To compile optional modules (such as Schnorr signatures), you need to run `./configure` with additional flags (such as `--enable-module-schnorrsig`). Run `./configure --help` to see the full list of available flags.
@ -79,24 +116,23 @@ To maintain a pristine source tree, CMake encourages to perform an out-of-source
### Building on POSIX systems ### Building on POSIX systems
$ mkdir build && cd build $ cmake -B build # Generate a build system in subdirectory "build"
$ cmake .. $ cmake --build build # Run the actual build process
$ cmake --build . $ ctest --test-dir build # Run the test suite
$ ctest # run the test suite $ sudo cmake --install build # Install the library into the system (optional)
$ sudo cmake --install . # optional
To compile optional modules (such as Schnorr signatures), you need to run `cmake` with additional flags (such as `-DSECP256K1_ENABLE_MODULE_SCHNORRSIG=ON`). Run `cmake .. -LH` to see the full list of available flags. To compile optional modules (such as Schnorr signatures), you need to run `cmake` with additional flags (such as `-DSECP256K1_ENABLE_MODULE_SCHNORRSIG=ON`). Run `cmake -B build -LH` or `ccmake -B build` to see the full list of available flags.
### Cross compiling ### Cross compiling
To alleviate issues with cross compiling, preconfigured toolchain files are available in the `cmake` directory. To alleviate issues with cross compiling, preconfigured toolchain files are available in the `cmake` directory.
For example, to cross compile for Windows: For example, to cross compile for Windows:
$ cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/x86_64-w64-mingw32.toolchain.cmake $ cmake -B build -DCMAKE_TOOLCHAIN_FILE=cmake/x86_64-w64-mingw32.toolchain.cmake
To cross compile for Android with [NDK](https://developer.android.com/ndk/guides/cmake) (using NDK's toolchain file, and assuming the `ANDROID_NDK_ROOT` environment variable has been set): To cross compile for Android with [NDK](https://developer.android.com/ndk/guides/cmake) (using NDK's toolchain file, and assuming the `ANDROID_NDK_ROOT` environment variable has been set):
$ cmake .. -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake" -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=28 $ cmake -B build -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake" -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=28
### Building on Windows ### Building on Windows
@ -106,7 +142,7 @@ The following example assumes using of Visual Studio 2022 and CMake v3.21+.
In "Developer Command Prompt for VS 2022": In "Developer Command Prompt for VS 2022":
>cmake -G "Visual Studio 17 2022" -A x64 -S . -B build >cmake -G "Visual Studio 17 2022" -A x64 -B build
>cmake --build build --config RelWithDebInfo >cmake --build build --config RelWithDebInfo
Usage examples Usage examples
@ -116,6 +152,7 @@ Usage examples can be found in the [examples](examples) directory. To compile th
* [Schnorr signatures example](examples/schnorr.c) * [Schnorr signatures example](examples/schnorr.c)
* [Deriving a shared secret (ECDH) example](examples/ecdh.c) * [Deriving a shared secret (ECDH) example](examples/ecdh.c)
* [ElligatorSwift key exchange example](examples/ellswift.c) * [ElligatorSwift key exchange example](examples/ellswift.c)
* [Silent Payments send and receive example](examples/silentpayments.c)
To compile the Schnorr signature and ECDH examples, you also need to configure with `--enable-module-schnorrsig` and `--enable-module-ecdh`. To compile the Schnorr signature and ECDH examples, you also need to configure with `--enable-module-schnorrsig` and `--enable-module-ecdh`.

View file

@ -13,8 +13,8 @@ print_environment() {
# does not rely on bash. # does not rely on bash.
for var in WERROR_CFLAGS MAKEFLAGS BUILD \ for var in WERROR_CFLAGS MAKEFLAGS BUILD \
ECMULTWINDOW ECMULTGENKB ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \ ECMULTWINDOW ECMULTGENKB ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \
EXPERIMENTAL ECDH RECOVERY EXTRAKEYS MUSIG SCHNORRSIG ELLSWIFT \ EXPERIMENTAL ECDH RECOVERY EXTRAKEYS MUSIG SCHNORRSIG ELLSWIFT SILENTPAYMENTS \
SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\ SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS SYMBOL_CHECK \
EXAMPLES \ EXAMPLES \
HOST WRAPPER_CMD \ HOST WRAPPER_CMD \
CC CFLAGS CPPFLAGS AR NM \ CC CFLAGS CPPFLAGS AR NM \
@ -80,6 +80,7 @@ esac
--enable-module-extrakeys="$EXTRAKEYS" \ --enable-module-extrakeys="$EXTRAKEYS" \
--enable-module-schnorrsig="$SCHNORRSIG" \ --enable-module-schnorrsig="$SCHNORRSIG" \
--enable-module-musig="$MUSIG" \ --enable-module-musig="$MUSIG" \
--enable-module-silentpayments="$SILENTPAYMENTS" \
--enable-examples="$EXAMPLES" \ --enable-examples="$EXAMPLES" \
--enable-ctime-tests="$CTIMETESTS" \ --enable-ctime-tests="$CTIMETESTS" \
--with-valgrind="$WITH_VALGRIND" \ --with-valgrind="$WITH_VALGRIND" \
@ -94,10 +95,10 @@ if [ $build_exit_code -ne 0 ]; then
*snapshot*) *snapshot*)
# Ignore internal compiler errors in gcc-snapshot and clang-snapshot # Ignore internal compiler errors in gcc-snapshot and clang-snapshot
grep -e "internal compiler error:" -e "PLEASE submit a bug report" make.log grep -e "internal compiler error:" -e "PLEASE submit a bug report" make.log
return $?; exit $?
;; ;;
*) *)
return 1; exit 1
;; ;;
esac esac
fi fi
@ -107,6 +108,20 @@ file *tests* || true
file bench* || true file bench* || true
file .libs/* || true file .libs/* || true
if [ "$SYMBOL_CHECK" = "yes" ]
then
python3 --version
case "$HOST" in
*mingw*)
ls -l .libs
python3 ./tools/symbol-check.py .libs/libsecp256k1-5.dll
;;
*)
python3 ./tools/symbol-check.py .libs/libsecp256k1.so
;;
esac
fi
# This tells `make check` to wrap test invocations. # This tells `make check` to wrap test invocations.
export LOG_COMPILER="$WRAPPER_CMD" export LOG_COMPILER="$WRAPPER_CMD"

View file

@ -32,7 +32,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
gcc-powerpc64le-linux-gnu libc6-dev-ppc64el-cross libc6-dbg:ppc64el \ gcc-powerpc64le-linux-gnu libc6-dev-ppc64el-cross libc6-dbg:ppc64el \
gcc-mingw-w64-x86-64-win32 wine64 wine \ gcc-mingw-w64-x86-64-win32 wine64 wine \
gcc-mingw-w64-i686-win32 wine32 \ gcc-mingw-w64-i686-win32 wine32 \
python3 && \ python3-full && \
if ! ( dpkg --print-architecture | grep --quiet "arm64" ) ; then \ if ! ( dpkg --print-architecture | grep --quiet "arm64" ) ; then \
apt-get install --no-install-recommends -y \ apt-get install --no-install-recommends -y \
gcc-aarch64-linux-gnu libc6-dev-arm64-cross libc6-dbg:arm64 ;\ gcc-aarch64-linux-gnu libc6-dev-arm64-cross libc6-dbg:arm64 ;\
@ -77,3 +77,7 @@ RUN \
apt-get autoremove -y wget && \ apt-get autoremove -y wget && \
apt-get clean && rm -rf /var/lib/apt/lists/* apt-get clean && rm -rf /var/lib/apt/lists/*
ENV VIRTUAL_ENV=/root/venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
RUN pip install lief

View file

@ -5,8 +5,8 @@ AC_PREREQ([2.60])
# backwards-compatible and therefore at most increase the minor version. # backwards-compatible and therefore at most increase the minor version.
define(_PKG_VERSION_MAJOR, 0) define(_PKG_VERSION_MAJOR, 0)
define(_PKG_VERSION_MINOR, 6) define(_PKG_VERSION_MINOR, 6)
define(_PKG_VERSION_PATCH, 0) define(_PKG_VERSION_PATCH, 1)
define(_PKG_VERSION_IS_RELEASE, true) define(_PKG_VERSION_IS_RELEASE, false)
# The library version is based on libtool versioning of the ABI. The set of # The library version is based on libtool versioning of the ABI. The set of
# rules for updating the version can be found here: # rules for updating the version can be found here:
@ -14,7 +14,7 @@ define(_PKG_VERSION_IS_RELEASE, true)
# All changes in experimental modules are treated as if they don't affect the # All changes in experimental modules are treated as if they don't affect the
# interface and therefore only increase the revision. # interface and therefore only increase the revision.
define(_LIB_VERSION_CURRENT, 5) define(_LIB_VERSION_CURRENT, 5)
define(_LIB_VERSION_REVISION, 0) define(_LIB_VERSION_REVISION, 1)
define(_LIB_VERSION_AGE, 0) define(_LIB_VERSION_AGE, 0)
AC_INIT([libsecp256k1],m4_join([.], _PKG_VERSION_MAJOR, _PKG_VERSION_MINOR, _PKG_VERSION_PATCH)m4_if(_PKG_VERSION_IS_RELEASE, [true], [], [-dev]),[https://github.com/bitcoin-core/secp256k1/issues],[libsecp256k1],[https://github.com/bitcoin-core/secp256k1]) AC_INIT([libsecp256k1],m4_join([.], _PKG_VERSION_MAJOR, _PKG_VERSION_MINOR, _PKG_VERSION_PATCH)m4_if(_PKG_VERSION_IS_RELEASE, [true], [], [-dev]),[https://github.com/bitcoin-core/secp256k1/issues],[libsecp256k1],[https://github.com/bitcoin-core/secp256k1])
@ -111,7 +111,6 @@ AC_DEFUN([SECP_TRY_APPEND_DEFAULT_CFLAGS], [
SECP_TRY_APPEND_CFLAGS([-Wcast-align=strict], $1) # GCC >= 8.0 SECP_TRY_APPEND_CFLAGS([-Wcast-align=strict], $1) # GCC >= 8.0
SECP_TRY_APPEND_CFLAGS([-Wconditional-uninitialized], $1) # Clang >= 3.0 only SECP_TRY_APPEND_CFLAGS([-Wconditional-uninitialized], $1) # Clang >= 3.0 only
SECP_TRY_APPEND_CFLAGS([-Wreserved-identifier], $1) # Clang >= 13.0 only SECP_TRY_APPEND_CFLAGS([-Wreserved-identifier], $1) # Clang >= 13.0 only
SECP_TRY_APPEND_CFLAGS([-fvisibility=hidden], $1) # GCC >= 4.0
CFLAGS="$SECP_TRY_APPEND_DEFAULT_CFLAGS_saved_CFLAGS" CFLAGS="$SECP_TRY_APPEND_DEFAULT_CFLAGS_saved_CFLAGS"
fi fi
@ -192,6 +191,10 @@ AC_ARG_ENABLE(module_ellswift,
AS_HELP_STRING([--enable-module-ellswift],[enable ElligatorSwift module [default=yes]]), [], AS_HELP_STRING([--enable-module-ellswift],[enable ElligatorSwift module [default=yes]]), [],
[SECP_SET_DEFAULT([enable_module_ellswift], [yes], [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=yes]]), [],
[SECP_SET_DEFAULT([enable_module_silentpayments], [yes], [yes])])
AC_ARG_ENABLE(external_default_callbacks, AC_ARG_ENABLE(external_default_callbacks,
AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [], AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [],
[SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])]) [SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])])
@ -254,8 +257,8 @@ fi
print_msan_notice=no print_msan_notice=no
if test x"$enable_ctime_tests" = x"yes"; then if test x"$enable_ctime_tests" = x"yes"; then
SECP_MSAN_CHECK SECP_MSAN_CHECK
# MSan on Clang >=16 reports unitialized memory in function parameters and return values, even if # MSan on Clang >=16 reports uninitialized memory in function parameters and return values, even if
# the uninitalized variable is never actually "used". This is called "eager" checking, and it's # the uninitialized variable is never actually "used". This is called "eager" checking, and it's
# sounds like good idea for normal use of MSan. However, it yields many false positives in the # sounds like good idea for normal use of MSan. However, it yields many false positives in the
# ctime_tests because many return values depend on secret (i.e., "uninitialized") values, and # ctime_tests because many return values depend on secret (i.e., "uninitialized") values, and
# we're only interested in detecting branches (which count as "uses") on secret data. # we're only interested in detecting branches (which count as "uses") on secret data.
@ -398,6 +401,19 @@ SECP_CFLAGS="$SECP_CFLAGS $WERROR_CFLAGS"
# Processing must be done in a reverse topological sorting of the dependency graph # Processing must be done in a reverse topological sorting of the dependency graph
# (dependent module first). # (dependent module first).
if test x"$enable_module_silentpayments" = x"yes"; then
if test x"$enable_module_schnorrsig" = x"no"; then
AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the silentpayments module.])
fi
enable_module_schnorrsig=yes
if test x"$enable_module_extrakeys" = x"no"; then
AC_MSG_ERROR([Module dependency error: You have disabled the extrakeys module explicitly, but it is required by the silentpayments module.])
fi
enable_module_extrakeys=yes
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_SILENTPAYMENTS=1"
fi
if test x"$enable_module_ellswift" = x"yes"; then if test x"$enable_module_ellswift" = x"yes"; then
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1" SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1"
fi fi
@ -463,6 +479,7 @@ AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x"
AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_MUSIG], [test x"$enable_module_musig" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_MUSIG], [test x"$enable_module_musig" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_ELLSWIFT], [test x"$enable_module_ellswift" = 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_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"])
AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"]) AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"])
AM_CONDITIONAL([BUILD_WINDOWS], [test "$build_windows" = "yes"]) AM_CONDITIONAL([BUILD_WINDOWS], [test "$build_windows" = "yes"])
@ -477,6 +494,7 @@ echo "Build Options:"
echo " with external callbacks = $enable_external_default_callbacks" echo " with external callbacks = $enable_external_default_callbacks"
echo " with benchmarks = $enable_benchmark" echo " with benchmarks = $enable_benchmark"
echo " with tests = $enable_tests" echo " with tests = $enable_tests"
echo " with exhaustive tests = $enable_exhaustive_tests"
echo " with ctime tests = $enable_ctime_tests" echo " with ctime tests = $enable_ctime_tests"
echo " with coverage = $enable_coverage" echo " with coverage = $enable_coverage"
echo " with examples = $enable_examples" echo " with examples = $enable_examples"
@ -486,6 +504,7 @@ echo " module extrakeys = $enable_module_extrakeys"
echo " module schnorrsig = $enable_module_schnorrsig" echo " module schnorrsig = $enable_module_schnorrsig"
echo " module musig = $enable_module_musig" echo " module musig = $enable_module_musig"
echo " module ellswift = $enable_module_ellswift" echo " module ellswift = $enable_module_ellswift"
echo " module silentpayments = $enable_module_silentpayments"
echo echo
echo " asm = $set_asm" echo " asm = $set_asm"
echo " ecmult window size = $set_ecmult_window" echo " ecmult window size = $set_ecmult_window"

View file

@ -29,3 +29,7 @@ endif()
if(SECP256K1_ENABLE_MODULE_MUSIG) if(SECP256K1_ENABLE_MODULE_MUSIG)
add_example(musig) add_example(musig)
endif() endif()
if(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS)
add_example(silentpayments)
endif()

View file

@ -8,6 +8,7 @@
*************************************************************************/ *************************************************************************/
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
@ -33,7 +34,7 @@ int main(void) {
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
if (!fill_random(randomize, sizeof(randomize))) { if (!fill_random(randomize, sizeof(randomize))) {
printf("Failed to generate randomness\n"); printf("Failed to generate randomness\n");
return 1; return EXIT_FAILURE;
} }
/* Randomizing the context is recommended to protect against side-channel /* Randomizing the context is recommended to protect against side-channel
* leakage See `secp256k1_context_randomize` in secp256k1.h for more * leakage See `secp256k1_context_randomize` in secp256k1.h for more
@ -44,14 +45,14 @@ int main(void) {
/*** Key Generation ***/ /*** Key Generation ***/
if (!fill_random(seckey1, sizeof(seckey1)) || !fill_random(seckey2, sizeof(seckey2))) { if (!fill_random(seckey1, sizeof(seckey1)) || !fill_random(seckey2, sizeof(seckey2))) {
printf("Failed to generate randomness\n"); printf("Failed to generate randomness\n");
return 1; return EXIT_FAILURE;
} }
/* If the secret key is zero or out of range (greater than secp256k1's /* If the secret key is zero or out of range (greater than secp256k1's
* order), we fail. Note that the probability of this occurring is negligible * order), we fail. Note that the probability of this occurring is negligible
* with a properly functioning random number generator. */ * with a properly functioning random number generator. */
if (!secp256k1_ec_seckey_verify(ctx, seckey1) || !secp256k1_ec_seckey_verify(ctx, seckey2)) { if (!secp256k1_ec_seckey_verify(ctx, seckey1) || !secp256k1_ec_seckey_verify(ctx, seckey2)) {
printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n"); printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n");
return 1; return EXIT_FAILURE;
} }
/* Public key creation using a valid context with a verified secret key should never fail */ /* Public key creation using a valid context with a verified secret key should never fail */
@ -116,5 +117,5 @@ int main(void) {
secure_erase(shared_secret1, sizeof(shared_secret1)); secure_erase(shared_secret1, sizeof(shared_secret1));
secure_erase(shared_secret2, sizeof(shared_secret2)); secure_erase(shared_secret2, sizeof(shared_secret2));
return 0; return EXIT_SUCCESS;
} }

View file

@ -8,6 +8,7 @@
*************************************************************************/ *************************************************************************/
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
@ -40,7 +41,7 @@ int main(void) {
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
if (!fill_random(randomize, sizeof(randomize))) { if (!fill_random(randomize, sizeof(randomize))) {
printf("Failed to generate randomness\n"); printf("Failed to generate randomness\n");
return 1; return EXIT_FAILURE;
} }
/* Randomizing the context is recommended to protect against side-channel /* Randomizing the context is recommended to protect against side-channel
* leakage See `secp256k1_context_randomize` in secp256k1.h for more * leakage See `secp256k1_context_randomize` in secp256k1.h for more
@ -51,14 +52,14 @@ int main(void) {
/*** Key Generation ***/ /*** Key Generation ***/
if (!fill_random(seckey, sizeof(seckey))) { if (!fill_random(seckey, sizeof(seckey))) {
printf("Failed to generate randomness\n"); printf("Failed to generate randomness\n");
return 1; return EXIT_FAILURE;
} }
/* If the secret key is zero or out of range (greater than secp256k1's /* If the secret key is zero or out of range (greater than secp256k1's
* order), we fail. Note that the probability of this occurring is negligible * order), we fail. Note that the probability of this occurring is negligible
* with a properly functioning random number generator. */ * with a properly functioning random number generator. */
if (!secp256k1_ec_seckey_verify(ctx, seckey)) { if (!secp256k1_ec_seckey_verify(ctx, seckey)) {
printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n"); printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n");
return 1; return EXIT_FAILURE;
} }
/* Public key creation using a valid context with a verified secret key should never fail */ /* Public key creation using a valid context with a verified secret key should never fail */
@ -92,13 +93,13 @@ int main(void) {
/* Deserialize the signature. This will return 0 if the signature can't be parsed correctly. */ /* Deserialize the signature. This will return 0 if the signature can't be parsed correctly. */
if (!secp256k1_ecdsa_signature_parse_compact(ctx, &sig, serialized_signature)) { if (!secp256k1_ecdsa_signature_parse_compact(ctx, &sig, serialized_signature)) {
printf("Failed parsing the signature\n"); printf("Failed parsing the signature\n");
return 1; return EXIT_FAILURE;
} }
/* Deserialize the public key. This will return 0 if the public key can't be parsed correctly. */ /* Deserialize the public key. This will return 0 if the public key can't be parsed correctly. */
if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, compressed_pubkey, sizeof(compressed_pubkey))) { if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, compressed_pubkey, sizeof(compressed_pubkey))) {
printf("Failed parsing the public key\n"); printf("Failed parsing the public key\n");
return 1; return EXIT_FAILURE;
} }
/* Verify a signature. This will return 1 if it's valid and 0 if it's not. */ /* Verify a signature. This will return 1 if it's valid and 0 if it's not. */
@ -133,5 +134,5 @@ int main(void) {
* will remove any writes that aren't used. */ * will remove any writes that aren't used. */
secure_erase(seckey, sizeof(seckey)); secure_erase(seckey, sizeof(seckey));
return 0; return EXIT_SUCCESS;
} }

View file

@ -13,6 +13,7 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
@ -38,7 +39,7 @@ int main(void) {
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
if (!fill_random(randomize, sizeof(randomize))) { if (!fill_random(randomize, sizeof(randomize))) {
printf("Failed to generate randomness\n"); printf("Failed to generate randomness\n");
return 1; return EXIT_FAILURE;
} }
/* Randomizing the context is recommended to protect against side-channel /* Randomizing the context is recommended to protect against side-channel
* leakage. See `secp256k1_context_randomize` in secp256k1.h for more * leakage. See `secp256k1_context_randomize` in secp256k1.h for more
@ -49,14 +50,14 @@ int main(void) {
/*** Generate secret keys ***/ /*** Generate secret keys ***/
if (!fill_random(seckey1, sizeof(seckey1)) || !fill_random(seckey2, sizeof(seckey2))) { if (!fill_random(seckey1, sizeof(seckey1)) || !fill_random(seckey2, sizeof(seckey2))) {
printf("Failed to generate randomness\n"); printf("Failed to generate randomness\n");
return 1; return EXIT_FAILURE;
} }
/* If the secret key is zero or out of range (greater than secp256k1's /* If the secret key is zero or out of range (greater than secp256k1's
* order), we fail. Note that the probability of this occurring is negligible * order), we fail. Note that the probability of this occurring is negligible
* with a properly functioning random number generator. */ * with a properly functioning random number generator. */
if (!secp256k1_ec_seckey_verify(ctx, seckey1) || !secp256k1_ec_seckey_verify(ctx, seckey2)) { if (!secp256k1_ec_seckey_verify(ctx, seckey1) || !secp256k1_ec_seckey_verify(ctx, seckey2)) {
printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n"); printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n");
return 1; return EXIT_FAILURE;
} }
/* Generate ElligatorSwift public keys. This should never fail with valid context and /* Generate ElligatorSwift public keys. This should never fail with valid context and
@ -64,7 +65,7 @@ int main(void) {
optional, but recommended. */ optional, but recommended. */
if (!fill_random(auxrand1, sizeof(auxrand1)) || !fill_random(auxrand2, sizeof(auxrand2))) { if (!fill_random(auxrand1, sizeof(auxrand1)) || !fill_random(auxrand2, sizeof(auxrand2))) {
printf("Failed to generate randomness\n"); printf("Failed to generate randomness\n");
return 1; return EXIT_FAILURE;
} }
return_val = secp256k1_ellswift_create(ctx, ellswift_pubkey1, seckey1, auxrand1); return_val = secp256k1_ellswift_create(ctx, ellswift_pubkey1, seckey1, auxrand1);
assert(return_val); assert(return_val);
@ -117,5 +118,5 @@ int main(void) {
secure_erase(shared_secret1, sizeof(shared_secret1)); secure_erase(shared_secret1, sizeof(shared_secret1));
secure_erase(shared_secret2, sizeof(shared_secret2)); secure_erase(shared_secret2, sizeof(shared_secret2));
return 0; return EXIT_SUCCESS;
} }

View file

@ -12,6 +12,7 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
@ -193,7 +194,7 @@ int main(void) {
for (i = 0; i < N_SIGNERS; i++) { for (i = 0; i < N_SIGNERS; i++) {
if (!create_keypair(ctx, &signer_secrets[i], &signers[i])) { if (!create_keypair(ctx, &signer_secrets[i], &signers[i])) {
printf("FAILED\n"); printf("FAILED\n");
return 1; return EXIT_FAILURE;
} }
pubkeys_ptr[i] = &signers[i].pubkey; pubkeys_ptr[i] = &signers[i].pubkey;
} }
@ -208,7 +209,7 @@ int main(void) {
fflush(stdout); fflush(stdout);
if (!secp256k1_ec_pubkey_sort(ctx, pubkeys_ptr, N_SIGNERS)) { if (!secp256k1_ec_pubkey_sort(ctx, pubkeys_ptr, N_SIGNERS)) {
printf("FAILED\n"); printf("FAILED\n");
return 1; return EXIT_FAILURE;
} }
printf("ok\n"); printf("ok\n");
@ -219,7 +220,7 @@ int main(void) {
* while providing a non-NULL agg_pk argument. */ * while providing a non-NULL agg_pk argument. */
if (!secp256k1_musig_pubkey_agg(ctx, NULL, &cache, pubkeys_ptr, N_SIGNERS)) { if (!secp256k1_musig_pubkey_agg(ctx, NULL, &cache, pubkeys_ptr, N_SIGNERS)) {
printf("FAILED\n"); printf("FAILED\n");
return 1; return EXIT_FAILURE;
} }
printf("ok\n"); printf("ok\n");
printf("Tweaking................"); printf("Tweaking................");
@ -227,21 +228,21 @@ int main(void) {
/* Optionally tweak the aggregate key */ /* Optionally tweak the aggregate key */
if (!tweak(ctx, &agg_pk, &cache)) { if (!tweak(ctx, &agg_pk, &cache)) {
printf("FAILED\n"); printf("FAILED\n");
return 1; return EXIT_FAILURE;
} }
printf("ok\n"); printf("ok\n");
printf("Signing message........."); printf("Signing message.........");
fflush(stdout); fflush(stdout);
if (!sign(ctx, signer_secrets, signers, &cache, msg, sig)) { if (!sign(ctx, signer_secrets, signers, &cache, msg, sig)) {
printf("FAILED\n"); printf("FAILED\n");
return 1; return EXIT_FAILURE;
} }
printf("ok\n"); printf("ok\n");
printf("Verifying signature....."); printf("Verifying signature.....");
fflush(stdout); fflush(stdout);
if (!secp256k1_schnorrsig_verify(ctx, sig, msg, 32, &agg_pk)) { if (!secp256k1_schnorrsig_verify(ctx, sig, msg, 32, &agg_pk)) {
printf("FAILED\n"); printf("FAILED\n");
return 1; return EXIT_FAILURE;
} }
printf("ok\n"); printf("ok\n");
@ -256,5 +257,5 @@ int main(void) {
secure_erase(&signer_secrets[i], sizeof(signer_secrets[i])); secure_erase(&signer_secrets[i], sizeof(signer_secrets[i]));
} }
secp256k1_context_destroy(ctx); secp256k1_context_destroy(ctx);
return 0; return EXIT_SUCCESS;
} }

View file

@ -8,6 +8,7 @@
*************************************************************************/ *************************************************************************/
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
@ -34,7 +35,7 @@ int main(void) {
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
if (!fill_random(randomize, sizeof(randomize))) { if (!fill_random(randomize, sizeof(randomize))) {
printf("Failed to generate randomness\n"); printf("Failed to generate randomness\n");
return 1; return EXIT_FAILURE;
} }
/* Randomizing the context is recommended to protect against side-channel /* Randomizing the context is recommended to protect against side-channel
* leakage See `secp256k1_context_randomize` in secp256k1.h for more * leakage See `secp256k1_context_randomize` in secp256k1.h for more
@ -45,7 +46,7 @@ int main(void) {
/*** Key Generation ***/ /*** Key Generation ***/
if (!fill_random(seckey, sizeof(seckey))) { if (!fill_random(seckey, sizeof(seckey))) {
printf("Failed to generate randomness\n"); printf("Failed to generate randomness\n");
return 1; return EXIT_FAILURE;
} }
/* Try to create a keypair with a valid context. This only fails if the /* Try to create a keypair with a valid context. This only fails if the
* secret key is zero or out of range (greater than secp256k1's order). Note * secret key is zero or out of range (greater than secp256k1's order). Note
@ -53,7 +54,7 @@ int main(void) {
* functioning random number generator. */ * functioning random number generator. */
if (!secp256k1_keypair_create(ctx, &keypair, seckey)) { if (!secp256k1_keypair_create(ctx, &keypair, seckey)) {
printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n"); printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n");
return 1; return EXIT_FAILURE;
} }
/* Extract the X-only public key from the keypair. We pass NULL for /* Extract the X-only public key from the keypair. We pass NULL for
@ -90,7 +91,7 @@ int main(void) {
/* Generate 32 bytes of randomness to use with BIP-340 schnorr signing. */ /* Generate 32 bytes of randomness to use with BIP-340 schnorr signing. */
if (!fill_random(auxiliary_rand, sizeof(auxiliary_rand))) { if (!fill_random(auxiliary_rand, sizeof(auxiliary_rand))) {
printf("Failed to generate randomness\n"); printf("Failed to generate randomness\n");
return 1; return EXIT_FAILURE;
} }
/* Generate a Schnorr signature. /* Generate a Schnorr signature.
@ -110,7 +111,7 @@ int main(void) {
* be parsed correctly */ * be parsed correctly */
if (!secp256k1_xonly_pubkey_parse(ctx, &pubkey, serialized_pubkey)) { if (!secp256k1_xonly_pubkey_parse(ctx, &pubkey, serialized_pubkey)) {
printf("Failed parsing the public key\n"); printf("Failed parsing the public key\n");
return 1; return EXIT_FAILURE;
} }
/* Compute the tagged hash on the received messages using the same tag as the signer. */ /* Compute the tagged hash on the received messages using the same tag as the signer. */
@ -149,5 +150,5 @@ int main(void) {
* Here we are preventing these writes from being optimized out, as any good compiler * Here we are preventing these writes from being optimized out, as any good compiler
* will remove any writes that aren't used. */ * will remove any writes that aren't used. */
secure_erase(seckey, sizeof(seckey)); secure_erase(seckey, sizeof(seckey));
return 0; return EXIT_SUCCESS;
} }

View file

@ -0,0 +1,618 @@
/*************************************************************************
* 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_extrakeys.h>
#include <secp256k1_silentpayments.h>
#include "examples_util.h"
/* Use my_memcmp_var instead of memcmp.
*
* Normally, memcmp should be fine, but we use my_memcmp_var
* here to avoid a false positive from valgrind on macOS.
* TODO: remove this in the event the bug is fixed with valgrind in the future.
*/
static int my_memcmp_var(const void *s1, const void *s2, size_t n) {
const unsigned char *p1 = s1, *p2 = s2;
size_t i;
for (i = 0; i < n; i++) {
int diff = p1[i] - p2[i];
if (diff != 0) {
return diff;
}
}
return 0;
}
/* Static data for Bob and Carol's silent payment addresses */
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_key[32] = {
0x9d, 0x6a, 0xd8, 0x55, 0xce, 0x34, 0x17, 0xef,
0x84, 0xe8, 0x36, 0x89, 0x2e, 0x5a, 0x56, 0x39,
0x2b, 0xfb, 0xa0, 0x5f, 0xa5, 0xd9, 0x7c, 0xce,
0xa3, 0x0e, 0x26, 0x6f, 0x54, 0x0e, 0x08, 0xb3
};
static unsigned char bob_spend_pubkey[33] = {
0x02, 0x5c, 0xc9, 0x85, 0x6d, 0x6f, 0x83, 0x75,
0x35, 0x0e, 0x12, 0x39, 0x78, 0xda, 0xac, 0x20,
0x0c, 0x26, 0x0c, 0xb5, 0xb5, 0xae, 0x83, 0x10,
0x6c, 0xab, 0x90, 0x48, 0x4d, 0xcd, 0x8f, 0xcf, 0x36
};
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
},
{
0x03, 0x0b, 0x9d, 0xd4, 0x9d, 0xf2, 0xc6, 0x85,
0x23, 0xbb, 0x0c, 0x72, 0xd4, 0xfb, 0x59, 0xb6,
0x4c, 0xe5, 0xc9, 0xa9, 0x33, 0x6d, 0x0b, 0xef,
0x94, 0x9e, 0xe0, 0x77, 0x5b, 0xea, 0x61, 0xef, 0x05
}
};
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 implemented here as a demonstration
* of how the label lookup callback is meant to query a label cache and return
* the label tweak when a match is found. This is for demonstration purposes
* 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.
*
* Recipients not using labels can ignore these steps and simply pass `NULL`
* for the label_lookup and label_context arguments:
*
* secp256k1_silentpayments_recipient_scan_outputs(..., NULL, NULL);
*/
struct label_cache_entry {
unsigned char label[33];
unsigned char label_tweak[32];
};
struct labels_cache {
size_t entries_used;
struct label_cache_entry entries[5];
};
const unsigned char* label_lookup(
const unsigned char* label33,
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 (my_memcmp_var(cache->entries[i].label, label33, 33) == 0) {
return cache->entries[i].label_tweak;
}
}
return NULL;
}
int main(void) {
enum { N_INPUTS = 2, N_OUTPUTS = 3 };
unsigned char randomize[32];
unsigned char serialized_xonly[32];
secp256k1_xonly_pubkey tx_inputs[N_INPUTS];
const secp256k1_xonly_pubkey *tx_input_ptrs[N_INPUTS];
secp256k1_xonly_pubkey tx_outputs[N_OUTPUTS];
secp256k1_xonly_pubkey *tx_output_ptrs[N_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_keypairs[N_INPUTS];
const secp256k1_keypair *sender_keypair_ptrs[N_INPUTS];
secp256k1_silentpayments_recipient recipients[N_OUTPUTS];
const secp256k1_silentpayments_recipient *recipient_ptrs[N_OUTPUTS];
unsigned char (*sp_addresses[N_OUTPUTS])[2][33];
unsigned char seckey[32];
/*** 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 extract from the transaction inputs.
*
* If the secret key is zero or out of range (bigger than secp256k1's
* order), fail. Note that the probability of this happening is
* negligible. */
for (i = 0; i < N_INPUTS; i++) {
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_keypairs[i], seckey)) {
sender_keypair_ptrs[i] = &sender_keypairs[i];
ret = secp256k1_keypair_xonly_pub(
ctx,
&tx_inputs[i],
NULL,
&sender_keypairs[i]
);
assert(ret);
} 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 labeled address
* 2. Two outputs for Carol
*
* To create multiple outputs for Carol, Alice simply passes Carol's
* silent payment address mutltiple times.
*/
sp_addresses[0] = &carol_address;
sp_addresses[1] = &bob_address;
sp_addresses[2] = &carol_address;
for (i = 0; i < N_OUTPUTS; i++) {
ret = secp256k1_ec_pubkey_parse(ctx,
&recipients[i].scan_pubkey,
(*(sp_addresses[i]))[0],
33
);
ret &= secp256k1_ec_pubkey_parse(ctx,
&recipients[i].labeled_spend_pubkey,
(*(sp_addresses[i]))[1],
33
);
if (!ret) {
printf("\n");
printf("Something went wrong, this is not a valid silent payments address.");
return 1;
}
/* Alice creates the recipient objects and adds the index of the
* original ordering (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.
*/
recipients[i].index = i;
recipient_ptrs[i] = &recipients[i];
}
for (i = 0; i < N_OUTPUTS; i++) {
tx_output_ptrs[i] = &tx_outputs[i];
}
/* To keep things simple, we cast the tx_output_ptr array to remove the
* const qualifer, so that we can create the outputs. We want the const
* qualifer because this same array will be passed to the scan function
* later in the example.
*/
ret = secp256k1_silentpayments_sender_create_outputs(ctx,
tx_output_ptrs,
recipient_ptrs, N_OUTPUTS,
smallest_outpoint,
sender_keypair_ptrs, N_INPUTS,
NULL, 0
);
assert(ret);
printf("Alice created the following outputs for Bob and Carol: \n");
for (i = 0; i < N_OUTPUTS; i++) {
printf(" ");
secp256k1_xonly_pubkey_serialize(ctx,
serialized_xonly,
&tx_outputs[i]
);
print_hex(serialized_xonly, sizeof(serialized_xonly));
}
/* 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. */
secure_erase(seckey, sizeof(seckey));
for (i = 0; i < N_INPUTS; i++) {
secure_erase(&sender_keypairs[i], sizeof(sender_keypairs[i]));
}
}
/*** Receiving ***/
{
unsigned char light_client_data33[33];
for (i = 0; i < N_INPUTS; i++) {
tx_input_ptrs[i] = &tx_inputs[i];
}
for (i = 0; i < N_OUTPUTS; i++) {
tx_output_ptrs[i] = &tx_outputs[i];
}
{
/*** Scanning as a full node (Bob) ***
*
* Since Bob has access to the full transaction, scanning is simple:
*
* 1. Collect the relevant public data from the transaction
* and call `_silentpayments_recipient_public_data_create`
* 2. Call `_silentpayments_recipient_scan_outputs`
*
*/
secp256k1_silentpayments_found_output found_outputs[N_OUTPUTS];
secp256k1_silentpayments_found_output *found_output_ptrs[N_OUTPUTS];
secp256k1_silentpayments_recipient_public_data public_data;
secp256k1_pubkey spend_pubkey;
secp256k1_pubkey labeled_spend_pubkey;
secp256k1_pubkey address_labeled_spend_pubkey;
size_t n_found_outputs;
struct labels_cache labels_cache;
for (i = 0; i < N_OUTPUTS; i++) {
found_output_ptrs[i] = &found_outputs[i];
}
{
/** Labels setup
*
* These steps are only necessary if the recipient is using the
* optional labels feature. Recipients not using labels can
* ignore these steps and simply pass `NULL` for the
* label_lookup and label_context arguments:
*
* _silentpayments_recipient_scan_outputs(..., NULL, NULL);
*
* In this case, since Bob has access to the full transaction
* outputs when scanning, it's 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.
*/
size_t len = 33;
secp256k1_pubkey label;
unsigned int m = 1;
/* Load Bob's spend public key */
ret = secp256k1_ec_pubkey_parse(ctx,
&spend_pubkey,
bob_spend_pubkey,
33
);
assert(ret);
/* Add an entry to the cache. This implies Bob has previously
* called
* `secp256k1_silentpayments_recipient_create_labeled_spend_pubkey`
* and is using the resulting labeled spend pubkey to encode a
* labeled silent payments address.
*/
ret = secp256k1_silentpayments_recipient_create_label(ctx,
&label,
labels_cache.entries[0].label_tweak,
bob_scan_key,
m
);
assert(ret);
ret = secp256k1_ec_pubkey_serialize(ctx,
labels_cache.entries[0].label,
&len,
&label,
SECP256K1_EC_COMPRESSED
);
assert(ret);
labels_cache.entries_used = 1;
/* Verify the label we just created and added to the cache is the
* same one used in Bob's silent payment address.
*/
ret = secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(ctx,
&labeled_spend_pubkey,
&spend_pubkey,
&label
);
assert(ret);
/* Load the labeled spend public key from Bob's address */
ret = secp256k1_ec_pubkey_parse(ctx,
&address_labeled_spend_pubkey,
bob_address[1],
33
);
assert(ret);
if (secp256k1_ec_pubkey_cmp(ctx, &labeled_spend_pubkey, &address_labeled_spend_pubkey) != 0) {
printf("\n");
printf("Something went wrong, the labeled spend public key does not match Bob's address.");
};
}
/* Bob collects the public data from the transaction inputs and
* creates a `secp256k1_silentpayments_recipient_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 provide these `public_data` objects, i.e. you don't need to be
* a silent payments wallet, just someone interested in providing 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_INPUTS,
NULL, 0 /* NULL because no eligible plain pubkey inputs were found in the tx */
);
if (!ret) {
/* We need to always check that the public data object is valid
* before proceeding, since a malicious actor could create a transaction
* such that the input public keys sum to the point at infinity, which
* could cause our node to crash if, e.g., we assume that public_data_create
* will always succeed."
*/
printf("\n");
printf("This transaction is not valid for silent payments, skipping.");
return 0;
}
/* Serialize the public data object for later use. */
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,
(const secp256k1_xonly_pubkey * const *)tx_output_ptrs, N_OUTPUTS,
bob_scan_key,
&public_data,
&spend_pubkey,
label_lookup, &labels_cache /* NULL, NULL for no labels */
);
if (!ret) {
/* Since we've already validated the public data, this shouldn't fail, but
* better to be careful here since we are scanning data that could have been
* maliciously created.
*/
printf("\n");
printf("Something went wrong while scanning this transaction, skipping.");
return 0;
}
if (n_found_outputs > 0) {
secp256k1_keypair kp;
secp256k1_xonly_pubkey xonly_output;
unsigned char full_seckey[32];
printf("\n");
printf("Bob found the following outputs: \n");
for (i = 0; i < n_found_outputs; i++) {
printf(" ");
secp256k1_xonly_pubkey_serialize(ctx,
serialized_xonly,
&found_outputs[i].output
);
print_hex(serialized_xonly, sizeof(serialized_xonly));
/* Verify that this output is spendable by Bob by reconstructing the full
* secret key for the xonly output.
*
* This is done by adding the tweak from the transaction to Bob's spend key.
* If the output was sent to a labeled address, the label tweak has
* already been added to the tweak returned in `_silentpayments_found_output`.
*
* To verify that we are able to sign for this output, it is sufficient to
* check that the public key generated from `full_seckey` matches the output
* in the transaction. For a full example on signing for a schnorr ouput,
* see `examples/schnorr.c` */
memcpy(&full_seckey, &bob_spend_key, 32);
ret = secp256k1_ec_seckey_tweak_add(ctx, full_seckey, found_outputs[i].tweak);
ret &= secp256k1_keypair_create(ctx, &kp, full_seckey);
ret &= secp256k1_keypair_xonly_pub(
ctx,
&xonly_output,
NULL,
&kp
);
/* We assert here because the only way the seckey_tweak_add operation can fail
* is if the tweak is the negation of Bob's spend key.
*
* We also assert that the generated public key matches the transaction output,
* as it should be impossible for a mismatch at this point considering the
* scanning function completed without errors and indicated found outputs. */
assert(ret);
assert(secp256k1_xonly_pubkey_cmp(ctx, &xonly_output, &found_outputs[i].output) == 0);
}
} else {
printf("\n");
printf("Bob did not find any outputs in this transaction.");
}
}
{
/*** 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. It's
* also difficult for Carol to efficiently scan for labels without
* the transaction outputs, but Carol can still use labels as a
* light client by pregenerating all possible labels and adding them
* to the generated output (i.e., `k = 0`). Once at least one output
* is found, she can request the full block and scan the full
* transaction. This assumes Carol will only use a small number of
* of labels as a light client.
*
* Additionally, Carol likely does not have access to the
* transaction inputs and prevout information, so she uses the
* `public_data` object created by Bob's full node earlier. This
* serialized `public_data` object contains everything she needs for
* generating the shared secret, i.e., `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_recipient_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 serialized `public_data`
* created by Bob's full node
*/
ret = secp256k1_silentpayments_recipient_public_data_parse(ctx,
&public_data,
light_client_data33
);
if (!ret) {
printf("\n");
printf("This transaction is not valid for silent payments, skipping.");
return 0;
}
ret = secp256k1_silentpayments_recipient_create_shared_secret(ctx,
shared_secret,
carol_scan_key,
&public_data
);
/* Since we've already validated the public data, the only reason this could fail
* is if we input a bad scan key or bad spend public key, which should never happen
* because this is data under our control.
*/
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_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;
}
}
}
if (n_found_outputs > 0) {
/* Carol would spend these outputs the same as Bob, by tweaking her
* spend key with the tweak corresponding to the found output. See above
* for an example for Bob's outputs. */
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);
}
} else {
printf("\n");
printf("Carol did not find any outputs in this transaction.");
}
}
}
/* This will clear everything from the context and free the memory */
secp256k1_context_destroy(ctx);
return 0;
}

View file

@ -230,10 +230,10 @@ typedef int (*secp256k1_nonce_function)(
* *
* It is highly recommended to call secp256k1_selftest before using this context. * It is highly recommended to call secp256k1_selftest before using this context.
*/ */
SECP256K1_API const secp256k1_context *secp256k1_context_static; SECP256K1_API const secp256k1_context * const secp256k1_context_static;
/** Deprecated alias for secp256k1_context_static. */ /** Deprecated alias for secp256k1_context_static. */
SECP256K1_API const secp256k1_context *secp256k1_context_no_precomp SECP256K1_API const secp256k1_context * const secp256k1_context_no_precomp
SECP256K1_DEPRECATED("Use secp256k1_context_static instead"); SECP256K1_DEPRECATED("Use secp256k1_context_static instead");
/** Perform basic self tests (to be used in conjunction with secp256k1_context_static) /** Perform basic self tests (to be used in conjunction with secp256k1_context_static)
@ -701,21 +701,13 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_seckey_negate(
unsigned char *seckey unsigned char *seckey
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
/** Same as secp256k1_ec_seckey_negate, but DEPRECATED. Will be removed in
* future versions. */
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_negate(
const secp256k1_context *ctx,
unsigned char *seckey
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2)
SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_negate instead");
/** Negates a public key in place. /** Negates a public key in place.
* *
* Returns: 1 always * Returns: 1 always
* Args: ctx: pointer to a context object * Args: ctx: pointer to a context object
* In/Out: pubkey: pointer to the public key to be negated. * In/Out: pubkey: pointer to the public key to be negated.
*/ */
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_negate( SECP256K1_API int secp256k1_ec_pubkey_negate(
const secp256k1_context *ctx, const secp256k1_context *ctx,
secp256k1_pubkey *pubkey secp256k1_pubkey *pubkey
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
@ -741,15 +733,6 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_seckey_tweak_add(
const unsigned char *tweak32 const unsigned char *tweak32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Same as secp256k1_ec_seckey_tweak_add, but DEPRECATED. Will be removed in
* future versions. */
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_tweak_add(
const secp256k1_context *ctx,
unsigned char *seckey,
const unsigned char *tweak32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_tweak_add instead");
/** Tweak a public key by adding tweak times the generator to it. /** Tweak a public key by adding tweak times the generator to it.
* *
* Returns: 0 if the arguments are invalid or the resulting public key would be * Returns: 0 if the arguments are invalid or the resulting public key would be
@ -788,15 +771,6 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_seckey_tweak_mul(
const unsigned char *tweak32 const unsigned char *tweak32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Same as secp256k1_ec_seckey_tweak_mul, but DEPRECATED. Will be removed in
* future versions. */
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_tweak_mul(
const secp256k1_context *ctx,
unsigned char *seckey,
const unsigned char *tweak32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_tweak_mul instead");
/** Tweak a public key by multiplying it by a tweak value. /** Tweak a public key by multiplying it by a tweak value.
* *
* Returns: 0 if the arguments are invalid. 1 otherwise. * Returns: 0 if the arguments are invalid. 1 otherwise.
@ -883,7 +857,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_combine(
* msg: pointer to an array containing the message * msg: pointer to an array containing the message
* msglen: length of the message array * msglen: length of the message array
*/ */
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_tagged_sha256( SECP256K1_API int secp256k1_tagged_sha256(
const secp256k1_context *ctx, const secp256k1_context *ctx,
unsigned char *hash32, unsigned char *hash32,
const unsigned char *tag, const unsigned char *tag,

View file

@ -90,7 +90,7 @@ SECP256K1_API int secp256k1_xonly_pubkey_cmp(
* the negation of the pubkey and set to 0 otherwise. * the negation of the pubkey and set to 0 otherwise.
* In: pubkey: pointer to a public key that is converted. * In: pubkey: pointer to a public key that is converted.
*/ */
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_from_pubkey( SECP256K1_API int secp256k1_xonly_pubkey_from_pubkey(
const secp256k1_context *ctx, const secp256k1_context *ctx,
secp256k1_xonly_pubkey *xonly_pubkey, secp256k1_xonly_pubkey *xonly_pubkey,
int *pk_parity, int *pk_parity,
@ -179,7 +179,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_create(
* Out: seckey: pointer to a 32-byte buffer for the secret key. * Out: seckey: pointer to a 32-byte buffer for the secret key.
* In: keypair: pointer to a keypair. * In: keypair: pointer to a keypair.
*/ */
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_sec( SECP256K1_API int secp256k1_keypair_sec(
const secp256k1_context *ctx, const secp256k1_context *ctx,
unsigned char *seckey, unsigned char *seckey,
const secp256k1_keypair *keypair const secp256k1_keypair *keypair
@ -192,7 +192,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_sec(
* Out: pubkey: pointer to a pubkey object, set to the keypair public key. * Out: pubkey: pointer to a pubkey object, set to the keypair public key.
* In: keypair: pointer to a keypair. * In: keypair: pointer to a keypair.
*/ */
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_pub( SECP256K1_API int secp256k1_keypair_pub(
const secp256k1_context *ctx, const secp256k1_context *ctx,
secp256k1_pubkey *pubkey, secp256k1_pubkey *pubkey,
const secp256k1_keypair *keypair const secp256k1_keypair *keypair
@ -211,7 +211,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_pub(
* pk_parity argument of secp256k1_xonly_pubkey_from_pubkey. * pk_parity argument of secp256k1_xonly_pubkey_from_pubkey.
* In: keypair: pointer to a keypair. * In: keypair: pointer to a keypair.
*/ */
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_xonly_pub( SECP256K1_API int secp256k1_keypair_xonly_pub(
const secp256k1_context *ctx, const secp256k1_context *ctx,
secp256k1_xonly_pubkey *pubkey, secp256k1_xonly_pubkey *pubkey,
int *pk_parity, int *pk_parity,

View file

@ -0,0 +1,419 @@
#ifndef SECP256K1_SILENTPAYMENTS_H
#define SECP256K1_SILENTPAYMENTS_H
#include <stdint.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 secret or public keys and the derivation of a shared secret using
* Elliptic Curve Diffie-Hellman. Combined are either:
* - spender's secret keys and recipient's public key (a * B, sender side)
* - spender's public keys and recipient's secret key (A * b, recipient 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.
*/
static const unsigned char secp256k1_silentpayments_public_data_magic[4] = { 0xa7, 0x1c, 0xd3, 0x5e };
/** This struct serves as an input parameter for passing the silent payment
* address data to `silentpayments_sender_create_outputs`.
*
* The index field is for when more than one address is being sent to in
* a transaction. Index is set to the position of this recipient in the
* `recipients` array passed to `silentpayments_sender_create_outputs`
* and used to return the generated outputs matching the original ordering.
*
* The spend public key field is named `labeled_spend_pubkey` to indicate this
* spend public key may be tweaked with an optional label. This is not relevant
* for the sender and is purely a documentation convention to differentiate
* between other uses of `spend_pubkey` in this API, where it is meant to refer
* to the unlabeled spend public key.
*/
typedef struct {
secp256k1_pubkey scan_pubkey;
secp256k1_pubkey labeled_spend_pubkey;
size_t index;
} secp256k1_silentpayments_recipient;
/** Create Silent Payment outputs for recipient(s).
*
* Given a list of n secret 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.
*
* `outpoint_smallest` refers to the smallest outpoint lexicographically
* from the transaction inputs (both silent payments eligible and non-eligible
* inputs). This value MUST be the smallest outpoint out of all of the
* transaction inputs, otherwise the recipient will be unable to find the
* payment. Determining the smallest outpoint from the list of transaction
* inputs is the responsibility of the caller. It is strongly recommended
* that implementations ensure they are doing this correctly by using the
* test vectors from BIP352.
*
* If necessary, the secret keys are negated to enforce the right y-parity.
* For that reason, the secret keys have to be passed in via two different
* parameter pairs, depending on whether the seckeys correspond to x-only
* outputs or not.
*
* Returns: 1 if creation of outputs was successful. 0 if an error occurred.
* Args: ctx: pointer to a context object
* Out: generated_outputs: pointer to an array of pointers to xonly pubkeys,
* one per recipient.
* The outputs here are sorted by the index value
* provided in the recipient objects.
* 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_smallest: serialized (36-byte) smallest outpoint
* (lexicographically) from the transaction inputs
* taproot_seckeys: pointer to an array of pointers to taproot
* keypair inputs (can be NULL if no secret keys
* of taproot inputs are used)
* n_taproot_seckeys: the number of sender's taproot input secret keys
* plain_seckeys: pointer to an array of pointers to 32-byte
* secret keys of non-taproot inputs (can be NULL
* if no secret keys of non-taproot inputs are
* used)
* n_plain_seckeys: the number of sender's non-taproot input secret
* 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 32 byte scan 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 occurred.
* Args: ctx: pointer to a context object
* Out: label: pointer to the resulting label public key
* label_tweak32: pointer to the 32 byte label tweak
* In: recipient_scan_key32: pointer to the recipient's 32 byte scan key
* m: label integer (0 is used for change outputs)
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_label(
const secp256k1_context *ctx,
secp256k1_pubkey *label,
unsigned char *label_tweak32,
const unsigned char *recipient_scan_key32,
const uint32_t m
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
/** Create Silent Payment labeled spend public key.
*
* Given a recipient's spend public key B_spend and a label, calculate the
* corresponding labeled 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
* (labeled) spend public key each.
*
* Returns: 1 if labeled spend public key creation was successful.
* 0 if an error occurred.
* Args: ctx: pointer to a context object
* Out: labeled_spend_pubkey: pointer to the resulting labeled spend
* public key
* In: recipient_spend_pubkey: pointer to the recipient's spend pubkey
* label: pointer to the recipient's label public
* key
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(
const secp256k1_context *ctx,
secp256k1_pubkey *labeled_spend_pubkey,
const secp256k1_pubkey *recipient_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 101 bytes in
* size. It can be safely copied/moved. Created with
* `secp256k1_silentpayments_recipient_public_data_create`. Can be serialized as a
* compressed public key using
* `secp256k1_silentpayments_recipient_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_recipient_public_data_parse`.
*/
typedef struct {
unsigned char data[101];
} secp256k1_silentpayments_recipient_public_data;
/** 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, create a
* `public_data` object. This object summarizes the public data from the
* transaction inputs needed for scanning.
*
* `outpoint_smallest36` refers to the smallest outpoint lexicographically
* from the transaction inputs (both silent payments eligible and non-eligible
* inputs). This value MUST be the smallest outpoint out of all of the
* transaction inputs, otherwise the recipient will be unable to find the
* payment.
*
* 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 can be used for scanning on the recipient side, or
* stored in an index for later use (e.g., wallet rescanning, vending data to
* light clients).
*
* If calling this function for simply aggregating the public transaction data
* for later use, the caller can save the result with
* `silentpayments_recipient_public_data_serialize`.
*
* Returns: 1 if public data creation was successful. 0 if an error occurred.
* 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 (lexicographically)
* from the transaction inputs
* 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_recipient_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);
/** Serialize a silentpayments_recipient_public_data object into a 33-byte sequence.
*
* Returns: 1 always.
*
* Args: ctx: pointer to a context object
* Out: output33: pointer to a 33-byte array to place the serialized
* `silentpayments_recipient_public_data` in
* In: public_data: pointer to an initialized silentpayments_recipient_public_data
* object
*/
SECP256K1_API int secp256k1_silentpayments_recipient_public_data_serialize(
const secp256k1_context *ctx,
unsigned char *output33,
const secp256k1_silentpayments_recipient_public_data *public_data
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** 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 represent a valid
* public key).
*
* Args: ctx: pointer to a context object.
* Out: public_data: pointer to a silentpayments_recipient_public_data object. If 1 is
* returned, it is set to a parsed version of input33.
* In: input33: pointer to a serialized silentpayments_recipient_public_data.
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_public_data_parse(
const secp256k1_context *ctx,
secp256k1_silentpayments_recipient_public_data *public_data,
const unsigned char *input33
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Callback function for label lookups
*
* This function is implemented by the recipient to check if a value exists in
* the recipients label cache during scanning.
*
* For creating the labels cache,
* `secp256k1_silentpayments_recipient_create_label` can be used.
*
* Returns: pointer to the 32-byte label tweak if there is a match.
* NULL pointer if there is no match.
*
* In: label: pointer to the label pubkey to check (computed during
* scanning)
* label_context: pointer to the recipients label cache.
*/
typedef const unsigned char* (*secp256k1_silentpayments_label_lookup)(const unsigned char* label33, const void* label_context);
/** Found outputs struct
*
* Struct for holding a found output along with data needed to spend it later.
*
* output: the x-only public key for the taproot output
* tweak: the 32-byte tweak needed to spend the output
* found_with_label: boolean value to indicate if the output was sent to a
* labelled address. If true, label will be set with a valid
* public key.
* label: public key representing the label used.
* If found_with_label = false, this is set to an invalid
* public key.
*/
typedef struct {
secp256k1_xonly_pubkey output;
unsigned char tweak[32];
int found_with_label;
secp256k1_pubkey label;
} secp256k1_silentpayments_found_output;
/** Scan for Silent Payment transaction outputs.
*
* Given a public_data object, a recipient's 32 byte scan key and spend public key,
* and the relevant transaction outputs, scan for outputs belonging 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.
*
* For the labels cache, `secp256k1_silentpayments_recipient_create_label`
* can be used.
*
* Returns: 1 if output scanning was successful.
* 0 if an error occurred.
*
* 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
* recipient_scan_key32: pointer to the recipient's 32 byte scan key
* public_data: pointer to the transaction public data
* (see `_recipient_public_data_create`).
* recipient_spend_pubkey: pointer to the recipient's spend pubkey
* label_lookup: pointer to a callback function for looking up
* a label value. This function takes a label
* pubkey as an argument and returns a pointer to
* the label tweak if the label exists, otherwise
* returns a NULL pointer (NULL if labels are not
* used)
* label_context: pointer to a label context object (NULL if
* labels are not used)
*/
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 *recipient_scan_key32,
const secp256k1_silentpayments_recipient_public_data *public_data,
const secp256k1_pubkey *recipient_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 (secp256k1_silentpayments_recipient_public_data),
* and the recipient's 32 byte scan key, calculate the shared secret.
*
* The resulting shared secret is needed as input for creating silent payments
* outputs belonging to the same recipient scan public key. This function is
* intended for light clients, i.e., scenarios where the caller does not have
* access to the full transaction. If the caller does have access to the full
* transaction, `secp256k1_silentpayments_recipient_scan_outputs` should be
* used instead.
*
* Returns: 1 if shared secret creation was successful. 0 if an error occurred.
* Args: ctx: pointer to a context object
* Out: shared_secret33: pointer to the resulting 33-byte shared secret
* In: recipient_scan_key32: pointer to the recipient's 32 byte scan key
* public_data: pointer to the public_data object, loaded using
* `_recipient_public_data_parse`
*/
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_key32,
const secp256k1_silentpayments_recipient_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,
* create an output public key.
*
* 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 occurred.
* 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 recipient's perspective with routines from
* above
* recipient_spend_pubkey: pointer to the recipient'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 *recipient_spend_pubkey,
const uint32_t k
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
#ifdef __cplusplus
}
#endif
#endif /* SECP256K1_SILENTPAYMENTS_H */

View file

@ -5,6 +5,7 @@
***********************************************************************/ ***********************************************************************/
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "../include/secp256k1.h" #include "../include/secp256k1.h"
@ -15,18 +16,26 @@ static void help(int default_iters) {
printf("Benchmarks the following algorithms:\n"); printf("Benchmarks the following algorithms:\n");
printf(" - ECDSA signing/verification\n"); printf(" - ECDSA signing/verification\n");
#ifdef ENABLE_MODULE_ECDH
printf(" - ECDH key exchange (optional module)\n");
#endif
#ifdef ENABLE_MODULE_RECOVERY #ifdef ENABLE_MODULE_RECOVERY
printf(" - Public key recovery (optional module)\n"); printf(" - Public key recovery (optional module)\n");
#endif #endif
#ifdef ENABLE_MODULE_ECDH
printf(" - ECDH key exchange (optional module)\n");
#endif
#ifdef ENABLE_MODULE_SCHNORRSIG #ifdef ENABLE_MODULE_SCHNORRSIG
printf(" - Schnorr signatures (optional module)\n"); printf(" - Schnorr signatures (optional module)\n");
#endif #endif
#ifdef ENABLE_MODULE_ELLSWIFT
printf(" - ElligatorSwift (optional module)\n");
#endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
printf(" - Silent payments (optional module)\n");
#endif
printf("\n"); printf("\n");
printf("The default number of iterations for each benchmark is %d. This can be\n", default_iters); printf("The default number of iterations for each benchmark is %d. This can be\n", default_iters);
printf("customized using the SECP256K1_BENCH_ITERS environment variable.\n"); printf("customized using the SECP256K1_BENCH_ITERS environment variable.\n");
@ -63,6 +72,10 @@ static void help(int default_iters) {
printf(" ellswift_ecdh : ECDH on ElligatorSwift keys\n"); printf(" ellswift_ecdh : ECDH on ElligatorSwift keys\n");
#endif #endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
printf(" silentpayments : Silent payments recipient scanning\n");
#endif
printf("\n"); printf("\n");
} }
@ -165,6 +178,10 @@ static void bench_keygen_run(void *arg, int iters) {
# include "modules/ellswift/bench_impl.h" # include "modules/ellswift/bench_impl.h"
#endif #endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
# include "modules/silentpayments/bench_impl.h"
#endif
int main(int argc, char** argv) { int main(int argc, char** argv) {
int i; int i;
secp256k1_pubkey pubkey; secp256k1_pubkey pubkey;
@ -179,7 +196,7 @@ int main(int argc, char** argv) {
char* valid_args[] = {"ecdsa", "verify", "ecdsa_verify", "sign", "ecdsa_sign", "ecdh", "recover", char* valid_args[] = {"ecdsa", "verify", "ecdsa_verify", "sign", "ecdsa_sign", "ecdh", "recover",
"ecdsa_recover", "schnorrsig", "schnorrsig_verify", "schnorrsig_sign", "ec", "ecdsa_recover", "schnorrsig", "schnorrsig_verify", "schnorrsig_sign", "ec",
"keygen", "ec_keygen", "ellswift", "encode", "ellswift_encode", "decode", "keygen", "ec_keygen", "ellswift", "encode", "ellswift_encode", "decode",
"ellswift_decode", "ellswift_keygen", "ellswift_ecdh"}; "ellswift_decode", "ellswift_keygen", "ellswift_ecdh", "silentpayments"};
size_t valid_args_size = sizeof(valid_args)/sizeof(valid_args[0]); size_t valid_args_size = sizeof(valid_args)/sizeof(valid_args[0]);
int invalid_args = have_invalid_args(argc, argv, valid_args, valid_args_size); int invalid_args = have_invalid_args(argc, argv, valid_args, valid_args_size);
@ -188,11 +205,11 @@ int main(int argc, char** argv) {
|| have_flag(argc, argv, "--help") || have_flag(argc, argv, "--help")
|| have_flag(argc, argv, "help")) { || have_flag(argc, argv, "help")) {
help(default_iters); help(default_iters);
return 0; return EXIT_SUCCESS;
} else if (invalid_args) { } else if (invalid_args) {
fprintf(stderr, "./bench: unrecognized argument.\n\n"); fprintf(stderr, "./bench: unrecognized argument.\n\n");
help(default_iters); help(default_iters);
return 1; return EXIT_FAILURE;
} }
} }
@ -201,7 +218,7 @@ int main(int argc, char** argv) {
if (have_flag(argc, argv, "ecdh")) { if (have_flag(argc, argv, "ecdh")) {
fprintf(stderr, "./bench: ECDH module not enabled.\n"); fprintf(stderr, "./bench: ECDH module not enabled.\n");
fprintf(stderr, "Use ./configure --enable-module-ecdh.\n\n"); fprintf(stderr, "Use ./configure --enable-module-ecdh.\n\n");
return 1; return EXIT_FAILURE;
} }
#endif #endif
@ -209,7 +226,7 @@ int main(int argc, char** argv) {
if (have_flag(argc, argv, "recover") || have_flag(argc, argv, "ecdsa_recover")) { if (have_flag(argc, argv, "recover") || have_flag(argc, argv, "ecdsa_recover")) {
fprintf(stderr, "./bench: Public key recovery module not enabled.\n"); fprintf(stderr, "./bench: Public key recovery module not enabled.\n");
fprintf(stderr, "Use ./configure --enable-module-recovery.\n\n"); fprintf(stderr, "Use ./configure --enable-module-recovery.\n\n");
return 1; return EXIT_FAILURE;
} }
#endif #endif
@ -217,7 +234,7 @@ int main(int argc, char** argv) {
if (have_flag(argc, argv, "schnorrsig") || have_flag(argc, argv, "schnorrsig_sign") || have_flag(argc, argv, "schnorrsig_verify")) { if (have_flag(argc, argv, "schnorrsig") || have_flag(argc, argv, "schnorrsig_sign") || have_flag(argc, argv, "schnorrsig_verify")) {
fprintf(stderr, "./bench: Schnorr signatures module not enabled.\n"); fprintf(stderr, "./bench: Schnorr signatures module not enabled.\n");
fprintf(stderr, "Use ./configure --enable-module-schnorrsig.\n\n"); fprintf(stderr, "Use ./configure --enable-module-schnorrsig.\n\n");
return 1; return EXIT_FAILURE;
} }
#endif #endif
@ -227,6 +244,14 @@ int main(int argc, char** argv) {
have_flag(argc, argv, "ellswift_ecdh")) { have_flag(argc, argv, "ellswift_ecdh")) {
fprintf(stderr, "./bench: ElligatorSwift module not enabled.\n"); fprintf(stderr, "./bench: ElligatorSwift module not enabled.\n");
fprintf(stderr, "Use ./configure --enable-module-ellswift.\n\n"); fprintf(stderr, "Use ./configure --enable-module-ellswift.\n\n");
return EXIT_FAILURE;
}
#endif
#ifndef ENABLE_MODULE_SILENTPAYMENTS
if (have_flag(argc, argv, "silentpayments")) {
fprintf(stderr, "./bench: silentpayments module not enabled.\n");
fprintf(stderr, "Use ./configure --enable-module-silentpayments.\n\n");
return 1; return 1;
} }
#endif #endif
@ -275,5 +300,11 @@ int main(int argc, char** argv) {
run_ellswift_bench(iters, argc, argv); run_ellswift_bench(iters, argc, argv);
#endif #endif
return 0; #ifdef ENABLE_MODULE_SILENTPAYMENTS
/* SilentPayments benchmarks */
run_silentpayments_bench(iters, argc, argv);
#endif
return EXIT_SUCCESS;
} }

View file

@ -24,7 +24,7 @@ static int64_t gettime_i64(void) {
struct timespec tv; struct timespec tv;
if (!timespec_get(&tv, TIME_UTC)) { if (!timespec_get(&tv, TIME_UTC)) {
fputs("timespec_get failed!", stderr); fputs("timespec_get failed!", stderr);
exit(1); exit(EXIT_FAILURE);
} }
return (int64_t)tv.tv_nsec / 1000 + (int64_t)tv.tv_sec * 1000000LL; return (int64_t)tv.tv_nsec / 1000 + (int64_t)tv.tv_sec * 1000000LL;
#else #else

View file

@ -4,6 +4,7 @@
* file COPYING or https://www.opensource.org/licenses/mit-license.php.* * file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/ ***********************************************************************/
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include "secp256k1.c" #include "secp256k1.c"
#include "../include/secp256k1.h" #include "../include/secp256k1.h"
@ -287,7 +288,7 @@ int main(int argc, char **argv) {
|| have_flag(argc, argv, "--help") || have_flag(argc, argv, "--help")
|| have_flag(argc, argv, "help")) { || have_flag(argc, argv, "help")) {
help(argv); help(argv);
return 0; return EXIT_SUCCESS;
} else if(have_flag(argc, argv, "pippenger_wnaf")) { } else if(have_flag(argc, argv, "pippenger_wnaf")) {
printf("Using pippenger_wnaf:\n"); printf("Using pippenger_wnaf:\n");
data.ecmult_multi = secp256k1_ecmult_pippenger_batch_single; data.ecmult_multi = secp256k1_ecmult_pippenger_batch_single;
@ -299,7 +300,7 @@ int main(int argc, char **argv) {
} else { } else {
fprintf(stderr, "%s: unrecognized argument '%s'.\n\n", argv[0], argv[1]); fprintf(stderr, "%s: unrecognized argument '%s'.\n\n", argv[0], argv[1]);
help(argv); help(argv);
return 1; return EXIT_FAILURE;
} }
} }
@ -363,5 +364,5 @@ int main(int argc, char **argv) {
free(data.output); free(data.output);
free(data.expected_output); free(data.expected_output);
return(0); return EXIT_SUCCESS;
} }

View file

@ -4,6 +4,7 @@
* file COPYING or https://www.opensource.org/licenses/mit-license.php.* * file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/ ***********************************************************************/
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include "secp256k1.c" #include "secp256k1.c"
#include "../include/secp256k1.h" #include "../include/secp256k1.h"
@ -393,7 +394,7 @@ int main(int argc, char **argv) {
|| have_flag(argc, argv, "--help") || have_flag(argc, argv, "--help")
|| have_flag(argc, argv, "help")) { || have_flag(argc, argv, "help")) {
help(default_iters); help(default_iters);
return 0; return EXIT_SUCCESS;
} }
} }
@ -432,5 +433,5 @@ int main(int argc, char **argv) {
if (d || have_flag(argc, argv, "context")) run_benchmark("context_create", bench_context, bench_setup, NULL, &data, 10, iters); if (d || have_flag(argc, argv, "context")) run_benchmark("context_create", bench_context, bench_setup, NULL, &data, 10, iters);
return 0; return EXIT_SUCCESS;
} }

View file

@ -5,6 +5,7 @@
***********************************************************************/ ***********************************************************************/
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "../include/secp256k1.h" #include "../include/secp256k1.h"
@ -39,6 +40,10 @@
#include "../include/secp256k1_ellswift.h" #include "../include/secp256k1_ellswift.h"
#endif #endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
#include "../include/secp256k1_silentpayments.h"
#endif
static void run_tests(secp256k1_context *ctx, unsigned char *key); static void run_tests(secp256k1_context *ctx, unsigned char *key);
int main(void) { int main(void) {
@ -49,7 +54,7 @@ int main(void) {
if (!SECP256K1_CHECKMEM_RUNNING()) { if (!SECP256K1_CHECKMEM_RUNNING()) {
fprintf(stderr, "This test can only usefully be run inside valgrind because it was not compiled under msan.\n"); fprintf(stderr, "This test can only usefully be run inside valgrind because it was not compiled under msan.\n");
fprintf(stderr, "Usage: libtool --mode=execute valgrind ./ctime_tests\n"); fprintf(stderr, "Usage: libtool --mode=execute valgrind ./ctime_tests\n");
return 1; return EXIT_FAILURE;
} }
ctx = secp256k1_context_create(SECP256K1_CONTEXT_DECLASSIFY); ctx = secp256k1_context_create(SECP256K1_CONTEXT_DECLASSIFY);
/** In theory, testing with a single secret input should be sufficient: /** In theory, testing with a single secret input should be sufficient:
@ -69,7 +74,7 @@ int main(void) {
CHECK(ret); CHECK(ret);
secp256k1_context_destroy(ctx); secp256k1_context_destroy(ctx);
return 0; return EXIT_SUCCESS;
} }
static void run_tests(secp256k1_context *ctx, unsigned char *key) { static void run_tests(secp256k1_context *ctx, unsigned char *key) {
@ -93,6 +98,25 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
unsigned char ellswift[64]; unsigned char ellswift[64];
static const unsigned char prefix[64] = {'t', 'e', 's', 't'}; static const unsigned char prefix[64] = {'t', 'e', 's', 't'};
#endif #endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
secp256k1_xonly_pubkey generated_output;
secp256k1_xonly_pubkey *generated_outputs[1];
secp256k1_silentpayments_recipient recipient;
const secp256k1_silentpayments_recipient *recipients[1];
unsigned char outpoint_smallest[36] = { 0 };
secp256k1_keypair taproot_seckey;
const secp256k1_keypair *taproot_seckeys[1];
const unsigned char *plain_seckeys[1];
secp256k1_silentpayments_found_output *found_outputs[1];
size_t n_found_outputs;
const secp256k1_xonly_pubkey *tx_outputs[1];
secp256k1_silentpayments_recipient_public_data public_data;
unsigned char label_tweak[32] = { 0 };
secp256k1_xonly_pubkey xonly_pubkey;
const secp256k1_xonly_pubkey *xonly_pubkeys[1];
secp256k1_pubkey plain_pubkey;
const secp256k1_pubkey *plain_pubkeys[1];
#endif
for (i = 0; i < 32; i++) { for (i = 0; i < 32; i++) {
msg[i] = i + 1; msg[i] = i + 1;
@ -262,5 +286,61 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
CHECK(ret == 1); CHECK(ret == 1);
} }
#endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
SECP256K1_CHECKMEM_DEFINE(key, 32);
generated_outputs[0] = &generated_output;
/* Initialize recipient */
CHECK(secp256k1_ec_pubkey_create(ctx, &recipient.scan_pubkey, key));
key[31] ^= 1;
CHECK(secp256k1_ec_pubkey_create(ctx, &recipient.labeled_spend_pubkey, key));
key[31] ^= (1 << 1);
recipient.index = 0;
recipients[0] = &recipient;
/* Set up secret keys */
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
ret = secp256k1_keypair_create(ctx, &taproot_seckey, key);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret);
key[31] ^= (1 << 2);
taproot_seckeys[0] = &taproot_seckey;
plain_seckeys[0] = key;
ret = secp256k1_silentpayments_sender_create_outputs(ctx, generated_outputs, recipients, 1, outpoint_smallest, taproot_seckeys, 1, plain_seckeys, 1);
CHECK(ret == 1);
/* TODO: use non-confusing public key */
ret = secp256k1_silentpayments_recipient_create_label(ctx, &recipient.labeled_spend_pubkey, label_tweak, key, 0);
key[31] ^= (1 << 3);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
CHECK(secp256k1_keypair_xonly_pub(ctx, &xonly_pubkey, NULL, &taproot_seckey));
SECP256K1_CHECKMEM_DEFINE(&xonly_pubkey, sizeof(xonly_pubkey));
xonly_pubkeys[0] = &xonly_pubkey;
ret = secp256k1_ec_pubkey_create(ctx, &plain_pubkey, plain_seckeys[0]);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
SECP256K1_CHECKMEM_DEFINE(&plain_pubkey, sizeof(plain_pubkey));
plain_pubkeys[0] = &plain_pubkey;
ret = secp256k1_silentpayments_recipient_public_data_create(ctx, &public_data, outpoint_smallest, xonly_pubkeys, 1, plain_pubkeys, 1);
CHECK(ret == 1);
tx_outputs[0] = generated_outputs[0];
n_found_outputs = 1;
SECP256K1_CHECKMEM_DEFINE(&recipient.labeled_spend_pubkey, sizeof(recipient.labeled_spend_pubkey));
/* TODO: make sure we're actually go through all relevant code paths */
ret = secp256k1_silentpayments_recipient_scan_outputs(ctx, found_outputs, &n_found_outputs, tx_outputs, 1, key, &public_data, &recipient.labeled_spend_pubkey, NULL, NULL);
CHECK(ret == 1);
/* TODO: this fails */
/* CHECK(secp256k1_silentpayments_recipient_create_shared_secret(ctx, shared_secret, key, &public_data)); */
/* TODO: test secp256k1_silentpayments_recipient_create_output_pubkey */
#endif #endif
} }

View file

@ -9,6 +9,7 @@
#include "eckey.h" #include "eckey.h"
#include "util.h"
#include "scalar.h" #include "scalar.h"
#include "field.h" #include "field.h"
#include "group.h" #include "group.h"
@ -35,6 +36,8 @@ static int secp256k1_eckey_pubkey_parse(secp256k1_ge *elem, const unsigned char
} }
static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *pub, size_t *size, int compressed) { static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *pub, size_t *size, int compressed) {
VERIFY_CHECK(compressed == 0 || compressed == 1);
if (secp256k1_ge_is_infinity(elem)) { if (secp256k1_ge_is_infinity(elem)) {
return 0; return 0;
} }

View file

@ -80,7 +80,11 @@ static void secp256k1_ge_set_gej(secp256k1_ge *r, secp256k1_gej *a);
/** Set a group element equal to another which is given in jacobian coordinates. */ /** Set a group element equal to another which is given in jacobian coordinates. */
static void secp256k1_ge_set_gej_var(secp256k1_ge *r, secp256k1_gej *a); static void secp256k1_ge_set_gej_var(secp256k1_ge *r, secp256k1_gej *a);
/** Set a batch of group elements equal to the inputs given in jacobian coordinates */ /** Set group elements r[0:len] (affine) equal to group elements a[0:len] (jacobian).
* None of the group elements in a[0:len] may be infinity. Constant time. */
static void secp256k1_ge_set_all_gej(secp256k1_ge *r, const secp256k1_gej *a, size_t len);
/** Set group elements r[0:len] (affine) equal to group elements a[0:len] (jacobian). */
static void secp256k1_ge_set_all_gej_var(secp256k1_ge *r, const secp256k1_gej *a, size_t len); static void secp256k1_ge_set_all_gej_var(secp256k1_ge *r, const secp256k1_gej *a, size_t len);
/** Bring a batch of inputs to the same global z "denominator", based on ratios between /** Bring a batch of inputs to the same global z "denominator", based on ratios between

View file

@ -195,6 +195,44 @@ static void secp256k1_ge_set_gej_var(secp256k1_ge *r, secp256k1_gej *a) {
SECP256K1_GE_VERIFY(r); SECP256K1_GE_VERIFY(r);
} }
static void secp256k1_ge_set_all_gej(secp256k1_ge *r, const secp256k1_gej *a, size_t len) {
secp256k1_fe u;
size_t i;
#ifdef VERIFY
for (i = 0; i < len; i++) {
SECP256K1_GEJ_VERIFY(&a[i]);
VERIFY_CHECK(!secp256k1_gej_is_infinity(&a[i]));
}
#endif
if (len == 0) {
return;
}
/* Use destination's x coordinates as scratch space */
r[0].x = a[0].z;
for (i = 1; i < len; i++) {
secp256k1_fe_mul(&r[i].x, &r[i - 1].x, &a[i].z);
}
secp256k1_fe_inv(&u, &r[len - 1].x);
for (i = len - 1; i > 0; i--) {
secp256k1_fe_mul(&r[i].x, &r[i - 1].x, &u);
secp256k1_fe_mul(&u, &u, &a[i].z);
}
r[0].x = u;
for (i = 0; i < len; i++) {
secp256k1_ge_set_gej_zinv(&r[i], &a[i], &r[i].x);
}
#ifdef VERIFY
for (i = 0; i < len; i++) {
SECP256K1_GE_VERIFY(&r[i]);
}
#endif
}
static void secp256k1_ge_set_all_gej_var(secp256k1_ge *r, const secp256k1_gej *a, size_t len) { static void secp256k1_ge_set_all_gej_var(secp256k1_ge *r, const secp256k1_gej *a, size_t len) {
secp256k1_fe u; secp256k1_fe u;
size_t i; size_t i;

View file

@ -395,6 +395,7 @@ static void secp256k1_nonce_function_musig(secp256k1_scalar *k, const unsigned c
static int secp256k1_musig_nonce_gen_internal(const secp256k1_context* ctx, secp256k1_musig_secnonce *secnonce, secp256k1_musig_pubnonce *pubnonce, const unsigned char *input_nonce, const unsigned char *seckey, const secp256k1_pubkey *pubkey, const unsigned char *msg32, const secp256k1_musig_keyagg_cache *keyagg_cache, const unsigned char *extra_input32) { static int secp256k1_musig_nonce_gen_internal(const secp256k1_context* ctx, secp256k1_musig_secnonce *secnonce, secp256k1_musig_pubnonce *pubnonce, const unsigned char *input_nonce, const unsigned char *seckey, const secp256k1_pubkey *pubkey, const unsigned char *msg32, const secp256k1_musig_keyagg_cache *keyagg_cache, const unsigned char *extra_input32) {
secp256k1_scalar k[2]; secp256k1_scalar k[2];
secp256k1_ge nonce_pts[2]; secp256k1_ge nonce_pts[2];
secp256k1_gej nonce_ptj[2];
int i; int i;
unsigned char pk_ser[33]; unsigned char pk_ser[33];
size_t pk_ser_len = sizeof(pk_ser); size_t pk_ser_len = sizeof(pk_ser);
@ -444,13 +445,20 @@ static int secp256k1_musig_nonce_gen_internal(const secp256k1_context* ctx, secp
secp256k1_musig_secnonce_save(secnonce, k, &pk); secp256k1_musig_secnonce_save(secnonce, k, &pk);
secp256k1_musig_secnonce_invalidate(ctx, secnonce, !ret); secp256k1_musig_secnonce_invalidate(ctx, secnonce, !ret);
/* Compute pubnonce as two gejs */
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
secp256k1_gej nonce_ptj; secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &nonce_ptj[i], &k[i]);
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &nonce_ptj, &k[i]);
secp256k1_ge_set_gej(&nonce_pts[i], &nonce_ptj);
secp256k1_declassify(ctx, &nonce_pts[i], sizeof(nonce_pts[i]));
secp256k1_scalar_clear(&k[i]); secp256k1_scalar_clear(&k[i]);
secp256k1_gej_clear(&nonce_ptj); }
/* Batch convert to two public ges */
secp256k1_ge_set_all_gej(nonce_pts, nonce_ptj, 2);
for (i = 0; i < 2; i++) {
secp256k1_gej_clear(&nonce_ptj[i]);
}
for (i = 0; i < 2; i++) {
secp256k1_declassify(ctx, &nonce_pts[i], sizeof(nonce_pts[i]));
} }
/* None of the nonce_pts will be infinity because k != 0 with overwhelming /* None of the nonce_pts will be infinity because k != 0 with overwhelming
* probability */ * probability */

View file

@ -94,6 +94,8 @@ static int nonce_function_bip340(unsigned char *nonce32, const unsigned char *ms
secp256k1_sha256_write(&sha, msg, msglen); secp256k1_sha256_write(&sha, msg, msglen);
secp256k1_sha256_finalize(&sha, nonce32); secp256k1_sha256_finalize(&sha, nonce32);
secp256k1_sha256_clear(&sha); secp256k1_sha256_clear(&sha);
secp256k1_memclear(masked_key, sizeof(masked_key));
return 1; return 1;
} }

View file

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

View file

@ -0,0 +1,156 @@
/***********************************************************************
* Copyright (c) 2024 josibake *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
#ifndef SECP256K1_MODULE_SILENTPAYMENTS_BENCH_H
#define SECP256K1_MODULE_SILENTPAYMENTS_BENCH_H
#include "../../../include/secp256k1_silentpayments.h"
typedef struct {
secp256k1_context *ctx;
secp256k1_pubkey spend_pubkey;
unsigned char scan_key[32];
unsigned char input_pubkey33[33];
secp256k1_xonly_pubkey tx_outputs[2];
secp256k1_xonly_pubkey tx_inputs[2];
secp256k1_silentpayments_found_output found_outputs[2];
unsigned char scalar[32];
unsigned char smallest_outpoint[36];
} bench_silentpayments_data;
/* we need a non-null pointer for the cache */
static int noop;
void* label_cache = &noop;
const unsigned char* label_lookup(const unsigned char* key, const void* cache_ptr) {
(void)key;
(void)cache_ptr;
return NULL;
}
static void bench_silentpayments_scan_setup(void* arg) {
int i;
bench_silentpayments_data *data = (bench_silentpayments_data*)arg;
const unsigned char tx_outputs[2][32] = {
{0x84,0x17,0x92,0xc3,0x3c,0x9d,0xc6,0x19,0x3e,0x76,0x74,0x41,0x34,0x12,0x5d,0x40,0xad,0xd8,0xf2,0xf4,0xa9,0x64,0x75,0xf2,0x8b,0xa1,0x50,0xbe,0x03,0x2d,0x64,0xe8},
{0x2e,0x84,0x7b,0xb0,0x1d,0x1b,0x49,0x1d,0xa5,0x12,0xdd,0xd7,0x60,0xb8,0x50,0x96,0x17,0xee,0x38,0x05,0x70,0x03,0xd6,0x11,0x5d,0x00,0xba,0x56,0x24,0x51,0x32,0x3a},
};
const unsigned char static_tx_input[32] = {
0xf2,0x07,0x16,0x2b,0x1a,0x7a,0xbc,0x51,
0xc4,0x20,0x17,0xbe,0xf0,0x55,0xe9,0xec,
0x1e,0xfc,0x3d,0x35,0x67,0xcb,0x72,0x03,
0x57,0xe2,0xb8,0x43,0x25,0xdb,0x33,0xac
};
const 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,
};
const unsigned char 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
};
const unsigned char 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
};
secp256k1_keypair input_keypair;
secp256k1_pubkey input_pubkey;
size_t pubkeylen = 33;
for (i = 0; i < 32; i++) {
data->scalar[i] = i + 1;
}
for (i = 0; i < 2; i++) {
CHECK(secp256k1_xonly_pubkey_parse(data->ctx, &data->tx_outputs[i], tx_outputs[i]));
}
/* Create the first input public key from the scalar.
* This input is also used to create the serialized public data object for the light client
*/
CHECK(secp256k1_keypair_create(data->ctx, &input_keypair, data->scalar));
CHECK(secp256k1_keypair_pub(data->ctx, &input_pubkey, &input_keypair));
CHECK(secp256k1_ec_pubkey_serialize(data->ctx, data->input_pubkey33, &pubkeylen, &input_pubkey, SECP256K1_EC_COMPRESSED));
/* Create the input public keys for the full scan */
CHECK(secp256k1_keypair_xonly_pub(data->ctx, &data->tx_inputs[0], NULL, &input_keypair));
CHECK(secp256k1_xonly_pubkey_parse(data->ctx, &data->tx_inputs[1], static_tx_input));
CHECK(secp256k1_ec_pubkey_parse(data->ctx, &data->spend_pubkey, spend_pubkey, pubkeylen));
memcpy(data->scan_key, scan_key, 32);
memcpy(data->smallest_outpoint, smallest_outpoint, 36);
}
static void bench_silentpayments_output_scan(void* arg, int iters) {
int i, k = 0;
bench_silentpayments_data *data = (bench_silentpayments_data*)arg;
secp256k1_silentpayments_recipient_public_data public_data;
for (i = 0; i < iters; i++) {
unsigned char shared_secret[33];
secp256k1_xonly_pubkey xonly_output;
CHECK(secp256k1_silentpayments_recipient_public_data_parse(data->ctx, &public_data, data->input_pubkey33));
CHECK(secp256k1_silentpayments_recipient_create_shared_secret(data->ctx,
shared_secret,
data->scan_key,
&public_data
));
CHECK(secp256k1_silentpayments_recipient_create_output_pubkey(data->ctx,
&xonly_output,
shared_secret,
&data->spend_pubkey,
k
));
}
}
static void bench_silentpayments_full_tx_scan(void* arg, int iters) {
int i;
size_t n_found = 0;
secp256k1_silentpayments_found_output *found_output_ptrs[2];
const secp256k1_xonly_pubkey *tx_output_ptrs[2];
const secp256k1_xonly_pubkey *tx_input_ptrs[2];
bench_silentpayments_data *data = (bench_silentpayments_data*)arg;
secp256k1_silentpayments_recipient_public_data public_data;
for (i = 0; i < 2; i++) {
found_output_ptrs[i] = &data->found_outputs[i];
tx_output_ptrs[i] = &data->tx_outputs[i];
tx_input_ptrs[i] = &data->tx_inputs[i];
}
for (i = 0; i < iters; i++) {
CHECK(secp256k1_silentpayments_recipient_public_data_create(data->ctx,
&public_data,
data->smallest_outpoint,
tx_input_ptrs, 2,
NULL, 0
));
CHECK(secp256k1_silentpayments_recipient_scan_outputs(data->ctx,
found_output_ptrs, &n_found,
tx_output_ptrs, 2,
data->scan_key,
&public_data,
&data->spend_pubkey,
label_lookup, label_cache)
);
}
}
static void run_silentpayments_bench(int iters, int argc, char** argv) {
bench_silentpayments_data data;
int d = argc == 1;
/* create a context with no capabilities */
data.ctx = secp256k1_context_create(SECP256K1_FLAGS_TYPE_CONTEXT);
if (d || have_flag(argc, argv, "silentpayments")) run_benchmark("silentpayments_full_tx_scan", bench_silentpayments_full_tx_scan, bench_silentpayments_scan_setup, NULL, &data, 10, iters);
if (d || have_flag(argc, argv, "silentpayments")) run_benchmark("silentpayments_output_scan", bench_silentpayments_output_scan, bench_silentpayments_scan_setup, NULL, &data, 10, iters);
secp256k1_context_destroy(data.ctx);
}
#endif /* SECP256K1_MODULE_SILENTPAYMENTS_BENCH_H */

View file

@ -0,0 +1,731 @@
/***********************************************************************
* 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_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 *ctx) {
return secp256k1_ec_pubkey_cmp((secp256k1_context *)ctx,
&(*(const secp256k1_silentpayments_recipient **)pk1)->scan_pubkey,
&(*(const secp256k1_silentpayments_recipient **)pk2)->scan_pubkey
);
}
static void secp256k1_silentpayments_recipient_sort(const secp256k1_context* ctx, const secp256k1_silentpayments_recipient **recipients, size_t n_recipients) {
/* 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, (void *)ctx);
#if defined(_MSC_VER) && (_MSC_VER < 1933)
#pragma warning(pop)
#endif
}
/** 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 len;
int ret;
secp256k1_silentpayments_sha256_init_inputs(&hash);
secp256k1_sha256_write(&hash, outpoint_smallest36, 36);
ret = secp256k1_eckey_pubkey_serialize(pubkey_sum, pubkey_sum_ser, &len, 1);
VERIFY_CHECK(ret && len == sizeof(pubkey_sum_ser));
(void)ret;
secp256k1_sha256_write(&hash, pubkey_sum_ser, sizeof(pubkey_sum_ser));
secp256k1_sha256_finalize(&hash, input_hash);
}
static void secp256k1_silentpayments_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, const secp256k1_scalar *secret_component, const secp256k1_ge *public_component) {
secp256k1_gej ss_j;
secp256k1_ge ss;
size_t len;
int ret;
/* Compute shared_secret = tweaked_secret_component * Public_component */
secp256k1_ecmult_const(&ss_j, public_component, secret_component);
secp256k1_ge_set_gej(&ss, &ss_j);
secp256k1_declassify(ctx, &ss, sizeof(ss));
/* This can only fail if the shared secret is the point at infinity, which should be
* impossible at this point, considering we have already validated the public key and
* the secret key being used
*/
ret = secp256k1_eckey_pubkey_serialize(&ss, shared_secret33, &len, 1);
VERIFY_CHECK(ret && len == 33);
(void)ret;
/* While not technically "secret" data, explicitly clear the shared secret since leaking this would allow an attacker
* to identify the resulting transaction as a silent payments transaction and potentially link the transaction
* back to the silent payment address
*/
secp256k1_ge_clear(&ss);
secp256k1_gej_clear(&ss_j);
}
/** 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, uint32_t k) {
secp256k1_sha256 hash;
unsigned char hash_ser[32];
unsigned char k_serialized[4];
int overflow = 0;
/* 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, &overflow);
VERIFY_CHECK(!overflow);
VERIFY_CHECK(!secp256k1_scalar_is_zero(t_k_scalar));
/* While not technically "secret" data, explicitly clear hash_ser since leaking this would allow an attacker
* to identify the resulting transaction as a silent payments transaction and potentially link the transaction
* back to the silent payment address
*/
secp256k1_memclear(hash_ser, sizeof(hash_ser));
}
static int secp256k1_silentpayments_create_output_pubkey(const secp256k1_context *ctx, secp256k1_xonly_pubkey *P_output_xonly, const unsigned char *shared_secret33, const secp256k1_pubkey *recipient_labeled_spend_pubkey, uint32_t k) {
secp256k1_ge P_output_ge;
secp256k1_scalar t_k_scalar;
int ret;
/* Calculate and return P_output_xonly = B_spend + t_k * G
* This will fail if B_spend is the point at infinity or if
* B_spend + t_k*G is the point at infinity.
*/
secp256k1_silentpayments_create_t_k(&t_k_scalar, shared_secret33, k);
if (!secp256k1_pubkey_load(ctx, &P_output_ge, recipient_labeled_spend_pubkey)) {
secp256k1_scalar_clear(&t_k_scalar);
return 0;
}
ret = secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar);
/* tweak add only fails if t_k_scalar is equal to the dlog of P_output_ge, but t_k_scalar is the output of a collision resistant hash function. */
/* TODO: consider declassify ret */
/* TODO: but we don't want to imply this can never happen */
VERIFY_CHECK(ret);
#ifndef VERIFY
(void) ret;
#endif
secp256k1_xonly_pubkey_save(P_output_xonly, &P_output_ge);
/* While not technically "secret" data, explicitly clear t_k since leaking this would allow an attacker
* to identify the resulting transaction as a silent payments transaction and potentially link the transaction
* back to the silent payment address
*/
secp256k1_scalar_clear(&t_k_scalar);
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, input_hash_scalar;
secp256k1_ge A_sum_ge;
secp256k1_gej A_sum_gej;
unsigned char input_hash[32];
unsigned char shared_secret[33];
secp256k1_silentpayments_recipient last_recipient;
int overflow = 0;
int ret;
/* Sanity check inputs. */
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
ARG_CHECK(generated_outputs != NULL);
ARG_CHECK(recipients != NULL);
ARG_CHECK(n_recipients > 0);
ARG_CHECK((plain_seckeys != NULL) || (taproot_seckeys != NULL));
if (taproot_seckeys != NULL) {
ARG_CHECK(n_taproot_seckeys > 0);
} else {
ARG_CHECK(n_taproot_seckeys == 0);
}
if (plain_seckeys != NULL) {
ARG_CHECK(n_plain_seckeys > 0);
} else {
ARG_CHECK(n_plain_seckeys == 0);
}
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++) {
ret = secp256k1_scalar_set_b32_seckey(&addend, plain_seckeys[i]);
secp256k1_declassify(ctx, &ret, sizeof(ret));
if (!ret) {
secp256k1_scalar_clear(&addend);
secp256k1_scalar_clear(&a_sum_scalar);
return 0;
}
secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend);
}
/* 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;
ret = secp256k1_keypair_load(ctx, &addend, &addend_point, taproot_seckeys[i]);
secp256k1_declassify(ctx, &ret, sizeof(ret));
if (!ret) {
secp256k1_scalar_clear(&addend);
secp256k1_scalar_clear(&a_sum_scalar);
return 0;
}
secp256k1_declassify(ctx, &ret, sizeof(ret));
if (secp256k1_fe_is_odd(&addend_point.y)) {
secp256k1_scalar_negate(&addend, &addend);
}
secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend);
}
/* If there are any failures in loading/summing up the secret keys, fail early */
ret = secp256k1_scalar_is_zero(&a_sum_scalar);
secp256k1_declassify(ctx, &ret, sizeof(ret));
if (ret) {
secp256k1_scalar_clear(&addend);
secp256k1_scalar_clear(&a_sum_scalar);
return 0;
} else {
/* Clear the addend variable as this is no longer needed at this point
* and contains secret data. This saves from needing to remember to clear
* this variable from multiple places below */
secp256k1_scalar_clear(&addend);
}
/* 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);
/* TODO: comment */
secp256k1_declassify(ctx, &A_sum_ge, sizeof(A_sum_ge));
/* Calculate the input hash and tweak a_sum, i.e., a_sum_tweaked = a_sum * input_hash
* This should fail if input hash is greater than the curve order, but this is stastically improbable so
* we only do a verify_check here.
*/
secp256k1_silentpayments_calculate_input_hash(input_hash, outpoint_smallest36, &A_sum_ge);
secp256k1_scalar_set_b32(&input_hash_scalar, input_hash, &overflow);
VERIFY_CHECK(!overflow);
secp256k1_scalar_mul(&a_sum_scalar, &a_sum_scalar, &input_hash_scalar);
/* _recipient_sort sorts the array of recipients in place by their scan public keys (lexicographically).
* This ensures that all recipients with the same scan public key are grouped together, as specified in BIP0352.
*
* More specifically, this ensures `k` is incremented from 0 to the number of requested outputs for each recipient group,
* where a recipient group is all addresses with the same scan public key.
*/
secp256k1_silentpayments_recipient_sort(ctx, recipients, n_recipients);
last_recipient = *recipients[0];
k = 0;
for (i = 0; i < n_recipients; i++) {
if ((i == 0) || (secp256k1_ec_pubkey_cmp(ctx, &last_recipient.scan_pubkey, &recipients[i]->scan_pubkey) != 0)) {
/* If we are on a different scan pubkey, its time to recreate the shared secret and reset k to 0.
* It's very unlikely the scan public key is invalid by this point, since this means the caller would
* have created the _silentpayments_recipient object incorrectly, but just to be sure we still check that
* the public key is valid.
*/
secp256k1_ge pk;
if (!secp256k1_pubkey_load(ctx, &pk, &recipients[i]->scan_pubkey)) {
secp256k1_scalar_clear(&a_sum_scalar);
return 0;
}
secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, &a_sum_scalar, &pk);
k = 0;
}
if (!secp256k1_silentpayments_create_output_pubkey(ctx, generated_outputs[recipients[i]->index], shared_secret, &recipients[i]->labeled_spend_pubkey, k)) {
secp256k1_scalar_clear(&a_sum_scalar);
return 0;
}
k++;
last_recipient = *recipients[i];
}
/* Explicitly clear variables containing secret data */
secp256k1_scalar_clear(&a_sum_scalar);
/* While technically not "secret data," explicitly clear the shared secret since leaking this
* could result in a third party being able to identify the transaction as a silent payments transaction
* and potentially link the transaction back to a silent payment address
*/
secp256k1_memclear(&shared_secret, sizeof(shared_secret));
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(const secp256k1_context *ctx, secp256k1_pubkey *label, unsigned char *label_tweak32, const unsigned char *recipient_scan_key32, const uint32_t m) {
secp256k1_sha256 hash;
unsigned char m_serialized[4];
/* Sanity check inputs. */
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(label != NULL);
ARG_CHECK(label_tweak32 != NULL);
ARG_CHECK(recipient_scan_key32 != 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, recipient_scan_key32, 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 */
return secp256k1_ec_pubkey_create(ctx, label, label_tweak32);
}
int secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(const secp256k1_context *ctx, secp256k1_pubkey *labeled_spend_pubkey, const secp256k1_pubkey *recipient_spend_pubkey, const secp256k1_pubkey *label) {
secp256k1_ge B_m, label_addend;
secp256k1_gej result_gej;
secp256k1_ge result_ge;
int ret;
/* Sanity check inputs. */
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(labeled_spend_pubkey != NULL);
ARG_CHECK(recipient_spend_pubkey != NULL);
ARG_CHECK(label != NULL);
/* Calculate B_m = B_spend + label
* If either the label or spend public key is an invalid public key,
* return early
*/
ret = secp256k1_pubkey_load(ctx, &B_m, recipient_spend_pubkey);
ret &= secp256k1_pubkey_load(ctx, &label_addend, label);
if (!ret) {
return ret;
}
secp256k1_gej_set_ge(&result_gej, &B_m);
secp256k1_gej_add_ge_var(&result_gej, &result_gej, &label_addend, NULL);
if (secp256k1_gej_is_infinity(&result_gej)) {
return 0;
}
secp256k1_ge_set_gej(&result_ge, &result_gej);
secp256k1_pubkey_save(labeled_spend_pubkey, &result_ge);
return 1;
}
/** A explanation of the public_data object and its usage:
*
* The public_data object contains:
*
* [magic: 4 bytes][boolean: 1 byte][A_sum: 64 bytes][input_hash: 32 bytes]
*
* The magic bytes are checked by functions using the public_data object to
* check that the public data object was initialized correctly.
*
* The boolean (combined) indicates whether or not the A_sum point and the
* input_hash scalar have already been combined or are both included. The reason
* for keeping input_hash and A_sum separate is so that an elliptic curve multiplication
* can be avoided when creating the shared secret, i.e., (b_scan * input_hash) * A_sum.
*
* But when storing the public data object, either to send to light clients or for
* wallet rescans, we can save 32-bytes by combining the input_hash and A_sum and saving
* the resulting point serialized as a compressed public key, i.e., input_hash * A_sum.
*
* For the each function:
*
* - `_recipient_public_data_create` always creates a public_data object with combined = false
* - `_recipient_public_data_serialize` only accepts a public_data object with combined = false
* and then performs an EC mult before serializing the resulting public key as a compressed
* public key
* - `_recpient_public_data_parse` assumes the input represents a previously serialized
* public_data object and always deserializes into a public_data object with combined = true
* (and the input_hash portion zeroed out).
*/
int secp256k1_silentpayments_recipient_public_data_create(
const secp256k1_context *ctx,
secp256k1_silentpayments_recipient_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 = 64;
secp256k1_ge A_sum_ge, addend;
secp256k1_gej A_sum_gej;
secp256k1_scalar input_hash_scalar;
unsigned char input_hash_local[32];
int overflow = 0;
int ret = 1;
/* Sanity check inputs */
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(public_data != NULL);
ARG_CHECK(outpoint_smallest36 != NULL);
ARG_CHECK((plain_pubkeys != NULL) || (xonly_pubkeys != NULL));
if (xonly_pubkeys != NULL) {
ARG_CHECK(n_xonly_pubkeys > 0);
} else {
ARG_CHECK(n_xonly_pubkeys == 0);
}
if (plain_pubkeys != NULL) {
ARG_CHECK(n_plain_pubkeys > 0);
} else {
ARG_CHECK(n_plain_pubkeys == 0);
}
secp256k1_memclear(input_hash_local, 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++) {
ret &= secp256k1_pubkey_load(ctx, &addend, plain_pubkeys[i]);
secp256k1_gej_add_ge_var(&A_sum_gej, &A_sum_gej, &addend, NULL);
}
for (i = 0; i < n_xonly_pubkeys; i++) {
ret &= secp256k1_xonly_pubkey_load(ctx, &addend, xonly_pubkeys[i]);
secp256k1_gej_add_ge_var(&A_sum_gej, &A_sum_gej, &addend, NULL);
}
/* Since an attacker can maliciously craft transactions where the public keys sum to zero, fail early here
* to avoid making the caller do extra work, e.g., when building an index or scanning many malicious transactions
*
* This will also fail if any of the provided input public keys are malformed.
*/
if (!ret || secp256k1_gej_is_infinity(&A_sum_gej)) {
return 0;
}
/* Compute input_hash = hash(outpoint_L || A_sum) */
secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej);
secp256k1_silentpayments_calculate_input_hash(input_hash_local, outpoint_smallest36, &A_sum_ge);
/* Convert input_hash to a scalar to ensure the value is less than the curve order.
*
* This can only fail if the output of the hash function is greater than the curve order, which
* is statistically improbable.
*
* TODO: BIP0352 is currently underspecified with respect to input_hash, update to specify how to
* handle the conversion of input_hash to a scalar.*/
secp256k1_scalar_set_b32(&input_hash_scalar, input_hash_local, &overflow);
VERIFY_CHECK(!overflow);
/* serialize the public_data struct */
memcpy(&public_data->data[0], secp256k1_silentpayments_public_data_magic, 4);
public_data->data[4] = 0;
secp256k1_ge_to_bytes(&public_data->data[5], &A_sum_ge);
memcpy(&public_data->data[5 + pubkeylen], input_hash_local, 32);
return 1;
}
static int secp256k1_silentpayments_recipient_public_data_load_pubkey(const secp256k1_context* ctx, secp256k1_ge *ge, const secp256k1_silentpayments_recipient_public_data *public_data) {
ARG_CHECK(secp256k1_memcmp_var(&public_data->data[0], secp256k1_silentpayments_public_data_magic, 4) == 0);
secp256k1_ge_from_bytes(ge, &public_data->data[5]);
return 1;
}
static int secp256k1_silentpayments_recipient_public_data_load_input_hash(const secp256k1_context* ctx, secp256k1_scalar *input_hash_scalar, const secp256k1_silentpayments_recipient_public_data *public_data) {
ARG_CHECK(secp256k1_memcmp_var(&public_data->data[0], secp256k1_silentpayments_public_data_magic, 4) == 0);
secp256k1_scalar_set_b32(input_hash_scalar, &public_data->data[5 + 64], NULL);
return 1;
}
int secp256k1_silentpayments_recipient_public_data_serialize(const secp256k1_context *ctx, unsigned char *output33, const secp256k1_silentpayments_recipient_public_data *public_data) {
secp256k1_ge ge;
secp256k1_scalar input_hash_scalar;
size_t pubkeylen = 33;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(output33 != NULL);
ARG_CHECK(public_data != NULL);
ARG_CHECK(secp256k1_memcmp_var(&public_data->data[0], secp256k1_silentpayments_public_data_magic, 4) == 0);
/* Only allow public_data to be serialized if it has the hash and the summed public key
* This helps protect against accidentally serializing just the summed public key A
*/
ARG_CHECK(public_data->data[4] == 0);
/* These functions should never fail at this point considering:
* - `_public_data_load` functions can only fail if the public data object was created incorrectly
* and we already check for this above
* - `_tweak_mul` can only fail if input_hash_scalar is zero, but assuming the public_data object
* was created correctly, this is impossible because input_hash_scalar is the output of a hash function
* - `_eckey_pubkey_serialize` can only fail if the point we are trying to serialize is the point at infinity
*
* Note: we don't verify that the input hash is less than the curve order since this is verified when the
* public data object is created.
*/
secp256k1_silentpayments_recipient_public_data_load_pubkey(ctx, &ge, public_data);
secp256k1_silentpayments_recipient_public_data_load_input_hash(ctx, &input_hash_scalar, public_data);
secp256k1_eckey_pubkey_tweak_mul(&ge, &input_hash_scalar);
secp256k1_eckey_pubkey_serialize(&ge, output33, &pubkeylen, 1);
VERIFY_CHECK(pubkeylen == 33);
return 1;
}
int secp256k1_silentpayments_recipient_public_data_parse(const secp256k1_context *ctx, secp256k1_silentpayments_recipient_public_data *public_data, const unsigned char *input33) {
size_t inputlen = 33;
secp256k1_ge pk;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(public_data != NULL);
ARG_CHECK(input33 != NULL);
/* Since an attacker can send us malicious data that looks like a serialized public key but is not, fail early */
if (!secp256k1_eckey_pubkey_parse(&pk, input33, inputlen)) {
return 0;
}
/* A serialized public data will always have have the input_hash multiplied in, so we set combined = true.
* Additionally, we zero out the 32 bytes where the input_hash would be
*/
memcpy(&public_data->data[0], secp256k1_silentpayments_public_data_magic, 4);
public_data->data[4] = 1;
secp256k1_ge_to_bytes(&public_data->data[5], &pk);
memset(&public_data->data[5 + 64], 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 *recipient_scan_key32,
const secp256k1_silentpayments_recipient_public_data *public_data,
const secp256k1_pubkey *recipient_spend_pubkey,
const secp256k1_silentpayments_label_lookup label_lookup,
const void *label_context
) {
secp256k1_scalar t_k_scalar, rsk_scalar;
secp256k1_ge label_ge, recipient_spend_pubkey_ge, A_sum_ge;
secp256k1_xonly_pubkey P_output_xonly;
unsigned char shared_secret[33];
const unsigned char *label_tweak = NULL;
size_t i, k, n_found, found_idx;
int found, combined;
int ret = 1;
/* Sanity check inputs */
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(found_outputs != NULL);
ARG_CHECK(n_found_outputs != NULL);
ARG_CHECK(tx_outputs != NULL);
ARG_CHECK(n_tx_outputs > 0);
ARG_CHECK(recipient_scan_key32 != NULL);
ARG_CHECK(public_data != NULL);
ARG_CHECK(recipient_spend_pubkey != NULL);
/* Passing a context without a lookup function is non-sensical */
if (label_context != NULL) {
ARG_CHECK(label_lookup != NULL);
}
/* Recall: a scan key isnt really "secret" data in that leaking the scan key will only leak privacy.
*
* However, if there is something wrong with the recipient scan key, recipient spend pubkey, or the public data,
* we fail early and make sure to clear the scan key from memory. */
ret = secp256k1_scalar_set_b32_seckey(&rsk_scalar, recipient_scan_key32);
secp256k1_declassify(ctx, &ret, sizeof(ret));
if (!ret) {
secp256k1_scalar_clear(&rsk_scalar);
return 0;
}
ret = secp256k1_silentpayments_recipient_public_data_load_pubkey(ctx, &A_sum_ge, public_data);
combined = (int)public_data->data[4];
if (!combined) {
secp256k1_scalar input_hash_scalar;
ret &= secp256k1_silentpayments_recipient_public_data_load_input_hash(ctx, &input_hash_scalar, public_data);
secp256k1_scalar_mul(&rsk_scalar, &rsk_scalar, &input_hash_scalar);
}
ret &= secp256k1_pubkey_load(ctx, &recipient_spend_pubkey_ge, recipient_spend_pubkey);
if (!ret) {
secp256k1_scalar_clear(&rsk_scalar);
return 0;
}
secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, &rsk_scalar, &A_sum_ge);
found_idx = 0;
n_found = 0;
k = 0;
while (1) {
secp256k1_ge P_output_ge = recipient_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
* This can fail if t_k is the negation of B_spend, but this is statistically
* improbable as t_k is the output of a hash function. */
ret = secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar);
VERIFY_CHECK(ret);
found = 0;
secp256k1_xonly_pubkey_save(&P_output_xonly, &P_output_ge);
for (i = 0; i < n_tx_outputs; i++) {
if (secp256k1_xonly_pubkey_cmp(ctx, &P_output_xonly, tx_outputs[i]) == 0) {
label_tweak = NULL;
found = 1;
found_idx = i;
break;
}
/* If not found, proceed to check for labels (if the labels cache is present) */
if (label_lookup != NULL) {
secp256k1_ge P_output_negated_ge, tx_output_ge;
secp256k1_gej tx_output_gej, label_gej;
unsigned char label33[33];
size_t len;
secp256k1_xonly_pubkey_load(ctx, &tx_output_ge, tx_outputs[i]);
secp256k1_gej_set_ge(&tx_output_gej, &tx_output_ge);
secp256k1_ge_neg(&P_output_negated_ge, &P_output_ge);
/* Negate the generated output and calculate first scan label candidate:
* label1 = tx_output - P_output */
secp256k1_gej_add_ge_var(&label_gej, &tx_output_gej, &P_output_negated_ge, NULL);
secp256k1_ge_set_gej(&label_ge, &label_gej);
ret = secp256k1_eckey_pubkey_serialize(&label_ge, label33, &len, 1);
/* serialize must succeed because the point was just loaded */
VERIFY_CHECK(ret && len == 33);
label_tweak = label_lookup(label33, label_context);
if (label_tweak != NULL) {
found = 1;
found_idx = i;
break;
}
secp256k1_gej_neg(&label_gej, &tx_output_gej);
/* If not found, negate the tx_output and calculate second scan label candidate:
* label2 = -tx_output - P_output */
secp256k1_gej_add_ge_var(&label_gej, &label_gej, &P_output_negated_ge, NULL);
secp256k1_ge_set_gej(&label_ge, &label_gej);
ret = secp256k1_eckey_pubkey_serialize(&label_ge, label33, &len, 1);
/* serialize must succeed because the point was just loaded */
VERIFY_CHECK(ret && len == 33);
label_tweak = label_lookup(label33, label_context);
if (label_tweak != NULL) {
found = 1;
found_idx = i;
break;
}
}
}
if (found) {
found_outputs[n_found]->output = *tx_outputs[found_idx];
secp256k1_scalar_get_b32(found_outputs[n_found]->tweak, &t_k_scalar);
if (label_tweak != NULL) {
found_outputs[n_found]->found_with_label = 1;
/* This is extremely unlikely to fail in that it can only really fail if label_tweak
* is the negation of the shared secret tweak. But since both tweak and label_tweak are
* created by hashing data, practically speaking this would only happen if an attacker
* tricked us into using a particular label_tweak (deviating from the protocol).
*/
ret = secp256k1_ec_seckey_tweak_add(ctx, found_outputs[n_found]->tweak, label_tweak);
VERIFY_CHECK(ret);
secp256k1_pubkey_save(&found_outputs[n_found]->label, &label_ge);
} else {
found_outputs[n_found]->found_with_label = 0;
/* Set the label public key with an invalid public key value */
secp256k1_memclear(&found_outputs[n_found]->label, sizeof(secp256k1_pubkey));
}
/* Set everything for the next round of scanning */
label_tweak = NULL;
n_found++;
k++;
} else {
break;
}
}
*n_found_outputs = n_found;
/* Explicitly clear secrets. Recall that the scan key is not quite "secret" in that leaking the scan key
* results in a loss of privacy, not a loss of funds
*/
secp256k1_scalar_clear(&rsk_scalar);
/* Explicitly clear the shared secret. While this isn't technically "secret data," any third party
* with access to the shared secret could potentially identify and link the transaction back to the
* recipient address
*/
secp256k1_scalar_clear(&t_k_scalar);
secp256k1_memclear(shared_secret, sizeof(shared_secret));
return ret;
}
int secp256k1_silentpayments_recipient_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, const unsigned char *recipient_scan_key32, const secp256k1_silentpayments_recipient_public_data *public_data) {
secp256k1_scalar rsk;
secp256k1_ge A_tweaked_ge;
int ret = 1;
/* Sanity check inputs */
ARG_CHECK(shared_secret33 != NULL);
ARG_CHECK(recipient_scan_key32 != NULL);
ARG_CHECK(public_data != NULL);
ARG_CHECK(public_data->data[4] == 1);
/* TODO: do we need a _cmov operation here to avoid leaking information about the scan key?
* Recall: a scan key is not really "secret" data, its functionally the same as an xpub
*/
ret &= secp256k1_scalar_set_b32_seckey(&rsk, recipient_scan_key32);
ret &= secp256k1_silentpayments_recipient_public_data_load_pubkey(ctx, &A_tweaked_ge, public_data);
/* If there are any issues with the recipient scan key or public data, return early */
if (!ret) {
return 0;
}
secp256k1_silentpayments_create_shared_secret(ctx, shared_secret33, &rsk, &A_tweaked_ge);
/* Explicitly clear secrets */
secp256k1_scalar_clear(&rsk);
return ret;
}
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 *recipient_spend_pubkey, const uint32_t k)
{
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(P_output_xonly != NULL);
ARG_CHECK(shared_secret33 != NULL);
ARG_CHECK(recipient_spend_pubkey != NULL);
return secp256k1_silentpayments_create_output_pubkey(ctx, P_output_xonly, shared_secret33, recipient_spend_pubkey, k);
}
#endif

View file

@ -0,0 +1,622 @@
/***********************************************************************
* 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"
/** Constants
*
* Addresses: scan and spend public keys for Bob and Carol
* Seckey: secret key for Alice
* Outputs: generated outputs from Alice's secret key and Bob/Carol's
* scan public keys
* Smallest Outpoint: smallest outpoint lexicographically from the transaction
* orderc: a scalar which overflows the secp256k1 group order
* Malformed Seckey: a seckey that is all zeros
*
* The values themselves are not important.
*/
static unsigned char ORDERC[32] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b,
0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41
};
static unsigned char MALFORMED_SECKEY[32] = { 0x00 };
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_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
}
};
static unsigned char BOB_OUTPUT[32] = {
0x46,0x0d,0x68,0x08,0x65,0x64,0x45,0xee,
0x4d,0x4e,0xc0,0x8e,0xba,0x8a,0x66,0xea,
0x66,0x8e,0x4e,0x12,0x98,0x9a,0x0e,0x60,
0x4b,0x5c,0x36,0x0e,0x43,0xf5,0x5a,0xfa
};
static unsigned char CAROL_OUTPUT_ONE[32] = {
0xb7,0xf3,0xc6,0x79,0x30,0x4a,0xef,0x8c,
0xc0,0xc7,0x61,0xf1,0x00,0x99,0xdd,0x7b,
0x20,0x65,0x20,0xd7,0x11,0x6f,0xb7,0x91,
0xee,0x74,0x54,0xa2,0xfc,0x22,0x79,0xf4
};
static unsigned char CAROL_OUTPUT_TWO[32] = {
0x4b,0x81,0x34,0x5d,0x53,0x89,0xba,0xa3,
0xd8,0x93,0xe2,0xfb,0xe7,0x08,0xdd,0x6d,
0x82,0xdc,0xd8,0x49,0xab,0x03,0xc1,0xdb,
0x68,0xbe,0xc7,0xe9,0x2a,0x45,0xfa,0xc5
};
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 ALICE_SECKEY[32] = {
0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea,
0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73,
0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42,
0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1
};
/* sha256("message") */
static unsigned char MSG32[32] = {
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
};
/* sha256("random auxiliary data") */
static unsigned char AUX32[32] = {
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
};
struct label_cache_entry {
unsigned char label[33];
unsigned char label_tweak[32];
};
struct labels_cache {
size_t entries_used;
struct label_cache_entry entries[10];
};
struct labels_cache labels_cache;
const unsigned char* label_lookup(const unsigned char* 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_memcmp_var(cache->entries[i].label, key, 33) == 0) {
return cache->entries[i].label_tweak;
}
}
return NULL;
}
static void test_recipient_sort_helper(unsigned char (*sp_addresses[3])[2][33], unsigned char (*sp_outputs[3])[32]) {
unsigned char const *seckey_ptrs[1];
secp256k1_silentpayments_recipient recipients[3];
const secp256k1_silentpayments_recipient *recipient_ptrs[3];
secp256k1_xonly_pubkey generated_outputs[3];
secp256k1_xonly_pubkey *generated_output_ptrs[3];
unsigned char xonly_ser[32];
size_t i;
int ret;
seckey_ptrs[0] = ALICE_SECKEY;
for (i = 0; i < 3; i++) {
CHECK(secp256k1_ec_pubkey_parse(CTX, &recipients[i].scan_pubkey, (*sp_addresses[i])[0], 33));
CHECK(secp256k1_ec_pubkey_parse(CTX, &recipients[i].labeled_spend_pubkey,(*sp_addresses[i])[1], 33));
recipients[i].index = i;
recipient_ptrs[i] = &recipients[i];
generated_output_ptrs[i] = &generated_outputs[i];
}
ret = secp256k1_silentpayments_sender_create_outputs(CTX,
generated_output_ptrs,
recipient_ptrs, 3,
SMALLEST_OUTPOINT,
NULL, 0,
seckey_ptrs, 1
);
CHECK(ret);
for (i = 0; i < 3; i++) {
secp256k1_xonly_pubkey_serialize(CTX, xonly_ser, &generated_outputs[i]);
CHECK(secp256k1_memcmp_var(xonly_ser, (*sp_outputs[i]), 32) == 0);
}
}
static void test_recipient_sort(void) {
unsigned char (*sp_addresses[3])[2][33];
unsigned char (*sp_outputs[3])[32];
/* With a fixed set of addresses and a fixed set of inputs,
* test that we always get the same outputs, regardless of the ordering
* of the recipients
*/
sp_addresses[0] = &CAROL_ADDRESS;
sp_addresses[1] = &BOB_ADDRESS;
sp_addresses[2] = &CAROL_ADDRESS;
sp_outputs[0] = &CAROL_OUTPUT_ONE;
sp_outputs[1] = &BOB_OUTPUT;
sp_outputs[2] = &CAROL_OUTPUT_TWO;
test_recipient_sort_helper(sp_addresses, sp_outputs);
sp_addresses[0] = &CAROL_ADDRESS;
sp_addresses[1] = &CAROL_ADDRESS;
sp_addresses[2] = &BOB_ADDRESS;
sp_outputs[0] = &CAROL_OUTPUT_ONE;
sp_outputs[1] = &CAROL_OUTPUT_TWO;
sp_outputs[2] = &BOB_OUTPUT;
test_recipient_sort_helper(sp_addresses, sp_outputs);
sp_addresses[0] = &BOB_ADDRESS;
sp_addresses[1] = &CAROL_ADDRESS;
sp_addresses[2] = &CAROL_ADDRESS;
/* Note: in this case, the second output for Carol comes before the first.
* This is because heapsort is an unstable sorting algorithm, i.e., the ordering
* of identical elements is not guaranteed to be preserved
*/
sp_outputs[0] = &BOB_OUTPUT;
sp_outputs[1] = &CAROL_OUTPUT_TWO;
sp_outputs[2] = &CAROL_OUTPUT_ONE;
test_recipient_sort_helper(sp_addresses, sp_outputs);
}
static void test_send_api(void) {
unsigned char (*sp_addresses[2])[2][33];
unsigned char const *p[1];
secp256k1_keypair const *t[1];
secp256k1_silentpayments_recipient r[2];
const secp256k1_silentpayments_recipient *rp[2];
secp256k1_xonly_pubkey o[2];
secp256k1_xonly_pubkey *op[2];
secp256k1_keypair taproot;
size_t i;
/* Set up Bob and Carol as the recipients */
sp_addresses[0] = &BOB_ADDRESS;
sp_addresses[1] = &CAROL_ADDRESS;
for (i = 0; i < 2; i++) {
CHECK(secp256k1_ec_pubkey_parse(CTX, &r[i].scan_pubkey, (*sp_addresses[i])[0], 33));
CHECK(secp256k1_ec_pubkey_parse(CTX, &r[i].labeled_spend_pubkey,(*sp_addresses[i])[1], 33));
/* Set the index value incorrectly */
r[i].index = 0;
rp[i] = &r[i];
op[i] = &o[i];
}
/* Set up a taproot key and a plain key for Alice */
CHECK(secp256k1_keypair_create(CTX, &taproot, ALICE_SECKEY));
t[0] = &taproot;
p[0] = ALICE_SECKEY;
/* Fails if the index is set incorrectly */
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1));
/* Set the index correctly for the next tests */
for (i = 0; i < 2; i++) {
r[i].index = i;
}
CHECK(secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1));
/* Check that null arguments are handled */
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, NULL, rp, 2, SMALLEST_OUTPOINT, t, 1, p, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, NULL, 2, SMALLEST_OUTPOINT, t, 1, p, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, NULL, t, 1, p, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 1, p, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, t, 1, NULL, 1));
/* Check that array arguments are verified */
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, NULL, 0));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 0, SMALLEST_OUTPOINT, NULL, 0, p, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, t, 0, p, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, t, 1, p, 0));
/* Create malformed keys for Alice by using a key that will overflow */
p[0] = ORDERC;
CHECK(secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1) == 0);
/* Create malformed keys for Alice by using a zero'd seckey */
p[0] = MALFORMED_SECKEY;
CHECK(secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1) == 0);
p[0] = ALICE_SECKEY;
/* Create malformed recipients by setting all of the public key bytes to zero.
* Realistically, this would never happen since a bad public key would get caught when
* trying to parse the public key with _ec_pubkey_parse
*/
memset(&r[1].labeled_spend_pubkey.data, 0, sizeof(secp256k1_pubkey));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1));
{
secp256k1_pubkey tmp = r[1].labeled_spend_pubkey;
memset(&r[1].labeled_spend_pubkey, 0, sizeof(r[1].labeled_spend_pubkey));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1));
r[1].labeled_spend_pubkey = tmp;
}
{
secp256k1_pubkey tmp = r[1].scan_pubkey;
int32_t ecount = 0;
memset(&r[1].scan_pubkey, 0, sizeof(r[1].scan_pubkey));
secp256k1_context_set_illegal_callback(CTX, counting_callback_fn, &ecount);
CHECK(secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1) == 0);
CHECK(ecount == 2);
secp256k1_context_set_illegal_callback(CTX, NULL, NULL);
r[1].scan_pubkey = tmp;
}
}
static void test_label_api(void) {
secp256k1_pubkey l, s, ls, e; /* label pk, spend pk, labelled spend pk, expected labelled spend pk */
unsigned char lt[32]; /* label tweak */
const unsigned char expected[33] = {
0x03,0xdc,0x7f,0x09,0x9a,0xbe,0x95,0x7a,
0x58,0x43,0xd2,0xb6,0xbb,0x35,0x79,0x61,
0x5c,0x60,0x36,0xa4,0x9b,0x86,0xf4,0xbe,
0x46,0x38,0x60,0x28,0xa8,0x1a,0x77,0xd4,0x91
};
/* Create a label and labelled spend public key, verify we get the expected result */
CHECK(secp256k1_ec_pubkey_parse(CTX, &s, BOB_ADDRESS[1], 33));
CHECK(secp256k1_silentpayments_recipient_create_label(CTX, &l, lt, ALICE_SECKEY, 1));
CHECK(secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &l));
CHECK(secp256k1_ec_pubkey_parse(CTX, &e, expected, 33));
CHECK(secp256k1_ec_pubkey_cmp(CTX, &ls, &e) == 0);
/* Check null values are handled */
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_label(CTX, NULL, lt, ALICE_SECKEY, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_label(CTX, &l, NULL, ALICE_SECKEY, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_label(CTX, &l, lt, NULL, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, NULL, &s, &l));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, NULL, &l));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, NULL));
}
static void test_recipient_api(void) {
secp256k1_silentpayments_recipient_public_data pd; /* public data */
secp256k1_silentpayments_found_output f; /* a silent payment found output */
secp256k1_silentpayments_found_output *fp[1]; /* array of pointers to found outputs */
secp256k1_xonly_pubkey t; /* taproot x-only public key */
secp256k1_xonly_pubkey const *tp[1]; /* array of pointers to xonly pks */
secp256k1_pubkey p; /* plain public key */
secp256k1_pubkey const *pp[1]; /* array of pointers to plain pks */
unsigned char o[33]; /* serialized public data, serialized shared secret */
unsigned char malformed[33] = { 0x01 }; /* malformed public key serialization */
size_t n_f; /* number of found outputs */
CHECK(secp256k1_ec_pubkey_parse(CTX, &p, BOB_ADDRESS[0], 33));
CHECK(secp256k1_xonly_pubkey_parse(CTX, &t, &BOB_ADDRESS[0][1]));
tp[0] = &t;
pp[0] = &p;
fp[0] = &f;
CHECK(secp256k1_silentpayments_recipient_public_data_create(CTX, &pd, SMALLEST_OUTPOINT, tp, 1, pp, 1));
/* Check null values are handled */
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_create(CTX, NULL, SMALLEST_OUTPOINT, tp, 1, pp, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_create(CTX, &pd, NULL, tp, 1, pp, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_create(CTX, &pd, SMALLEST_OUTPOINT, NULL, 1, pp, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_create(CTX, &pd, SMALLEST_OUTPOINT, tp, 1, NULL, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_serialize(CTX, NULL, &pd));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_serialize(CTX, o, NULL));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_parse(CTX, NULL, o));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_parse(CTX, &pd, NULL));
/* Check that malformed serializations are rejected */
CHECK(secp256k1_silentpayments_recipient_public_data_parse(CTX, &pd, malformed) == 0);
/* This public_data object was created with combined = 0, i.e., it has both the input hash and summed public keypair.
* In instances where the caller has access to the full transaction, they should use `_scan_outputs` instead, so
* verify trying to use `_recipient_create_shared_secret` will fail */
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_shared_secret(CTX, o, ALICE_SECKEY, &pd));
/* Parse a public_data object from a 33 byte serialization and check that trying to serialize this public_data object will fail */
CHECK(secp256k1_silentpayments_recipient_public_data_parse(CTX, &pd, BOB_ADDRESS[0]));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_serialize(CTX, o, &pd));
/* Try to create a shared secret with a malformed recipient scan key (all zeros) */
CHECK(secp256k1_silentpayments_recipient_create_shared_secret(CTX, o, MALFORMED_SECKEY, &pd) == 0);
/* Try to create a shared secret with a malformed public data (all zeros) */
memset(&pd.data[1], 0, sizeof(pd.data) - 1);
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_shared_secret(CTX, o, ALICE_SECKEY, &pd));
/* Reset pd to a valid public data object */
CHECK(secp256k1_silentpayments_recipient_public_data_parse(CTX, &pd, BOB_ADDRESS[0]));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_create(CTX, &pd, SMALLEST_OUTPOINT, tp, 0, pp, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_create(CTX, &pd, SMALLEST_OUTPOINT, tp, 1, pp, 0));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_create(CTX, &pd, SMALLEST_OUTPOINT, NULL, 0, pp, 0));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_public_data_create(CTX, &pd, SMALLEST_OUTPOINT, NULL, 0, NULL, 0));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_shared_secret(CTX, NULL, ALICE_SECKEY, &pd));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_shared_secret(CTX, o, NULL, &pd));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_shared_secret(CTX, o, ALICE_SECKEY, NULL));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_output_pubkey(CTX, NULL, o, &p, 0));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_output_pubkey(CTX, &t, NULL, &p, 0));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_output_pubkey(CTX, &t, o, NULL, 0));
n_f = 0;
labels_cache.entries_used = 0;
CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, &p, &label_lookup, &labels_cache));
CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, &p, NULL, NULL));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, NULL, &n_f, tp, 1, ALICE_SECKEY, &pd, &p, &label_lookup, &labels_cache));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, NULL, tp, 1, ALICE_SECKEY, &pd, &p, &label_lookup, &labels_cache));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, NULL, 1, ALICE_SECKEY, &pd, &p, &label_lookup, &labels_cache));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, NULL, &pd, &p, &label_lookup, &labels_cache));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, NULL, &p, &label_lookup, &labels_cache));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, NULL, &label_lookup, &labels_cache));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 0, ALICE_SECKEY, &pd, &p, &label_lookup, &labels_cache));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_scan_outputs(CTX, fp, &n_f, tp, 1, ALICE_SECKEY, &pd, &p, NULL, &labels_cache));
}
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, k;
int match, ret;
/* 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].labeled_spend_pubkey, test->recipient_pubkeys[i].labeled_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++) {
CHECK(secp256k1_keypair_create(CTX, &taproot_keypairs[i], test->taproot_seckeys[i]));
taproot_keypair_ptrs[i] = &taproot_keypairs[i];
}
ret = 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
);
/* If we are unable to create outputs, e.g., the input keys sum to zero, check that the
* expected number of recipient outputs for this test case is zero
*/
if (!ret) {
CHECK(test->num_recipient_outputs == 0);
return;
}
match = 0;
for (i = 0; i < test->num_output_sets; i++) {
size_t n_matches = 0;
for (j = 0; j < test->num_outputs; j++) {
CHECK(secp256k1_xonly_pubkey_serialize(CTX, created_output, &generated_outputs[j]));
/* Loop over both lists to ensure tests don't fail due to different orderings of outputs */
for (k = 0; k < test->num_recipient_outputs; k++) {
if (secp256k1_memcmp_var(created_output, test->recipient_outputs[i][k], 32) == 0) {
n_matches++;
break;
}
}
}
if (n_matches == test->num_recipient_outputs) {
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 recipient_scan_pubkey;
secp256k1_pubkey recipient_spend_pubkey;
secp256k1_pubkey label;
size_t len = 33;
size_t i,j;
int match, ret;
size_t n_found = 0;
unsigned char found_output[32];
unsigned char found_signatures[10][64];
secp256k1_silentpayments_recipient_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];
}
ret = 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
);
/* If we are unable to create the public_data object, e.g., the input public keys sum to
* zero, check that the expected number of recipient outputs for this test case is zero
*/
if (!ret) {
CHECK(test->num_found_output_pubkeys == 0);
return;
}
}
/* 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 recipient part, so let's compute them */
CHECK(secp256k1_ec_pubkey_create(CTX, &recipient_scan_pubkey, test->scan_seckey));
CHECK(secp256k1_ec_pubkey_create(CTX, &recipient_spend_pubkey, test->spend_seckey));
/* create labels cache */
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(CTX, &label, cache_entry->label_tweak, test->scan_seckey, m));
CHECK(secp256k1_ec_pubkey_serialize(CTX, cache_entry->label, &len, &label, SECP256K1_EC_COMPRESSED));
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,
&recipient_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];
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) */
match = 0;
for (i = 0; i < n_found; i++) {
CHECK(secp256k1_xonly_pubkey_serialize(CTX, found_output, &found_outputs[i]->output));
for (j = 0; j < test->num_found_output_pubkeys; j++) {
if (secp256k1_memcmp_var(&found_output, test->found_output_pubkeys[j], 32) == 0) {
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);
match = 1;
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;
while(1) {
CHECK(secp256k1_silentpayments_recipient_create_output_pubkey(CTX,
&potential_output,
shared_secret_lightclient,
&recipient_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;
for (i = 0; i < test->num_to_scan_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) {
test_recipient_sort();
test_send_api();
test_label_api();
test_recipient_api();
run_silentpayments_test_vectors();
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@
#include <inttypes.h> #include <inttypes.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include "../include/secp256k1.h" #include "../include/secp256k1.h"
@ -62,7 +63,7 @@ int main(void) {
fp = fopen(outfile, "w"); fp = fopen(outfile, "w");
if (fp == NULL) { if (fp == NULL) {
fprintf(stderr, "Could not open %s for writing!\n", outfile); fprintf(stderr, "Could not open %s for writing!\n", outfile);
return -1; return EXIT_FAILURE;
} }
fprintf(fp, "/* This file was automatically generated by precompute_ecmult. */\n"); fprintf(fp, "/* This file was automatically generated by precompute_ecmult. */\n");
@ -86,5 +87,5 @@ int main(void) {
fprintf(fp, "#undef S\n"); fprintf(fp, "#undef S\n");
fclose(fp); fclose(fp);
return 0; return EXIT_SUCCESS;
} }

View file

@ -6,6 +6,7 @@
#include <inttypes.h> #include <inttypes.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include "../include/secp256k1.h" #include "../include/secp256k1.h"
@ -64,7 +65,7 @@ int main(int argc, char **argv) {
fp = fopen(outfile, "w"); fp = fopen(outfile, "w");
if (fp == NULL) { if (fp == NULL) {
fprintf(stderr, "Could not open %s for writing!\n", outfile); fprintf(stderr, "Could not open %s for writing!\n", outfile);
return -1; return EXIT_FAILURE;
} }
fprintf(fp, "/* This file was automatically generated by precompute_ecmult_gen. */\n"); fprintf(fp, "/* This file was automatically generated by precompute_ecmult_gen. */\n");
@ -96,5 +97,5 @@ int main(int argc, char **argv) {
fprintf(fp, "#undef S\n"); fprintf(fp, "#undef S\n");
fclose(fp); fclose(fp);
return 0; return EXIT_SUCCESS;
} }

View file

@ -13,6 +13,8 @@ extern "C" {
#include "ecmult.h" #include "ecmult.h"
#include "group.h" #include "group.h"
#include "util_local_visibility.h"
#if defined(EXHAUSTIVE_TEST_ORDER) #if defined(EXHAUSTIVE_TEST_ORDER)
# if EXHAUSTIVE_TEST_ORDER == 7 # if EXHAUSTIVE_TEST_ORDER == 7
# define WINDOW_G 3 # define WINDOW_G 3
@ -27,8 +29,8 @@ static secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)];
static secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)]; static secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)];
#else /* !defined(EXHAUSTIVE_TEST_ORDER) */ #else /* !defined(EXHAUSTIVE_TEST_ORDER) */
# define WINDOW_G ECMULT_WINDOW_SIZE # define WINDOW_G ECMULT_WINDOW_SIZE
extern const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)]; SECP256K1_LOCAL_VAR const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)];
extern const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)]; SECP256K1_LOCAL_VAR const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)];
#endif /* defined(EXHAUSTIVE_TEST_ORDER) */ #endif /* defined(EXHAUSTIVE_TEST_ORDER) */
#ifdef __cplusplus #ifdef __cplusplus

View file

@ -13,10 +13,12 @@ extern "C" {
#include "group.h" #include "group.h"
#include "ecmult_gen.h" #include "ecmult_gen.h"
#include "util_local_visibility.h"
#ifdef EXHAUSTIVE_TEST_ORDER #ifdef EXHAUSTIVE_TEST_ORDER
static secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[COMB_BLOCKS][COMB_POINTS]; static secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[COMB_BLOCKS][COMB_POINTS];
#else #else
extern const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[COMB_BLOCKS][COMB_POINTS]; SECP256K1_LOCAL_VAR const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[COMB_BLOCKS][COMB_POINTS];
#endif /* defined(EXHAUSTIVE_TEST_ORDER) */ #endif /* defined(EXHAUSTIVE_TEST_ORDER) */
#ifdef __cplusplus #ifdef __cplusplus

View file

@ -90,11 +90,11 @@ static void secp256k1_scalar_split_lambda_verify(const secp256k1_scalar *r1, con
#endif #endif
/* /*
* Both lambda and beta are primitive cube roots of unity. That is lamba^3 == 1 mod n and * Both lambda and beta are primitive cube roots of unity. That is lambda^3 == 1 mod n and
* beta^3 == 1 mod p, where n is the curve order and p is the field order. * beta^3 == 1 mod p, where n is the curve order and p is the field order.
* *
* Furthermore, because (X^3 - 1) = (X - 1)(X^2 + X + 1), the primitive cube roots of unity are * Furthermore, because (X^3 - 1) = (X - 1)(X^2 + X + 1), the primitive cube roots of unity are
* roots of X^2 + X + 1. Therefore lambda^2 + lamba == -1 mod n and beta^2 + beta == -1 mod p. * roots of X^2 + X + 1. Therefore lambda^2 + lambda == -1 mod n and beta^2 + beta == -1 mod p.
* (The other primitive cube roots of unity are lambda^2 and beta^2 respectively.) * (The other primitive cube roots of unity are lambda^2 and beta^2 respectively.)
* *
* Let l = -1/2 + i*sqrt(3)/2, the complex root of X^2 + X + 1. We can define a ring * Let l = -1/2 + i*sqrt(3)/2, the complex root of X^2 + X + 1. We can define a ring

View file

@ -71,8 +71,8 @@ static const secp256k1_context secp256k1_context_static_ = {
{ secp256k1_default_error_callback_fn, 0 }, { secp256k1_default_error_callback_fn, 0 },
0 0
}; };
const secp256k1_context *secp256k1_context_static = &secp256k1_context_static_; const secp256k1_context * const secp256k1_context_static = &secp256k1_context_static_;
const secp256k1_context *secp256k1_context_no_precomp = &secp256k1_context_static_; const secp256k1_context * const secp256k1_context_no_precomp = &secp256k1_context_static_;
/* Helper function that determines if a context is proper, i.e., is not the static context or a copy thereof. /* Helper function that determines if a context is proper, i.e., is not the static context or a copy thereof.
* *
@ -280,7 +280,7 @@ int secp256k1_ec_pubkey_serialize(const secp256k1_context* ctx, unsigned char *o
ARG_CHECK(pubkey != NULL); ARG_CHECK(pubkey != NULL);
ARG_CHECK((flags & SECP256K1_FLAGS_TYPE_MASK) == SECP256K1_FLAGS_TYPE_COMPRESSION); ARG_CHECK((flags & SECP256K1_FLAGS_TYPE_MASK) == SECP256K1_FLAGS_TYPE_COMPRESSION);
if (secp256k1_pubkey_load(ctx, &Q, pubkey)) { if (secp256k1_pubkey_load(ctx, &Q, pubkey)) {
ret = secp256k1_eckey_pubkey_serialize(&Q, output, &len, flags & SECP256K1_FLAGS_BIT_COMPRESSION); ret = secp256k1_eckey_pubkey_serialize(&Q, output, &len, !!(flags & SECP256K1_FLAGS_BIT_COMPRESSION));
if (ret) { if (ret) {
*outputlen = len; *outputlen = len;
} }
@ -634,10 +634,6 @@ int secp256k1_ec_seckey_negate(const secp256k1_context* ctx, unsigned char *seck
return ret; return ret;
} }
int secp256k1_ec_privkey_negate(const secp256k1_context* ctx, unsigned char *seckey) {
return secp256k1_ec_seckey_negate(ctx, seckey);
}
int secp256k1_ec_pubkey_negate(const secp256k1_context* ctx, secp256k1_pubkey *pubkey) { int secp256k1_ec_pubkey_negate(const secp256k1_context* ctx, secp256k1_pubkey *pubkey) {
int ret = 0; int ret = 0;
secp256k1_ge p; secp256k1_ge p;
@ -681,10 +677,6 @@ int secp256k1_ec_seckey_tweak_add(const secp256k1_context* ctx, unsigned char *s
return ret; return ret;
} }
int secp256k1_ec_privkey_tweak_add(const secp256k1_context* ctx, unsigned char *seckey, const unsigned char *tweak32) {
return secp256k1_ec_seckey_tweak_add(ctx, seckey, tweak32);
}
static int secp256k1_ec_pubkey_tweak_add_helper(secp256k1_ge *p, const unsigned char *tweak32) { static int secp256k1_ec_pubkey_tweak_add_helper(secp256k1_ge *p, const unsigned char *tweak32) {
secp256k1_scalar term; secp256k1_scalar term;
int overflow = 0; int overflow = 0;
@ -729,10 +721,6 @@ int secp256k1_ec_seckey_tweak_mul(const secp256k1_context* ctx, unsigned char *s
return ret; return ret;
} }
int secp256k1_ec_privkey_tweak_mul(const secp256k1_context* ctx, unsigned char *seckey, const unsigned char *tweak32) {
return secp256k1_ec_seckey_tweak_mul(ctx, seckey, tweak32);
}
int secp256k1_ec_pubkey_tweak_mul(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const unsigned char *tweak32) { int secp256k1_ec_pubkey_tweak_mul(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const unsigned char *tweak32) {
secp256k1_ge p; secp256k1_ge p;
secp256k1_scalar factor; secp256k1_scalar factor;
@ -829,3 +817,7 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32,
#ifdef ENABLE_MODULE_ELLSWIFT #ifdef ENABLE_MODULE_ELLSWIFT
# include "modules/ellswift/main_impl.h" # include "modules/ellswift/main_impl.h"
#endif #endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
# include "modules/silentpayments/main_impl.h"
#endif

View file

@ -87,15 +87,6 @@ static void counting_callback_fn(const char* str, void* data) {
(*p)++; (*p)++;
} }
static void uncounting_illegal_callback_fn(const char* str, void* data) {
/* Dummy callback function that just counts (backwards). */
int32_t *p;
(void)str;
p = data;
CHECK(*p != INT32_MIN);
(*p)--;
}
static void run_xoshiro256pp_tests(void) { static void run_xoshiro256pp_tests(void) {
{ {
size_t i; size_t i;
@ -3821,14 +3812,38 @@ static void test_ge(void) {
/* Test batch gej -> ge conversion without known z ratios. */ /* Test batch gej -> ge conversion without known z ratios. */
{ {
secp256k1_ge *ge_set_all_var = (secp256k1_ge *)checked_malloc(&CTX->error_callback, (4 * runs + 1) * sizeof(secp256k1_ge));
secp256k1_ge *ge_set_all = (secp256k1_ge *)checked_malloc(&CTX->error_callback, (4 * runs + 1) * sizeof(secp256k1_ge)); secp256k1_ge *ge_set_all = (secp256k1_ge *)checked_malloc(&CTX->error_callback, (4 * runs + 1) * sizeof(secp256k1_ge));
secp256k1_ge_set_all_gej_var(ge_set_all, gej, 4 * runs + 1); secp256k1_ge_set_all_gej_var(&ge_set_all_var[0], &gej[0], 4 * runs + 1);
for (i = 0; i < 4 * runs + 1; i++) { for (i = 0; i < 4 * runs + 1; i++) {
secp256k1_fe s; secp256k1_fe s;
testutil_random_fe_non_zero(&s); testutil_random_fe_non_zero(&s);
secp256k1_gej_rescale(&gej[i], &s); secp256k1_gej_rescale(&gej[i], &s);
CHECK(secp256k1_gej_eq_ge_var(&gej[i], &ge_set_all[i])); CHECK(secp256k1_gej_eq_ge_var(&gej[i], &ge_set_all_var[i]));
} }
/* Skip infinity at &gej[0]. */
secp256k1_ge_set_all_gej(&ge_set_all[1], &gej[1], 4 * runs);
for (i = 1; i < 4 * runs + 1; i++) {
secp256k1_fe s;
testutil_random_fe_non_zero(&s);
secp256k1_gej_rescale(&gej[i], &s);
CHECK(secp256k1_gej_eq_ge_var(&gej[i], &ge_set_all[i]));
CHECK(secp256k1_ge_eq_var(&ge_set_all_var[i], &ge_set_all[i]));
}
/* Test with an array of length 1. */
secp256k1_ge_set_all_gej_var(ge_set_all_var, &gej[1], 1);
secp256k1_ge_set_all_gej(ge_set_all, &gej[1], 1);
CHECK(secp256k1_gej_eq_ge_var(&gej[1], &ge_set_all_var[1]));
CHECK(secp256k1_gej_eq_ge_var(&gej[1], &ge_set_all[1]));
CHECK(secp256k1_ge_eq_var(&ge_set_all_var[1], &ge_set_all[1]));
/* Test with an array of length 0. */
secp256k1_ge_set_all_gej_var(NULL, NULL, 0);
secp256k1_ge_set_all_gej(NULL, NULL, 0);
free(ge_set_all_var);
free(ge_set_all); free(ge_set_all);
} }
@ -6248,11 +6263,6 @@ static void run_eckey_negate_test(void) {
CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 1); CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 1);
CHECK(secp256k1_memcmp_var(seckey, seckey_tmp, 32) == 0); CHECK(secp256k1_memcmp_var(seckey, seckey_tmp, 32) == 0);
/* Check that privkey alias gives same result */
CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 1);
CHECK(secp256k1_ec_privkey_negate(CTX, seckey_tmp) == 1);
CHECK(secp256k1_memcmp_var(seckey, seckey_tmp, 32) == 0);
/* Negating all 0s fails */ /* Negating all 0s fails */
memset(seckey, 0, 32); memset(seckey, 0, 32);
memset(seckey_tmp, 0, 32); memset(seckey_tmp, 0, 32);
@ -6413,22 +6423,15 @@ static void test_ecdsa_end_to_end(void) {
if (testrand_int(3) == 0) { if (testrand_int(3) == 0) {
int ret1; int ret1;
int ret2; int ret2;
int ret3;
unsigned char rnd[32]; unsigned char rnd[32];
unsigned char privkey_tmp[32];
secp256k1_pubkey pubkey2; secp256k1_pubkey pubkey2;
testrand256_test(rnd); testrand256_test(rnd);
memcpy(privkey_tmp, privkey, 32);
ret1 = secp256k1_ec_seckey_tweak_add(CTX, privkey, rnd); ret1 = secp256k1_ec_seckey_tweak_add(CTX, privkey, rnd);
ret2 = secp256k1_ec_pubkey_tweak_add(CTX, &pubkey, rnd); ret2 = secp256k1_ec_pubkey_tweak_add(CTX, &pubkey, rnd);
/* Check that privkey alias gives same result */
ret3 = secp256k1_ec_privkey_tweak_add(CTX, privkey_tmp, rnd);
CHECK(ret1 == ret2); CHECK(ret1 == ret2);
CHECK(ret2 == ret3);
if (ret1 == 0) { if (ret1 == 0) {
return; return;
} }
CHECK(secp256k1_memcmp_var(privkey, privkey_tmp, 32) == 0);
CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey2, privkey) == 1); CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey2, privkey) == 1);
CHECK(secp256k1_memcmp_var(&pubkey, &pubkey2, sizeof(pubkey)) == 0); CHECK(secp256k1_memcmp_var(&pubkey, &pubkey2, sizeof(pubkey)) == 0);
} }
@ -6437,22 +6440,15 @@ static void test_ecdsa_end_to_end(void) {
if (testrand_int(3) == 0) { if (testrand_int(3) == 0) {
int ret1; int ret1;
int ret2; int ret2;
int ret3;
unsigned char rnd[32]; unsigned char rnd[32];
unsigned char privkey_tmp[32];
secp256k1_pubkey pubkey2; secp256k1_pubkey pubkey2;
testrand256_test(rnd); testrand256_test(rnd);
memcpy(privkey_tmp, privkey, 32);
ret1 = secp256k1_ec_seckey_tweak_mul(CTX, privkey, rnd); ret1 = secp256k1_ec_seckey_tweak_mul(CTX, privkey, rnd);
ret2 = secp256k1_ec_pubkey_tweak_mul(CTX, &pubkey, rnd); ret2 = secp256k1_ec_pubkey_tweak_mul(CTX, &pubkey, rnd);
/* Check that privkey alias gives same result */
ret3 = secp256k1_ec_privkey_tweak_mul(CTX, privkey_tmp, rnd);
CHECK(ret1 == ret2); CHECK(ret1 == ret2);
CHECK(ret2 == ret3);
if (ret1 == 0) { if (ret1 == 0) {
return; return;
} }
CHECK(secp256k1_memcmp_var(privkey, privkey_tmp, 32) == 0);
CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey2, privkey) == 1); CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey2, privkey) == 1);
CHECK(secp256k1_memcmp_var(&pubkey, &pubkey2, sizeof(pubkey)) == 0); CHECK(secp256k1_memcmp_var(&pubkey, &pubkey2, sizeof(pubkey)) == 0);
} }
@ -7455,6 +7451,10 @@ static void run_ecdsa_wycheproof(void) {
# include "modules/ellswift/tests_impl.h" # include "modules/ellswift/tests_impl.h"
#endif #endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
# include "modules/silentpayments/tests_impl.h"
#endif
static void run_secp256k1_memczero_test(void) { static void run_secp256k1_memczero_test(void) {
unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf1[6] = {1, 2, 3, 4, 5, 6};
unsigned char buf2[sizeof(buf1)]; unsigned char buf2[sizeof(buf1)];
@ -7823,6 +7823,10 @@ int main(int argc, char **argv) {
run_ellswift_tests(); run_ellswift_tests();
#endif #endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
run_silentpayments_tests();
#endif
/* util tests */ /* util tests */
run_secp256k1_memczero_test(); run_secp256k1_memczero_test();
run_secp256k1_is_zero_array_test(); run_secp256k1_is_zero_array_test();
@ -7837,5 +7841,5 @@ int main(int argc, char **argv) {
testrand_finish(); testrand_finish();
printf("no problems found\n"); printf("no problems found\n");
return 0; return EXIT_SUCCESS;
} }

View file

@ -383,7 +383,7 @@ int main(int argc, char** argv) {
this_core = strtol(argv[4], NULL, 0); this_core = strtol(argv[4], NULL, 0);
if (num_cores < 1 || this_core >= num_cores) { if (num_cores < 1 || this_core >= num_cores) {
fprintf(stderr, "Usage: %s [count] [seed] [numcores] [thiscore]\n", argv[0]); fprintf(stderr, "Usage: %s [count] [seed] [numcores] [thiscore]\n", argv[0]);
return 1; return EXIT_FAILURE;
} }
printf("running tests for core %lu (out of [0..%lu])\n", (unsigned long)this_core, (unsigned long)num_cores - 1); printf("running tests for core %lu (out of [0..%lu])\n", (unsigned long)this_core, (unsigned long)num_cores - 1);
} }
@ -462,5 +462,5 @@ int main(int argc, char** argv) {
testrand_finish(); testrand_finish();
printf("no problems found\n"); printf("no problems found\n");
return 0; return EXIT_SUCCESS;
} }

View file

@ -232,7 +232,7 @@ static SECP256K1_INLINE void secp256k1_memclear(void *ptr, size_t len) {
* As best as we can tell, this is sufficient to break any optimisations that * As best as we can tell, this is sufficient to break any optimisations that
* might try to eliminate "superfluous" memsets. * might try to eliminate "superfluous" memsets.
* This method is used in memzero_explicit() the Linux kernel, too. Its advantage is that it * This method is used in memzero_explicit() the Linux kernel, too. Its advantage is that it
* is pretty efficient, because the compiler can still implement the memset() efficently, * is pretty efficient, because the compiler can still implement the memset() efficiently,
* just not remove it entirely. See "Dead Store Elimination (Still) Considered Harmful" by * just not remove it entirely. See "Dead Store Elimination (Still) Considered Harmful" by
* Yang et al. (USENIX Security 2017) for more background. * Yang et al. (USENIX Security 2017) for more background.
*/ */

View file

@ -0,0 +1,12 @@
#ifndef SECP256K1_LOCAL_VISIBILITY_H
#define SECP256K1_LOCAL_VISIBILITY_H
/* Global variable visibility */
/* See: https://github.com/bitcoin-core/secp256k1/issues/1181 */
#if !defined(_WIN32) && defined(__GNUC__) && (__GNUC__ >= 4)
# define SECP256K1_LOCAL_VAR extern __attribute__ ((visibility ("hidden")))
#else
# define SECP256K1_LOCAL_VAR extern
#endif
#endif /* SECP256K1_LOCAL_VISIBILITY_H */

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,72 @@
#!/usr/bin/env python3
"""Check that a libsecp256k1 shared library exports only expected symbols.
Usage examples:
- When building with Autotools:
./tools/symbol-check.py .libs/libsecp256k1.so
./tools/symbol-check.py .libs/libsecp256k1-<V>.dll
./tools/symbol-check.py .libs/libsecp256k1.dylib
- When building with CMake:
./tools/symbol-check.py build/lib/libsecp256k1.so
./tools/symbol-check.py build/bin/libsecp256k1-<V>.dll
./tools/symbol-check.py build/lib/libsecp256k1.dylib"""
import re
import sys
import subprocess
import lief
class UnexpectedExport(RuntimeError):
pass
def get_exported_exports(library) -> list[str]:
"""Adapter function to get exported symbols based on the library format."""
if library.format == lief.Binary.FORMATS.ELF:
return [symbol.name for symbol in library.exported_symbols]
elif library.format == lief.Binary.FORMATS.PE:
return [entry.name for entry in library.get_export().entries]
elif library.format == lief.Binary.FORMATS.MACHO:
return [symbol.name[1:] for symbol in library.exported_symbols]
raise NotImplementedError(f"Unsupported format: {library.format}")
def grep_expected_symbols() -> list[str]:
"""Guess the list of expected exported symbols from the source code."""
grep_output = subprocess.check_output(
["git", "grep", r"^\s*SECP256K1_API", "--", "include"],
universal_newlines=True,
encoding="utf-8"
)
lines = grep_output.split("\n")
pattern = re.compile(r'\bsecp256k1_\w+')
exported: list[str] = [pattern.findall(line)[-1] for line in lines if line.strip()]
return exported
def check_symbols(library, expected_exports) -> None:
"""Check that the library exports only the expected symbols."""
actual_exports = get_exported_exports(library)
unexpected_exports = set(actual_exports) - set(expected_exports)
if unexpected_exports != set():
raise UnexpectedExport(f"Unexpected exported symbols: {unexpected_exports}")
def main():
if len(sys.argv) != 2:
print(__doc__)
return 1
library = lief.parse(sys.argv[1])
expected_exports = grep_expected_symbols()
try:
check_symbols(library, expected_exports)
except UnexpectedExport as e:
print(f"{sys.argv[0]}: In {sys.argv[1]}: {e}")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,310 @@
#!/usr/bin/env python3
'''
A script to convert BIP352 test vectors from JSON to a C header.
Usage:
./tools/tests_silentpayments_generate.py src/modules/silentpayments/bip352_send_and_receive_test_vectors.json > ./src/modules/silentpayments/vectors.h
'''
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 = 4
MAX_PERMUTATIONS_PER_SENDING_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 += '""'
out += ",\n"
out += " },\n"
def emit_recipient_addr_material(recipient_addresses):
global out
out += f" {len(recipient_addresses)}," + "\n"
out += " { /* recipient pubkeys (address data) */\n"
for i in range(MAX_OUTPUTS_PER_TEST_CASE):
out += " {\n"
if i < len(recipient_addresses):
B_scan, B_spend = decode_silent_payments_address(recipient_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 += " }"
out += ",\n"
out += " },\n"
def emit_sending_outputs(comment, output_sets, include_count=False):
global out
if include_count:
out += f" {len(output_sets)}," + "\n"
out += f" {len(output_sets[0])}," + "\n"
if comment:
out += f" {{ /* {comment} */" + "\n"
else:
out += " {\n"
for i in range(MAX_PERMUTATIONS_PER_SENDING_TEST_CASE):
if i < len(output_sets):
emit_outputs(comment=None, outputs=output_sets[i], include_count=False, spacing=12)
else:
emit_outputs(comment=None, outputs=[], include_count=False, spacing=12)
out += " }"
out += ","
out += "\n"
def emit_outputs(comment, outputs, include_count=False, last=False, spacing=8):
global out
if include_count:
out += spacing*" " + f"{len(outputs)}," + "\n"
if comment:
out += spacing*" " + f"{{ /* {comment} */" + "\n"
else:
out += spacing*" " + "{\n"
for i in range(MAX_OUTPUTS_PER_TEST_CASE):
if i < len(outputs):
out += spacing*" " + " {"
out += to_c_array(outputs[i])
out += "}"
else:
out += spacing*' ' + ' ""'
out += ",\n"
out += spacing*" " + "}"
if not last:
out += ","
out += "\n"
if len(sys.argv) != 2:
print("Usage: tests_silentpayments_generate.py vectors.json > vectors.h")
sys.exit(1)
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_recipient_addr_material(test_vector['sending'][0]['given']['recipients'])
# emit recipient outputs
emit_sending_outputs("recipient outputs", test_vector['sending'][0]['expected']['outputs'], include_count=True)
# emit recipient scan/spend seckeys
recv_test_given = test_vector['receiving'][0]['given']
recv_test_expected = test_vector['receiving'][0]['expected']
out += " /* recipient 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 recipient 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}
#define MAX_PERMUTATIONS_PER_SENDING_TEST_CASE {MAX_PERMUTATIONS_PER_SENDING_TEST_CASE}
struct bip352_recipient_addressdata {{
unsigned char scan_pubkey[33];
unsigned char labeled_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_recipient_addressdata recipient_pubkeys[MAX_OUTPUTS_PER_TEST_CASE];
/* Expected sender data */
size_t num_output_sets;
size_t num_recipient_outputs;
unsigned char recipient_outputs[MAX_PERMUTATIONS_PER_SENDING_TEST_CASE][MAX_OUTPUTS_PER_TEST_CASE][32];
/* Given recipient 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 recipient 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("};")

View file

@ -18,6 +18,7 @@ add_executable(test_bitcoin
bech32_tests.cpp bech32_tests.cpp
bip32_tests.cpp bip32_tests.cpp
bip324_tests.cpp bip324_tests.cpp
bip352_tests.cpp
blockchain_tests.cpp blockchain_tests.cpp
blockencodings_tests.cpp blockencodings_tests.cpp
blockfilter_index_tests.cpp blockfilter_index_tests.cpp
@ -129,6 +130,7 @@ include(TargetDataSources)
target_json_data_sources(test_bitcoin target_json_data_sources(test_bitcoin
data/base58_encode_decode.json data/base58_encode_decode.json
data/bip341_wallet_vectors.json data/bip341_wallet_vectors.json
data/bip352_send_and_receive_vectors.json
data/blockfilters.json data/blockfilters.json
data/key_io_invalid.json data/key_io_invalid.json
data/key_io_valid.json data/key_io_valid.json

188
src/test/bip352_tests.cpp Normal file
View file

@ -0,0 +1,188 @@
#include <common/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<KeyPair> taproot_keys;
for (const auto& input : given["vin"].getValues()) {
COutPoint outpoint{Txid::FromHex(input["txid"].get_str()).value(), 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()).ComputeKeyPair(nullptr));
} 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(), bip352::BIP352Comparator());
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;
}
}
auto sp_tr_dests = bip352::GenerateSilentPaymentTaprootDestinations(sp_dests, keys, taproot_keys, *smallest_outpoint);
// This means the inputs summed to zero, which realistically would only happen maliciously. In this case, just move on
if (!sp_tr_dests.has_value()) continue;
bool match = false;
for (const auto& candidate_set : expected["outputs"].getValues()) {
BOOST_CHECK(sp_tr_dests->size() == candidate_set.size());
std::vector<WitnessV1Taproot> expected_spks;
for (const auto& output : candidate_set.getValues()) {
const WitnessV1Taproot tap{XOnlyPubKey(ParseHex(output.get_str()))};
expected_spks.push_back(tap);
}
match = true;
for (const auto& [_, spk]: *sp_tr_dests) {
if (std::find(expected_spks.begin(), expected_spks.end(), spk) == expected_spks.end()) {
match = false;
break;
}
}
if (!match) {
continue;
} else {
break;
}
}
BOOST_CHECK(match);
}
// 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{Txid::FromHex(input["txid"].get_str()).value(), 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};
}
auto pub_tweak_data = bip352::GetSilentPaymentsPublicData(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;
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& found_outputs = bip352::ScanForSilentPaymentOutputs(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_TEST_MESSAGE(found_outputs->size());
BOOST_CHECK(found_outputs->size() == expected_outputs.size());
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

@ -154,9 +154,17 @@ FUZZ_TARGET(script, .init = initialize_script)
const CScript dest{GetScriptForDestination(tx_destination_1)}; const CScript dest{GetScriptForDestination(tx_destination_1)};
const bool valid{IsValidDestination(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)) { if (!std::get_if<PubKeyDestination>(&tx_destination_1)) {
// Only try to round trip non-pubkey destinations since PubKeyDestination has no encoding // 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(tx_destination_1 == DecodeDestination(encoded_dest));
Assert(valid == IsValidDestinationString(encoded_dest)); Assert(valid == IsValidDestinationString(encoded_dest));
} }

View file

@ -216,6 +216,11 @@ CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) no
[&] { [&] {
tx_destination = PayToAnchor{}; tx_destination = PayToAnchor{};
}, },
[&] {
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)}; std::vector<unsigned char> program{ConsumeRandomLengthByteVector(fuzzed_data_provider, /*max_length=*/40)};
if (program.size() < 2) { if (program.size() < 2) {

View file

@ -452,6 +452,7 @@ public:
UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); } UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); }
UniValue operator()(const PubKeyDestination& 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 UniValue operator()(const PKHash& pkhash) const
{ {