Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python33"""4A script to convert BIP352 test vectors from JSON to a C header.6Usage:8./tools/tests_silentpayments_generate.py src/modules/silentpayments/bip352_send_and_receive_test_vectors.json > ./src/modules/silentpayments/vectors.h9"""11import hashlib12import json13import sys15NUMS_H = bytes.fromhex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")16MAX_INPUTS_PER_TEST_CASE = 317MAX_OUTPUTS_PER_TEST_CASE = 2324 # K_max + 118MAX_RECIPIENT_ENTRIES_PER_TEST_CASE = 419MAX_LABELS_PER_TEST_CASE = 420MAX_PERMUTATIONS_PER_SENDING_TEST_CASE = 1221MAX_RECEIVE_SUBTESTS = 223def sha256(s):24return hashlib.sha256(s).digest()26def smallest_outpoint(outpoints):27serialized_outpoints = [bytes.fromhex(txid)[::-1] + n.to_bytes(4, 'little') for txid, n in outpoints]28return sorted(serialized_outpoints)[0]30def is_p2tr(s): # OP_1 OP_PUSHBYTES_32 <32 bytes>31return (len(s) == 34) and (s[0] == 0x51) and (s[1] == 0x20)33def get_pubkey_from_input(input_data, pubkey_hex):34"""Extract the correct pubkey for an input, handling NUMS_H filtering and format conversion"""35spk = bytes.fromhex(input_data['prevout']['scriptPubKey']['hex'])36pubkey = bytes.fromhex(pubkey_hex)38if is_p2tr(spk): # taproot input39# Check for NUMS_H in witness (should be skipped)40witness = bytes.fromhex(input_data.get('txinwitness', ''))41# Parse witness stack42witness_stack = []43num_witness_items = 044if len(witness) > 0:45num_witness_items = witness[0]46witness = witness[1:]47for i in range(num_witness_items):48item_len = witness[0]49witness_stack.append(witness[1:item_len+1])50witness = witness[item_len+1:]52# Check for script-path spend with NUMS_H53if len(witness_stack) > 1 and witness_stack[-1][0] == 0x50:54witness_stack.pop()55if len(witness_stack) > 1: # script-path spend?56control_block = witness_stack[-1]57internal_key = control_block[1:33]58if internal_key == NUMS_H: # skip59return b''61# Convert to x-only (32 bytes) for taproot62if len(pubkey) == 33:63pubkey = pubkey[1:] # Remove prefix byte64return pubkey65else: # regular input - use full compressed pubkey (33 bytes)66return pubkey68def gen_byte_array(hex):69assert len(hex) % 2 == 070if hex == "":71return "{0x00}"72s = ',0x'.join(a + b for a, b in zip(hex[::2], hex[1::2]))73return "{0x" + s + "}"75def maybe_gen_comment(comment):76if comment:77return f" /* {comment} */"78else:79return ""81def gen_key_material(keys, comment=None, prepend_count=False):82assert len(keys) <= MAX_INPUTS_PER_TEST_CASE83out = ""84if prepend_count:85out += f" {len(keys)},\n"86out += f" {{{maybe_gen_comment(comment)}\n"87for k in keys:88out += f" {gen_byte_array(k)},\n"89if not keys:90out += ' "",\n'91out += " },\n"92return out94def gen_recipient_addr_material(recipients):95assert len(recipients) <= MAX_RECIPIENT_ENTRIES_PER_TEST_CASE96num_outputs = sum([r.get('count', 1) for r in recipients])97assert num_outputs <= MAX_OUTPUTS_PER_TEST_CASE98out = f" {num_outputs},\n"99out += f" {len(recipients)},\n"100out += " { /* recipient pubkey entries (address data) */\n"101for i in range(MAX_RECIPIENT_ENTRIES_PER_TEST_CASE):102out += " {\n"103if i < len(recipients):104# Use the scan_pubkey and spend_pubkey directly from the recipient105scan_pubkey = bytes.fromhex(recipients[i]['scan_pub_key'])106spend_pubkey = bytes.fromhex(recipients[i]['spend_pub_key'])108out += f" {gen_byte_array(scan_pubkey.hex())},\n"109out += f" {gen_byte_array(spend_pubkey.hex())},\n"110out += f" {recipients[i].get('count', 1)},\n"111else:112out += ' "",\n'113out += ' "",\n'114out += ' 0,\n'115out += " },\n"116out += " },\n"117return out119def gen_sending_outputs(output_sets, comment=None, prepend_count=False):120assert len(output_sets) <= MAX_PERMUTATIONS_PER_SENDING_TEST_CASE121out = ""122if prepend_count:123out += f" {len(output_sets)},\n"124out += f" {len(output_sets[0])},\n"125out += f" {{{maybe_gen_comment(comment)}\n"126for o in output_sets:127out += gen_outputs(outputs=o, prepend_count=False, indent=12)128if not output_sets:129out += gen_outputs(comment=None, outputs=[], prepend_count=False, indent=12)130out += " },\n"131return out133def gen_outputs(outputs, comment=None, prepend_count=False, indent=8):134out = ""135spaces = indent * " "136if prepend_count:137out += spaces + f"{len(outputs)},\n"138out += f" {{{maybe_gen_comment(comment)}\n"139for o in outputs:140out += spaces + f" {gen_byte_array(o)},\n"141if not outputs:142out += spaces + ' "",\n'143out += spaces + "},\n"144return out146def gen_labels(labels):147assert len(labels) <= MAX_LABELS_PER_TEST_CASE148out = " /* labels */\n"149out += f" {len(labels)}, {{"150for i in range(MAX_LABELS_PER_TEST_CASE):151if i < len(labels):152out += f"{labels[i]}, "153else:154out += "0xffffffff, "155out += "},\n"156return out158def gen_preamble(test_vectors):159out = "/* Note: this file was autogenerated using tests_silentpayments_generate.py. Do not edit. */\n"160out += f"""161#include <stddef.h>163#define MAX_INPUTS_PER_TEST_CASE {MAX_INPUTS_PER_TEST_CASE}164#define MAX_OUTPUTS_PER_TEST_CASE {MAX_OUTPUTS_PER_TEST_CASE}165#define MAX_RECIPIENT_ENTRIES_PER_TEST_CASE {MAX_RECIPIENT_ENTRIES_PER_TEST_CASE}166#define MAX_LABELS_PER_TEST_CASE {MAX_LABELS_PER_TEST_CASE}167#define MAX_PERMUTATIONS_PER_SENDING_TEST_CASE {MAX_PERMUTATIONS_PER_SENDING_TEST_CASE}168#define MAX_RECEIVE_SUBTESTS {MAX_RECEIVE_SUBTESTS}170struct bip352_recipient_addressdata {{171unsigned char scan_pubkey[33];172unsigned char spend_pubkey[33];173size_t count;174}};176struct bip352_receive_subtest {{177/* Given recipient data */178unsigned char scan_seckey[32];179unsigned char spend_seckey[32];180size_t num_to_scan_outputs;181unsigned char to_scan_outputs[MAX_OUTPUTS_PER_TEST_CASE][32];182size_t num_labels;183unsigned int label_integers[MAX_LABELS_PER_TEST_CASE];185/* Expected recipient data */186size_t full_check; /* 1..detailed check against tweaks and signatures, 0..only check found outputs count */187size_t num_found_output_pubkeys;188unsigned char found_output_pubkeys[MAX_OUTPUTS_PER_TEST_CASE][32];189unsigned char found_seckey_tweaks[MAX_OUTPUTS_PER_TEST_CASE][32];190unsigned char found_signatures[MAX_OUTPUTS_PER_TEST_CASE][64];191}};193struct bip352_test_vector {{194/* Inputs (private keys / public keys + smallest outpoint) */195size_t num_plain_inputs;196unsigned char plain_seckeys[MAX_INPUTS_PER_TEST_CASE][32];197unsigned char plain_pubkeys[MAX_INPUTS_PER_TEST_CASE][33];198size_t num_taproot_inputs;199unsigned char taproot_seckeys[MAX_INPUTS_PER_TEST_CASE][32];200unsigned char xonly_pubkeys[MAX_INPUTS_PER_TEST_CASE][32];201unsigned char outpoint_smallest[36];203/* Given sender data (pubkeys encoded per output address to send to) */204size_t num_outputs;205size_t num_recipient_entries;206struct bip352_recipient_addressdata recipient_pubkeys[MAX_RECIPIENT_ENTRIES_PER_TEST_CASE];208/* Expected sender data */209size_t num_output_sets;210size_t num_recipient_outputs;211unsigned char recipient_outputs[MAX_PERMUTATIONS_PER_SENDING_TEST_CASE][MAX_OUTPUTS_PER_TEST_CASE][32];213size_t num_receive_subtests;214struct bip352_receive_subtest receive_subtests[MAX_RECEIVE_SUBTESTS];215}};216"""217return out219def gen_test_vectors(test_vectors):220out = f"#define SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS {len(test_vectors)}\n\n"221out += "static const struct bip352_test_vector bip352_test_vectors[SECP256K1_SILENTPAYMENTS_NUMBER_TESTVECTORS] = {\n"222for test_i, test_vector in enumerate(test_vectors):223# determine input private and public keys, grouped into plain and taproot/x-only224input_plain_seckeys = []225input_taproot_seckeys = []226input_plain_pubkeys = []227input_xonly_pubkeys = []228outpoints = []230# To keep this script simple and the generated test vectors file (vectors.h) small, we take231# use of the fact that currently all BIP352 test vectors only have a single sending subtest.232# If that ever changes in the future, the following assertion would fail and this script,233# its emitted data structures and the test code consuming the generated header would need to234# be extended, in a similar way to how it's already done for the receiving subtests.235assert len(test_vector['sending']) == 1237pubkey_index = 0238input_pubkeys_hex = test_vector['sending'][0]['expected']['input_pub_keys']240for vec in test_vector['sending'][0]['given']['vin']:241outpoints.append((vec['txid'], vec['vout']))243if pubkey_index < len(input_pubkeys_hex):244pubkey = get_pubkey_from_input(vec, input_pubkeys_hex[pubkey_index])245if len(pubkey) == 33: # regular input246input_plain_seckeys.append(vec['private_key'])247input_plain_pubkeys.append(pubkey.hex())248pubkey_index += 1249elif len(pubkey) == 32: # taproot input250input_taproot_seckeys.append(vec['private_key'])
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.