├── cry ├── py │ ├── __init__.py │ ├── tree │ │ ├── __init__.py │ │ ├── node │ │ │ ├── __init__.py │ │ │ ├── utils.py │ │ │ ├── bitnode.py │ │ │ ├── op.py │ │ │ ├── node.py │ │ │ └── optbitnode.py │ │ ├── trace.py │ │ ├── anf.py │ │ └── orderer.py │ ├── anf │ │ ├── __init__.py │ │ ├── mobius.py │ │ └── symbolic.py │ ├── containers │ │ ├── __init__.py │ │ ├── rect.py │ │ └── vector.py │ ├── env.py │ └── utils │ │ ├── __init__.py │ │ └── cache.py ├── sbox2 │ ├── algorithms │ │ ├── __init__.py │ │ └── multiset.py │ ├── Makefile │ ├── ddt │ ├── __init__.py │ ├── generators │ │ ├── __init__.py │ │ ├── registry.py │ │ ├── linear.py │ │ ├── feistel.py │ │ └── simple.py │ ├── equiv │ │ ├── test.py │ │ ├── linear.py │ │ ├── __init__.py │ │ └── linear_generic.py │ ├── ddt_fast.cpp │ ├── tables.py │ └── sbox2.py ├── eq │ ├── __init__.py │ ├── affine.py │ ├── lattice.py │ └── interpolator.py ├── __init__.py ├── utils │ ├── __init__.py │ └── cache.py ├── fields │ ├── __init__.py │ └── basis_change.py ├── env.py ├── spaces.py ├── plots.py ├── sagestuff.py ├── perm.py ├── matrix.py └── rsa │ └── rsa.py ├── .gitignore ├── test_py.sh ├── test_sage.sh ├── pyproject.toml ├── README.md ├── tests_sage ├── test_lattice.py ├── test_sbox2.py └── test_rsa.py └── poetry.lock /cry/py/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sbox2/ddt 2 | -------------------------------------------------------------------------------- /cry/sbox2/algorithms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cry/py/tree/__init__.py: -------------------------------------------------------------------------------- 1 | from .node import * 2 | -------------------------------------------------------------------------------- /cry/sbox2/Makefile: -------------------------------------------------------------------------------- 1 | ddt: ddt_fast.cpp 2 | g++ -O3 ddt_fast.cpp -o ddt 3 | -------------------------------------------------------------------------------- /cry/sbox2/ddt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellman/cry/HEAD/cry/sbox2/ddt -------------------------------------------------------------------------------- /cry/sbox2/__init__.py: -------------------------------------------------------------------------------- 1 | from .sbox2 import SBox2 2 | 3 | from . import generators 4 | -------------------------------------------------------------------------------- /cry/eq/__init__.py: -------------------------------------------------------------------------------- 1 | from .interpolator import Interpolator 2 | from .affine import AffineSystem 3 | -------------------------------------------------------------------------------- /cry/py/anf/__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from .mobius import mobius 4 | from .symbolic import Bit 5 | -------------------------------------------------------------------------------- /cry/py/containers/__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from .vector import Vector 4 | from .rect import Rect 5 | -------------------------------------------------------------------------------- /test_py.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # prereq: 3 | # pip3 install -U pytest 4 | 5 | pytest --doctest-modules cry/py tests_py/ 6 | -------------------------------------------------------------------------------- /test_sage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # prereq: 3 | # sage -pip install -U pytest 4 | 5 | sage -sh -c 'pytest --doctest-modules cry/ tests_sage/' 6 | -------------------------------------------------------------------------------- /cry/py/tree/node/__init__.py: -------------------------------------------------------------------------------- 1 | from .node import Node 2 | from .bitnode import BitNode 3 | from .optbitnode import OptBitNode 4 | 5 | Bit = OptBitNode 6 | -------------------------------------------------------------------------------- /cry/__init__.py: -------------------------------------------------------------------------------- 1 | # check that the interpreting mode is correct (python or sage -python) 2 | assert type(12345) == int 3 | assert 2 ^ 1 == 3 4 | assert 2**1 == 2 5 | -------------------------------------------------------------------------------- /cry/sbox2/generators/__init__.py: -------------------------------------------------------------------------------- 1 | from .registry import initialize_registry 2 | initialize_registry() 3 | 4 | from . import simple 5 | from . import linear 6 | from . import feistel 7 | -------------------------------------------------------------------------------- /cry/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from cry.sagestuff import binomial 2 | from cry.py.utils import * 3 | from cry.utils.cache import * 4 | 5 | 6 | def sumbinom(n, d): 7 | return sum(binomial(n, i) for i in range(d+1)) 8 | -------------------------------------------------------------------------------- /cry/py/tree/node/utils.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | def tag_bits(**k): 4 | for name, bit in k.items(): 5 | bit.meta["tag"] = name 6 | 7 | def fake_inputs(**k): 8 | for name, bit in k.items(): 9 | bit.meta["fake-input"] = name 10 | -------------------------------------------------------------------------------- /cry/py/env.py: -------------------------------------------------------------------------------- 1 | from collections import Counter, defaultdict 2 | from itertools import product, combinations 3 | 4 | from pprint import pprint 5 | 6 | from binteger import Bin 7 | 8 | from random import randint, choice, shuffle 9 | 10 | from cry.py.anf import Bit, mobius 11 | 12 | from cry.py.containers.vector import Vector 13 | from cry.py.utils import * 14 | -------------------------------------------------------------------------------- /cry/sbox2/generators/registry.py: -------------------------------------------------------------------------------- 1 | from ..sbox2 import SBox2 2 | 3 | 4 | class GeneratorRegistry(object): 5 | def register(self, func): 6 | setattr(self, func.__name__, func) 7 | 8 | 9 | def initialize_registry(): 10 | if getattr(SBox2, SBox2.GENERATORS_ATTRIBUTE, None) is None: 11 | setattr(SBox2, SBox2.GENERATORS_ATTRIBUTE, GeneratorRegistry()) 12 | -------------------------------------------------------------------------------- /cry/fields/__init__.py: -------------------------------------------------------------------------------- 1 | from cry.sagestuff import PolynomialRing, GF 2 | 3 | from .basis_change import * 4 | 5 | 6 | def all_irreducible_polynomials(N, p=2): 7 | FR = PolynomialRing(GF(p), names=("X",)) 8 | for poly in FR.polynomials(of_degree=N): 9 | if poly.is_irreducible(): 10 | yield poly 11 | 12 | 13 | def all_fields_of_dimension(N, p=2, name='a'): 14 | for poly in all_irreducible_polynomials(N, p): 15 | yield GF(p**N, name=name, modulus=poly) 16 | -------------------------------------------------------------------------------- /cry/py/tree/trace.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | def circuit_trace(outputs, input_order, inputs_list): 4 | traces = defaultdict(int) 5 | input_names = [bit.name() for bit in input_order] 6 | for n, input_values in enumerate(inputs_list): 7 | trace = dict(zip(input_order, input_values)) 8 | for bit in outputs: 9 | bit.eval(trace) 10 | 11 | for bit, value in trace.items(): 12 | value &= 1 13 | traces[bit] |= (value << n) 14 | return traces 15 | -------------------------------------------------------------------------------- /cry/env.py: -------------------------------------------------------------------------------- 1 | from .sagestuff import * 2 | 3 | from .py.env import * 4 | 5 | from .sbox2 import SBox2 6 | from .matrix import matrix_mult_int, mat_to_tuples 7 | from .utils import IntervalCheck, sumbinom 8 | from .plots import save_plot 9 | 10 | from .fields import all_irreducible_polynomials, all_fields_of_dimension 11 | 12 | from .eq.affine import AffineSystem 13 | from .eq.lattice import Lattice 14 | 15 | from .rsa.rsa import RSA 16 | 17 | 18 | def gcd_generic(u, v): 19 | while v != 0: 20 | u, v = v, u % v 21 | return u 22 | -------------------------------------------------------------------------------- /cry/py/tree/anf.py: -------------------------------------------------------------------------------- 1 | from cry.py.anf.symbolic import Bit 2 | 3 | 4 | def compute_anfs(bit): 5 | if bit.is_input(): 6 | bit.meta["anf"] = Bit(bit.name()) 7 | return 8 | 9 | if bit.is_const(): 10 | bit.meta["anf"] = Bit(bit.value()) 11 | return 12 | 13 | TreeBit = bit.__class__ 14 | res = [] 15 | for sub in bit.args: 16 | if isinstance(sub, TreeBit) and "anf" not in sub.meta: 17 | compute_anfs(sub) 18 | res.append(sub.meta["anf"]) 19 | bit.meta["anf"] = bit.OP.eval(bit.op, res) 20 | # print "BIT", bit, "ANF", bit.meta["anf"] 21 | -------------------------------------------------------------------------------- /cry/spaces.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | def xor_closure(nums): 4 | res = {0} 5 | for x in nums: 6 | if x not in res: 7 | for y in res.copy(): 8 | res.add(x ^ y) 9 | return tuple(res) 10 | 11 | 12 | def complete_basis(basis, n): 13 | """Compute some basis2 such that basis2 with @basis span the whole space of dimensions n""" 14 | space = set(xor_closure(basis)) 15 | res = [] 16 | for v in xrange(1, 2**n): 17 | if v not in space: 18 | for u in space.copy(): 19 | space.add(u ^ v) 20 | res.append(v) 21 | assert len(res) + len(basis) == n 22 | return tuple(res) 23 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "cry" 3 | version = "0.2.5" 4 | description = "Cry: SageMath/Python Toolkit for Cryptanalytic Research" 5 | authors = [ 6 | {name = "Aleksei Udovenko", email = "aleksei@affine.group"} 7 | ] 8 | license = "MIT" 9 | readme = "README.md" 10 | keywords = ["cryptanalysis", "cryptography", "S-Boxes"] 11 | classifiers = [ 12 | "Intended Audience :: Science/Research", 13 | "Topic :: Scientific/Engineering :: Mathematics", 14 | "Topic :: Security :: Cryptography", 15 | "Development Status :: 4 - Beta", 16 | "Programming Language :: Python", 17 | ] 18 | requires-python = ">=3.9" 19 | dependencies = [ 20 | "binteger>=0.15.1" 21 | ] 22 | 23 | [project.urls] 24 | repository = "http://github.com/hellman/cry" 25 | 26 | [tool.poetry.group.dev.dependencies] 27 | pytest = "^6.1.1" 28 | 29 | [build-system] 30 | requires = ["poetry-core>=2.0.0,<3.0.0"] 31 | build-backend = "poetry.core.masonry.api" -------------------------------------------------------------------------------- /cry/sbox2/equiv/test.py: -------------------------------------------------------------------------------- 1 | from cry.sagestuff import * 2 | 3 | from cry.sbox2 import SBox2 4 | 5 | N = 6 6 | 7 | 8 | def gen(): 9 | return SBox2.new.random_permutation(N) 10 | # return SBox2.new.random_function(N) 11 | 12 | 13 | def try_good(): 14 | A = SBox2.new.random_linear_permutation(N) 15 | B = SBox2.new.random_linear_permutation(N) 16 | s1 = gen() 17 | s2 = B * s1 * A 18 | At, Bt = SBox2.are_linear_equivalent(s1, s2) 19 | assert Bt * s1 * At == s2 20 | assert At.is_linear() 21 | assert Bt.is_linear() 22 | assert At.is_permutation() 23 | assert Bt.is_permutation() 24 | # print "GOOD OK" 25 | 26 | 27 | def try_bad(): 28 | s1 = gen() 29 | s2 = gen() 30 | res = SBox2.are_linear_equivalent(s1, s2) 31 | assert res is False 32 | # print "BAD OK" 33 | 34 | 35 | def main(): 36 | for i in range(100): 37 | try_good() 38 | try_bad() 39 | 40 | 41 | if __name__ == '__main__': 42 | main() 43 | -------------------------------------------------------------------------------- /cry/py/tree/node/bitnode.py: -------------------------------------------------------------------------------- 1 | from .op import BitOP 2 | from .node import Node 3 | 4 | 5 | class BitNode(Node): 6 | OP = BitOP() 7 | 8 | def make_binary(op): 9 | def f(a, b): 10 | if isinstance(b, int): 11 | b = a.const(b) 12 | return a.__class__(op, a, b) 13 | return f 14 | 15 | Xor = __xor__ = __rxor__ = make_binary(OP.XOR) 16 | And = __and__ = __rand__ = make_binary(OP.AND) 17 | Or = __or__ = __ror__ = make_binary(OP.OR) 18 | del make_binary 19 | 20 | def __invert__(self): 21 | return self.new(self.OP.NOT, self) 22 | Not = __invert__ 23 | 24 | def is_const(self): 25 | return self.op in (self.OP.ONE, self.OP.ZERO) 26 | 27 | def value(self): 28 | assert self.op in (self.OP.ONE, self.OP.ZERO) 29 | return int(self.op == self.OP.ONE) 30 | 31 | @classmethod 32 | def const(cls, v): 33 | return cls.new(cls.OP.ONE) if int(v) else cls.new(cls.OP.ZERO) 34 | 35 | @classmethod 36 | def consts(cls, vs): 37 | return [cls.const(v) for v in vs] 38 | -------------------------------------------------------------------------------- /cry/sbox2/generators/linear.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | from cry.sagestuff import GF 4 | 5 | from cry.matrix import matrix_mult_int 6 | from cry.matrix import ( 7 | random_matrix, 8 | random_invertible_matrix, 9 | random_permutation_matrix, 10 | ) 11 | 12 | from cry.sbox2 import SBox2 13 | 14 | register = SBox2.new.register 15 | 16 | 17 | @register 18 | def from_matrix(mat): 19 | s = [] 20 | for x in range(2**mat.ncols()): 21 | s.append(matrix_mult_int(mat, x)) 22 | return SBox2(s, m=mat.nrows()) 23 | 24 | 25 | @register 26 | def random_linear_permutation(n): 27 | return from_matrix(random_invertible_matrix(GF(2), n)) 28 | 29 | 30 | @register 31 | def random_affine_permutation(n): 32 | xor = randint(0, 2**n-1) 33 | return SBox2(y ^ xor for y in from_matrix(random_invertible_matrix(n))) 34 | 35 | 36 | @register 37 | def random_linear(n, m=None): 38 | if m is None: 39 | m = n 40 | return from_matrix(random_matrix(GF(2), m, n)) 41 | 42 | 43 | @register 44 | def random_affine(*args): 45 | lin = random_linear(*args) 46 | xor = randint(0, max(lin)) 47 | return SBox2(y ^ xor for y in lin) 48 | 49 | 50 | @register 51 | def random_bit_permutation(n): 52 | return from_matrix(random_permutation_matrix(GF(2), n)) 53 | -------------------------------------------------------------------------------- /cry/py/anf/mobius.py: -------------------------------------------------------------------------------- 1 | def mobius(bf): 2 | assert isinstance(bf, list) or isinstance(bf, tuple) 3 | if len(bf) == 1: 4 | return (bf[0],) 5 | assert len(bf) & 1 == 0 6 | h = len(bf) // 2 7 | sub0 = mobius(bf[:h]) 8 | sub1 = mobius(bf[h:]) 9 | return sub0 + tuple(a ^ b for a, b in zip(sub0, sub1)) 10 | 11 | 12 | def walsh(bf): 13 | assert isinstance(bf, list) or isinstance(bf, tuple) 14 | if len(bf) == 1: 15 | return (bf[0],) 16 | assert len(bf) & 1 == 0 17 | h = len(bf) // 2 18 | sub0 = walsh(bf[:h]) 19 | sub1 = walsh(bf[h:]) 20 | res0 = tuple(a + b for a, b in zip(sub0, sub1)) 21 | res1 = tuple(a - b for a, b in zip(sub0, sub1)) 22 | return res0 + res1 23 | 24 | 25 | def submask_sum(f): 26 | assert isinstance(f, list) or isinstance(f, tuple) 27 | if len(f) == 1: 28 | return (f[0],) 29 | assert len(f) & 1 == 0 30 | h = len(f) // 2 31 | sub0 = submask_sum(f[:h]) 32 | sub1 = submask_sum(f[h:]) 33 | return sub0 + tuple(a + b for a, b in zip(sub0, sub1)) 34 | 35 | 36 | def undo_submask_sum(f): 37 | assert isinstance(f, list) or isinstance(f, tuple) 38 | if len(f) == 1: 39 | return (f[0],) 40 | assert len(f) & 1 == 0 41 | h = len(f) // 2 42 | f0 = f[:h] 43 | f1 = tuple(a - b for a, b in zip(f[:h], f[h:])) 44 | sub0 = undo_submask_sum(f0) 45 | sub1 = undo_submask_sum(f1) 46 | return sub0 + sub1 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cry: SageMath/Python Toolkit for Cryptanalytic Research 2 | 3 | This repository contains a bunch of various crypto-related algorithms implemented in Python 3 and SageMath. Pure Python code is located in cry/py package and can be imported from python code. The other modules must be imported from the SageMath interpreter. 4 | 5 | The most significant part is formed by S-Box analysis algorithms, implemented in the cry.sbox2.SBox2 class, which is similar to from sage.crypto.SBox but is much more rich. Another cool S-Box library is [SboxU](https://github.com/lpp-crypto/sboxU) by Léo Perrin. It contains some more advanced algorithms, highly recommended! 6 | 7 | **WARNING:** This library is not well-shaped yet and many things (including API and structure) may change in future. For now, I will try to keep compatability only for minor versions. That is, lock to the minor version if you use this package. 8 | 9 | **NOTE** Before, this library was called *cryptools*, but since this name is used on PyPI, I decided to switch to *cry*, which is shorter. 10 | 11 | Currently, there is no documentation but examples will be added soon. 12 | 13 | ## Installation 14 | 15 | ```bash 16 | # for SageMath 17 | $ sage pip install -U cry 18 | # for python3 19 | $ pip3 install -U cry 20 | ``` 21 | 22 | Previous python2 version (cryptools) can be found in the tag *py2-arhived*. 23 | 24 | ## Development 25 | 26 | For development or building this repository, [poetry](https://python-poetry.org/) is needed. 27 | -------------------------------------------------------------------------------- /cry/plots.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from cry.sagestuff import matrix_plot 4 | from cry.matrix import mat_min, mat_max, mat_distribution 5 | 6 | 7 | def save_plot(mat, fname, **kwargs): 8 | print("[i] Saving %s" % fname, file=sys.stderr) 9 | print( 10 | "min %d max %d: distrib %s\n" % 11 | (mat_min(mat), mat_max(mat), mat_distribution(mat)), 12 | file=sys.stderr 13 | ) 14 | 15 | absolute = kwargs.pop("abs", False) 16 | if absolute: 17 | for y in range(mat.nrows()): 18 | for x in range(mat.ncols()): 19 | mat[y, x] = abs(mat[y, x]) 20 | 21 | opts = dict( 22 | colorbar=True, 23 | figsize=15, 24 | cmap="gist_earth_r" 25 | ) 26 | opts.update(kwargs) 27 | 28 | plt = matrix_plot(mat, **opts) 29 | plt.save(fname) 30 | return plt 31 | 32 | 33 | def save_plot_lat(s, fname, **kwargs): 34 | print("[i] Saving %s" % fname, file=sys.stderr) 35 | m = s.lat() 36 | absolute = kwargs.pop("abs", False) 37 | if absolute: 38 | for y in range(m.nrows()): 39 | for x in range(m.ncols()): 40 | m[y, x] = abs(m[y, x]) 41 | return save_plot(m, fname, **kwargs) 42 | 43 | 44 | def save_plot_ddt(s, fname, **kwargs): 45 | print("[i] Saving %s" % fname, file=sys.stderr) 46 | m = s.ddt() 47 | absolute = kwargs.pop("abs", False) 48 | if absolute: 49 | for y in range(m.nrows()): 50 | for x in range(m.ncols()): 51 | m[y, x] = abs(m[y, x]) 52 | return save_plot(m, fname, **kwargs) 53 | -------------------------------------------------------------------------------- /cry/sagestuff.py: -------------------------------------------------------------------------------- 1 | """ 2 | Trying to import only required stuff from sage. 3 | Single point of import is good. 4 | 5 | Tried to import from inner modules, but probably worthless. 6 | Seems sage.all must be imported. 7 | """ 8 | 9 | # sage is a gigantic mess 10 | 11 | from sage.all_cmdline import ( 12 | copy, power_mod, 13 | Integer, Zmod, ZZ, QQ, RR, CDF, Integer, GF, 14 | loads, dumps, 15 | matrix, identity_matrix, random_matrix, matrix_plot, 16 | vector, random_vector, 17 | binomial, 18 | lcm, gcd, log, 19 | Combinations, Permutation, 20 | LinearCode, 21 | floor, ceil, inverse_mod, floor, 22 | ) 23 | 24 | from random import randint, shuffle, choice 25 | 26 | # import sage.structure 27 | 28 | # from sage.plot.matrix_plot import matrix_plot 29 | 30 | # from sage.misc.persist import loads, dumps 31 | 32 | # from sage.matrix.constructor import matrix, identity_matrix, random_matrix 33 | # from sage.modules.free_module_element import vector 34 | # Matrix = matrix 35 | 36 | # from sage.rings.finite_rings.finite_field_constructor import GF 37 | # from sage.rings.integer import Integer 38 | # from sage.rings.integer_ring import ZZ 39 | from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing 40 | from sage.rings.polynomial.polynomial_ring_constructor import BooleanPolynomialRing_constructor as BooleanPolynomialRing 41 | # from sage.groups.matrix_gps.catalog import GL 42 | 43 | # from sage.functions import log 44 | 45 | # from sage.misc.prandom import randint, shuffle, choice 46 | # from sage.arith.all import lcm, gcd 47 | 48 | from sage.crypto.boolean_function import BooleanFunction 49 | 50 | # from sage.combinat.combination import Combinations 51 | # from sage.functions.other import binomial 52 | -------------------------------------------------------------------------------- /cry/sbox2/ddt_fast.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define CONCAT3_NX(x, y, z) x ## y ## z 4 | #define CONCAT3(x, y, z) CONCAT3_NX(x, y, z) 5 | #define VAR(name) CONCAT3(__tmpvar__, name, __LINE__) 6 | #define TYPE(x) __typeof(x) 7 | 8 | #define FOR(i, s, n) for (TYPE(n) i=(s), VAR(end)=(n); i < VAR(end); i++) 9 | #define FORN(i, n) FOR(i, 0, n) 10 | #define SCi(a) scanf("%d", &a) 11 | #define SCl(a) scanf("%llu", &a) 12 | 13 | unsigned long long N; 14 | unsigned int *sbox; 15 | unsigned int *ddt; 16 | unsigned long long *counts; 17 | 18 | void *assert_not_null(void *p) { 19 | if (!p) { 20 | perror("error"); 21 | exit(1); 22 | } 23 | } 24 | 25 | int main() { 26 | scanf("%llu\n", &N); 27 | 28 | sbox = (unsigned int*)calloc(N, sizeof(unsigned int)); 29 | assert_not_null(sbox); 30 | ddt = (unsigned int*)calloc(N, sizeof(unsigned int)); 31 | assert_not_null(ddt); 32 | counts = (unsigned long long*)calloc(N + 1024, sizeof(unsigned long long)); 33 | assert_not_null(counts); 34 | 35 | FORN(i, N) { 36 | SCi(sbox[i]); 37 | } 38 | 39 | FOR(dx, 1, N) { 40 | FORN(x, N) { 41 | int x2 = x ^ dx; 42 | int y = sbox[x]; 43 | int y2 = sbox[x2]; 44 | int dy = y ^ y2; 45 | ddt[dy] += 1; 46 | // printf(" x %d x2 %d dx %d y %d y2 %d dy %d ddt[dy] %d\n", x, x2, dx, y, y2, dy, ddt[dy]); 47 | } 48 | FOR(dy, 0, N) { 49 | // printf("dx: %d dy: %d ddt[dy]: %d counts[]: %d\n", dx, dy, ddt[dy], counts[ddt[dy]]); 50 | counts[ddt[dy]]++; 51 | ddt[dy] = 0; 52 | } 53 | } 54 | 55 | printf("{"); 56 | FORN(c, N + 1) { 57 | if (counts[c]) { 58 | printf("%llu: %llu, ", c, counts[c]); 59 | } 60 | } 61 | printf("}\n"); 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /cry/py/utils/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from .cache import * 4 | 5 | 6 | class IntervalCheck(object): 7 | def __init__(self, interval, total=None): 8 | self.start_time = self.last_hit = time.time() 9 | self.total = total 10 | self.itr = 0 11 | 12 | self.last_hit = time.time() 13 | self.interval = float(interval) 14 | assert self.interval > 0 15 | 16 | def touch(self): 17 | self.last_hit = time.time() 18 | 19 | def check(self): 20 | self.itr += 1 21 | 22 | t = time.time() 23 | if t - self.last_hit >= self.interval: 24 | self.last_hit = t 25 | return True 26 | 27 | def check_print(self, itr=None, total=None): 28 | if not self.check(): 29 | return 30 | 31 | elapsed = time.time() - self.start_time 32 | if self.total is None: 33 | print >>sys.stderr, "#%d/?. Elapsed: %ds (%.2f hours)." % (self.itr, elapsed, elapsed / 3600.0) 34 | else: 35 | remaining = elapsed * (self.total * 1.0 / self.itr - 1) 36 | perc = (self.itr * 100.0 / self.total) 37 | print >>sys.stderr, "#%d/%d (%.2f%%)." % (self.itr, self.total, perc), 38 | print >>sys.stderr, "Elapsed: %ds (%.2f hours);" % (elapsed, elapsed / 3600.0), 39 | print >>sys.stderr, "remaining: %ds (%.2f hours)" % (remaining, remaining / 3600.0), 40 | print >>sys.stderr 41 | return True 42 | 43 | 44 | def tmap(*a, **k): 45 | return tuple(map(*a, **k)) 46 | 47 | 48 | def isiterable(s): 49 | try: 50 | list(s) 51 | return True 52 | except TypeError: 53 | return False 54 | 55 | 56 | from itertools import product 57 | 58 | 59 | def ranges(*ns, list=None): 60 | if list: 61 | assert not ns 62 | ns = list 63 | return product(*[range(n) for n in ns]) 64 | -------------------------------------------------------------------------------- /cry/perm.py: -------------------------------------------------------------------------------- 1 | from cry.sagestuff import matrix, GF, Permutation 2 | 3 | F2 = GF(2) 4 | 5 | 6 | class Perm: 7 | def __init__(self, p, n=None): 8 | self.p = tuple(p) 9 | self.n = n if n is not None else (max(self.p) + 1) 10 | self.m = len(self.p) 11 | assert all(0 <= i < self.n for i in self.p) 12 | 13 | @classmethod 14 | def from_matrix(cls, m): 15 | res = [None] * m.nrows() 16 | for y, x in m.support(): 17 | res[y] = x 18 | return cls(res, n=m.ncols()) 19 | 20 | def to_matrix(self, field=F2): 21 | m = matrix(Permutation([v + 1 for v in self.p])) 22 | m = m.transpose().change_ring(field) 23 | return m 24 | 25 | def complete(self, n): 26 | assert n >= self.n 27 | s = set(self.p) 28 | return self.Perm(self.p + tuple(v for v in range(n) if v not in s)) 29 | 30 | def __invert__(self): 31 | res = [None] * len(self) 32 | for x, y in enumerate(self): 33 | res[y] = x 34 | return Perm(res) 35 | 36 | def apply(self, x, inverse=False): 37 | if inverse: 38 | assert self.n == self.m == len(x) 39 | res = [0] * len(x) 40 | for i, j in enumerate(self.p): 41 | res[j] = x[i] 42 | return tuple(res) 43 | else: 44 | return tuple(x[i] for i in self.p) 45 | 46 | def unapply(self, x): 47 | return self.apply(x, inverse=True) 48 | 49 | def __eq__(self, other): 50 | return self.p == other 51 | 52 | def __str__(self): 53 | return str(self.p) 54 | 55 | def __repr__(self): 56 | return f"Perm({self.p}, n={self.n})" 57 | 58 | def __len__(self): 59 | return self.m 60 | 61 | def __iter__(self): 62 | return iter(self.p) 63 | 64 | 65 | def test_perm(): 66 | p = (0, 3, 2, 6, 5, 4, 7, 1) 67 | assert Perm.from_matrix(Perm(p).to_matrix()) == p 68 | 69 | x = list(range(8)) 70 | assert (~Perm(p)).apply(Perm(p).apply(x)) == tuple(range(8)) 71 | assert Perm(p).unapply(Perm(p).apply(x)) == tuple(range(8)) 72 | print("test ok") 73 | 74 | 75 | if __name__ == '__main__': 76 | test_perm() 77 | -------------------------------------------------------------------------------- /cry/sbox2/generators/feistel.py: -------------------------------------------------------------------------------- 1 | from cry.sbox2 import SBox2 2 | 3 | register = SBox2.new.register 4 | 5 | 6 | @register 7 | def feistel_round_xor(func, swap=False): 8 | func = SBox2(func) 9 | n = func.in_bits 10 | res = [] 11 | for al in range(2**n): 12 | for ar in range(2**n): 13 | l, r = al, ar 14 | l ^= func(r) 15 | if swap: 16 | l, r = r, l 17 | res.append((l << n) | r) 18 | return res 19 | 20 | 21 | @register 22 | def feistel_round_add(func, swap=False): 23 | func = SBox2.new(func) 24 | n = func.in_bits 25 | mask = (1 << n) - 1 26 | res = [] 27 | for al in range(2**n): 28 | for ar in range(2**n): 29 | l, r = al, ar 30 | l = (l + func(r)) & mask 31 | if swap: 32 | l, r = r, l 33 | res.append((l << n) | r) 34 | return res 35 | 36 | 37 | @register 38 | def feistel_network_xor( 39 | funcs=None, n=None, nrounds=None, permutations=False, degree=None 40 | ): 41 | if funcs is None: 42 | assert n is not None 43 | assert nrounds is not None 44 | assert (not permutations) or (degree is None), \ 45 | "Not implemented to generate permutations of arbitrary degree" 46 | fgen = SBox2.new.random_function 47 | if permutations: 48 | fgen = SBox2.new.random_permutation 49 | if degree: 50 | fgen = lambda n: SBox2.new.random_function_of_degree(n, degree) 51 | funcs = [fgen(n) for i in range(nrounds)] 52 | else: 53 | funcs = list(map(SBox2, funcs)) 54 | n = funcs[0].in_bits 55 | 56 | res = [] 57 | for al in range(2**n): 58 | for ar in range(2**n): 59 | l, r = al, ar 60 | for func in funcs: 61 | l ^= func[r] 62 | l, r = r, l 63 | l, r = r, l 64 | res.append((l << n) | r) 65 | return res 66 | 67 | 68 | @register 69 | def feistel_round_minicipher(n, func=None, swap=False): 70 | if func is None: 71 | func = SBox2.new.random_minicipher(n) 72 | res = [] 73 | for al in range(2**n): 74 | for ar in range(2**n): 75 | l, r = al, ar 76 | l = func[(r< 10: 74 | break 75 | 76 | 77 | if __name__ == '__main__': 78 | test() 79 | -------------------------------------------------------------------------------- /cry/py/tree/node/op.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import sys 4 | import operator 5 | from random import randint 6 | 7 | class OP(object): 8 | OPS = {} 9 | 10 | def __init__(self): 11 | self.free_id = 10 # to avoid bugs, e.g. Bit(0) will raise error (for const need to use Bit.const(0)) 12 | 13 | self.name = {} 14 | self.arity = {} 15 | 16 | self.operator = {} 17 | self.symbol = {} 18 | self.symmetric = {} 19 | 20 | for name, data in self.OPS: 21 | self.add_op(name, data) 22 | 23 | def add_op(self, name, data): 24 | name = name.upper() 25 | opnum = data.get("id", self.free_id) 26 | self.free_id = max(self.free_id, opnum + 1) 27 | 28 | setattr(self, name, opnum) 29 | for alias in data.get("aliases", ()): 30 | setattr(self, alias, opnum) 31 | 32 | self.name[opnum] = name 33 | self.arity[opnum] = int(data["arity"]) 34 | 35 | self.process_data(opnum, name, data) 36 | 37 | def process_data(self, opnum, name, data): 38 | self.operator[opnum] = data.get("operator", None) 39 | self.symbol[opnum] = data.get("symbol", None) 40 | self.symmetric[opnum] = data.get("symmetric", False) 41 | 42 | def eval(self, op, args): 43 | if not self.operator[op]: 44 | raise NotImplementedError("Operator %s can not be evaluated" % self.name[op]) 45 | return self.operator[op](*args) 46 | 47 | def __contains__(self, op): 48 | return op in self.name 49 | 50 | def dump(self, file=sys.stderr): 51 | for opnum, name in sorted(self.name.items()): 52 | print >>file, "%2d: %s" % (opnum, name) 53 | 54 | class BitOP(OP): 55 | OPS = ( 56 | ("XOR", dict(operator=operator.xor, 57 | symbol="^", 58 | symmetric=True, 59 | arity=2)), 60 | ("AND", dict(operator=operator.and_, 61 | symbol="&", 62 | symmetric=True, 63 | arity=2)), 64 | ("OR", dict(operator=operator.or_, 65 | symbol="|", 66 | symmetric=True, 67 | arity=2)), 68 | 69 | ("NOT", dict(operator=lambda a: 1^a, 70 | symbol="~", 71 | arity=1)), 72 | 73 | ("ZERO", dict(operator=lambda: 0, 74 | symbol="0", 75 | arity=0)), 76 | ("ONE", dict(operator=lambda: 1, 77 | symbol="1", 78 | arity=0)), 79 | 80 | # special nodes / ops 81 | ("INPUT", dict(operator=None, 82 | symbol="i", 83 | arity=0)), 84 | ("OUTPUT", dict(operator=lambda a: a, 85 | symbol="o", 86 | arity=1)), 87 | ("FREE", dict(operator=None, 88 | symbol="f", 89 | arity=1)), 90 | 91 | ("RANDOM", dict(operator=lambda: randint(0, 1), 92 | symbol="$", 93 | arity=0)), 94 | ) 95 | 96 | -------------------------------------------------------------------------------- /tests_sage/test_lattice.py: -------------------------------------------------------------------------------- 1 | from cry.eq.lattice import Lattice 2 | from cry.sagestuff import RR 3 | 4 | 5 | def test_SECCON_2020_sharsable(): 6 | from bint import Bin 7 | 8 | # SECCON 2020 - sharsable 9 | n = 142793817321992828777925840162504083304079023834001118099549928854335392622287928254035247188624975743042449746066633491912316354241339908190889792327014012472372654378644158878787350693992259970146885854641856991605625756536504266728483088687985429310233421251081614258665472164668993082471923690196082829593 # noqa 10 | e1, c1 = [ 11 | 82815162880874815458042429141267540989513396527359063805652845923737062346339641683097075730151688566721221542188377672708478777831586255213972947470222613130635483227797717393291856129771004300757155687587305350059401683671715424063527610425941387424425367153041852997937972925839362190900175155479532582934, # noqa 12 | 108072697038795075732704334514926058617161875495016327352871122917196026504758904760148391499245235850616838765611460630089577948665981247735905622903872682862860306107704253287284051312867625831877418240290183661755993649928399992531008191618616452091127799880839665225093055618092869662205901927957599941568, # noqa 13 | ] 14 | e2, c2 = [ 15 | 84856171747859965508406237198459622554468224770252249975158471902036102010991476445962577679301719179079633469099994226630172251817358960347828156301869905575867853640850107406452911333646573296923235424617864473580743418995994067645338437540627399276292679100115018844287273293945121023787594592185295794983, # noqa 16 | 101960082023987498941061751761131381167414505957511290567652602520714324823481487410890478130601013005035303795327512367595187718926017321227779179404306882163521882309833982882201152721855538832465833869251505131262098978117904455226014402089126682222497271578420753565370375178303927777655414023662528363360, # noqa 17 | ] 18 | 19 | m = Lattice([ 20 | [e1, 1, 0], 21 | [e2, 0, 1], 22 | [n, 0, 0], 23 | ]) 24 | m.set_bounds([n**0.66, n**0.16, n**0.16], precision=1) 25 | d1, d2 = m.LLL().apply_map(abs)[0][1:] 26 | msg = int(pow(c1, int(d1), n) * pow(c2, int(d2), n) % n) 27 | assert Bin(msg).bytes == b'SECCON{sh4r4bl3_r54_1s_useful?}' 28 | 29 | assert d1, d2 == m.BKZ().apply_map(abs)[0][1:] 30 | 31 | 32 | def test_bound(): 33 | from random import randrange 34 | for delta in (0.4, 0.5, 0.75, 0.9, 0.99): 35 | for n in (10, 20, 50, 100, 200, 256, 512, 1024, 2048): 36 | for d in (2, 3, 5, 10, 15): 37 | m = Lattice([ 38 | [randrange(2**n) for _ in range(d)] 39 | for _ in range(d) 40 | ]) 41 | m.set_bounds([2**randrange(n) for _ in range(d)], precision=1) 42 | bound = m.get_bound_LLL() 43 | ml = m.LLL() 44 | norm = int(RR(ml[0].norm())) 45 | print( 46 | "delta", delta, "n", n, "d", d, 47 | "logs:", 48 | int(norm).bit_length(), "" % (self.h, self.w) 101 | -------------------------------------------------------------------------------- /cry/matrix.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | from copy import copy 4 | 5 | from random import shuffle 6 | from cry.sagestuff import ( 7 | vector, matrix, identity_matrix, random_matrix, 8 | GF, LinearCode 9 | ) 10 | 11 | from binteger import Bin 12 | 13 | 14 | def matrix_mult_int(mat, x): 15 | """ 16 | MSB to LSB vector 17 | >>> matrix_mult_int( \ 18 | matrix(GF(2), [[1, 0, 1], [1, 0, 0]]), \ 19 | 0b110) # read as 6 -> 1,1,0 -> 1,1 -> 3 20 | 3 21 | """ 22 | assert mat.base_ring() == GF(2) 23 | n = mat.ncols() 24 | x = vector(GF(2), Bin(x, n).tuple) 25 | y = mat * x 26 | return Bin(y).int 27 | 28 | 29 | def matrix_mult_int_rev(mat, x): 30 | """ 31 | LSB to MSB vector 32 | >>> matrix_mult_int_rev( \ 33 | matrix(GF(2), [[1, 0, 1], [1, 0, 0]]), \ 34 | 0b110) # read as 6 -> 0,1,1 -> 1,0 -> 1 35 | 1 36 | """ 37 | assert mat.base_ring() == GF(2) 38 | n = mat.ncols() 39 | x = vector(GF(2), Bin(x, n).tuple[::-1]) 40 | y = mat * x 41 | return Bin(y[::-1]).int 42 | 43 | 44 | def matrix_is_mds(mat): 45 | """@mat is considered as operator M*x""" 46 | n = mat.ncols() 47 | m = mat.nrows() 48 | 49 | mat2 = matrix(mat.base_ring(), m * 2, n) 50 | mat2[:m, :n] = identity_matrix(mat.base_ring(), m, n) 51 | mat2[m:, :n] = mat 52 | 53 | # linear code: x -> (x || M*x) 54 | C = LinearCode(mat2.transpose()) 55 | D = C.minimum_distance() 56 | K = n 57 | N = 2 * m 58 | 59 | # Singleton bound 60 | # MDS on equality 61 | assert D <= N - K + 1 62 | return D == N - K + 1 63 | 64 | 65 | def mat_from_linear_func(m, n, func): 66 | mat = matrix(GF(2), n, m) 67 | for i, e in enumerate(reversed(range(m))): 68 | x = 1 << e 69 | mat.set_column(i, Bin(func(x), n).tuple) 70 | return mat 71 | 72 | 73 | def mat_field_mul_const(field, c): 74 | assert field.base_ring() == GF(2) 75 | d = field.degree() 76 | m = matrix(GF(2), d, d) 77 | for i, e in enumerate(reversed(range(d))): 78 | x = 1 << e 79 | res = field.fetch_int(x) * field.fetch_int(c) 80 | res = res.integer_representation() 81 | m.set_column(i, Bin(res, d).tuple) 82 | return m 83 | 84 | 85 | def mat_to_tuples(mat): 86 | """Useful for hashing matrices (set/dict)""" 87 | return tuple(map(tuple, mat)) 88 | 89 | 90 | def mat_max(mat): 91 | return max(max(v) for v in mat) 92 | 93 | 94 | def mat_min(mat): 95 | return min(min(v) for v in mat) 96 | 97 | 98 | def mat_distribution(mat): 99 | return Counter(sum(map(list, mat), [])) 100 | 101 | 102 | def mat_zero_zero(mat): 103 | mat = copy(mat) 104 | for y in range(mat.nrows()): 105 | mat[y, 0] = 0 106 | for x in range(mat.ncols()): 107 | mat[0, x] = 0 108 | return mat 109 | 110 | 111 | def random_invertible_matrix(field, n): 112 | """Hack because GL(GF(2), n).random_element() 113 | uses weird own random generator state 114 | """ 115 | while 1: 116 | m = random_matrix(field, n) 117 | if m.is_invertible(): 118 | return m 119 | 120 | 121 | def random_permutation_matrix(field, n): 122 | rows = list(identity_matrix(n)) 123 | shuffle(rows) 124 | return matrix(field, rows) 125 | -------------------------------------------------------------------------------- /cry/py/tree/node/node.py: -------------------------------------------------------------------------------- 1 | class Node(object): 2 | COUNTER = 0 3 | OP = NotImplemented 4 | 5 | meta = {} 6 | 7 | def __init__(self, op, *args, **kwargs): 8 | assert op in self.OP 9 | self.op = op 10 | self.args = list(args) 11 | self.meta = self.meta.copy() 12 | self.meta.update(kwargs) 13 | 14 | self.id = self.COUNTER 15 | type(self).COUNTER += 1 16 | 17 | @classmethod 18 | def new(cls, *args, **kwargs): 19 | return cls(*args, **kwargs) 20 | 21 | def __iter__(self): 22 | for sub in self.args: 23 | if isinstance(sub, Node): 24 | yield sub 25 | 26 | def __str__(self): 27 | if self.op == self.OP.INPUT: 28 | return str(self.args[0]) 29 | if self.meta.get("fake-input"): 30 | return self.meta.get("fake-input") 31 | sym = str(self.OP.symbol[self.op]) 32 | 33 | if len(self.args) > 1: 34 | return "(" + (" " + sym + " ").join(map(str, self.args)) + ")" 35 | elif len(self.args) == 1: 36 | return sym + str(self.args[0]) 37 | elif len(self.args) == 0: 38 | return sym 39 | 40 | def __repr__(self): 41 | cls = self.__class__.__name__ 42 | 43 | op = self.OP.name[self.op] 44 | 45 | args = [] 46 | for sub in self.args: 47 | if isinstance(sub, Node): 48 | assert sub.__class__ == self.__class__ 49 | if sub.is_input(): 50 | args.append(sub.args[0]) 51 | else: 52 | args.append("#%r" % (sub.id)) 53 | else: 54 | args.append(repr(sub)) 55 | return "<%s#%d = %s(%s)>" % (cls, self.id, op, ",".join(map(str, args))) 56 | 57 | def __hash__(self): 58 | return hash(id(self)) 59 | 60 | def structure_hash(self): 61 | if self.__dict__.get("_sh", None) is None: 62 | self._sh = hash((self.op,) + tuple(hash(v) for v in self.args)) 63 | return self._sh 64 | 65 | def is_input(self): 66 | return self.op == self.OP.INPUT 67 | 68 | @classmethod 69 | def input(cls, name): 70 | return cls(cls.OP.INPUT, name) 71 | 72 | @classmethod 73 | def inputs(cls, name, n, tostr=False): 74 | return tuple( 75 | cls.input(name+str(i) if tostr else (name, i)) 76 | for i in range(n) 77 | ) 78 | 79 | def name(self): 80 | assert self.is_input() 81 | return self.args[0] 82 | 83 | def flatten(self, out=None): 84 | if out is None: 85 | out = set() 86 | if self in out: 87 | return 88 | for sub in self.args: 89 | if isinstance(sub, Node): 90 | sub.flatten(out=out) 91 | out.add(self) 92 | return out 93 | 94 | @staticmethod 95 | def flatten_many(nodes): 96 | out = set() 97 | for node in nodes: 98 | node.flatten(out=out) 99 | return out 100 | 101 | def eval(self, acc): 102 | """ 103 | acc is dict {bit: value} with initial values (typically input bits) 104 | """ 105 | if self not in acc: 106 | acc[self] = self.OP.eval(self.op, [v.eval(acc) for v in self.args]) 107 | return acc[self] 108 | -------------------------------------------------------------------------------- /cry/py/containers/vector.py: -------------------------------------------------------------------------------- 1 | import operator 2 | from functools import reduce 3 | 4 | 5 | class Vector(list): 6 | ZERO = 0 7 | WIDTH = None 8 | 9 | @classmethod 10 | def make(cls, lst): 11 | lst = list(lst) 12 | if cls.WIDTH is not None: 13 | assert len(lst) == cls.WIDTH 14 | return cls(lst) 15 | 16 | def split(self, n=2): 17 | assert len(self) % n == 0 18 | w = len(self) // n 19 | return Vector(self.make(self[i:i+w]) for i in range(0, len(self), w)) 20 | 21 | def concat(self, *lst): 22 | v = list(self) 23 | for t in lst: 24 | v += list(t) 25 | return self.make(v) 26 | 27 | def rol(self, n=1): 28 | n %= len(self) 29 | return self.make(self[n:] + self[:n]) 30 | 31 | def ror(self, n=1): 32 | return self.rol(-n) 33 | 34 | def shl(self, n=1): 35 | assert n >= 0 36 | n = min(n, len(self)) 37 | return self.make(list(self[n:]) + [self._zero() for i in range(n)]) 38 | 39 | def shr(self, n=1): 40 | assert n >= 0 41 | n = min(n, len(self)) 42 | return self.make([self._zero() for i in range(n)] + list(self[:-n])) 43 | 44 | def _zero(self): 45 | """method because sometimes need different instances""" 46 | return self.ZERO 47 | 48 | def __repr__(self): 49 | return "" % (len(self), list(self)) 50 | 51 | def flatten(self): 52 | if isinstance(self[0], Vector): 53 | return self[0].concat(*self[1:]) 54 | return reduce(operator.add, list(self)) 55 | 56 | def permute(self, perm, inverse=False): 57 | """ 58 | Perm contains indexes in original vector 59 | Example: 60 | vec = [0, 1, 2, 3] 61 | perm = [1, 2, 3, 0] 62 | res = [1, 2, 3, 0] 63 | """ 64 | if not inverse: 65 | lst = [self[i] for i in perm] 66 | else: 67 | lst = [None] * len(self) 68 | for i, j in enumerate(perm): 69 | lst[j] = self[i] 70 | return self.make(lst) 71 | 72 | def map(self, f, with_coord=False): 73 | if with_coord: 74 | return self.make(f(i, v) for i, v in enumerate(self)) 75 | else: 76 | return self.make(f(v) for v in self) 77 | 78 | def __xor__(self, other): 79 | assert isinstance(other, Vector) 80 | assert len(self) == len(other) 81 | return self.make(a ^ b for a, b in zip(self, other)) 82 | 83 | def __or__(self, other): 84 | assert isinstance(other, Vector) 85 | assert len(self) == len(other) 86 | return self.make(a | b for a, b in zip(self, other)) 87 | 88 | def __and__(self, other): 89 | assert isinstance(other, Vector) 90 | assert len(self) == len(other) 91 | return self.make(a & b for a, b in zip(self, other)) 92 | 93 | def __neg__(self): 94 | return self.make(-a for a in self) 95 | 96 | def __invert__(self): 97 | return self.make(~a for a in self) 98 | 99 | def set(self, x, val): 100 | return self.make(v if i != x else val for i, v in enumerate(self)) 101 | 102 | # for overriding 103 | def __add__(self, other): 104 | raise NotImplementedError("add vectors?") 105 | __radd__ = __add__ 106 | -------------------------------------------------------------------------------- /cry/py/tree/node/optbitnode.py: -------------------------------------------------------------------------------- 1 | from .op import BitOP 2 | from .bitnode import BitNode 3 | 4 | 5 | class OptBitNode(BitNode): 6 | OP = BitOP() 7 | 8 | CANCEL_DOUBLE_NOT = True 9 | SINGLETON_CONSTANTS = True 10 | PRECOMPUTE_CONSTANTS = True 11 | PRECOMPUTE_ANNIHILATION = True 12 | CANCEL_NEUTRAL = True 13 | XOR1_TO_NEGATION = True 14 | 15 | _ONE = _ZERO = None 16 | 17 | # UNARY 18 | def __invert__(self): 19 | if self.CANCEL_DOUBLE_NOT and self.op == self.OP.NOT: 20 | return self.args[0] 21 | if self.PRECOMPUTE_CONSTANTS and self.is_const(): 22 | return self.const(self.value() ^ 1) 23 | return self.new(self.OP.NOT, self) 24 | Not = __invert__ 25 | 26 | # BINARY 27 | def make_binary(op): 28 | def f(a, b): 29 | if isinstance(b, int): 30 | b = a.const(b) 31 | a0, b0 = a, b 32 | 33 | # ensure b is constant if at least one is constant 34 | if a.is_const(): 35 | a, b = b, a 36 | if not b.is_const(): 37 | return a.new(op, a0, b0) 38 | 39 | if a.PRECOMPUTE_CONSTANTS: 40 | # both consts 41 | if a.is_const() and b.is_const(): 42 | return a.const(a.OP.eval(op, (a.value(), b.value()))) 43 | 44 | if a.PRECOMPUTE_ANNIHILATION: 45 | if b.is_const(): 46 | if op == a.OP.AND and b.value() == 0: 47 | return a.const(0) 48 | if op == a.OP.OR and b.value() == 1: 49 | return a.const(1) 50 | 51 | if a.CANCEL_NEUTRAL: 52 | if b.is_const(): 53 | if op == a.OP.AND and b.value() == 1: 54 | return a 55 | if op == a.OP.OR and b.value() == 0: 56 | return a 57 | if op == a.OP.XOR and b.value() == 0: 58 | return a 59 | 60 | if a.XOR1_TO_NEGATION: 61 | if op == a.OP.XOR and b.value() == 1: 62 | return ~a 63 | 64 | return a.new(op, a0, b0) 65 | return f 66 | 67 | Xor = __xor__ = __rxor__ = make_binary(OP.XOR) 68 | And = __and__ = __rand__ = make_binary(OP.AND) 69 | Or = __or__ = __ror__ = make_binary(OP.OR) 70 | del make_binary 71 | 72 | # const optimizations 73 | @classmethod 74 | def const(cls, v): 75 | if cls.SINGLETON_CONSTANTS: 76 | if cls._ONE is None: 77 | cls._ZERO = cls(cls.OP.ZERO) 78 | cls._ONE = cls(cls.OP.ONE) 79 | return cls._ONE if int(v) else cls._ZERO 80 | else: 81 | return cls(cls.OP.ONE) if int(v) else cls(cls.OP.ZERO) 82 | 83 | 84 | if __name__ == '__main__': 85 | Bit = OptBitNode 86 | x = Bit.inputs("x", 8, tostr=True) 87 | y = ~(x[0] ^ x[1] & x[2]) ^ 1 88 | print("expr", y) 89 | print("flattened:") 90 | for v in y.flatten(): 91 | print(" ", v) 92 | 93 | def updict(d): 94 | # return {("x", k): v for k, v in d.items()} 95 | return {("x%d" % k): v for k, v in d.items()} 96 | 97 | y = ~(x[0] ^ x[1] & x[2]) 98 | print("1 =?", y.eval(updict({0: 1, 1: 1, 2: 1}))) 99 | y = (x[0] ^ x[1] & x[2]) 100 | print("0 =?", y.eval(updict({0: 1, 1: 1, 2: 1}))) 101 | -------------------------------------------------------------------------------- /cry/fields/basis_change.py: -------------------------------------------------------------------------------- 1 | from itertools import product 2 | 3 | from cry.sagestuff import GF 4 | from cry.sbox2 import SBox2 5 | 6 | BASIS_LIST = "polynomial normal".split() 7 | 8 | 9 | class SubfieldDecomposition(object): 10 | ''' 11 | Decomposing @flarge as product of two @fsmall 12 | Using @polynomial(?) basis with irreducible polynomial 13 | y**2 + A*y + B 14 | ''' 15 | def __init__(self, flarge, fsmall): 16 | self.flarge = flarge 17 | self.fsmall = fsmall 18 | 19 | assert flarge.characteristic() == 2 20 | assert fsmall.characteristic() == 2 21 | self.N = self.flarge.degree() 22 | self.D = self.fsmall.degree() 23 | assert self.N == self.D * 2 24 | self.mask = (1 << self.D) - 1 25 | 26 | self.X = self.fsmall.polynomial_ring().gen() 27 | 28 | self.g1 = self.flarge.gen() 29 | self.exp1, self.log1 = self.compute_explog(self.g1, in_field=True) 30 | 31 | def set_poly(self, A, B, basis="polynomial"): 32 | self.intA = A 33 | self.intB = B 34 | self.A = self.fsmall.fetch_int(A) 35 | self.iA = 1/self.A 36 | self.B = self.fsmall.fetch_int(B) 37 | self.basis = basis 38 | assert basis in BASIS_LIST 39 | 40 | X = self.X 41 | p = X**2 + self.A*X + self.B 42 | if not p.is_irreducible(): 43 | return 44 | 45 | def check_gens(self, only_linear=True): 46 | for g2 in xrange(2, 2**self.N): 47 | try: 48 | exp2, log2 = self.compute_explog(g2, in_field=False, mulfunc=self.mul) 49 | except ValueError: 50 | continue 51 | large_to_double = self.make_transition(exp2, log2) 52 | if not only_linear or large_to_double.is_linear(): 53 | yield ((self.basis, self.intA, self.intB, g2), large_to_double) 54 | 55 | def iterate_polys(self, only_linear=True, polynomial=True, normal=False): 56 | ''' 57 | For GF(2**6) -> GF(2**3) decomposition, 58 | polynomial covers all normal basis 59 | ''' 60 | for a, b in product(range(1, 2**self.D), repeat=2): 61 | if polynomial: 62 | self.set_poly(a, b, basis="polynomial") 63 | for res in self.check_gens(only_linear=only_linear): 64 | yield res 65 | if normal: 66 | self.set_poly(a, b, basis="normal") 67 | for res in self.check_gens(only_linear=only_linear): 68 | yield res 69 | 70 | def make_transition(self, exp2, log2): 71 | large_to_double = [0, 1] 72 | for x in xrange(2, 2**self.N): 73 | l = self.log1[x] 74 | y = exp2[l] 75 | large_to_double.append(y) 76 | large_to_double = SBox2(large_to_double) 77 | return large_to_double 78 | 79 | def compute_explog(self, g, in_field, mulfunc=None): 80 | exp = [] 81 | log = [None] * 2**self.N 82 | 83 | cur = self.flarge(1) if in_field else 1 84 | seen = set() 85 | for e in xrange(2**self.N-1): 86 | toput = cur.integer_representation() if in_field else cur 87 | exp.append(toput) 88 | log[toput] = e 89 | if toput in seen: 90 | raise ValueError("Not a generator: %r: powers %r" % (g, exp)) 91 | seen.add(toput) 92 | cur = g * cur if not mulfunc else mulfunc(g, cur) 93 | return exp, log 94 | 95 | def mul(self, x, y): 96 | toF = self.fsmall.fetch_int 97 | a, b = toF(x >> self.D), toF(x & self.mask) 98 | c, d = toF(y >> self.D), toF(y & self.mask) 99 | 100 | if self.basis == "polynomial": 101 | a, b = ( 102 | (a*d + b*c + self.A*a*c).integer_representation(), 103 | (b*d + self.B*a*c).integer_representation() 104 | ) 105 | elif self.basis == "normal": 106 | t = (a + b)*(c + d)*self.iA*self.B 107 | a, b = ( 108 | (a*c*self.A + t).integer_representation(), 109 | (b*d*self.A + t).integer_representation() 110 | ) 111 | else: 112 | assert 0 113 | return (a << self.D) | b 114 | -------------------------------------------------------------------------------- /cry/sbox2/generators/simple.py: -------------------------------------------------------------------------------- 1 | from random import randint, random, shuffle 2 | 3 | from cry.sagestuff import ( 4 | Integer, GF, PolynomialRing 5 | ) 6 | from cry.utils import ranges 7 | from cry.sbox2 import SBox2 8 | 9 | from binteger import Bin 10 | 11 | register = SBox2.new.register 12 | 13 | 14 | @register 15 | def id(n): 16 | return SBox2(range(2**n)) 17 | 18 | 19 | identity = id 20 | 21 | 22 | @register 23 | def const(c, n): 24 | return SBox2([c] * 2**n) 25 | 26 | 27 | @register 28 | def swap(h): 29 | s = [] 30 | for l, r in ranges(2**h, 2**h): 31 | s.append((r << h) | l) 32 | return SBox2(s) 33 | 34 | 35 | @register 36 | def parallel(funcs): 37 | input_sizes = [f.input_size() for f in funcs] 38 | output_sizes = [f.output_size() for f in funcs] 39 | s = [] 40 | for xs in ranges(list=[2**w for w in input_sizes]): 41 | fully = 0 42 | for f, x, w in zip(funcs, xs, output_sizes): 43 | fully = (fully << w) | f(x) 44 | s.append(fully) 45 | return SBox2(s, m=sum(output_sizes)) 46 | 47 | 48 | @register 49 | def concat(funcs): 50 | assert len(set(f.input_size() for f in funcs)) == 1 51 | sizes = tuple(f.output_size() for f in funcs) 52 | return SBox2( 53 | [Bin.concat(*Bin.array(ys, ns=sizes)) for ys in zip(*funcs)], 54 | m=sum(sizes) 55 | ) 56 | 57 | 58 | @register 59 | def random_permutation(n, zero_zero=False): 60 | s = list(range(2**n)) 61 | shuffle(s) 62 | if zero_zero: 63 | i = s.index(0) 64 | s[0], s[i] = s[i], s[0] 65 | return SBox2(s) 66 | 67 | 68 | @register 69 | def random_function(n, m=None, zero_zero=False): 70 | m = m or n 71 | s = [randint(0, 2**m-1) for i in range(2**n)] 72 | if zero_zero: 73 | s[0] = 0 74 | return SBox2(s, m=m) 75 | 76 | 77 | @register 78 | def random_function_of_degree( 79 | n, m, d, zero_zero=False, 80 | force_all_maxterms=False 81 | ): 82 | anf = [0] * m 83 | mindeg = 1 if zero_zero else 0 84 | for deg in range(mindeg, d + 1): 85 | for mask in hamming_masks(m, deg): 86 | for out_bit in range(m): 87 | take = int(randint(0, 1) or (force_all_maxterms and deg == d)) 88 | anf[mask] = (anf[mask] << 1) | take 89 | return SBox2(anf, m=m).mobius() 90 | 91 | 92 | @register 93 | def random_involution(n, allow_fixed_points=True): 94 | s = list(range(2**n)) 95 | if not allow_fixed_points: 96 | pairs = list(range(2**n)) 97 | shuffle(pairs) 98 | else: 99 | """ 100 | Uniform? 101 | n elements 102 | a(k): number of involutions with k transpositions 103 | a(k) / a(k-1) = (n-2*k+2) * (n-2*k+1) / (2*k) 104 | approximate by floats to avoid bigints, should be ok 105 | """ 106 | ak = [1.0] 107 | for k in range(1, 2**(n-1)+1): 108 | ak.append( 109 | ak[-1] * (n-2*k+2) * (n-2*k+1) / (2*k) 110 | ) 111 | r = random() * sum(ak) 112 | acc = 0 113 | for k in range(2**(n-1)+1): 114 | acc += ak[k] 115 | if acc >= r: 116 | break 117 | 118 | pairs = list(range(2**n)) 119 | shuffle(pairs) 120 | pairs = pairs[:2*k] 121 | 122 | for i in range(0, len(pairs), 2): 123 | a, b = s[i:i+2] 124 | s[a] = b 125 | s[b] = a 126 | return SBox2(s) 127 | 128 | 129 | def hamming_masks(n, h): 130 | if h == 0: 131 | yield 0 132 | return 133 | if h == 1: 134 | for e in range(n): 135 | yield 1 << e 136 | return 137 | if h == n: 138 | yield (1 << n) - 1 139 | return 140 | for x in range(2**n): 141 | if Integer(x).popcount() == h: 142 | yield x 143 | 144 | 145 | @register 146 | def random_minicipher(n, kn=None): 147 | s = [] 148 | for k in range(2**kn): 149 | p = list(range(2**n)) 150 | shuffle(p) 151 | s.extend(p) 152 | return SBox2(s) 153 | 154 | 155 | @register 156 | def power(e, n=None, field=None): 157 | assert (n is not None) ^ (field is not None) 158 | field = field or GF(2**n, name='a') 159 | x = PolynomialRing(field, names='x').gen() 160 | return SBox2(x**e) 161 | -------------------------------------------------------------------------------- /tests_sage/test_sbox2.py: -------------------------------------------------------------------------------- 1 | from cry.sbox2 import SBox2 2 | from cry.sagestuff import Integer 3 | 4 | 5 | def test_main(): 6 | from cry.sbox2 import SBox2 7 | s = SBox2([3, 4, 7, 2, 1, 1, 6, 6], m=4) 8 | 9 | assert s.n == s.input_size() == 3 10 | assert s.m == s.output_size() == 4 11 | assert len(s) == 8 12 | 13 | assert list(s.input_range()) == list(range(8)) 14 | assert list(s.output_range()) == list(range(16)) 15 | 16 | assert list(s.graph()) == [ 17 | (0, 3), (1, 4), (2, 7), (3, 2), (4, 1), (5, 1), (6, 6), (7, 6) 18 | ] 19 | 20 | assert s.as_hex(sep=":") == "3:4:7:2:1:1:6:6" 21 | assert s.as_hex(sep="") == "34721166" 22 | 23 | ss = SBox2([3, 4, 7, 2, 1, 1, 6, 6]) 24 | assert s != ss 25 | assert s == ss.resize(4) 26 | 27 | ss = SBox2([3, 4, 7, 2, 1, 1, 6, 6], 4) 28 | assert s == ss 29 | 30 | ss = SBox2([2, 4, 7, 2, 1, 1, 6, 6], 4) 31 | assert s != ss 32 | 33 | assert s == [3, 4, 7, 2, 1, 1, 6, 6] 34 | assert s == (3, 4, 7, 2, 1, 1, 6, 6) 35 | assert s != (2, 4, 7, 2, 1, 1, 6, 6) 36 | 37 | assert hash(s) != 0 38 | 39 | assert s ^ 1 == s ^ 1 == s ^ Integer(1) == s ^ SBox2([1] * 8, m=4) \ 40 | == (2, 5, 6, 3, 0, 0, 7, 7) 41 | assert s ^ s == [0] * 8 42 | 43 | assert s[0] == s(0) == s[0, 0, 0] == 3 44 | assert s[3] == s(3) == s[0, 1, 1] == 2 45 | assert s[7] == s(7) == s[1, 1, 1] == 6 46 | assert tuple(s)[1:3] == (4, 7) 47 | assert tuple(s)[-3:] == (1, 6, 6) 48 | 49 | 50 | def props(s): 51 | fs = [ 52 | s.is_involution, 53 | s.is_permutation, 54 | s.is_zero, 55 | s.is_identity, 56 | s.is_constant, 57 | s.is_linear, 58 | s.is_affine, 59 | s.is_balanced, 60 | ] 61 | return {f for f in fs if f()} 62 | 63 | 64 | def test_properties(): 65 | s = SBox2([5, 6, 3, 2, 1, 7, 0, 4]) 66 | assert props(s) == {s.is_permutation, s.is_balanced} 67 | assert props(s.resize(4)) == set() 68 | 69 | s = SBox2((0, 13, 14, 3, 8, 5, 6, 11, 12, 1, 2, 15, 4, 9, 10, 7)) 70 | assert props(s) == { 71 | s.is_permutation, s.is_balanced, s.is_affine, s.is_linear 72 | } 73 | 74 | s = SBox2((14, 13, 6, 5, 0, 3, 8, 11, 15, 12, 7, 4, 1, 2, 9, 10)) 75 | assert props(s) == {s.is_permutation, s.is_balanced, s.is_affine} 76 | 77 | s = SBox2([1] * 16) 78 | assert props(s) == {s.is_affine, s.is_constant} 79 | 80 | s = SBox2([0] * 16) 81 | assert props(s) == { 82 | s.is_affine, s.is_constant, s.is_linear, s.is_zero, s.is_balanced 83 | } 84 | 85 | s = SBox2([0] * 15 + [1]) 86 | assert props(s) == set() 87 | 88 | s = SBox2(range(16)) 89 | assert props(s) == { 90 | s.is_identity, s.is_permutation, s.is_balanced, 91 | s.is_involution, s.is_affine, s.is_linear, 92 | } 93 | 94 | s = SBox2([0] * 8 + [1] * 8) 95 | assert props(s) == {s.is_balanced, s.is_affine, s.is_linear} 96 | 97 | s = SBox2([1] * 8 + [0] * 8) 98 | assert props(s) == {s.is_balanced, s.is_affine} 99 | 100 | s = SBox2(list(range(8, 16)) + list(range(8))) 101 | assert props(s) == { 102 | s.is_balanced, s.is_permutation, s.is_involution, s.is_affine, 103 | } 104 | 105 | 106 | def test_transform(): 107 | s = SBox2([5, 6, 3, 2, 1, 7, 0, 4]) 108 | assert s.xor(0, 3) == s ^ 3 109 | assert s.mobius().mobius() == s 110 | 111 | 112 | def test_degrees(): 113 | s = SBox2([0] * 15 + [1]) 114 | assert s.degrees() == (4,) 115 | assert str(s.anfs()) == "[x0*x1*x2*x3]" 116 | 117 | s = SBox2([1] * 8 + [0] * 8) 118 | assert s.degrees() == (1,) 119 | assert str(s.anfs()) == "[x0 + 1]" 120 | 121 | s = SBox2([0] * 16) 122 | assert s.degrees() == () 123 | assert str(s.anfs()) == "[]" 124 | 125 | s = SBox2([1] * 16) 126 | assert s.degrees() == (0,) 127 | assert str(s.anfs()) == "[1]" 128 | 129 | 130 | def test_inversion(): 131 | s = SBox2([5, 6, 3, 2, 1, 7, 0, 4]) 132 | 133 | assert ~s == s**(-1) == [6, 4, 3, 2, 7, 0, 1, 5] 134 | 135 | s = SBox2([3, 4, 7, 2, 1, 1, 6, 6], m=4) 136 | assert s.image() == {1, 2, 3, 4, 6, 7} 137 | assert s.preimage(2) == 3 138 | assert s.preimage(1) == 4 139 | assert s.preimage(6) == 6 140 | assert s.preimages(2) == (3,) 141 | assert s.preimages(1) == (4, 5) 142 | assert s.preimages(6) == (6, 7) 143 | 144 | assert sorted(s.preimage_structure().items()) == [(1, 4), (2, 2)] 145 | -------------------------------------------------------------------------------- /cry/eq/lattice.py: -------------------------------------------------------------------------------- 1 | from cry.sagestuff import matrix, ZZ, QQ, RR, copy, lcm, gcd 2 | 3 | INF = float("+inf") 4 | 5 | 6 | class Lattice: 7 | """ 8 | Simple wrapper for LLL/BKZ. 9 | 10 | Main improvement is easy weighting. 11 | :meth:`set_bounds` allows to set approximate bounds for resulting 12 | vector's coordinates. This means that columns must be scaled 13 | inverse-proportional to bounds. LLL can work with QQ, so usually 14 | scaling can be done by multiplying columns by 1/w_i. However, BKZ only 15 | works for ZZ. This can be overcome by pre-multiplying everything by 16 | lcm of the bounds and then multiplying by 1/w_i keeps us in ZZ. 17 | Since bounds do not have to be precise, we can keep only few MSBs, 18 | to make lcm smaller. This is an optional argument for :meth:`set_bounds`, 19 | which by default is 8. On practice, 1 bit of precision is sufficient 20 | for most cases. 21 | 22 | In addition, after setting bounds, this wrapper allows to compute the bound 23 | given by LLL (function of the determinant) and/or the approximation factor. 24 | """ 25 | def __init__(self, *args): 26 | self.matrix = matrix(*args) 27 | assert self.matrix.base_ring() in (ZZ, QQ) 28 | self.n = self.matrix.nrows() 29 | self.m = self.matrix.ncols() 30 | 31 | # bounds are relative 32 | self.set_bounds((1,) * self.matrix.ncols()) 33 | 34 | def set_bounds(self, bounds, precision=8): 35 | assert precision >= 1, "less than 1 bit of precision?" 36 | assert len(bounds) == self.matrix.ncols() 37 | self.bounds = tuple(int(b) for b in bounds) 38 | self.precision = precision 39 | 40 | lst = [] 41 | for b in self.bounds: 42 | b = int(b) 43 | s = max(0, int(b).bit_length() - precision) 44 | b = (b >> s) << s 45 | lst.append(b) 46 | g = gcd(lst) 47 | lst = [b // g for b in lst] 48 | l = lcm(lst) 49 | self.weights = tuple(l // b for b in lst) 50 | 51 | self._m_scaled = None 52 | self._m_scaled_reduced = None 53 | self._m_result = None 54 | 55 | self._det = None 56 | 57 | @property 58 | def matrix_scaled(self): 59 | assert self.matrix.ncols() == len(self.bounds) == len(self.weights) 60 | if self._m_scaled is None: 61 | self._m_scaled = copy(self.matrix) 62 | 63 | for x, w in enumerate(self.weights): 64 | col = w * self._m_scaled.column(x) 65 | self._m_scaled.set_column(x, col) 66 | return self._m_scaled 67 | 68 | @property 69 | def det(self): 70 | if self._det is None: 71 | self._det = self.matrix_scaled.determinant() 72 | return self._det 73 | 74 | def get_bound_LLL(self, delta=0.75): 75 | """ 76 | Returns the bound on the first output vector (as a function of the 77 | determinant). 78 | https://en.wikipedia.org/wiki/Lenstra%E2%80%93Lenstra%E2%80%93Lov%C3%A1sz_lattice_basis_reduction_algorithm#Properties_of_LLL-reduced_basis 79 | """ 80 | coef = self.get_apf_LLL(delta=delta) 81 | return int(coef.sqrt() * abs(self.det)**(1/RR(self.n))) 82 | 83 | def get_apf_LLL(self, delta=0.75): 84 | """ 85 | Approximation factor of lattice is the bound on how much longer 86 | can be the first output vector than the real shortest vector. 87 | https://en.wikipedia.org/wiki/Lenstra%E2%80%93Lenstra%E2%80%93Lov%C3%A1sz_lattice_basis_reduction_algorithm#Properties_of_LLL-reduced_basis 88 | """ 89 | return (QQ(4)/(4 * delta - 1))**(RR(self.n-1)/2.0) 90 | 91 | @property 92 | def matrix_result(self): 93 | assert self.matrix.ncols() == len(self.bounds) == len(self.weights) 94 | assert self._m_scaled_reduced is not None, "no result yet" 95 | if self._m_result is None: 96 | self._m_result = copy(self._m_scaled_reduced) 97 | for x, w in enumerate(self.weights): 98 | col = [v // w for v in self._m_result.column(x)] 99 | self._m_result.set_column(x, col) 100 | return self._m_result 101 | 102 | def LLL(self, *args): 103 | self._m_scaled_reduced = self.matrix_scaled.LLL(*args) 104 | return self.matrix_result 105 | 106 | def BKZ(self, *args): 107 | self._m_scaled_reduced = self.matrix_scaled.BKZ(*args) 108 | return self.matrix_result 109 | -------------------------------------------------------------------------------- /cry/py/utils/cache.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import sys 4 | import ast, pickle, marshal 5 | import random 6 | from functools import wraps 7 | import hashlib 8 | H = lambda s: hashlib.sha256(s).hexdigest() 9 | 10 | def truncrepr(a, limit=128): 11 | s = repr(a) 12 | if len(s) > limit: 13 | s = s[:limit] + "..." 14 | return s 15 | 16 | def ast_cache(filename_prefix): 17 | def deco(func): 18 | @wraps(func) 19 | def wrapper(*a, **k): 20 | cache_key = tuple(a) + tuple(sorted(k.items())) 21 | cache_key = H(str(cache_key)) 22 | 23 | filename = filename_prefix + "." + cache_key 24 | try: 25 | lines = list(open(filename)) 26 | assert lines[-1] == "EndCache" 27 | cache_exists = True 28 | except: 29 | cache_exists = False 30 | 31 | if cache_exists: 32 | res = ast.literal_eval(lines[1]) 33 | # print "loaded cache" 34 | return res 35 | # print "computing & writing cache.." 36 | with open(filename, "w") as f: 37 | res = func(*a, **k) 38 | f.write("Cache: %s(%s; %s)\n" % ( 39 | func.__name__, 40 | ", ".join(map(truncrepr, a)), 41 | ", ".join("%s=%s" % (kk, truncrepr(vv)) for kk, vv in sorted(k.items())) 42 | )) 43 | assert ast.literal_eval(repr(res)) == res, "non-serializable res" 44 | f.write(repr(res) + "\n") 45 | f.write("EndCache") 46 | return res 47 | return wrapper 48 | return deco 49 | 50 | def pypickle_cache(filename_prefix): 51 | def deco(func): 52 | @wraps(func) 53 | def wrapper(*a, **k): 54 | cache_key = tuple(a) + tuple(sorted(k.items())) 55 | cache_key = H(str(cache_key)) 56 | 57 | filename = filename_prefix + "." + cache_key 58 | try: 59 | f = open(filename) 60 | f.readline() 61 | return pickle.load(f) 62 | except: 63 | pass 64 | 65 | with open(filename, "w") as f: 66 | res = func(*a, **k) 67 | f.write("Cache: %s(%s; %s)\n" % ( 68 | func.__name__, 69 | ", ".join(map(truncrepr, a)), 70 | ", ".join("%s=%s" % (kk, truncrepr(vv)) for kk, vv in sorted(k.items())) 71 | )) 72 | pickle.dump(res, f) 73 | return res 74 | return wrapper 75 | return deco 76 | 77 | 78 | 79 | def marshal_cache_iter(filename_prefix_or_func): 80 | if isinstance(filename_prefix_or_func, str): 81 | filename_prefix = filename_prefix_or_func 82 | else: 83 | filename_prefix = filename_prefix_or_func.__name__ 84 | 85 | def deco(func): 86 | @wraps(func) 87 | def wrapper(*a, **k): 88 | cache_key = tuple(a) + tuple(sorted(k.items())) 89 | cache_key = H(str(cache_key)) 90 | 91 | filename = filename_prefix + "." + cache_key 92 | try: 93 | f = open(filename) 94 | info = f.readline() 95 | assert info.startswith("Cache: ") 96 | token = marshal.load(f) 97 | while True: 98 | obj = marshal.load(f) 99 | if obj == token: 100 | print >>sys.stderr, "Success loading cache:", func.__name__, filename 101 | return 102 | yield obj 103 | except KeyboardInterrupt: 104 | print >>sys.stderr, "KB received, raising..." 105 | raise 106 | except Exception as err: 107 | print >>sys.stderr, "Error loading cache:", func.__name__, filename, ":", err 108 | 109 | token = "%064x" % random.randrange(2**256) 110 | with open(filename, "w") as f: 111 | f.write("Cache: %s(%s; %s)\n" % ( 112 | func.__name__, 113 | ", ".join(map(truncrepr, a)), 114 | ", ".join("%s=%s" % (kk, truncrepr(vv)) for kk, vv in sorted(k.items())) 115 | )) 116 | f.flush() 117 | marshal.dump(token, f) 118 | for res in func(*a, **k): 119 | marshal.dump(res, f) 120 | yield res 121 | marshal.dump(token, f) 122 | return 123 | return wrapper 124 | 125 | if isinstance(filename_prefix_or_func, str): 126 | return deco 127 | else: 128 | return deco(filename_prefix_or_func) 129 | -------------------------------------------------------------------------------- /cry/utils/cache.py: -------------------------------------------------------------------------------- 1 | import time, os, logging 2 | import ast 3 | from functools import wraps 4 | 5 | import hashlib 6 | H = lambda s: hashlib.sha256(s).hexdigest() 7 | 8 | from cry.sagestuff import loads, dumps 9 | 10 | log = logging.getLogger("cry.cache") 11 | 12 | # old 13 | # def pickle_cache(fn): 14 | # fname = ".cache:%s:%s" % (fn.func_name, permanent_func_hash(fn)[:16]) 15 | # try: 16 | # res = loads(open(fname).read()) 17 | # msg("[i] reusing %s" % fname) 18 | # return res 19 | # except (EOFError, IOError): 20 | # msg("[i] calculating %s" % fname) 21 | # result = fn() 22 | # open(fname, "wb").write(dumps(result)) 23 | # return result 24 | 25 | 26 | def msg(*args): 27 | log.debug(" ".join(map(str, args))) 28 | 29 | 30 | def sage_cache(path): 31 | if not os.path.exists(path): 32 | os.makedirs(path) 33 | 34 | def deco(func): 35 | @wraps(func) 36 | def wrapper(*a, **k): 37 | cache_key = tuple(a) + tuple(sorted(k.items())) 38 | cache_key = H(str(cache_key)) 39 | 40 | filename = os.path.join(path, cache_key) 41 | try: 42 | f = open(filename) 43 | while True: 44 | line = f.readline() 45 | if line.strip() == "===": 46 | break 47 | if not line.strip(): 48 | raise EOFError() 49 | return loads(f.read()) 50 | except: 51 | pass 52 | 53 | with open(filename, "w") as f: 54 | t0 = time.time() 55 | res = func(*a, **k) 56 | t1 = time.time() 57 | # some metadata 58 | f.write("cache: %s(%s; %s)\n" % ( 59 | func.__name__, 60 | ", ".join(map(repr, a)), 61 | ", ".join("%s=%r" % item for item in sorted(k.items())), 62 | )) 63 | f.write("key: %s\n" % cache_key) 64 | f.write("time: %r\n" % (t1 - t0)) 65 | f.write("===\n") 66 | f.write(dumps(res)) 67 | return res 68 | return wrapper 69 | return deco 70 | 71 | 72 | def single_cache(filename=None): 73 | def cacher(fn): 74 | fname = filename or ".cache:%s" % fn.func_name 75 | 76 | hash = None 77 | try: 78 | hash, result = loads(open(fname).read()) 79 | except (EOFError, IOError): 80 | pass 81 | 82 | new_hash = permanent_func_hash(fn) 83 | if hash == new_hash: 84 | msg("[i] reusing %s (%s)" % (fname, new_hash)) 85 | else: 86 | msg("[i] calculating %s (%s)" % (fname, new_hash)) 87 | result = fn() 88 | msg("[i] saving %s (%s)" % (fname, new_hash)) 89 | open(fname, "wb").write(dumps((new_hash, result))) 90 | return result 91 | return cacher 92 | 93 | 94 | def single_load(filename): 95 | return loads(open(filename).read())[1] 96 | 97 | 98 | def line_cache(fn): 99 | fname = ".cache:%s:%s" % (fn.func_name, permanent_func_hash(fn)[:16]) 100 | try: 101 | res = [] 102 | for line in open(fname): 103 | line = line.strip() 104 | if line == ":cache:finished:": 105 | break 106 | if line: 107 | res.append(ast.literal_eval(line)) 108 | else: 109 | msg("[i] cache incomplete %s: %s items" % (fname, len(res))) 110 | raise EOFError("Cache incomplete") 111 | msg("[i] reusing %s: %s items" % (fname, len(res))) 112 | return res 113 | except (EOFError, IOError): 114 | msg("[i] calculating %s" % fname) 115 | fo = open(fname, "wb") 116 | res = [] 117 | for obj in fn(): 118 | fo.write(repr(obj) + "\n") 119 | res.append(obj) 120 | fo.write(":cache:finished:") 121 | fo.close() 122 | msg("[i] calculated %s: %s items" % (fname, len(res))) 123 | return res 124 | 125 | 126 | def permanent_func_hash(fn): 127 | res = [] 128 | code = fn.func_code 129 | for attr in dir(code): 130 | if attr == "co_consts": 131 | for c in getattr(code, attr): 132 | if "at 0x" in str(c): 133 | continue 134 | res.append(c) 135 | continue 136 | if attr.startswith("co_") and attr != "co_firstlineno": 137 | res.append(getattr(code, attr)) 138 | for g in code.co_names: 139 | v = fn.func_globals.get(g, None) 140 | if not callable(v): 141 | res.append(str(fn.func_globals.get(g, None))) 142 | return H(str(res)) 143 | -------------------------------------------------------------------------------- /cry/eq/interpolator.py: -------------------------------------------------------------------------------- 1 | from sage.all import * 2 | 3 | from itertools import product 4 | 5 | 6 | # class Interpolator: 7 | # """ 8 | # Interpolator(3) 9 | # Interpolator(("x1", "x2")) 10 | # """ 11 | # def __init__(self, spec): 12 | # if isinstance(spec, int): 13 | # self.n = int(spec) 14 | # self.names = tuple("x%d" % i for i in range(self.n)) 15 | # elif isinstance(spec, (tuple, list)): 16 | # self.n = len(spec) 17 | # self.names = tuple(spec) 18 | # self.monos = set() 19 | # self.data = [] 20 | 21 | # def add_data(self, xs): 22 | # assert len(xs) == self.n 23 | # row = [] 24 | # for es in self.monos: 25 | # row.append(prod(x**e for x, e in zip(xs, es))) 26 | # self.data.append(row) 27 | 28 | # def sort(self, key=sum, reverse=True): 29 | # assert not self.data 30 | # self.monos = sorted(self.monos, key=key, reverse=reverse) 31 | 32 | # def solve(self, order="deglex", groebner=False, debug=False): 33 | # assert isinstance(self.monos, list), "not sorted" 34 | # mat = self.matrix = matrix(self.data) 35 | # if debug: 36 | # print(f"matrix: {mat.nrows()} x {mat.ncols()} rank {mat.rank()}") 37 | 38 | # self.R = PolynomialRing(mat.base_ring(), names=self.names, order=order) 39 | # xs = self.R.gens() 40 | # res = [] 41 | # rk = mat.right_kernel() 42 | # if debug: 43 | # print("kernel:", rk) 44 | # for sol in rk.matrix(): 45 | # assert len(sol) == len(self.monos) 46 | # poly = 0 47 | # for c, es in zip(sol, self.monos): 48 | # mono = prod(x**e for x, e in zip(xs, es)) 49 | # poly += c * mono 50 | # res.append(poly) 51 | # return res 52 | 53 | 54 | class Interpolator: 55 | def __init__(self, n=None, field=None, names=None): 56 | self.n = n 57 | self.data = [] 58 | self.monos = set() 59 | self.field = field 60 | 61 | if isinstance(names, str): 62 | names = names.split(",") 63 | self.names = names 64 | if names and self.n is None: 65 | self.n = len(names) 66 | 67 | def add_data(self, row): 68 | if self.n is None: 69 | self.n = len(row) 70 | 71 | if self.field is None: 72 | self.field = row[0].parent() 73 | assert self.field.is_field(), "only fields supported" 74 | 75 | assert len(row) == self.n 76 | self.data.append(tuple(row)) 77 | 78 | def add_mono(self, es): 79 | self.monos.add(tuple(es)) 80 | 81 | def add_mono_upto(self, maxes): 82 | for es in product(*[range(mx+1) for mx in maxes]): 83 | self.monos.add(es) 84 | 85 | def add_monos_degsum(self, degsum): 86 | for es in self.partition(degsum): 87 | self.add_mono(es) 88 | 89 | def add_monos_degsep(self, deg): 90 | for es in product(range(deg+1), repeat=self.n): 91 | self.add_mono(es) 92 | 93 | def partition(self, degsum, pos=0): 94 | for i in range(degsum + 1): 95 | if pos < self.n - 1: 96 | for sub in self.partition(degsum - i, pos + 1): 97 | yield (i,) + sub 98 | else: 99 | yield (i,) 100 | 101 | def solve(self, order="deglex", groebner=False, debug=False): 102 | assert self.monos, "monos not added" 103 | #monos = list(self.monos) 104 | 105 | data = [] 106 | for xs in self.data: 107 | row = [] 108 | for es in self.monos: 109 | row.append(prod(x**e for x, e in zip(xs, es))) 110 | data.append(row) 111 | 112 | mat = self.matrix = matrix(data) 113 | 114 | if debug: 115 | print(f"matrix: {mat.nrows()} x {mat.ncols()} rank {mat.rank()}") 116 | 117 | if not self.names: 118 | names = ["x%d" % i for i in range(self.n)] 119 | 120 | self.R = PolynomialRing( 121 | mat.base_ring(), names=self.names, order=order 122 | ) 123 | xs = self.R.gens() 124 | res = [] 125 | rk = mat.right_kernel() 126 | if debug: 127 | print("kernel:", rk) 128 | for sol in rk.matrix(): 129 | assert len(sol) == len(self.monos) 130 | poly = [] 131 | for c, es in zip(sol, self.monos): 132 | mono = prod(x**e for x, e in zip(xs, es)) 133 | poly.append(c * mono) 134 | poly = sum(poly) 135 | res.append(poly) 136 | 137 | if groebner and res: 138 | return Ideal(res).groebner_basis() 139 | return res 140 | -------------------------------------------------------------------------------- /cry/rsa/rsa.py: -------------------------------------------------------------------------------- 1 | from cry.sagestuff import ( 2 | inverse_mod, floor, power_mod, ceil, 3 | Zmod, PolynomialRing, RR, log, gcd, lcm, ZZ 4 | ) 5 | import warnings 6 | 7 | from binteger import Bin 8 | 9 | 10 | class RSA: 11 | def __init__(self, n=None, *, p=None, q=None, e=0x10001, d=None, phi=None): 12 | self.n = self.p = self.q = self.e = self.d = self.phi = self.carm = None 13 | self.update(n=n, p=p, q=q, e=e, d=d, phi=phi) 14 | 15 | self._n_sqrt = None 16 | 17 | def update(self, n=None, p=None, q=None, e=None, d=None, phi=None): 18 | if n is not None: 19 | self.n = int(n) 20 | if p is not None: 21 | self.p = int(p) 22 | if q is not None: 23 | self.q = int(q) 24 | if e is not None: 25 | self.e = int(e) 26 | if d is not None: 27 | self.d = int(d) 28 | if phi is not None: 29 | self.phi = phi 30 | 31 | if self.p and self.q: 32 | self.phi = self.n - self.p - self.q + 1 33 | self.carm = lcm(self.p-1, self.q-1) 34 | self.d = inverse_mod(self.e, self.phi) 35 | 36 | def decrypt(self, msg): 37 | msg = Bin(msg).int 38 | msg = power_mod(msg, self.d, self.n) 39 | return Bin(msg).bytes 40 | 41 | def n_sqrt(self): 42 | if self._n_sqrt is None: 43 | self._n_sqrt = floor(ZZ(self.n).sqrt()) 44 | return self._n_sqrt 45 | 46 | def polyring(self, n=None, names=None): 47 | if isinstance(names, str): 48 | assert n is None 49 | names = names.replace(",", " ").split() 50 | elif isinstance(names, (tuple, list)): 51 | assert n is None 52 | else: 53 | if n is None: 54 | n = 1 55 | assert names is None 56 | names = list("xyzwuv")[:n] 57 | PR = PolynomialRing(Zmod(self.n), names=",".join(names)) 58 | return (PR,) + tuple(PR.gens()) 59 | 60 | def bound_primes_from_approximate_sum(self, sumpq, eps): 61 | """ 62 | p + q = s + e, 0 <= e < eps 63 | p + n/p = s+e 64 | p**2 - (s+e)*p + n = 0 65 | 66 | p = (s + e - sqrt((s + e)**2 - 4*n)) / 2 67 | q = (s + e + sqrt((s + e)**2 - 4*n)) / 2 68 | """ 69 | n = self.n 70 | if sumpq**2 <= 4*n: 71 | mx = sumpq + eps 72 | sumpq = ceil(ZZ(4*n).sqrt()) 73 | eps = mx - sumpq 74 | assert (sumpq + eps)**2 > 4*n 75 | assert eps >= 0 76 | 77 | Dmin = floor(ZZ(sumpq**2 - 4 * n).sqrt()) 78 | Dmax = ceil(ZZ((sumpq + eps)**2 - 4 * n).sqrt()) 79 | 80 | p_max = (sumpq - Dmin) // 2 81 | p_min = (sumpq + eps - Dmax) // 2 82 | assert p_min <= p_max 83 | 84 | q_min = (sumpq + Dmin) // 2 85 | q_max = (sumpq + eps + Dmax) // 2 86 | assert q_min <= q_max 87 | 88 | # do not have to hold 89 | if not (p_max * q_min <= n) or not(p_min * q_max >= n): 90 | warnings.warn("strangely, bounds can be improved") 91 | q_min = max(q_min, n // p_max + 1) 92 | q_max = min(q_max, n // p_min) 93 | return (p_min, p_max), (q_min, q_max) 94 | 95 | def factor_with_prime_hint(self, low_mod=None, bounds=None, beta=0.49): 96 | """ 97 | """ 98 | if low_mod is None: 99 | low_mod = 0, 1 100 | low, mod = low_mod 101 | assert 0 <= low < mod 102 | 103 | if bounds is None: 104 | bounds = 0, self.n_sqrt() 105 | pmin, pmax = bounds 106 | 107 | if beta < 0.5: 108 | assert pmax <= self.n_sqrt(), \ 109 | "upper bound must be <= sqrt(n) when beta < 0.5" 110 | else: 111 | assert pmin >= self.n_sqrt(), \ 112 | "lower bound must be >= sqrt(n) when beta >= 0.5" 113 | assert 1 <= pmin <= pmax 114 | 115 | # next value hitting low % mod 116 | if pmin % mod <= low: 117 | pmin = pmin - pmin % mod + low 118 | else: 119 | pmin = pmin - pmin % mod + mod + low 120 | 121 | xmax = ZZ(pmax - pmin) // mod 122 | 123 | PR, x = self.polyring() 124 | poly = (mod * x + pmin).monic() 125 | 126 | # for debugging 127 | # global p 128 | # assert p % mod == low 129 | # sol = (p-pmin)//mod 130 | # print("sol size", ZZ(sol).nbits(), " 0, "impossible attack" 139 | 140 | roots = poly.small_roots(X=xmax, beta=beta, epsilon=epsilon) 141 | for sol in roots: 142 | p = int(poly.subs(x=sol)) 143 | p = abs(gcd(p, self.n)) 144 | if 1 < p < self.n: 145 | q = self.n // p 146 | assert self.n == p * q, "multi-prime? ouch" 147 | self.update(p=p, q=q) 148 | break 149 | else: 150 | raise RuntimeError("attack failed") 151 | return p, q 152 | -------------------------------------------------------------------------------- /cry/sbox2/equiv/linear.py: -------------------------------------------------------------------------------- 1 | try: 2 | from Queue import Queue 3 | except: 4 | from queue import Queue 5 | 6 | 7 | class LEContext(object): 8 | def __init__(self, s1, s2, findall=False): 9 | self.s1 = tuple(s1) 10 | self.s2 = tuple(s2) 11 | self.s1inv = tuple(s1.inverse()) 12 | self.s2inv = tuple(s2.inverse()) 13 | 14 | self.ret = [] 15 | self.findall = findall 16 | 17 | def run(self): 18 | if self.s1[0] == 0 and self.s2[0] != 0: 19 | return False 20 | if self.s1[0] != 0 and self.s2[0] == 0: 21 | return False 22 | 23 | s = LEState(self) 24 | x, y = s.dual(0, 0, 0) 25 | assert s.learn(1, x, y) 26 | x, y = s.dual(1, 0, 0) 27 | assert s.learn(0, x, y) 28 | s.check() 29 | 30 | if self.findall: 31 | return self.ret 32 | assert len(self.ret) <= 1 33 | return self.ret[0] if self.ret else False 34 | 35 | def input_range(self, no_zero=False): 36 | if no_zero: 37 | return range(1, len(self.s1)) 38 | else: 39 | return range(len(self.s1)) 40 | 41 | 42 | class LEState(object): 43 | SIDE_A = 0 44 | SIDE_B = 1 45 | 46 | def __init__(self, ctx, linear_maps=None, unknown_inputs=None, unknown_outputs=None): 47 | self.ctx = ctx 48 | 49 | self.linear_maps = linear_maps or [{0: 0}, {0: 0}] 50 | self.unknown_inputs = unknown_inputs or [set(self.ctx.input_range(no_zero=True)) for i in range(2)] 51 | self.unknown_outputs = unknown_outputs or [set(self.ctx.input_range(no_zero=True)) for i in range(2)] 52 | 53 | self.queue = Queue() 54 | self.depth = 0 55 | 56 | def copy(self): 57 | lm2 = [self.linear_maps[i].copy() for i in range(2)] 58 | ui2 = [self.unknown_inputs[i].copy() for i in range(2)] 59 | uo2 = [self.unknown_outputs[i].copy() for i in range(2)] 60 | st = LEState(self.ctx, lm2, ui2, uo2) 61 | st.depth = self.depth + 1 62 | return st 63 | 64 | def learn(self, side, inp, out, queue=True): 65 | if inp in self.linear_maps[side]: 66 | return self.linear_maps[side][inp] == out 67 | elif out not in self.unknown_outputs[side]: 68 | return False 69 | self.linear_maps[side][inp] = out 70 | self.unknown_inputs[side].remove(inp) 71 | self.unknown_outputs[side].remove(out) 72 | if queue: 73 | self.queue.put((side, inp)) 74 | return True 75 | 76 | def dual(self, side, inp, out): 77 | if side == 0: 78 | dual_inp = self.ctx.s1[out] 79 | dual_out = self.ctx.s2[inp] 80 | else: 81 | dual_out = self.ctx.s1inv[inp] 82 | dual_inp = self.ctx.s2inv[out] 83 | return dual_inp, dual_out 84 | 85 | def expand_horizontal(self, side, inp, out): 86 | if len(self.linear_maps[side]) < len(self.ctx.s1) / 2: 87 | for old_inp, old_out in list(self.linear_maps[side].items()): 88 | new_inp = inp ^ old_inp 89 | new_out = out ^ old_out 90 | yield new_inp, new_out 91 | else: 92 | for unk_inp in self.unknown_inputs[side].copy(): 93 | old_inp = inp ^ unk_inp 94 | if old_inp in self.linear_maps[side]: 95 | old_out = self.linear_maps[side][old_inp] 96 | new_inp = inp ^ old_inp # unk_inp 97 | new_out = out ^ old_out 98 | yield new_inp, new_out 99 | 100 | def check(self): 101 | while self.queue.qsize() > 0: 102 | side, inp = self.queue.get() 103 | 104 | learn_inp = inp 105 | learn_out = self.linear_maps[side][inp] 106 | 107 | # learn horizontally (same side) 108 | for new_inp, new_out in self.expand_horizontal(side, learn_inp, learn_out): 109 | if not self.learn(side, new_inp, new_out, queue=False): 110 | return False 111 | 112 | # learn vertically (other side) 113 | new_inp2, new_out2 = self.dual(side, new_inp, new_out) 114 | if not self.learn(side ^ 1, new_inp2, new_out2, queue=True): 115 | return False 116 | 117 | if len(self.linear_maps[0]) == len(self.linear_maps[1]) == len(self.ctx.s1): 118 | return self.result_ready() 119 | 120 | # queues are empty 121 | # need to guess 122 | inp = min(self.unknown_inputs[0]) 123 | for out in self.unknown_outputs[0]: 124 | sub = self.copy() 125 | sub.learn(0, inp, out) 126 | res = sub.check() 127 | if res and not self.ctx.findall: 128 | return res 129 | return False 130 | 131 | def result_ready(self): 132 | elems = self.ctx.input_range() 133 | A = self.ctx.sbox(self.linear_maps[0][x] for x in elems) 134 | if not A.is_linear(): 135 | return False 136 | B = self.ctx.sbox(self.linear_maps[1][x] for x in elems) 137 | if not B.is_linear(): 138 | return False 139 | 140 | # result is fine, now yield or continue? 141 | self.ctx.ret.append((A, B)) 142 | return not self.ctx.findall 143 | -------------------------------------------------------------------------------- /cry/py/anf/symbolic.py: -------------------------------------------------------------------------------- 1 | class Bit(object): 2 | def __init__(self, v): 3 | if isinstance(v, set): 4 | self.anf = v 5 | # TODO: checks on the anf structure? 6 | elif isinstance(v, int): 7 | v = int(v) 8 | self.anf = {()} if v else set() 9 | elif isinstance(v, str): 10 | # may be expression str? 11 | self.anf = {(v,)} 12 | else: 13 | assert 0, "Bit(%s) = ?" % type(v) 14 | 15 | def __eq__(self, other): 16 | if isinstance(other, int) or isinstance(other, str): 17 | return self == Bit(other) 18 | assert isinstance(other, Bit) 19 | return self.anf == other.anf 20 | 21 | def __hash__(self): 22 | # to make {Bit(0): 3}[0] return 3 23 | if self.is_const(): 24 | return hash(self.const()) 25 | if self.is_var(): 26 | return hash(self.var()) 27 | return hash(tuple(sorted(self.anf))) 28 | 29 | def __xor__(self, other): 30 | if not isinstance(other, Bit): 31 | other = Bit(other) 32 | return Bit(self.anf ^ other.anf) 33 | __add__ = __xor__ 34 | __rxor__ = __xor__ 35 | __radd__ = __add__ 36 | 37 | def __and__(self, other): 38 | # heavy! 39 | res = set() 40 | for t1 in self.anf: 41 | for t2 in other.anf: 42 | res ^= {self._merge_products(t1, t2)} 43 | return Bit(res) 44 | __mul__ = __and__ 45 | __rand__ = __and__ 46 | __rmul__ = __mul__ 47 | 48 | def __invert__(self): 49 | return self ^ Bit.ONE 50 | 51 | def __or__(self, other): 52 | # ok? 53 | return ~((~self) & (~other)) 54 | 55 | def _merge_products(self, t1, t2): 56 | assert isinstance(t1, tuple) 57 | assert isinstance(t2, tuple) 58 | res = set(t1) | set(t2) 59 | return tuple(sorted(res)) 60 | 61 | def __str__(self): 62 | res = [] 63 | for t in sorted(self.anf): 64 | if t == (): 65 | res.append("1") 66 | else: 67 | res.append("*".join(map(str, sorted(t)))) 68 | return " ^ ".join(sorted(res)) or "0" 69 | 70 | def __repr__(self): 71 | return "Bit(%r)" % self.anf 72 | 73 | def degree(self, filter_func=None): 74 | if filter_func is None: 75 | return max(len(t) for t in self.anf) if self.anf else -1 # -inf? 76 | else: 77 | res = 0 78 | for t in self.anf: 79 | res = max(res, sum(1 for v in t if filter_func(v))) 80 | return res 81 | 82 | def variables(self, filter_func=None): 83 | vs = set() 84 | for t in self.anf: 85 | for v in t: 86 | if not filter_func or filter_func(v): 87 | vs.add(v) 88 | return vs 89 | 90 | def varcount(self, filter_func=None): 91 | return len(self.variables(filter_func=filter_func)) 92 | 93 | def subs(self, lst): 94 | if len(lst) == 0: 95 | return self 96 | if len(lst) > 1: 97 | res = self.subs([lst[0]]) 98 | return res.subs(lst[1:]) 99 | 100 | var, bit = lst[0] 101 | # print("subs", var, bit) 102 | if isinstance(var, Bit): 103 | var = var.var() 104 | if isinstance(bit, int): 105 | bit = Bit(bit) 106 | assert isinstance(var, str) 107 | assert isinstance(bit, Bit) 108 | res = self.anf.copy() 109 | for t1 in self.anf: 110 | if var in t1: 111 | res ^= {t1} 112 | i = t1.index(var) 113 | t1 = t1[:i] + t1[i+1:] 114 | for t2 in bit.anf: 115 | res ^= {self._merge_products(t1, t2)} 116 | return Bit(res) 117 | 118 | def is_const(self): 119 | if self.anf == {()}: 120 | return True 121 | if self.anf == set(): 122 | return True 123 | return False 124 | 125 | def const(self): 126 | return int(() in self.anf) 127 | 128 | def __nonzero__(self): 129 | return self.anf != {} 130 | 131 | def __int__(self): 132 | if self.anf == {1}: 133 | return 1 134 | if self.anf == set(): 135 | return 0 136 | raise ValueError("Not constant expression!") 137 | 138 | def is_var(self): 139 | if len(self.anf) == 1: 140 | for t in self.anf: 141 | if len(t) == 1: 142 | return True 143 | return False 144 | 145 | def var(self): 146 | if len(self.anf) == 1: 147 | for t in self.anf: 148 | if len(t) == 1: 149 | return t[0] 150 | raise ValueError("Not a single variable!") 151 | 152 | 153 | Bit.ZERO = Bit(0) 154 | Bit.ONE = Bit(1) 155 | 156 | 157 | def test_bit(): 158 | from cry.py.anf.symbolic import Bit 159 | assert str(Bit(0)) == "0" 160 | assert str(Bit(1)) == "1" 161 | assert str(Bit("x1")) == "x1" 162 | assert str(Bit("x2")) == "x2" 163 | assert str(Bit("x1") & Bit("x2")) == "x1*x2" 164 | assert str(Bit("x1") ^ Bit("x2")) == "x1 ^ x2" 165 | assert str( 166 | Bit(1) * Bit("x1") * Bit("x2") * Bit(1) + 167 | Bit(0) * Bit("x3") + Bit(1) 168 | ) == "1 ^ x1*x2" 169 | 170 | ex = Bit("a") * Bit("b") + Bit("R") * Bit("b") 171 | bb = Bit(1) + Bit("Q") + Bit("c") * Bit("b") 172 | assert str(ex.subs([("b", bb)]) + 1) \ 173 | == "1 ^ Q*R ^ Q*a ^ R ^ R*b*c ^ a ^ a*b*c" 174 | assert {Bit(0): 123}[0] == 123 175 | -------------------------------------------------------------------------------- /tests_sage/test_rsa.py: -------------------------------------------------------------------------------- 1 | from sage.all import ( 2 | EllipticCurve, Zmod, QQ, 3 | is_prime, inverse_mod, crt 4 | ) 5 | 6 | from bint import Bin 7 | from cry.env import RSA 8 | 9 | 10 | def test_SECCON_2020_crypto01(): 11 | masked_flag = 8077275413285507538357977814283814136815067070436948406142717872478737670143034266458000767181162602217137657160614964831459653429727469965712631294123225 12 | ciphertexts = [ 13 | ( 14 | 886775008733978973740257525650074677865489026053222554158847150065839924739525729402395428639350660027796013999414358689067171124475540042281892825140355436902197270317502862419355737833115120643020255177020178331283541002427279377648285229476212651491301857891044943211950307475969543789857568915505152189, 15 | 428559018841948586040450870835405618958247103990365896475476359721456520823105336402250177610460462882418373365551361955769011150860740050608377302711322189733933012507453380208960247649622047461113987220437673085, 16 | (80103920082434941308951713928956232093682985133090231319482222058601362901404235742975739345738195056858602864819514638254604213654261535503537998613664606957411824998071339098079622119781772477940471338367084695408560473046701072890754255641388442248683562485936267601216704036749070688609079527182189924, 842529568002033827493313169405480104392127700904794758022297608679141466431472390397211660887148306350328312067659313538964875094877785244888004725864621826211254824064492961341512012172365417791553455922872091302295614295785447354268009914265614957287377743897340475899980395298019735631787570231395791009), 17 | 59051335744243522933765175665, 18 | ), 19 | ( 20 | 37244493713246153778174562251362609960152354778990433088693945121840521598316370898923829767722653817418453309557995775960963654893571162621888675965423771020678918714406461976659339420718804301006282789714856197345257634581330970033427061846386874027391337895557558939538516456076874074642348992933600929747, 21 | 152657520846237173082171645969128953029734435220247551748055538422696193261367413610113664730705318574898280226247526051526753570012356026049784218573767765351341949785570284026342156807244268439356037529507501666987, 22 | (14301224815357080657295611483943004076662022528706782110580690613822599700117720290144067866898573981166927919045773324162651193822888938569692341428439887892150361361566562459037438581637126808773605536449091609307652818862273375400935935851849153633881318435727224452416837785155763820052159016539063161774, 711567521767597846126014317418558888899966530302382481896965868976010995445213619798358362850235642988939870722858624888544954189591439381230859036120045216842190726357421800117080884618285875510251442474167884705409694074544449959388473647082485564659040849556130494057742499029963072560315800049629531101), 23 | 56178670950277431873900982569, 24 | ), 25 | ( 26 | 6331516792334912993168705942035497262087604457828016897033541606942408964421467661323530702416001851055818803331278149668787143629857676822265398153269496232656278975610802921303671791426728632525217213666490425139054750899596417296214549901614709644307007461708968510489409278966392105040423902797155302293, 27 | 2041454339352622193656627164408774503102680941657965640324880658919242765901541504713444825283928928662755298871656269360429687747026596290805541345773049732439830112665647963627438433525577186905830365395002284129, 28 | (4957181230366693742871089608567285130576943723414681430592899115510633732100117146460557849481224254840925101767808247789524040371495334272723624632991086495766920694854539353934108291010560628236400352109307607758762704896162926316651462302829906095279281186440520100069819712951163378589378112311816255944, 2715356151156550887523953822376791368905549782137733960800063674240100539578667744855739741955125966795181973779300950371154326834354436541128751075733334790425302253483346802547763927140263874521376312837536602237535819978061120675338002761853910905172273772708894687214799261210445915799607932569795429868), 29 | 70953285682097151767648136059, 30 | ), 31 | ] 32 | HINTSIZE = 96 33 | 34 | def decrypt(c, d, n): 35 | n = int(n) 36 | size = n.bit_length() // 2 37 | c_high, c_low = c 38 | b = (c_low**2 - c_high**3) % n 39 | EC = EllipticCurve(Zmod(n), [0, b]) 40 | m_high, m_low = (EC((c_high, c_low)) * d).xy() 41 | m_high, m_low = int(m_high), int(m_low) 42 | return (m_high << size) | m_low 43 | 44 | hintmod = 2**HINTSIZE 45 | todec = masked_flag 46 | for data in ciphertexts: 47 | n, e, c, hint = data 48 | c_high, c_low = c 49 | for f in (e/QQ(n)).continued_fraction().convergents(): 50 | y = f.numerator() 51 | x = f.denominator() 52 | if is_prime(x) and is_prime(y): 53 | print("good", x, y) 54 | break 55 | 56 | plo = hint 57 | qlo = n * inverse_mod(plo, hintmod) % hintmod 58 | slo = (plo + qlo) % hintmod 59 | assert plo * qlo % hintmod == n % hintmod 60 | 61 | a = e * x - (n + 1) * y 62 | 63 | z_mod_y = (-a) % y 64 | z_mod_hintmod = (-a + slo * y) % hintmod 65 | midmod = hintmod * y 66 | zmid = crt([z_mod_y, z_mod_hintmod], [y, hintmod]) 67 | 68 | aa = a - slo * y + zmid 69 | assert aa % midmod == 0 70 | aa //= midmod 71 | 72 | # shi = (p + q) // hintmod 73 | # zhi is ~216 bits 74 | # assert shi == aa + zhi 75 | 76 | sumpq = aa * hintmod 77 | eps = 2**216 * hintmod 78 | 79 | rsa = RSA(n) 80 | pbound, qbound = rsa.bound_primes_from_approximate_sum(sumpq-eps, 2*eps) 81 | 82 | try: 83 | print("factor1") 84 | p, q = rsa.factor_with_prime_hint( 85 | low_mod=(plo, hintmod), bounds=pbound, beta=0.49 86 | ) 87 | except RuntimeError: 88 | print("factor2") 89 | p, q = rsa.factor_with_prime_hint( 90 | low_mod=(qlo, hintmod), bounds=pbound, beta=0.49 91 | ) 92 | print("ok!") 93 | d = inverse_mod(e, n + p + q + 1) 94 | mask = decrypt(c, d, n) 95 | todec ^= mask 96 | assert Bin(todec).bytes == b'SECCON{gut_poweeeeeeeeeeeeeer}' 97 | -------------------------------------------------------------------------------- /cry/sbox2/algorithms/multiset.py: -------------------------------------------------------------------------------- 1 | r""" 2 | 3 | Some simple checks for multiset properties in S-Boxes. 4 | 5 | EXAMPLES:: 6 | 7 | >>> from cry.sbox2 import SBox2 8 | >>> from cry.utils import ranges 9 | >>> from itertools import product 10 | >>> from pprint import pprint 11 | >>> from binteger import Bin 12 | 13 | Simple example: 14 | 15 | >>> a, b, c = (SBox2.new.random_permutation(4) for _ in range(3)) 16 | >>> s = SBox2.new.parallel([a, b, c]) 17 | >>> split = Split(s, 3, 3) 18 | 19 | Manual specification of input permutation position: 20 | 21 | >>> split.propagate_single_permutation(pos=0) 22 | 'pcc' 23 | >>> split.propagate_single_permutation(pos=1) 24 | 'cpc' 25 | >>> split.propagate_single_permutation(pos=2) 26 | 'ccp' 27 | 28 | Automatic check of all positions: 29 | 30 | >>> pprint( split.check_single_permutation() ) 31 | {'ccp': 'ccp', 'cpc': 'cpc', 'pcc': 'pcc'} 32 | 33 | Example with balanced and unknown: 34 | 35 | >>> f = SBox2.new.random_function(6, 3) 36 | >>> s = SBox2( 37 | ... Bin.concat(x, x^y, f((x << 3) | y), n=3) 38 | ... for x, y in ranges(8, 8) ) 39 | >>> s = SBox2.new.parallel([a, b, c]) 40 | >>> split = Split(s, 3, 3) 41 | 42 | >>> pprint( split.check_single_permutation() ) 43 | {'ccp': 'ccp', 'cpc': 'cpc', 'pcc': 'pcc'} 44 | 45 | """ 46 | 47 | from collections import defaultdict 48 | from itertools import product 49 | 50 | from binteger import Bin 51 | 52 | PERM = "p" 53 | CONST = "c" 54 | BALANCED = "b" 55 | UNKNOWN = "?" 56 | 57 | 58 | class Split(object): 59 | """ 60 | Split input and output bits in equal portions. 61 | Used further for checking for simple multiset properties. 62 | """ 63 | def __init__(self, sbox, num_in, num_out): 64 | self.s = sbox 65 | assert sbox.input_size() % num_in == 0 66 | assert sbox.output_size() % num_out == 0 67 | self.width_in = sbox.input_size() // num_in 68 | self.width_out = sbox.output_size() // num_out 69 | self.num_in = num_in 70 | self.num_out = num_out 71 | 72 | def check_single_permutation(self): 73 | res = {} 74 | for pos in range(self.num_in): 75 | inp = [CONST] * self.num_in 76 | inp[pos] = PERM 77 | inp = "".join(inp) 78 | out = self.propagate_single_permutation(pos) 79 | if out: 80 | res[inp] = out 81 | return res 82 | 83 | def propagate_single_permutation(self, pos): 84 | outs_by_consts = [defaultdict(dict) for _ in range(self.num_out)] 85 | for x, y in self.s.graph(): 86 | xs = Bin(x, self.num_in * self.width_in).split(parts=self.num_in) 87 | ys = Bin(y, self.num_out * self.width_out).split(parts=self.num_out) 88 | key = tuple(xs[i] for i in range(self.num_in) if i != pos) 89 | for j, y in enumerate(ys): 90 | d = outs_by_consts[j][key] 91 | d.setdefault(y, 0) 92 | d[y] += 1 93 | 94 | out_prop = [] 95 | for j in range(self.num_out): 96 | has_const = False 97 | has_perm = False 98 | has_balanced = False 99 | has_unknown = False 100 | for key, d in outs_by_consts[j].items(): 101 | if self.width_in == self.width_out and\ 102 | len(d) == 2**self.width_in: 103 | has_perm = True 104 | continue 105 | if len(d) == 1: 106 | has_const = True 107 | continue 108 | 109 | xorsum = 0 110 | for y, cnt in d.items(): 111 | if cnt & 1: 112 | xorsum ^= y 113 | if xorsum == 0: 114 | has_balanced = True 115 | else: 116 | has_unknown = True 117 | break 118 | 119 | if has_unknown: 120 | result = UNKNOWN 121 | elif has_balanced: 122 | result = BALANCED 123 | elif has_const and not has_perm: 124 | result = CONST 125 | elif has_perm and not has_const: 126 | result = PERM 127 | else: 128 | assert False 129 | out_prop.append(result) 130 | return "".join(out_prop) 131 | 132 | def __getitem__(self, lst): 133 | assert len(lst) == self.num_in 134 | x = Bin.concat(Bin.array(lst, n=self.width_in)) 135 | y = self.s[x] 136 | return Bin(y, self.width_out).split(parts=self.num_out) 137 | 138 | def TU_decomposition(self): 139 | ''' 140 | Return None on fail or 141 | { 142 | T: [T0, T1, ...], 143 | U: [U0, U1, ...], 144 | swap_input: True, 145 | swap_output: False, 146 | } 147 | ''' 148 | assert self.num_in == self.num_out == 2 149 | assert self.width_in == self.width_out 150 | w = self.width_in 151 | mask = (1 << w) - 1 152 | for mi, mo in self.check_single_permutation().items(): 153 | if "p" not in mo: 154 | continue 155 | assert "p" in mi 156 | 157 | iswap = False 158 | oswap = False 159 | 160 | s = self.s 161 | if mi[1] != "p": 162 | s = s.swap_halves_input() 163 | iswap = True 164 | if mo[1] != "p": 165 | s = s.swap_halves_output() 166 | oswap = True 167 | 168 | T = [] 169 | for lk in range(2**w): 170 | t = [] 171 | for r in range(2**w): 172 | x = (lk << w) | r 173 | t.append(s[x] & mask) 174 | T.append(t) 175 | 176 | U = [list() for i in range(2**w)] 177 | for l, r in product(range(2**w), repeat=2): 178 | x = (l << w) | r 179 | r = T[l][r] 180 | U[r].append(s[x] >> w) 181 | return dict(T=T, U=U, swap_input=iswap, swap_output=oswap) 182 | raise RuntimeError("TU-decomposition failed!") 183 | 184 | 185 | def print_minicipher(T, latex_letter=None): 186 | for k, t in enumerate(T): 187 | if latex_letter: 188 | print( 189 | "$%s_%x$ &" % (latex_letter, k), " & ".join(map(hex, t)), r"\\" 190 | ) 191 | else: 192 | print("%x" % k, t) 193 | -------------------------------------------------------------------------------- /cry/sbox2/equiv/__init__.py: -------------------------------------------------------------------------------- 1 | from itertools import product 2 | 3 | from binteger import Bin 4 | 5 | from cry.sagestuff import BooleanPolynomialRing, Integer, matrix, GF, LinearCode 6 | from cry.sagestuff import BooleanFunction 7 | 8 | from .linear import LEContext 9 | from .linear_generic import LEContext as LEContextGeneric 10 | 11 | 12 | class Equiv(object): 13 | @staticmethod 14 | def are_XOR_equivalent(s1, s2): 15 | """ 16 | If yes, return (cx, cy) such that 17 | s1[x] = cy + s2[x + cx] 18 | (+ is xor) 19 | """ 20 | s1, s2 = convert_sboxes(s1, s2) 21 | assert_equal_sizes(s1, s2) 22 | n = s1.n 23 | for cx in range(2**n): 24 | cy = s1[0] ^ s2[0 ^ cx] 25 | for x in range(1, 2**n): 26 | if s1[x] ^ s2[x ^ cx] != cy: 27 | break 28 | else: 29 | return (cx, cy) 30 | 31 | def is_XOR_equivalent(self, other): 32 | return Equiv.are_XOR_equivalent(self, other) 33 | 34 | @staticmethod 35 | def are_linear_equivalent(s1, s2, findall=False): 36 | """ 37 | Algorithm is described in the following paper: 38 | @inproceedings{eurocrypt-2003-2059, 39 | title={A Toolbox for Cryptanalysis: Linear and Affine Equivalence Algorithms}, 40 | booktitle={Advances in Cryptology - EUROCRYPT 2003, International Conference on the Theory and Applications of Cryptographic Techniques, Warsaw, Poland, May 4-8, 2003, Proceedings}, 41 | series={Lecture Notes in Computer Science}, 42 | publisher={Springer}, 43 | volume={2656}, 44 | pages={33-50}, 45 | url={http://www.iacr.org/cryptodb/archive/2003/EUROCRYPT/2059/2059.pdf}, 46 | doi={10.1007/3-540-39200-9_3}, 47 | author={Alex Biryukov and Christophe De Cannière and An Braeken and Bart Preneel}, 48 | year=2003 49 | } 50 | """ 51 | s1, s2 = convert_sboxes(s1, s2) 52 | assert_equal_sizes(s1, s2) 53 | # currently 54 | # assert_permutations(s1, s2) 55 | 56 | LEcls = LEContext if s1.is_permutation() and s2.is_permutation() else LEContextGeneric 57 | LEcls.sbox = Equiv.new 58 | ctx = LEcls(s1, s2, findall=findall) 59 | res = ctx.run() 60 | if findall and res: 61 | for A, B in res: 62 | assert B * s1 * A == s2 63 | assert A.is_permutation() 64 | assert B.is_permutation() 65 | assert A.is_linear() 66 | assert B.is_linear() 67 | return res 68 | elif res: 69 | A, B = res 70 | assert B * s1 * A == s2 71 | assert A.is_permutation() 72 | assert B.is_permutation() 73 | return A, B 74 | return res 75 | 76 | def is_linear_equivalent(self, other, **kwargs): 77 | return self.are_linear_equivalent(self, other, **kwargs) 78 | 79 | @staticmethod 80 | def are_affine_equivalent(s1, s2, findall=False, counts_only=False): 81 | s1, s2 = convert_sboxes(s1, s2) 82 | assert_equal_sizes(s1, s2) 83 | # currently 84 | # assert_permutations(s1, s2) 85 | 86 | full_res = {} 87 | 88 | # temporary unoptimal algorithm 89 | for a, b in product(range(s1.input_range()), range(s1.output_range())): 90 | xor_s1 = Equiv.new(s1[x ^ a] ^ b for x in range(s1.input_range())) 91 | lin_res = xor_s1.is_linear_equivalent(s2, findall=findall) 92 | if findall and lin_res: 93 | for A, B in lin_res: 94 | assert A.is_permutation() 95 | assert B.is_permutation() 96 | assert (B * xor_s1 * A) == s2 97 | assert (B * s1.xor(a, b) * A) == s2 98 | xa = A.preimage(a) 99 | xb = B[b] 100 | assert (B * s1 * A).xor(xa, xb) == s2 101 | if counts_only: 102 | full_res.setdefault((a, b), 0) 103 | full_res[a, b] += len(lin_res) 104 | else: 105 | full_res.setdefault((a, b), []) 106 | full_res[a, b] += lin_res 107 | elif lin_res: 108 | A, B = lin_res 109 | assert A.is_permutation() 110 | assert B.is_permutation() 111 | assert (B * xor_s1 * A) == s2 112 | assert (B * s1.xor(a, b) * A) == s2 113 | xa = A.preimage(a) 114 | xb = B[b] 115 | assert (B * s1 * A).xor(xa, xb) == s2 116 | return xa, A, B, xb 117 | return full_res if findall else False 118 | 119 | def is_affine_equivalent(self, other, **kwargs): 120 | return self.are_affine_equivalent(self, other, **kwargs) 121 | 122 | @staticmethod 123 | def are_CCZ_equivalent(s1, s2): 124 | s1, s2 = convert_sboxes(s1, s2) 125 | assert_equal_sizes(s1, s2) 126 | lin1 = s1.is_linear() 127 | lin2 = s2.is_linear() 128 | if lin1 ^ lin2: 129 | return False 130 | if lin1 and lin2: 131 | return True 132 | 133 | inp, out = s1.n, s1.m 134 | M1 = matrix(GF(2), 1 + inp + out, 2**inp) 135 | M2 = matrix(GF(2), 1 + inp + out, 2**inp) 136 | for x in range(2**inp): 137 | M1.set_column( 138 | x, 139 | Bin.concat(Bin(1, 1) + Bin(x, inp) + Bin(s1[x], out)).tuple 140 | ) 141 | M2.set_column( 142 | x, 143 | Bin.concat(Bin(1, 1) + Bin(x, inp) + Bin(s2[x], out)).tuple 144 | ) 145 | 146 | L1 = LinearCode(M1) 147 | L2 = LinearCode(M2) 148 | # Annoying: this is not checked in "is_permutation_equivalent" code! 149 | # raises a TypeError instead 150 | if L1.generator_matrix().nrows() != L2.generator_matrix().nrows(): 151 | return False 152 | return L1.is_permutation_equivalent(L2) 153 | 154 | def is_CCZ_equivalent(self, other): 155 | return self.are_CCZ_equivalent(self, other) 156 | 157 | 158 | def assert_equal_sizes(s1, s2): 159 | assert s1.m == s2.m 160 | assert s1.n == s2.n 161 | 162 | 163 | def convert_sboxes(s1, s2): 164 | if not isinstance(s1, Equiv.cls): 165 | s1 = Equiv.new(s1) 166 | if not isinstance(s2, Equiv.cls): 167 | s2 = Equiv.new(s2) 168 | assert isinstance(s1, Equiv.cls) 169 | assert isinstance(s2, Equiv.cls) 170 | return s1, s2 171 | 172 | 173 | def assert_permutations(s1, s2): 174 | assert s1.is_permutation() 175 | assert s2.is_permutation() 176 | -------------------------------------------------------------------------------- /cry/py/tree/orderer.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | from queue import PriorityQueue 3 | from random import randint 4 | 5 | from .node.op import OP 6 | 7 | 8 | class Multiset(object): 9 | def __init__(self): 10 | self.data = {} 11 | 12 | def add(self, obj, num=1): 13 | self.data.setdefault(obj, 0) 14 | self.data[obj] += num 15 | 16 | def remove(self, obj, num=1): 17 | self.data[obj] -= num 18 | assert self.data[obj] >= 0 19 | if self.data[obj] == 0: 20 | del self.data[obj] 21 | 22 | def remove_all(self, obj): 23 | del self.data[obj] 24 | 25 | def items(self): 26 | return self.data.items() 27 | 28 | def __len__(self): 29 | return len(self.data) 30 | 31 | def __contains__(self, obj): 32 | return obj in self.data 33 | 34 | def __iter__(self): 35 | return self.data.__iter__() 36 | 37 | 38 | class ComputationOrder(object): 39 | ACTION_COMPUTE = "compute" 40 | ACTION_FREE = "free" 41 | # ACTION_ALLOC = "alloc" 42 | 43 | def __init__(self, code, xbits, ybits): 44 | self.code = code 45 | self.xbits = tuple(xbits) 46 | self.ybits = tuple(ybits) 47 | 48 | def max_state(self): 49 | mx = 0 50 | counter = 0 51 | for action, _ in self.code: 52 | if action == self.ACTION_FREE: 53 | counter -= 1 54 | else: 55 | counter += 1 56 | mx = max(mx, counter) 57 | return mx 58 | 59 | 60 | CO = ComputationOrder 61 | 62 | 63 | class Orderer(object): 64 | def __init__(self, xbits, ybits, quiet=False): 65 | self.ybits = ybits 66 | self.xbits = list(xbits) 67 | 68 | self.using = {} 69 | self.indeg = Counter() 70 | self.queue = [] 71 | self.sequential = PriorityQueue() 72 | self.ready = PriorityQueue() 73 | self.quiet = quiet 74 | 75 | def compile(self, max_id_rush): 76 | self.composite = set() 77 | 78 | if not self.quiet: 79 | print(":: CIRCUIT WALK START") 80 | self.visited = set() 81 | for b in self.ybits: 82 | self.dfs(b) 83 | del self.visited 84 | 85 | if not self.quiet: 86 | print(":: ORDERING START") 87 | code = tuple(self.generate(max_id_rush=max_id_rush)) 88 | # print ":: CODE SIZE %d OPERATIONS" % len(code) 89 | return ComputationOrder(xbits=self.xbits, ybits=self.ybits, code=code) 90 | 91 | def dfs(self, b): 92 | if b in self.visited: 93 | return 94 | self.sequential.put((b.id, b)) 95 | self.visited.add(b) 96 | 97 | if b.is_primitive(): 98 | self.indeg[b] = 0 99 | self.ready.put((b.id, b)) 100 | if b.is_const(): 101 | # assert not b.is_const(),\ 102 | # "not really needed assert, by better not to have consts 103 | # for good orderings" 104 | print( 105 | "WARNING", "not really needed assert,", 106 | "by better not to have consts for good orderings" 107 | ) 108 | for par in self.using[b]: 109 | print(OP.name[par.op], OP.name[b.op]) 110 | quit(1) 111 | 112 | if b.is_input(): 113 | assert b.args[0] in [bb.args[0] for bb in self.xbits], b.args[0] 114 | assert b in self.xbits, b.args[0] 115 | return 116 | 117 | for sub in b.args: 118 | if sub not in self.using: 119 | self.using[sub] = Multiset() 120 | self.using[sub].add(b) 121 | self.dfs(sub) 122 | 123 | self.indeg[b] = len(b.args) 124 | assert len(b.args) > 0 125 | self.composite.add(b) 126 | 127 | def generate(self, max_id_rush): 128 | # outputs are used and never can be freed 129 | for b in self.ybits: 130 | if b not in self.using: 131 | self.using[b] = Multiset() 132 | self.using[b].add(None) 133 | 134 | self.visited = set() 135 | 136 | while True: 137 | b = self.pop_queue(max_id_rush=max_id_rush) 138 | if b is None: 139 | break 140 | self.visited.add(b) 141 | assert self.indeg[b] == 0 142 | 143 | for sub, cnt in self.using[b].items(): 144 | self.indeg[sub] -= cnt 145 | if self.indeg[sub] == 0: 146 | self.ready.put((sub.id, sub)) 147 | 148 | if b.op == OP.OUTSOURCE or b.op == OP.OUTSOURCE_CLONE: 149 | yield CO.ACTION_COMPUTE, b 150 | continue 151 | 152 | if b not in self.composite: 153 | # simple bit (input/const) 154 | continue 155 | 156 | # complex bit, compute 157 | # yield "alloc", b 158 | # alloc included in compute? 159 | 160 | yield CO.ACTION_COMPUTE, b 161 | 162 | for sub in b.args: 163 | # repeated bit usage? 164 | if b not in self.using[sub]: 165 | continue 166 | self.using[sub].remove_all(b) 167 | if len(self.using[sub]) == 0: 168 | if sub.is_const(): 169 | continue 170 | # disallow reuse immutable vars 171 | # if sub in self.immutable: 172 | # continue 173 | yield CO.ACTION_FREE, sub 174 | 175 | def pop_queue(self, max_id_rush): 176 | while self.sequential.qsize(): 177 | _, b_first = item = self.sequential.get() 178 | if b_first not in self.visited: 179 | self.sequential.put(item) 180 | break 181 | if self.sequential.qsize() == 0: 182 | return 183 | 184 | while self.ready.qsize(): 185 | _, b = item = self.ready.get() 186 | if b_first.id + max_id_rush < b.id: 187 | self.ready.put(item) 188 | break 189 | self.queue.append(b) 190 | 191 | # print self.sequential.qsize(), self.ready.qsize(), len(self.queue) 192 | if not self.queue: 193 | print( 194 | "ERROR, no computable bits", 195 | " inside max_id_rush=%d" % max_id_rush 196 | ) 197 | print(b_first.id, OP.name[b_first.op]) 198 | print(b.id, OP.name[b.op]) 199 | quit(1) 200 | 201 | q = self.queue 202 | i = randint(0, len(q) - 1) 203 | b = q[i] 204 | assert b.id <= b_first.id + max_id_rush 205 | q[i], q[-1] = q[-1], q[i] 206 | q.pop() 207 | return b 208 | -------------------------------------------------------------------------------- /cry/sbox2/equiv/linear_generic.py: -------------------------------------------------------------------------------- 1 | # from Queue import Queue 2 | 3 | 4 | class LEContext(object): 5 | def __init__(self, s1, s2, findall=False): 6 | self.dim_in = s1.m 7 | self.dim_out = s1.n 8 | assert s2.m == s1.m 9 | assert s2.n == s1.n 10 | self.size_in = 2**self.dim_in 11 | self.size_out = 2**self.dim_out 12 | self.s1 = tuple(s1) 13 | self.s2 = tuple(s2) 14 | self.s1pre = s1.preimages() 15 | self.s2pre = s2.preimages() 16 | 17 | self.ret = [] 18 | self.findall = findall 19 | 20 | def run(self): 21 | if self.s1[0] == 0 and self.s2[0] != 0: 22 | return False 23 | if self.s1[0] != 0 and self.s2[0] == 0: 24 | return False 25 | 26 | s = LEState(self) 27 | x, y = s.dual_forward(0, 0) 28 | assert s.learn(1, x, y) 29 | if not s.reduce_from_out(0, 0): 30 | return False 31 | s.check() 32 | 33 | if self.findall: 34 | return self.ret 35 | assert len(self.ret) <= 1 36 | return self.ret[0] if self.ret else False 37 | 38 | def input_range(self, no_zero=False): 39 | if no_zero: 40 | return range(1, self.size_in) 41 | else: 42 | return range(self.size_in) 43 | 44 | def output_range(self, no_zero=False): 45 | if no_zero: 46 | return range(1, self.size_out) 47 | else: 48 | return range(self.size_out) 49 | 50 | class LEState(object): 51 | SIDE_A = 0 52 | SIDE_B = 1 53 | 54 | def __init__(self, ctx, linear_maps=None, unknown_inputs=None, unknown_outputs=None, possible_outputs=None): 55 | self.ctx = ctx 56 | 57 | self.linear_maps = linear_maps or [{0: 0}, {0: 0}] 58 | self.unknown_inputs = unknown_inputs or [set(self.ctx.input_range(no_zero=True)) for i in xrange(2)] 59 | self.unknown_outputs = unknown_outputs or [set(self.ctx.input_range(no_zero=True)) for i in xrange(2)] 60 | self.possible_outputs = possible_outputs or {} 61 | 62 | self.queue = Queue() 63 | self.depth = 0 64 | 65 | def copy(self): 66 | lm2 = [self.linear_maps[i].copy() for i in xrange(2)] 67 | ui2 = [self.unknown_inputs[i].copy() for i in xrange(2)] 68 | uo2 = [self.unknown_outputs[i].copy() for i in xrange(2)] 69 | po2 = self.possible_outputs.copy() 70 | st = LEState(self.ctx, lm2, ui2, uo2, po2) 71 | st.depth = self.depth + 1 72 | return st 73 | 74 | def dual_forward(self, inp, out): 75 | dual_inp = self.ctx.s1[out] 76 | dual_out = self.ctx.s2[inp] 77 | return dual_inp, dual_out 78 | 79 | def learn(self, side, inp, out, queue=True): 80 | if inp in self.linear_maps[side]: 81 | return self.linear_maps[side][inp] == out 82 | elif out not in self.unknown_outputs[side]: 83 | return False 84 | self.linear_maps[side][inp] = out 85 | self.unknown_inputs[side].remove(inp) 86 | self.unknown_outputs[side].remove(out) 87 | if queue: 88 | self.queue.put((side, inp)) 89 | return True 90 | 91 | def expand_horizontal(self, side, inp, out): 92 | if len(self.linear_maps[side]) < len(self.ctx.s1) / 2: 93 | for old_inp, old_out in self.linear_maps[side].items(): 94 | new_inp = inp ^ old_inp 95 | new_out = out ^ old_out 96 | yield new_inp, new_out 97 | else: 98 | for unk_inp in self.unknown_inputs[side].copy(): 99 | old_inp = inp ^ unk_inp 100 | if old_inp in self.linear_maps[side]: 101 | old_out = self.linear_maps[side][old_inp] 102 | new_inp = inp ^ old_inp # unk_inp 103 | new_out = out ^ old_out 104 | yield new_inp, new_out 105 | 106 | def reduce_from_out(self, inp, out): 107 | pre_out = self.ctx.s1pre[inp] 108 | pre_inp = self.ctx.s2pre[out] 109 | if len(pre_inp) != len(pre_out): 110 | return False 111 | for x in pre_inp: 112 | if x in self.possible_outputs and self.possible_outputs[x] != pre_out: 113 | return False 114 | self.possible_outputs[x] = pre_out 115 | return True 116 | 117 | def check(self): 118 | while self.queue.qsize() > 0: 119 | side, inp = self.queue.get() 120 | 121 | learn_inp = inp 122 | learn_out = self.linear_maps[side][inp] 123 | 124 | # learn horizontally (same side) 125 | for new_inp, new_out in self.expand_horizontal(side, learn_inp, learn_out): 126 | if not self.learn(side, new_inp, new_out, queue=False): 127 | return False 128 | 129 | # learn vertically (other side) 130 | if side == 0: 131 | # inp -> out easy 132 | new_inp2, new_out2 = self.dual_forward(new_inp, new_out) 133 | if not self.learn(side ^ 1, new_inp2, new_out2, queue=True): 134 | return False 135 | else: 136 | # out -> inp marks preimages 137 | if not self.reduce_from_out(new_inp, new_out): 138 | return False 139 | 140 | if len(self.linear_maps[0]) == self.ctx.size_in and len(self.linear_maps[1]) == self.ctx.size_out: 141 | return self.result_ready() 142 | 143 | # queues are empty 144 | # need to guess 145 | inp = min(self.unknown_inputs[0], 146 | key=lambda inp: len(self.possible_outputs[inp]) if inp in self.possible_outputs else len(self.ctx.s1)) 147 | outs = self.possible_outputs.get(inp, self.unknown_outputs[0]) 148 | for out in outs: 149 | if out not in self.unknown_outputs[0]: 150 | continue 151 | sub = self.copy() 152 | sub.learn(0, inp, out) 153 | res = sub.check() 154 | if res and not self.ctx.findall: 155 | return res 156 | return False 157 | 158 | def result_ready(self): 159 | A = self.ctx.sbox(self.linear_maps[0][x] for x in self.ctx.input_range()) 160 | if not A.is_linear(): 161 | return False 162 | B = self.ctx.sbox(self.linear_maps[1][x] for x in self.ctx.output_range()) 163 | if not B.is_linear(): 164 | return False 165 | 166 | # result is fine, now yield or continue? 167 | self.ctx.ret.append((A, B)) 168 | return not self.ctx.findall 169 | # TODO: bug on 170 | #(9, 28, 19, 33, 18, 42, 38, 5, 40, 4, 4, 0, 30, 25, 21, 42, 43, 7, 0, 36, 43, 30, 2, 0, 19, 43, 6, 31, 15, 41, 1, 8, 28, 10, 6, 31, 35, 6, 3, 14, 5, 15, 6, 17, 36, 17, 12, 28, 39, 39, 41, 30, 5, 18, 14, 0, 16, 40, 33, 19, 33, 28, 5, 7, 38, 29, 22, 32, 26, 41, 21, 18, 24, 40, 5, 24, 0, 10, 27, 27, 3, 34, 22, 14, 12, 15, 19, 14, 26, 0, 22, 37, 6, 39, 33, 7, 28, 38, 4, 26, 18, 18, 26, 42, 21, 39, 0, 7, 11, 0, 1, 13, 35, 26, 2, 35, 25, 41, 18, 3, 38, 40, 26, 22, 34, 6, 20, 10, 6, 33, 20, 35, 21, 13, 0, 32, 14, 40, 31, 40, 0, 37, 21, 12, 29, 40, 9, 39, 23, 24, 8, 23, 11, 12, 30, 33, 16, 9, 43, 40, 29, 2, 5, 38, 31, 1, 19, 10, 21, 24, 38, 32, 31, 20, 42, 20, 37, 23, 7, 3, 0, 22, 18, 8, 23, 9, 38, 39, 23, 14, 42, 25, 13, 43, 30, 21, 32, 21, 29, 16, 35, 38, 13, 8, 25, 8, 16, 38, 38, 12, 15, 22, 22, 5, 27, 17, 14, 5, 27, 11, 11, 10, 35, 22, 1, 28, 29, 30, 4, 22, 15, 7, 2, 15, 43, 11, 13, 11, 42, 33, 27, 43, 2, 26, 42, 36, 3, 30, 35, 37, 10, 0, 35, 37, 29, 27) 171 | # n=8, selfequiv linear 172 | -------------------------------------------------------------------------------- /cry/sbox2/tables.py: -------------------------------------------------------------------------------- 1 | import os.path as Path 2 | import subprocess 3 | from functools import reduce 4 | 5 | import ast 6 | from collections import Counter 7 | 8 | from binteger import Bin 9 | 10 | from cry.sagestuff import ZZ, GF, Integer, matrix, randint, Combinations 11 | 12 | from cry.py.anf import mobius 13 | 14 | DDT_EXE = Path.join(Path.abspath(Path.dirname(__file__)), "ddt") 15 | 16 | 17 | class Tables(object): 18 | def branch_number(self): 19 | res = self.m + self.n 20 | for x in range(self.insize): 21 | for dx in range(1, self.insize): 22 | y = self[x] 23 | y2 = self[x ^ dx] 24 | dy = y ^ y2 25 | res = min(res, Integer(dy).popcount() + Integer(dx).popcount) 26 | return res 27 | 28 | def kddt(self, k=3, zero_zero=False): 29 | kddt = matrix(ZZ, self.insize, self.outsize) 30 | for xs in Combinations(range(self.insize), k): 31 | ys = map(self, xs) 32 | dx = reduce(lambda a, b: a ^ b, xs) 33 | dy = reduce(lambda a, b: a ^ b, ys) 34 | kddt[dx, dy] += 1 35 | if zero_zero: 36 | kddt[0, 0] = 0 37 | return kddt 38 | 39 | def add_add_ddt(self): 40 | addt = matrix(ZZ, self.insize, self.outsize) 41 | for x in range(self.insize): 42 | for dx in range(1, self.insize): 43 | x2 = (x + dx) % self.insize 44 | y = self[x] 45 | y2 = self[x2] 46 | dy = (y2 - y) % self.outsize 47 | addt[dx, dy] += 1 48 | return addt 49 | 50 | def add_xor_ddt(self): 51 | axddt = matrix(ZZ, self.insize, self.outsize) 52 | for x in range(self.insize): 53 | for dx in range(1, self.insize): 54 | x2 = (x + dx) % self.insize 55 | y = self[x] 56 | y2 = self[x2] 57 | dy = y ^ y2 58 | axddt[dx, dy] += 1 59 | return axddt 60 | 61 | def xor_add_ddt(self): 62 | axddt = matrix(ZZ, self.insize, self.outsize) 63 | for x in range(self.insize): 64 | for dx in range(1, self.insize): 65 | x2 = x ^ dx 66 | y = self[x] 67 | y2 = self[x2] 68 | dy = (y2 - y) % self.outsize 69 | axddt[dx, dy] += 1 70 | return axddt 71 | 72 | def cmul_xor_ddt(self, F=None): 73 | if F is None: 74 | F = GF(self.insize, name='a') 75 | cxddt = matrix(ZZ, self.insize, self.outsize) 76 | for x in range(1, self.insize): 77 | for dx in range(2, self.insize): 78 | x2 = (F.fetch_int(x) * F.fetch_int(dx)).integer_representation() 79 | y = self[x] 80 | y2 = self[x2] 81 | dy = y2 ^ y 82 | cxddt[dx, dy] += 1 83 | return cxddt 84 | 85 | def cmul_cmul_ddt(self, F=None): 86 | if F is None: 87 | F = GF(self.insize, name='a') 88 | ccddt = matrix(ZZ, self.insize, self.outsize) 89 | for x in range(1, self.insize): 90 | for dx in range(2, self.insize): 91 | x2 = (F.fetch_int(x) * F.fetch_int(dx)).integer_representation() 92 | y = self[x] 93 | y2 = self[x2] 94 | dy = ( 95 | F.fetch_int(y2) * F.fetch_int(y)**(self.outsize - 2) 96 | ).integer_representation() 97 | ccddt[dx, dy] += 1 98 | return ccddt 99 | 100 | def xor_cmul_ddt(self, F=None): 101 | if F is None: 102 | F = GF(self.insize, name='a') 103 | xcddt = matrix(ZZ, self.insize, self.outsize) 104 | for x in range(self.insize): 105 | for dx in range(1, self.insize): 106 | x2 = x ^ dx 107 | y = self[x] 108 | y2 = self[x2] 109 | dy = ( 110 | F.fetch_int(y2) * F.fetch_int(y)**(self.outsize - 2) 111 | ).integer_representation() 112 | xcddt[dx, dy] += 1 113 | return xcddt 114 | 115 | def minilat(self, abs=False): 116 | """LAT taken on a basis points""" 117 | res = matrix(ZZ, self.m, self.n) 118 | for eu in range(self.m): 119 | for ev in range(self.n): 120 | u = Integer(2**eu) 121 | v = Integer(2**ev) 122 | for x in range(2**self.m): 123 | res[eu, ev] += ( 124 | (u & x).popcount() & 1 == (v & self[x]).popcount() & 1 125 | ) 126 | if abs: 127 | res = res.apply_map(_abs) 128 | return res 129 | 130 | def minilat_binary(self, mod=4): 131 | """minilat % mod and then mod/2 -> 1, 0 -> 0""" 132 | mat = self.minilat() % 4 133 | for x in mat.list(): 134 | assert x in (0, mod / 2), "invalid mod for given function" 135 | return (mat.lift() / (mod / 2)).change_ring(GF(2)) 136 | 137 | def hdim(self, right_to_left=False): 138 | """ 139 | hdim[i,j] = i-th output bit contains monomial x1...xn/xj 140 | """ 141 | res = matrix(GF(2), self.in_bits, self.out_bits) 142 | anf = mobius(tuple(self)) 143 | for j in range(self.in_bits): 144 | mask = (1 << self.in_bits) - 1 145 | mask ^= 1 << (self.in_bits - 1 - j) 146 | res.set_column(j, Bin(anf[mask], self.out_bits).tuple) 147 | if right_to_left: 148 | res = res[::-1,::-1] 149 | return res 150 | 151 | def ddt_distrib_fast(self): 152 | inp = "%d\n" % self.insize 153 | inp += " ".join(map(str, self)) 154 | p = subprocess.Popen(DDT_EXE, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 155 | s = p.communicate(inp)[0] 156 | return Counter(ast.literal_eval(s)) 157 | 158 | def ddt_max_estimation(self, iter=100, limit=None): 159 | mx = 0 160 | for i in range(iter): 161 | dx = randint(1, self.insize - 1) 162 | cnt = 0 163 | dys = Counter() 164 | for x in range(self.insize): 165 | dy = self[x] ^ self[x^dx] 166 | dys[dy] += 1 167 | if limit is not None and dys[dy] > limit: 168 | return dys[dy] 169 | mx = max(mx, dys[dy]) 170 | return mx 171 | 172 | def max_ddt(self): 173 | return max(self.ddt_distrib_fast().keys()) 174 | #return max(self.ddt(zero_zero=True).list()) 175 | 176 | def max_lat(self): 177 | return max(self.lat(zero_zero=True, abs=True).list()) 178 | 179 | def nonlinearity(self): 180 | #return min(c.nonlinearity() for c in self.components()) 181 | return 2**(self.m - 1) - self.max_lat() 182 | 183 | def is_APN(self): 184 | """ 185 | self.max_ddt() == 2 186 | """ 187 | if self.ddt_max_estimation(limit=2) > 2: 188 | return False 189 | return max(self.ddt_distrib_fast().keys()) <= 2 190 | 191 | def is_almost_bent(self): 192 | """ 193 | Achieving bound: (Sidelnikov-Chabaud-Vaudenay bound) 194 | self.nonlinearity() <= 2**(self.m-1) - 2**((self.m-1)/2 195 | <=> 196 | self.max_lat() >= 2**((self.m-1)/2) = sqrt(2**m) / sqrt(2) 197 | 198 | Defined only if self.n == self.m ??? 199 | Only possible if: 200 | self.m is odd 201 | Also, almost_bent => APN 202 | """ 203 | assert self.n == self.m 204 | if self.m & 1 == 0: 205 | return False 206 | max_lat = self.max_lat() 207 | assert max_lat >= 2**((self.m - 1)/2), "Bound fail" 208 | return max_lat == 2**((self.m - 1)/2) 209 | 210 | def is_bent(self): 211 | """ 212 | Achieving bound: (covering radius bound) 213 | self.nonlinearity() <= 2**(self.m-1) - 2**(self.m/2 - 1) 214 | <=> 215 | self.max_lat() >= 2**(self.m/2 - 1) = sqrt(2**m) / 2 216 | 217 | Only possible if: 218 | self.m is even 219 | and 220 | self.m >= self.n * 2 221 | """ 222 | max_lat = self.max_lat() 223 | if self.m & 1: 224 | return False 225 | assert max_lat >= 2**(self.m / 2 - 1), "Bound fail" 226 | return max_lat == 2**(self.m / 2 - 1) 227 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "atomicwrites" 5 | version = "1.4.1" 6 | description = "Atomic file writes." 7 | optional = false 8 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 9 | groups = ["dev"] 10 | markers = "sys_platform == \"win32\"" 11 | files = [ 12 | {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, 13 | ] 14 | 15 | [[package]] 16 | name = "attrs" 17 | version = "22.1.0" 18 | description = "Classes Without Boilerplate" 19 | optional = false 20 | python-versions = ">=3.5" 21 | groups = ["dev"] 22 | files = [ 23 | {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, 24 | {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, 25 | ] 26 | 27 | [package.extras] 28 | dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] 29 | docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] 30 | tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] 31 | tests-no-zope = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] 32 | 33 | [[package]] 34 | name = "binteger" 35 | version = "0.15.1" 36 | description = "Binary integer representation toolkit" 37 | optional = false 38 | python-versions = ">=3.5,<4.0" 39 | groups = ["main"] 40 | files = [ 41 | {file = "binteger-0.15.1-py3-none-any.whl", hash = "sha256:8aadecaa3bf79537963c6efb42b655ace3e208993539cb4c3dec8033eb7a405f"}, 42 | {file = "binteger-0.15.1.tar.gz", hash = "sha256:74e0a75fcc57780454474213d51e1abf67e635e8fe85fa3cf66b19537fb8110d"}, 43 | ] 44 | 45 | [[package]] 46 | name = "colorama" 47 | version = "0.4.5" 48 | description = "Cross-platform colored terminal text." 49 | optional = false 50 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 51 | groups = ["dev"] 52 | markers = "sys_platform == \"win32\"" 53 | files = [ 54 | {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, 55 | {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, 56 | ] 57 | 58 | [[package]] 59 | name = "importlib-metadata" 60 | version = "2.1.3" 61 | description = "Read metadata from Python packages" 62 | optional = false 63 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 64 | groups = ["dev"] 65 | markers = "python_version < \"3.8\"" 66 | files = [ 67 | {file = "importlib_metadata-2.1.3-py2.py3-none-any.whl", hash = "sha256:52e65a0856f9ba7ea8f2c4ced253fb6c88d1a8c352cb1e916cff4eb17d5a693d"}, 68 | {file = "importlib_metadata-2.1.3.tar.gz", hash = "sha256:02a9f62b02e9b1cc43871809ef99947e8f5d94771392d666ada2cafc4cd09d4f"}, 69 | ] 70 | 71 | [package.dependencies] 72 | zipp = ">=0.5" 73 | 74 | [package.extras] 75 | docs = ["rst.linker", "sphinx"] 76 | testing = ["importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pep517", "unittest2 ; python_version < \"3\""] 77 | 78 | [[package]] 79 | name = "iniconfig" 80 | version = "1.1.1" 81 | description = "iniconfig: brain-dead simple config-ini parsing" 82 | optional = false 83 | python-versions = "*" 84 | groups = ["dev"] 85 | files = [ 86 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 87 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 88 | ] 89 | 90 | [[package]] 91 | name = "packaging" 92 | version = "20.9" 93 | description = "Core utilities for Python packages" 94 | optional = false 95 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 96 | groups = ["dev"] 97 | files = [ 98 | {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, 99 | {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, 100 | ] 101 | 102 | [package.dependencies] 103 | pyparsing = ">=2.0.2" 104 | 105 | [[package]] 106 | name = "pathlib2" 107 | version = "2.3.7.post1" 108 | description = "Object-oriented filesystem paths" 109 | optional = false 110 | python-versions = "*" 111 | groups = ["dev"] 112 | markers = "python_version == \"3.5\"" 113 | files = [ 114 | {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, 115 | {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, 116 | ] 117 | 118 | [package.dependencies] 119 | six = "*" 120 | 121 | [[package]] 122 | name = "pluggy" 123 | version = "0.13.1" 124 | description = "plugin and hook calling mechanisms for python" 125 | optional = false 126 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 127 | groups = ["dev"] 128 | files = [ 129 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 130 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 131 | ] 132 | 133 | [package.dependencies] 134 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 135 | 136 | [package.extras] 137 | dev = ["pre-commit", "tox"] 138 | 139 | [[package]] 140 | name = "py" 141 | version = "1.11.0" 142 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 143 | optional = false 144 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 145 | groups = ["dev"] 146 | files = [ 147 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 148 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 149 | ] 150 | 151 | [[package]] 152 | name = "pyparsing" 153 | version = "2.4.7" 154 | description = "Python parsing module" 155 | optional = false 156 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 157 | groups = ["dev"] 158 | files = [ 159 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 160 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 161 | ] 162 | 163 | [[package]] 164 | name = "pytest" 165 | version = "6.1.2" 166 | description = "pytest: simple powerful testing with Python" 167 | optional = false 168 | python-versions = ">=3.5" 169 | groups = ["dev"] 170 | files = [ 171 | {file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"}, 172 | {file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"}, 173 | ] 174 | 175 | [package.dependencies] 176 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 177 | attrs = ">=17.4.0" 178 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 179 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 180 | iniconfig = "*" 181 | packaging = "*" 182 | pathlib2 = {version = ">=2.2.0", markers = "python_version < \"3.6\""} 183 | pluggy = ">=0.12,<1.0" 184 | py = ">=1.8.2" 185 | toml = "*" 186 | 187 | [package.extras] 188 | checkqa-mypy = ["mypy (==0.780)"] 189 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 190 | 191 | [[package]] 192 | name = "six" 193 | version = "1.17.0" 194 | description = "Python 2 and 3 compatibility utilities" 195 | optional = false 196 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 197 | groups = ["dev"] 198 | markers = "python_version == \"3.5\"" 199 | files = [ 200 | {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, 201 | {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, 202 | ] 203 | 204 | [[package]] 205 | name = "toml" 206 | version = "0.10.2" 207 | description = "Python Library for Tom's Obvious, Minimal Language" 208 | optional = false 209 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 210 | groups = ["dev"] 211 | files = [ 212 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 213 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 214 | ] 215 | 216 | [[package]] 217 | name = "zipp" 218 | version = "1.2.0" 219 | description = "Backport of pathlib-compatible object wrapper for zip files" 220 | optional = false 221 | python-versions = ">=2.7" 222 | groups = ["dev"] 223 | markers = "python_version < \"3.8\"" 224 | files = [ 225 | {file = "zipp-1.2.0-py2.py3-none-any.whl", hash = "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"}, 226 | {file = "zipp-1.2.0.tar.gz", hash = "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1"}, 227 | ] 228 | 229 | [package.extras] 230 | docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"] 231 | testing = ["func-timeout", "jaraco.itertools", "pathlib2", "unittest2"] 232 | 233 | [metadata] 234 | lock-version = "2.1" 235 | python-versions = "^3.5" 236 | content-hash = "d95160af5e4a4edffcec94f11c3c963f125ef5f919ec0623e1cdb71fc125c48b" 237 | -------------------------------------------------------------------------------- /cry/sbox2/sbox2.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from collections import defaultdict, Counter 3 | 4 | from binteger import Bin 5 | 6 | from cry.sagestuff import ( 7 | ZZ, Integer, lcm, matrix, GF, 8 | PolynomialRing, 9 | BooleanFunction, BooleanPolynomialRing, 10 | ) 11 | from cry.matrix import mat_distribution 12 | 13 | from sage.crypto.sbox import SBox as Sage_SBox 14 | from sage.rings.polynomial.polynomial_element import is_Polynomial 15 | from sage.structure.element import is_Vector 16 | 17 | from cry.py.anf import mobius 18 | 19 | 20 | class SBox2: 21 | """ 22 | Attrs: 23 | n (int): input bits 24 | m (int): output bits 25 | """ 26 | GENERATORS_ATTRIBUTE = "new" 27 | ALGORITHMS_ATTRIBUTE = "alg" 28 | new = None # will be set outside 29 | alg = None 30 | 31 | def __init__(self, spec, m=None): 32 | if is_Polynomial(spec): 33 | poly = spec 34 | assert len(poly.variables()) == 1 35 | 36 | fld = poly.base_ring() 37 | if m is None: 38 | m = ZZ(fld.order()-1).nbits() 39 | spec = [ 40 | poly.subs(fld.fetch_int(x)).integer_representation() 41 | for x in range(len(fld)) 42 | ] 43 | 44 | self._S = tuple(map(int, spec)) 45 | self.n = ZZ(len(self._S)-1).nbits() 46 | self.m = ZZ(max(self._S)).nbits() 47 | if m is not None: 48 | m = int(m) 49 | lim = 1 << self.m 50 | assert all(0 <= y < lim for y in self._S) 51 | else: 52 | m = ZZ(max(self._S)).nbits() 53 | self.m = m 54 | assert self.input_size() >= 1 55 | assert self.output_size() >= 0 56 | 57 | def to_sage(self): 58 | return Sage_SBox(self._S) 59 | 60 | def input_size(self): 61 | return self.n 62 | 63 | def output_size(self): 64 | return self.m 65 | 66 | def __len__(self): 67 | return (1 << self.n) 68 | 69 | def tuple(self): 70 | return self._S 71 | 72 | def list(self): 73 | return list(self._S) 74 | 75 | def input_range(self): 76 | return range(1 << self.input_size()) 77 | domain = input_size 78 | 79 | def output_range(self): 80 | return range(1 << self.output_size()) 81 | codomain = output_range 82 | 83 | def image(self): 84 | return set(self) 85 | 86 | def graph(self): 87 | return enumerate(self) 88 | 89 | def __eq__(self, other): 90 | # override: allow comparison with tuple 91 | if isinstance(other, type(self)): 92 | return ( 93 | self.output_size() == other.output_size() 94 | and self._S == other._S 95 | ) 96 | elif isinstance(other, (int, Integer)) and other == 0: 97 | return max(self) > 0 98 | return self.tuple() == tuple(other) 99 | 100 | def __hash__(self): 101 | """ 102 | Warning: does not match hash of the tuple, so will not match tuple in 103 | a set or dict 104 | """ 105 | return hash((self.output_size(), self.tuple())) 106 | 107 | def __iter__(self): 108 | return iter(self.tuple()) 109 | 110 | def __getitem__(self, x): 111 | x = int_tuple_list_vector(x) 112 | return self._S[x] 113 | __call__ = __getitem__ 114 | 115 | def __repr__(self): 116 | return repr(self.tuple()) 117 | 118 | @classmethod 119 | def get_x(cls, n=None, field=None): 120 | assert (n is not None) ^ (field is not None) 121 | field = field or GF(2**n, name='a') 122 | return PolynomialRing(field, names='x').gen() 123 | 124 | # ================================================= 125 | # REPRESENTATION 126 | # ================================================= 127 | def as_table(self, height=None, width=None): 128 | assert (width is None) ^ (height is None) 129 | if width is not None: 130 | height = self.input_size() // width 131 | else: 132 | width = self.input_size() // height 133 | assert height * width == self.input_size() 134 | m = matrix(ZZ, height, width) 135 | for x, y in self.graph(): 136 | m[divmod(x, width)] = y 137 | return m 138 | 139 | def as_matrix(self): 140 | assert self.is_linear() 141 | 142 | m = matrix(GF(2), self.output_size(), self.input_size()) 143 | for e in range(self.input_size()): 144 | vec = Bin(self[2**e], self.output_size()).tuple 145 | m.set_column(self.input_size() - 1 - e, vec) 146 | return m 147 | 148 | def as_hex(self, sep=""): 149 | numhex = (self.output_size() + 3) // 4 150 | hexformat = "%0" + str(numhex) + "x" 151 | return sep.join(hexformat % y for y in self) 152 | 153 | # ================================================= 154 | # COMPOSITION 155 | # ================================================= 156 | def __mul__(self, other): 157 | if not isinstance(other, type(self)) \ 158 | or other.output_size() != self.input_size(): 159 | other = type(self)(other, m=self.input_size()) 160 | return type(self)((self[y] for x, y in other.graph()), m=self.m) 161 | 162 | def __pow__(self, e): 163 | if e < 0: 164 | return (~self)**(-e) 165 | if e == 0: 166 | return self.new.identity(self.input_size) 167 | res = self if e & 1 else self.new.identity(self.input_size) 168 | a = self 169 | while e > 1: 170 | e >>= 1 171 | a = a * a 172 | if e & 1: 173 | res = res * a 174 | return res 175 | 176 | # ================================================= 177 | # CYCLES 178 | # ================================================= 179 | def cycles(self): 180 | assert self.is_permutation() 181 | xs = set(self.input_range()) 182 | _cycles = [] 183 | cycle = [] 184 | while xs: 185 | x = xs.pop() 186 | cycle.append(x) 187 | while 1: 188 | x = self[x] 189 | if x == cycle[0]: 190 | break 191 | cycle.append(x) 192 | xs.remove(x) 193 | _cycles.append(cycle) 194 | cycle = [] 195 | return _cycles 196 | 197 | def cycle_structure(self): 198 | return sorted(map(len, self.cycles())) 199 | 200 | def order(self): 201 | return lcm(self.cycle_structure()) 202 | 203 | # ================================================= 204 | # INVERSION 205 | # ================================================= 206 | def __invert__(self): 207 | assert self.is_permutation() 208 | table = [None] * len(self) 209 | for x, y in self.graph(): 210 | table[y] = x 211 | assert None not in table 212 | return type(self)(table) 213 | 214 | def preimage(self, x): 215 | x = int_tuple_list_vector(x) 216 | return self._S.index(x) 217 | 218 | def preimages(self, x): 219 | x = int_tuple_list_vector(x) 220 | return tuple(xx for xx, y in self.graph() if y == x) 221 | 222 | def preimages_all(self): 223 | res = defaultdict(list) 224 | for x, y in self.graph(): 225 | res[y].append(x) 226 | return res 227 | 228 | def preimage_structure(self): 229 | return Counter(map(len, self.preimages_all().values())) 230 | 231 | # ================================================= 232 | # PROPERTIES 233 | # ================================================= 234 | def is_involution(self): 235 | # override 236 | return all(self[y] == x for x, y in self.graph()) 237 | 238 | def is_permutation(self): 239 | if self.input_size() != self.output_size(): 240 | return False 241 | ret = [None] * 2**self.output_size() 242 | for x, y in self.graph(): 243 | ret[y] = 1 244 | return None not in ret 245 | 246 | def is_zero(self): 247 | return max(self) == 0 248 | 249 | def is_identity(self): 250 | return all(x == y for x, y in self.graph()) 251 | 252 | def is_constant(self): 253 | return min(self) == max(self) 254 | 255 | def is_linear(self): 256 | return self[0] == 0 and self.is_affine() 257 | 258 | def is_affine(self): 259 | a = self[0] 260 | res = [a] 261 | for i in range(self.input_size()): 262 | delta = self[1 << i] ^ a 263 | for y in res[::]: 264 | res.append(y ^ delta) 265 | if res[-1] != self[len(res)-1]: 266 | return False 267 | return True 268 | 269 | def is_balanced(self): 270 | expected = self.input_size() - self.output_size() 271 | if expected < 0: 272 | return False 273 | return ( 274 | list(self.preimage_structure().items()) == 275 | [(2**expected, 2**self.output_size())] 276 | ) 277 | 278 | # ================================================= 279 | # TRANSFORMATION 280 | # ================================================= 281 | def resize(self, m): 282 | return type(self)(self, m=m) 283 | 284 | def xor(self, input, output): 285 | input = int_tuple_list_vector(input) 286 | output = int_tuple_list_vector(output) 287 | assert input in self.input_range() 288 | assert output in self.output_range() 289 | return type(self)( 290 | [ 291 | self[x.__xor__(input)].__xor__(output) 292 | for x in self.input_range() 293 | ], 294 | m=self.m 295 | ) 296 | 297 | def __xor__(self, other): 298 | if isinstance(other, type(self)): 299 | assert self.input_size() == other.input_size() 300 | assert self.output_size() == other.output_size() 301 | return type(self)( 302 | [a.__xor__(b) for a, b in zip(self, other)], 303 | m=self.m 304 | ) 305 | other = int_tuple_list_vector(other) 306 | return type(self)([y ^ other for y in self], m=self.m) 307 | 308 | def __and__(self, other): 309 | if isinstance(other, int) or isinstance(other, Integer): 310 | assert self.input_size() == other.input_size() 311 | assert self.output_size() == other.output_size() 312 | return type(self)( 313 | [a.__and__(b) for a, b in zip(self, other)], 314 | m=self.m 315 | ) 316 | other = int_tuple_list_vector(other) 317 | return type(self)([y & other for y in self], m=self.m) 318 | 319 | def randomize_xor(self): 320 | a = randint(0, 2**self.input_size()-1) 321 | b = randint(0, 2**self.output_size()-1) 322 | return self.xor(a, b) 323 | 324 | def randomize_linear(self): 325 | A = self.SBox2.new.random_linear_permutation(self.input_size()) 326 | B = self.SBox2.new.random_linear_permutation(self.output_size()) 327 | return B * self * A 328 | 329 | def randomize_affine(self): 330 | A = self.SBox2.new.random_affine_permutation(self.input_size()) 331 | B = self.SBox2.new.random_affine_permutation(self.output_size()) 332 | return B * self * A 333 | 334 | def transform_graph(self, func): 335 | table = [None] * len(self) 336 | for x, y in self.graph(): 337 | x2, y2 = func(x, y) 338 | assert table[x2] is None 339 | table[x2] = y2 340 | return type(self)(table) 341 | 342 | def swap_halves(self, input=True, output=True): 343 | if input: 344 | fx = lambda x: Bin(x, self.input_size()).swap_halves().int 345 | else: 346 | fx = lambda x: x 347 | if output: 348 | fy = lambda y: Bin(y, self.output_size()).swap_halves().int 349 | else: 350 | fy = lambda y: y 351 | return self.transform_graph( 352 | lambda x, y: (fx(x), fy(y)) 353 | ) 354 | 355 | def squeeze_by_mask(self, mask): 356 | mask = int_tuple_list_vector(mask) 357 | assert mask in self.output_range() 358 | m = self.output_size() 359 | return type(self)( 360 | [Bin(y, m).squeeze_by_mask(mask).int for y in self], 361 | m=Bin(mask).hw 362 | ) 363 | 364 | # ================================================= 365 | # DEGREE STUFF 366 | # ================================================= 367 | def mobius(self): 368 | return type(self)(mobius(self.tuple()), m=self.m) 369 | 370 | def anfs(self): 371 | names = ["x%d" % e for e in range(self.input_size())] 372 | bpr = BooleanPolynomialRing(names=names) 373 | vs = list((bpr.gens())) 374 | res = [] 375 | for f in self.mobius().coordinates(): 376 | anf = bpr(0) 377 | for mask, take in f.graph(): 378 | if not take: 379 | continue 380 | clause = bpr(1) 381 | for b, v in zip(Bin(mask, self.input_size()).tuple, vs): 382 | if b: 383 | clause *= v 384 | anf += clause 385 | res.append(anf) 386 | return res 387 | 388 | def anfs_sage(self): 389 | """ 390 | ANFs for bits (MSB to LSB) 391 | /!\ warning: sage's implementation leaks a lot of memory 392 | """ 393 | for f in self.coordinates(): 394 | yield f.to_sage_BF().algebraic_normal_form() 395 | 396 | def degrees(self): 397 | return tuple(f.degree() for f in self.anfs()) 398 | 399 | def degree(self): 400 | return max(self.degrees()) 401 | 402 | # ================================================= 403 | # TABLES 404 | # ================================================= 405 | def difference_distribution_table(self, zero_zero=True): 406 | ddt = matrix(ZZ, 2**self.output_size(), 2**self.input_size()) 407 | for x in self.input_range(): 408 | for dx in self.input_range()[1:]: 409 | dy = self[x] ^ self[x ^ dx] 410 | ddt[dx, dy] += 1 411 | if zero_zero: 412 | ddt[0, 0] = 0 413 | return ddt 414 | DDT = difference_distribution_table 415 | 416 | def linear_approximation_table(self, zero_zero=True): 417 | lat = matrix(ZZ, 2**self.input_size(), 2**self.output_size(), [ 418 | self.component(mask).to_sage_BF().walsh_hadamard_transform() 419 | for mask in self.output_range() 420 | ]).transpose() 421 | if zero_zero: 422 | lat[0, 0] = 0 423 | if abs: 424 | lat = lat.apply_map(abs) 425 | return lat 426 | LAT = linear_approximation_table 427 | 428 | def DDT_distrib(self, *a, **k): 429 | return mat_distribution(self.DDT(*a, **k)) 430 | 431 | def LAT_distrib(self, *a, **k): 432 | return mat_distribution(self.LAT(*a, **k)) 433 | 434 | def coordinate(self, i): 435 | assert 0 <= i < self.output_size() 436 | tt = [ 437 | (y >> (self.output_size() - 1 - i)) & 1 438 | for y in self 439 | ] 440 | return type(self)(tt, m=1) 441 | 442 | def coordinates(self): 443 | for i in range(self.output_size()): 444 | yield self.coordinate(i) 445 | 446 | def component(self, mask): 447 | mask = int_tuple_list_vector(mask) 448 | tt = [ 449 | Bin(y & mask).parity 450 | for y in self 451 | ] 452 | return type(self)(tt, m=1) 453 | 454 | # TBD 455 | # def components(self, with_masks=False, with_anfs=False): 456 | # if with_anfs: 457 | # anfs = self.anfs() 458 | # for mask in range(1, 2**(self.n)): 459 | # mask = Integer(mask) 460 | # tt = [ 461 | # Integer(self[x] & mask).popcount() & 1 462 | # for x in range(self.insize) 463 | # ] 464 | # res = BooleanFunction(tt) 465 | # if with_masks or with_anfs: 466 | # res = [res] 467 | # if with_masks: 468 | # res.append(mask) 469 | # if with_anfs: 470 | # a = 0 471 | # for i, a2 in enumerate(anfs): 472 | # if mask & (1 << (self.n - 1 - i)): 473 | # a += a2 474 | # res.append(a) 475 | # yield res 476 | 477 | # ================================================= 478 | # Boolean Function only (m=1) 479 | # ================================================= 480 | def to_sage_BF(self): 481 | assert self.output_size() == 1 482 | return BooleanFunction(self.tuple()) 483 | 484 | def weight(self): 485 | if self.output_size() != 1: 486 | raise TypeError("Weight is defined only for Boolean Functions") 487 | return sum(self) 488 | 489 | 490 | def int_tuple_list_vector(v): 491 | if isinstance(v, int): 492 | return v 493 | if isinstance(v, Integer): 494 | return int(v) 495 | if isinstance(v, tuple): 496 | return Bin(v).int 497 | if isinstance(v, list): 498 | return Bin(v).int 499 | if is_Vector(v): 500 | return Bin(tuple(v)).int 501 | raise TypeError("%r is not tuple, list, vector or integer") 502 | --------------------------------------------------------------------------------