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.
251return out253def poly_frobeniusmod(poly, gf):254"""Compute x^(2^field_size) mod poly."""255out = [0, 1]256for _ in range(gf.field_size):257_, out = poly_divmod(poly_sqr(out, gf), poly, gf)258return out260def poly_find_roots(poly, gf):261"""Find the roots of poly if fully factorizable with unique roots, [] otherwise."""262assert len(poly) > 0263# If the polynomial is constant (and nonzero), it has no roots.264if len(poly) == 1:265return []266# Make the polynomial monic (which doesn't change its roots).267poly = poly_monic(poly, gf)268# If the polynomial is of the form x+a, return a.269if len(poly) == 2:270return [poly[0]]271# Otherwise, first test that poly can be completely factored into unique roots. The polynomial272# x^(2^fieldsize)-x has every field element once as root. Thus we want to know that that is a273# multiple of poly. Compute x^(field_size) mod poly, which needs to equal x if that is the case274# (unless poly has degree <= 1, but that case is handled above).275if poly_frobeniusmod(poly, gf) != [0, 1]:276return []278def rec_split(poly, randv):279"""Recursively split poly using the Berlekamp trace algorithm."""280# See https://hal.archives-ouvertes.fr/hal-00626997/document.281assert len(poly) > 1 and poly[-1] == 1 # Require a monic poly.282# If poly is of the form x+a, its root is a.283if len(poly) == 2:284return [poly[0]]285# Try consecutive randomization factors randv, until one is found that factors poly.286while True:287# Compute the trace of (randv*x) mod poly. This is a polynomial that maps half of the288# domain to 0, and the other half to 1. Which half that is is controlled by randv.289# By taking it modulo poly, we only add a multiple of poly. Thus the result has at least290# the shared roots of the trace polynomial and poly still, but may have others.291trace = poly_tracemod(poly, randv, gf)292# Using the set {2^i*a for i=0..fieldsize-1} gives optimally independent randv values293# (no more than fieldsize are ever needed).294randv = gf.mul2(randv)295# Now take the GCD of this trace polynomial with poly. The result is a polynomial296# that only has the shared roots of the trace polynomial and poly as roots.297gcd = poly_gcd(trace, poly, gf)298# If the result has a degree higher than 1, and lower than that of poly, we found a299# useful factorization.300if len(gcd) != len(poly) and len(gcd) > 1:301break302# Otherwise, continue with another randv.303# Find the actual factors: the monic version of the GCD above, and poly divided by it.304factor1 = poly_monic(gcd, gf)305factor2, _ = poly_divmod(poly, gcd, gf)306# Recurse.307return rec_split(factor1, randv) + rec_split(factor2, randv)309# Invoke the recursive splitting with a random initial factor, and sort the results.310return sorted(rec_split(poly, random.randrange(1, 1 << gf.field_size)))312class TestPolyFindRoots(unittest.TestCase):313"""Test class for poly_find_roots."""315def field_size_test(self, field_size):316"""Run tests for given field_size."""317gf = GF2Ops(field_size)318for test_size in [0, 1, 2, 3, 10]:319roots = [random.randrange(1 << field_size) for _ in range(test_size)]320roots_set = set(roots)321# Construct a polynomial with all elements of roots as roots (with multiplicity).322poly = [1]323for root in roots:324new_poly = [0] + poly325for n, c in enumerate(poly):326new_poly[n] ^= gf.mul(c, root)327poly = new_poly328# Invoke the root finding algorithm.329found_roots = poly_find_roots(poly, gf)330# The result must match the input, unless any roots were repeated.331if len(roots) == len(roots_set):332self.assertEqual(found_roots, sorted(roots))333else:334self.assertEqual(found_roots, [])336def test(self):337"""Run tests."""338for field_size in range(2, 65):339self.field_size_test(field_size)341def berlekamp_massey(syndromes, gf):342"""Implement the Berlekamp-Massey algorithm.344Takes as input a sequence of GF(2^field_size) elements, and returns the shortest LSFR345that generates it, represented as a polynomial.346"""347# See https://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm.348current = [1]349prev = [1]350b_inv = 1351for n, discrepancy in enumerate(syndromes):352# Compute discrepancy353for i in range(1, len(current)):354discrepancy ^= gf.mul(syndromes[n - i], current[i])356# Correct if discrepancy is nonzero.357if discrepancy:358x = n + 1 - (len(current) - 1) - (len(prev) - 1)359if 2 * (len(current) - 1) <= n:360tmp = list(current)361current.extend(0 for _ in range(len(prev) + x - len(current)))362mul = gf.mul(discrepancy, b_inv)363for i, v in enumerate(prev):364current[i + x] ^= gf.mul(mul, v)365prev = tmp366b_inv = gf.inv(discrepancy)367else:368mul = gf.mul(discrepancy, b_inv)369for i, v in enumerate(prev):370current[i + x] ^= gf.mul(mul, v)371return current373class Minisketch:374"""A Minisketch sketch.376This represents a sketch of a certain capacity, with elements of a certain bit size.377"""379def __init__(self, field_size, capacity):380"""Initialize an empty sketch with the specified field_size size and capacity."""381self.field_size = field_size382self.capacity = capacity383self.odd_syndromes = [0] * capacity384self.gf = GF2Ops(field_size)386def add(self, element):387"""Add an element to this sketch. 1 <= element < 2**field_size."""388sqr = self.gf.sqr(element)389for pos in range(self.capacity):390self.odd_syndromes[pos] ^= element391element = self.gf.mul(sqr, element)393def serialized_size(self):394"""Compute how many bytes a serialization of this sketch will be in size."""395return (self.capacity * self.field_size + 7) // 8397def serialize(self):398"""Serialize this sketch to bytes."""399val = 0400for i in range(self.capacity):401val |= self.odd_syndromes[i] << (self.field_size * i)402return val.to_bytes(self.serialized_size(), 'little')404def deserialize(self, byte_data):405"""Deserialize a byte array into this sketch, overwriting its contents."""406assert len(byte_data) == self.serialized_size()407val = int.from_bytes(byte_data, 'little')408for i in range(self.capacity):409self.odd_syndromes[i] = (val >> (self.field_size * i)) & ((1 << self.field_size) - 1)411def clone(self):412"""Return a clone of this sketch."""413ret = Minisketch(self.field_size, self.capacity)414ret.odd_syndromes = list(self.odd_syndromes)415ret.gf = self.gf416return ret418def merge(self, other):419"""Merge a sketch with another sketch. Corresponds to XOR'ing their serializations."""420assert self.capacity == other.capacity421assert self.field_size == other.field_size422for i in range(self.capacity):423self.odd_syndromes[i] ^= other.odd_syndromes[i]425def decode(self, max_count=None):426"""Decode the contents of this sketch.428Returns either a list of elements or None if undecodable.429"""430# We know the odd syndromes s1=x+y+..., s3=x^3+y^3+..., s5=..., and reconstruct the even431# syndromes from this:432# * s2 = x^2+y^2+.... = (x+y+...)^2 = s1^2433# * s4 = x^4+y^4+.... = (x^2+y^2+...)^2 = s2^2434# * s6 = x^6+y^6+.... = (x^3+y^3+...)^2 = s3^2435all_syndromes = [0 for _ in range(2 * len(self.odd_syndromes))]436for i in range(len(self.odd_syndromes)):437all_syndromes[i * 2] = self.odd_syndromes[i]438all_syndromes[i * 2 + 1] = self.gf.sqr(all_syndromes[i])439# Given the syndromes, find the polynomial that generates them.440poly = berlekamp_massey(all_syndromes, self.gf)441# Deal with failure and trivial cases.442if len(poly) == 0:443return None444if len(poly) == 1:445return []446if max_count is not None and len(poly) > 1 + max_count:447return None448# If the polynomial can be factored into (1-m1*x)*(1-m2*x)*...*(1-mn*x), then {m1,m2,...,mn}449# is our set. As each factor (1-m*x) has 1/m as root, we're really just looking for the450# inverses of the roots. We find these by reversing the order of the coefficients, and451# finding the roots.452roots = poly_find_roots(list(reversed(poly)), self.gf)453if len(roots) == 0:454return None455return roots457class TestMinisketch(unittest.TestCase):458"""Test class for Minisketch."""460@classmethod461def construct_data(cls, field_size, num_a_only, num_b_only, num_both):462"""Construct two random lists of elements in [1..2**field_size-1].464Each list will have unique elements that don't appear in the other (num_a_only in the first465and num_b_only in the second), and num_both elements will appear in both."""466sample = []467# Simulate random.sample here (which doesn't work with ranges over 2**63).468for _ in range(num_a_only + num_b_only + num_both):469while True:470r = random.randrange(1, 1 << field_size)471if r not in sample:472sample.append(r)473break474full_a = sample[:num_a_only + num_both]475full_b = sample[num_a_only:]476random.shuffle(full_a)477random.shuffle(full_b)478return full_a, full_b480def field_size_capacity_test(self, field_size, capacity):481"""Test Minisketch methods for a specific field and capacity."""482used_capacity = random.randrange(capacity + 1)483num_a = random.randrange(used_capacity + 1)484num_both = random.randrange(min(2 * capacity, (1 << field_size) - 1 - used_capacity) + 1)485full_a, full_b = self.construct_data(field_size, num_a, used_capacity - num_a, num_both)486sketch_a = Minisketch(field_size, capacity)487sketch_b = Minisketch(field_size, capacity)488for v in full_a:489sketch_a.add(v)490for v in full_b:491sketch_b.add(v)492sketch_combined = sketch_a.clone()493sketch_b_ser = sketch_b.serialize()494sketch_b_received = Minisketch(field_size, capacity)495sketch_b_received.deserialize(sketch_b_ser)496sketch_combined.merge(sketch_b_received)497decode = sketch_combined.decode()498self.assertEqual(decode, sorted(set(full_a) ^ set(full_b)))500def test(self):
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.