Skip to main content

Python implementation of Wagner's Algorithm for the Generalized Birthday Problem.

Project description

wagner

Python implementation of Wagner's Algorithm for the Generalized Birthday Problem.

This algorithm is used to solve what is known as the generalized birthday problem. Given a modulus $n$, and $k$ lists of random numbers $\{L_1, L_2, ..., L_k\}$, how can we find $k$ elements $\{x_1, x_2, ..., x_k\} : x_i \in L_i$ from those lists, such that they all sum to some constant $c$ mod $n$?

Check out my full-length article on the subject for more detailed info. This repository is meant as a demonstration for practically minded and inquisitive readers.

Usage

The primary export of this library is the solve method.

>>> import wagner
>>> wagner.solve(2**16)
[50320, 16960, 11687, 52082, 17220, 47751, 11228, 54896]
>>> sum(_) % (2**16)
0

This method solves the generalized birthday problem for a given modulus $n$. The higher $n$ is, the more difficult it is to find a solution and the longer the algorithm will take.

At no cost, the caller can also choose a desired sum other than zero.

n = 2 ** 16
sum(wagner.solve(n, 885)) % n # -> 885

To change the number of elements returned by solve, specify the height $H$ of the tree used to solve the problem. The number of elements in the solution will be $2^H$.

len(wagner.solve(n, tree_height=2)) # -> 4
len(wagner.solve(n, tree_height=3)) # -> 8
len(wagner.solve(n, tree_height=4)) # -> 16
len(wagner.solve(n, tree_height=5)) # -> 32

To specify how the random elements are generated, provide a generator callback. By default, wagner uses random.randrange(n) to generate random values. A common use case for Wagner's Algorithm is to find inputs whose hashes sum to some desired number. To ensure solve returns the preimages and not the hash outputs, return Lineage instances from your generator callback. This class holds is basically an integer with pointers to the element(s) which created it.

import random
import hashlib
import wagner


def hashfunc(r, n, index):
  r_bytes = r.to_bytes((int.bit_length(n) + 7) // 8, 'big')
  preimage = r_bytes + index.to_bytes(16, 'big')
  h = hashlib.sha1(preimage).digest()
  return int.from_bytes(h, 'big') % n


def generator(n, index):
  r = random.randrange(0, n)
  return wagner.Lineage(hashfunc(r, n, index), r)


if __name__ == "__main__":
  n = 2 ** 128
  preimages = wagner.solve(n, generator=generator)
  print(sum(hashfunc(r, n, index) for index, r in enumerate(preimages)) % n) # -> 0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wagner-0.0.2.tar.gz (5.4 kB view details)

Uploaded Source

File details

Details for the file wagner-0.0.2.tar.gz.

File metadata

  • Download URL: wagner-0.0.2.tar.gz
  • Upload date:
  • Size: 5.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: curl/7.74.0

File hashes

Hashes for wagner-0.0.2.tar.gz
Algorithm Hash digest
SHA256 83d8d934c35988d477bee83e3dd8a73c77d72b2d25148b6b3e758ea0b537df9c
MD5 5cc8aebfb781d41b70e3bab7ff7a7f60
BLAKE2b-256 f7bbe14241449ed5b279388298226dcb6951b49409c0e2c77ac48c46009ba33b

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page