Compare commits

..

11 commits
laptop ... main

51 changed files with 2587 additions and 13 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
__pycache__/
nimble.develop
nimble.paths

27
README
View file

@ -1 +1,28 @@
The "imbaud python library" (imp lib), or just imp for short!
TODO:
- define a getPrime function like PyCryptodome's
- rewrite nim-lang/bigints to implement features like Karatsuba multiplication, or even Toom-3 multiplication
PyCryptodome defines getPrime as follows:
```py
def getPrime(N, randfunc=None):
"""Return a random N-bit prime number.
N must be an integer larger than 1.
If randfunc is omitted, then :meth:`Random.get_random_bytes` is used.
"""
if randfunc is None:
randfunc = Random.get_random_bytes
if N < 2:
raise ValueError("N must be larger than 1")
while True:
number = getRandomNBitInteger(N, randfunc) | 1
if isPrime(number, randfunc=randfunc):
break
return number
```
in essence infinite random generation until a prime is found

6
README-primefac Normal file
View file

@ -0,0 +1,6 @@
`primefac-nim` is a translation of Lucas A. Brown's [primefac project](https://github.com/lucasaugustus/primefac)
into Nim. Why? 1. I like his work and wanted it in Nim, 2. I'm trying to learn number theory.
Some functions are not verbatim translation, and will be marked as such.
This allows me to implement my own optimisations, such as using
the Sieve of Atkins for prime generation rather than the Sieve of Eratosthenes.

10
celeste.nimble Normal file
View file

@ -0,0 +1,10 @@
# Package
version = "0.1.0"
author = "Emile Clark-Boman"
description = "Self contained framework for computational mathematics"
license = "MIT"
srcDir = "src"
# Dependencies
requires "nim >= 2.2.0"

View file

@ -0,0 +1,106 @@
from math import inf, ceil
from typing import Callable
from celeste.math.util import clamp_max
SPACER = b'\xff' # arbitrary spacing character
class DecryptFailed(Exception):
pass
def round_to_blocks(n: int, block_size: int) -> int:
return ceil(n / block_size)
def crack_secret_len(cipher: Callable[[str], str],
max_iters: int = inf) -> int | None:
# calculate length for i = 1
init_len = len(cipher(SPACER))
secret_len = None
i = 2
while True:
if i - 2 > max_iters:
break
elif len(cipher(SPACER*i)) != init_len:
secret_len = init_len - i
break
i += 1
return secret_len
'''
NOTE: pad_if_perfect exists for PKCS#7 which will add a full block
NOTE: of padding if the input is perfectly alligned to the blocks already.
'''
def crack(cipher: Callable[[str], str],
padfn: Callable[[bytes, int], bytes],
charset: list,
block_size: int,
max_secret_iters: int = inf,
batch_size: int = 1,
pad_if_perfect: bool = True,
debug: bool = False) -> str | None:
if len(charset) % batch_size:
raise ValueError(f'batch_size={batch_size} does not divide len(charset)={len(charset)}')
# calculate the secret length
secret_len = crack_secret_len(cipher, max_iters=max_secret_iters)
if debug:
print(f'[+] Found secret length: {secret_len}')
# calculate how many blocks the secret stretches over
# NOTE: secret_block_len - secret_len represents the number of
# NOTE: bytes required to make the secret fill the blocks with no padding
secret_block_len = round_to_blocks(secret_len, block_size) * block_size
default_push = (secret_block_len - secret_len)
known = b''
while True:
# the "full tail" is all characters we know + the 1 we're cracking (target)
# the "current tail" is the characters in the same block as the target
full_tail_bytes = len(known) + 1
tail_bytes = clamp_max(full_tail_bytes, block_size - 1)
# generate ALL possible tails (avoid padding if no padding required)
tails = [c + known[:tail_bytes] for c in charset]
if len(tails[0]) != block_size:
tails = [padfn(tail, 16) for tail in tails]
# calculate the "push" applied to the secret
push_size = (default_push + full_tail_bytes) % block_size
matched = False
NUM_BATCHES = len(tails) // batch_size
for i in range(NUM_BATCHES):
if debug:
print(f'{int(i/NUM_BATCHES*100)}%', end='\r')
batch = tails[i*batch_size : (i+1)*batch_size]
batch = b''.join(batch) + (SPACER * push_size) # apply spacing
# encrypt batch and split the ciphertext into blocks
ciphertext = cipher(batch)
num_blocks = len(ciphertext)//block_size
blocks = [ciphertext[i*block_size : (i+1)*block_size] for i in range(num_blocks)]
oracle_pos = round_to_blocks(full_tail_bytes, block_size)
if pad_if_perfect and (push_size + secret_len) % block_size == 0:
oracle_pos += 1
for j, cipher_block in enumerate(blocks[:batch_size]):
if cipher_block == blocks[-oracle_pos]:
char = charset[i*batch_size + j]
known = char + known
if debug:
print(f'[*] Found Tail: {known}')
matched = True
break
if matched:
break
if not matched:
break
elif len(known) == secret_len:
if debug:
print('[+] SUCCESS')
return known
# if we reached the end (no return)
# then the attack failed
err_msg = 'Padding oracle attack failed'
if not debug:
raise DecryptFailed(err_msg)
print(f'\n[!] {err_msg}')

View file

@ -9,6 +9,7 @@ ALPHA_UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ALPHA = ALPHA_LOWER + ALPHA_UPPER
ALPHANUM = ALPHA + DIGITS
SYMBOLS = '!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
PRINTABLE = ALPHANUM + SYMBOLS
# Other
DIGITS_BIN = '01'
DIGITS_OCT = '01234567'

View file

@ -12,7 +12,7 @@ Terminology:
substitutions to the entire message (ie vigenere)
'''
from imp.constants import ALPHA_LOWER, ALPHA_UPPER
from celeste.constants import ALPHA_LOWER, ALPHA_UPPER
'''
Constant Declarations

7
celeste/crypto/hash.py Normal file
View file

@ -0,0 +1,7 @@
import hashlib
def sha256(data: bytes, as_bytes: bool = False) -> bytes:
hasher = hashlib.sha256()
hasher.update(data)
hash = hasher.digest()
return hash if as_bytes else int.from_bytes(hash)

18
celeste/crypto/rsa.py Normal file
View file

@ -0,0 +1,18 @@
'''
Simplification of Euler's Totient function knowing
the prime factorisation for the public key N value.
'''
def _totient(p: int, q: int) -> int:
return (p - 1) * (q - 1)
'''
Implements RSA encryption as modular exponentiation.
'''
def encrypt(plaintext: int, e: int, N: int) -> int:
return pow(plaintext, e, N)
def decrypt(ciphertext: int, d: int, N: int) -> int:
return pow(ciphertext, d, N)
def gen_private_key(e: int, p: int, q: int) -> int:
return pow(e, -1, _totient(p, q))

0
celeste/math/__init__.py Normal file
View file

124
celeste/math/crt.py Normal file
View file

@ -0,0 +1,124 @@
def crt(eqs: list[tuple[int, int]]) -> tuple[int, int]:
'''
Solves simultaneous linear diophantine equations
using the Chinese-Remainder Theorem.
Example:
>>> crt([2, 3), (3, 5), (2, 7)])
(23, 105)
# aka x is congruent to 23 modulo 105
'''
# calculate the quotient and remainder form of the unknown
q = 1
r = 0
# store remainders and moduli for each linear equation
R = [eq[0] for eq in eqs]
M = [eq[1] for eq in eqs]
N = len(eqs)
for i in range(N):
(R_i, M_i) = (R[i], M[i])
# adjust quotient and remainder
r += R_i * q
q *= M_i
# apply current eq to future eqs
for j in range(i+1, N):
R[j] -= R_i
R[j] *= pow(M_i, -1, M[j])
R[j] %= M[j]
return r # NOTE: forall integers k: qk+r is also a solution
from math import isqrt, prod
def bsgs(g: int, h: int, n: int) -> int:
'''
The Baby-Step Giant-Step algorithm computes the
discrete logarithm (or order) of an element in a
finite abelian group.
Specifically, for a generator g of a finite abelian
group G order n, and an element h in G, the BSGS
algorithm returns the integer x such that g^x = h.
For example, if G = Z_n the cyclic group order n then:
bsgs solves g^x = h (mod n) for x.
'''
# ensure g and h are reduced modulo n
g %= n
h %= n
# ignore trivial case
# NOTE: for the Pohlig-Hellman algorithm to work properly
# NOTE: BSGS must return 1 NOT 0 for bsgs(1, 1, n)
if g == h: return 1
m = isqrt(n) + 1 # ceil(sqrt(n))
# store g^j values in a hash table
H = {j: pow(g, j, n) for j in range(m)}
I = pow(g, -m, n)
y = h
for i in range(m):
for j in range(m):
if H[j] == y:
return i*m + j
y = (y * I) % n
return None # No Solutions
def factors_prod(pf: list[tuple[int, int]]) -> int:
return prod(p**e for (p, e) in pf)
def sph(g: int, h: int, pf: list[tuple[int, int]]) -> int:
'''
The Silver-Pohlig-Hellman algorithm for computing
discrete logarithms in finite abelian groups whose
order is a smooth integer.
NOTE: this works in the special case,
NOTE: but I can't get the general case to work :(
'''
# ignore the trivial case
if g == h:
return 1
R = len(pf) # number of prime factors
N = factors_prod(pf) # pf is the prime factorisation of N
print('N', N)
# Special Case: groups of prime-power order:
if R == 1:
(p, e) = pf[0]
x = 0
y = pow(g, p**(e-1), N)
for k in range(e):
# temporary variables for defining h_k
w = pow(g, -x, N)
# NOTE: by construction the order of h_k must divide p
h_k = pow(w*h, p**(e-1-k), N)
# apply BSGS to find d such that y^d = h_k
d = bsgs(y, h_k, N)
if d is None:
return None # No Solutions
x = x + (p**k)*d
return x
# General Case:
eqs = []
for i in range(R):
(p_i, e_i) = pf[i]
# phi = (p_i - 1)*(p_i**(e_i - 1)) # Euler totient
# pe = p_i**e_i
# P = (N//(p_i**e_i)) % phi # reduce mod phi by Euler's theorem
g_i = pow(g, N//(p_i**e_i), N)
h_i = pow(h, N//(p_i**e_i), N)
print("g and h", g_i, h_i)
print("p^e", p_i, e_i)
x_i = sph(g_i, h_i, [(p_i,e_i)])
print("x_i", x_i)
if x_i is None:
return None # No Solutions
eqs.append((x_i, p_i**e_i))
print()
# ignore the quotient, solve CRT for 0 <= x <= n-1
x = crt(eqs) # NOTE: forall integers k: Nk+x is also a solution
print('solution:', x)
print(eqs)
return x
if __name__ == '__main__':
result = sph(3, 11, [(2, 1),(7,1)])

View file

@ -0,0 +1,2 @@
from .pftrialdivision import trial_division
from .factordb import DBResult, FType, FCertainty

View file

@ -0,0 +1,161 @@
'''
Simple interface for https://factordb.com inspired by
https://github.com/ihebski/factordb
TODO:
1. Implement primality certificate generation, read this:
https://reference.wolfram.com/language/PrimalityProving/ref/ProvablePrimeQ.html
'''
import requests
from enum import Enum, StrEnum
_FDB_URI = 'https://factordb.com'
# Generated by https://www.asciiart.eu/text-to-ascii-art
# using "ANSI Shadow" font and "Box drawings double" border with 1 H. Padding
_BANNER = '''
cli by imbored
'''.strip()
# Enumeration of number types based on their factorisation
class FType(Enum):
Unit = 1
Composite = 2
Prime = 3
Unknown = 4
class FCertainty(Enum):
Certain = 1
Partial = 2
Unknown = 3
# FactorDB result status codes
class DBStatus(StrEnum):
C = 'C'
CF = 'CF'
FF = 'FF'
P = 'P'
PRP = 'PRP'
U = 'U'
Unit = 'Unit' # just for 1
N = 'N'
Add = '*'
def is_unknown(self) -> bool:
return (self in [DBStatus.U, DBStatus.N, DBStatus.Add])
def classify(self) -> tuple[FType, FCertainty]:
return _STATUS_MAP[self]
def msg_verbose(self) -> str:
return _STATUS_MSG_VERBOSE[self]
# Map of DB Status codes to their factorisation type and certainty
_STATUS_MAP = {
DBStatus.Unit: (FType.Unit, FCertainty.Certain),
DBStatus.C: (FType.Composite, FCertainty.Unknown),
DBStatus.CF: (FType.Composite, FCertainty.Partial),
DBStatus.FF: (FType.Composite, FCertainty.Certain),
DBStatus.P: (FType.Prime, FCertainty.Certain),
DBStatus.PRP: (FType.Prime, FCertainty.Partial),
DBStatus.U: (FType.Unknown, FCertainty.Unknown),
DBStatus.N: (FType.Unknown, FCertainty.Unknown),
DBStatus.Add: (FType.Unknown, FCertainty.Unknown),
}
# Reference: https://factordb.com/status.html
# NOTE: my factor messages differ slightly from the reference
_STATUS_MSG_VERBOSE = {
DBStatus.Unit: 'Unit, trivial factorisation',
DBStatus.C: 'Composite, no factors known',
DBStatus.CF: 'Composite, *partially* factors',
DBStatus.FF: 'Composite, fully factored',
DBStatus.P: 'Prime',
DBStatus.PRP: 'Probable prime',
DBStatus.U: 'Unknown (*but in database)',
DBStatus.N: 'Not in database (-not added due to your settings)',
DBStatus.Add: 'Not in database (+added during request)',
}
# Struct for storing database results with named properties
class DBResult:
def __init__(self,
status: DBStatus,
factors: tuple[tuple[int, int]]) -> None:
self.status = status
self.factors = factors
self.ftype, self.certainty = self.status.classify()
def _make_cookie(fdbuser: str | None) -> dict[str, str]:
return {} if fdbuser is None else {'fdbuser': fdbuser}
def _get_key(by_id: bool):
return 'id' if by_id else 'query'
def _api_query(n: int,
fdbuser: str | None,
by_id: bool = False) -> requests.models.Response:
key = _get_key(by_id)
uri = f'{_FDB_URI}/api?{key}={n}'
return requests.get(uri, cookies=_make_cookie(fdbuser))
def _report_factor(n: int,
factor: int,
fdbuser: str | None,
by_id: bool = False) -> requests.models.Response:
key = _get_key(by_id)
uri = f'{_FDB_URI}/reportfactor.php?{key}={n}&factor={factor}'
return requests.get(uri, cookies=_make_cookie(fdbuser))
'''
Attempts a query to FactorDB, returns a DBResult object
on success, or None if the request failed due to the
get request raising a RequestException.
'''
def query(n: int,
token: str | None = None,
by_id: bool = False,
cli: bool = False) -> DBResult | None:
if cli:
print(_BANNER)
try:
resp = _api_query(n, token, by_id=by_id)
except requests.exceptions.RequestException:
return None
content = resp.json()
result = DBResult(
DBStatus(content['status']),
tuple((int(F[0]), F[1]) for F in content['factors'])
)
if cli:
print(f'Status: {result.status.msg_verbose()}')
print(result.factors)
# ensure the unit has the trivial factorisation (for consistency)
if result.status == DBStatus.Unit:
result.factors = ((1, 1),)
return result
'''
Reports a known factor to FactorDB, also tests it is
actually a factor to avoid wasting FactorDBs resources.
'''
def report(n: int,
factor: int,
by_id: int,
token: str | None = None) -> None:
try:
resp = _report_factor(n, factor, token)
except requests.exceptions.RequestException:
return None
content = resp.json()
print(content)

View file

@ -0,0 +1,46 @@
'''
The trial division algorithm is essentially the idea that
all factors of an integer n are less than or equal to isqrt(n),
where isqrt is floor(sqrt(n)).
Moreover, if p divides n, then all other factors of n must
be factors of n//p. Hence they must be <= isqrt(n//p).
'''
from math import isqrt # integer square root
# Returns the "multiplicity" of a prime factor
def pf_multiplicity(n: int, p: int) -> int:
mult = 0
while n % p == 0:
n //= p
mult += 1
return n, mult
'''
Trial division prime factorisation algorithm.
Returns a list of tuples (p, m) where p is
a prime factor and m is its multiplicity.
'''
def trial_division(n: int) -> list[tuple[int, int]]:
factors = []
# determine multiplicity of the only even prime (2)
n, mult_2 = pf_multiplicity(n, 2)
if mult_2: factors.append((2, mult_2))
# determine odd factors and their multiplicities
p = 3
mult = 0
limit = isqrt(n)
while p <= limit:
n, mult = pf_multiplicity(n, p)
if mult:
factors.append((p, mult))
limit = isqrt(n) # recalculate limit
mult = 0 # reset
else:
p += 2
# if n is still greater than 1, then n is a prime factor
if n > 1:
factors.append((n, 1))
return factors

View file

@ -6,7 +6,7 @@ Terminology:
the "prime proper divisors of n".
'''
from imp.extern.primefac import primefac
from celeste.extern.primefac import primefac
def factors(n: int) -> int:
pfactors: list[tuple[int, int]] = []

View file

@ -0,0 +1,21 @@
def euclidean_algo(a: int, b: int) -> int:
while b: a, b = b, a % b
return a
'''
Calculates coefficients x and y of Bezout's identity: ax + by = gcd(a,b)
NOTE: Based on the Extended Euclidean Algorithm's Wikipedia page
'''
def extended_euclid_algo(a: int, b: int) -> tuple[int, int]:
(old_r, r) = (a, b)
(old_s, s) = (1, 0)
(old_t, t) = (0, 1)
while r != 0:
q = old_r // r
(old_r, r) = (r, old_r - q*r)
(old_s, s) = (s, old_s - q*s)
(old_t, t) = (t, old_t - q*t)
# Bezout cofficients: (old_s, old_t)
# Greatest Common Divisor: old_r
# Quotients by the gcd: (t, s)
return (t, s)

View file

@ -1,7 +1,7 @@
from math import gcd, inf
from imp.math.numbers import bigomega, factors
from imp.extern.primefac import (
from celeste.math.numbers import bigomega, factors
from celeste.extern.primefac import (
isprime,
primegen as Primes,
)

View file

@ -0,0 +1,48 @@
from math import inf, isqrt # integer square root
from itertools import takewhile, compress
SMALL_PRIMES = (2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59)
'''
Euler's Totient (Phi) Function
Implemented in O(nloglog(n)) using the Sieve of Eratosthenes.
'''
def eulertotient(n: int) -> int:
phi = int(n > 1 and n)
for p in range(2, isqrt(n) + 1):
if not n % p:
phi -= phi // p
while not n % p:
n //= p
#if n is > 1 it means it is prime
if n > 1: phi -= phi // n
return phi
'''
Tests the primality of an integer using its totient.
NOTE: If totient(n) has already been calculated
then pass it as the optional phi parameter.
'''
def is_prime(n: int, phi: int = None) -> bool:
return n - 1 == (phi if phi is not None else eulertotient(n))
# Taken from Lucas A. Brown's primefac.py (some variables renamed)
def primegen(limit=inf) -> int:
ltlim = lambda x: x < limit
yield from takewhile(ltlim, SMALL_PRIMES)
pl, prime = [3,5,7], primegen()
for p in pl: next(prime)
n = next(prime); nn = n*n
while True:
n = next(prime); ll, nn = nn, n*n
delta = nn - ll
sieve = bytearray([True]) * delta
for p in pl:
k = (-ll) % p
sieve[k::p] = bytearray([False]) * ((delta-k)//p + 1)
if nn > limit: break
yield from compress(range(ll,ll+delta,2), sieve[::2])
pl.append(n)
yield from takewhile(ltlim, compress(range(ll,ll+delta,2), sieve[::2]))

29
celeste/math/util.py Normal file
View file

@ -0,0 +1,29 @@
from collections.abc import Iterable
from itertools import chain, combinations
def clamp(n: int, min: int, max: int) -> int:
if n < min:
return min
elif n > max:
return max
return n
def clamp_max(n: int, max: int) -> int:
return max if n > max else n
def clamp_min(n: int, max: int) -> int:
return min if n < min else n
def digits(n: int) -> int:
return len(str(n))
# NOTE: assumes A and B are equal length
def xor_bytes(A: bytes, B: bytes) -> bytes:
return b''.join([(a ^ b).to_bytes() for (a, b) in zip(A, B)])
def xor_str(A: str, B: str) -> str:
return ''.join([chr(ord(a) ^ ord(b)) for (a, b) in zip(A, B)])
def powerset(iterable: Iterable) -> Iterable:
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))

4
config.nims Normal file
View file

@ -0,0 +1,4 @@
# begin Nimble config (version 2)
when withDir(thisDir(), system.fileExists("nimble.paths")):
include "nimble.paths"
# end Nimble config

View file

@ -0,0 +1,92 @@
'''
Solution for https://cryptohack.org/courses/symmetric/bean_counter/
'''
import os
import requests
from math import ceil
from Crypto.Cipher import AES
from celeste.math.util import xor_bytes
class StepUpCounter(object):
def __init__(self, step_up=False):
self.value = os.urandom(16).hex()
self.step = 1
self.stup = step_up
def increment(self):
if self.stup:
self.newIV = hex(int(self.value, 16) + self.step)
else:
self.newIV = hex(int(self.value, 16) - self.stup)
self.value = self.newIV[2:len(self.newIV)]
return bytes.fromhex(self.value.zfill(32))
def __repr__(self):
self.increment()
return self.value
'''
NOTE: Since step_up is used ONLY as false, we can simplify:
class StepUpCounter(object):
def __init__(self):
self.value = os.urandom(16).hex()
# SAME AS DOING NOTHING
def increment(self):
return self.value()
def __repr__(self):
return self.value
'''
URL = 'https://aes.cryptohack.org/bean_counter'
def get_encrypted() -> bytes:
resp = requests.get(f'{URL}/encrypt/')
encrypted = bytes.fromhex(resp.json()['encrypted'])
return encrypted
PNG_HEADER = [
'89', '50', '4e', '47',
'0d', '0a', '1a', '0a',
'00', '00', '00', '0d',
'49', '48', '44', '52'
]
def main() -> None:
# NOTE: the counter is constant!
ctr = StepUpCounter()
# init = ctr.increment()
# init_val = ctr.value
# for i in range(2560000):
# if ctr.increment() != init:
# print('Counter Changed')
# print(i)
# break
# elif ctr.value != init_val:
# print('valued changed')
# print(i)
# break
# PNGs *should* have a constant header, we will use the first 16 bytes
# to determine what the counter value must be set to
encrypted = get_encrypted()
png_header = bytes.fromhex(''.join(PNG_HEADER))
ctr_value = xor_bytes(encrypted[:16], png_header)
# apply the counter value to the entire encrypted image (in blocks of 16 bytes)
BLOCK_SIZE = 16
with open('bean_flag.png', 'wb') as f:
for i in range(ceil(len(encrypted) / BLOCK_SIZE)):
block = encrypted[i*BLOCK_SIZE : (i+1)*BLOCK_SIZE]
# NOTE: the block we get is not guaranteed to be block size (ie if at end)
plaintext_block = xor_bytes(block, ctr_value[:len(block)])
f.write(plaintext_block)
if __name__ == '__main__':
try:
main()
except (KeyboardInterrupt, EOFError):
print('\n[!] Interrupt')

View file

@ -0,0 +1,55 @@
'''
Solution to https://cryptohack.org/courses/symmetric/flipping_cookie/
'''
import requests
from datetime import datetime, timedelta
URL = 'https://aes.cryptohack.org/flipping_cookie'
# NOTE: assumes A and B are equal length
def xor_bytes(A: bytes, B: bytes) -> bytes:
return b''.join([(a ^ b).to_bytes() for (a, b) in zip(A, B)])
def xor_str(A: str, B: str) -> str:
return ''.join([chr(ord(a) ^ ord(b)) for (a, b) in zip(A, B)])
def gen_expiry() -> str:
return (datetime.today() + timedelta(days=1)).strftime("%s")
def get_cookie() -> tuple[bytes, bytes]:
resp = requests.get(f'{URL}/get_cookie/')
cookie = resp.json()['cookie']
iv = bytes.fromhex(cookie[:32])
ciphertext = bytes.fromhex(cookie[32:])
return iv, ciphertext
def main() -> None:
# cookie flipping preprocessing step
admin_len = len('admin=')
expiry_len = len(';expiry=') + len(gen_expiry())
admin_mask = '\x00' * admin_len
expiry_mask = '\x00' * expiry_len
# we aim to replace "admin=False;" with "admin=True;;"
# NOTE: double semicolon ("True;;") is intentional
# NOTE: and the server won't ever realise it happened!
deletion = admin_mask + 'False' + expiry_mask
insertion = admin_mask + 'True;' + expiry_mask
# determine the value that replaces deletion with insertion
cookie_flip = xor_str(deletion, insertion)
# get our new cookie and apply the cookie flip!
iv, ciphertext = get_cookie()
flipped_iv = xor_bytes(cookie_flip.encode(), iv)
print('Flipped Cookie:')
print('IV:', flipped_iv.hex())
print('Body:', ciphertext.hex())
if __name__ == '__main__':
main()

39
ctf-solutions/symmetry.py Normal file
View file

@ -0,0 +1,39 @@
'''
Solution to https://cryptohack.org/courses/symmetric/symmetry/
'''
import os
import requests
from celeste.math.util import xor_bytes, xor_str
URL = 'https://aes.cryptohack.org/symmetry'
def get_encrypted_flag() -> tuple[bytes, bytes]:
resp = requests.get(f'{URL}/encrypt_flag/')
ciphertext = resp.json()['ciphertext']
iv = bytes.fromhex(ciphertext[:32])
flag = bytes.fromhex(ciphertext[32:])
return iv, flag
def encrypt(plaintext: bytes, iv: bytes) -> bytes:
plaintext, iv = plaintext.hex(), iv.hex()
resp = requests.get(f'{URL}/encrypt/{plaintext}/{iv}')
return bytes.fromhex(resp.json()['ciphertext'])
def main() -> None:
iv, flag = get_encrypted_flag()
# generate a random plaintext of equal length to the flag
random_plaintext = os.urandom(len(flag))
random_ciphertext = encrypt(random_plaintext, iv)
# XOR random plaintext with corresponding ciphertext to
# get the set generated by IV under the keyed AES permutation
aes_orbit = xor_bytes(random_plaintext, random_ciphertext)
# XOR this orbit with the flag to get the hexed flag plaintext
flag_plaintext = xor_bytes(flag, aes_orbit)
print('Flag:', flag_plaintext.decode())
print('IV:', iv.hex())
if __name__ == '__main__':
main()

10
default.nix Normal file
View file

@ -0,0 +1,10 @@
let
sources = import ./nix/sources.nix;
pkgs = import sources.nixpkgs {};
# Let all API attributes like "poetry2nix.mkPoetryApplication"
# use the packages and versions (python3, poetry etc.) from our pinned nixpkgs above
# under the hood:
poetry2nix = import sources.poetry2nix {inherit pkgs;};
myPythonApp = poetry2nix.mkPoetryApplication {projectDir = ./.;};
in
myPythonApp

View file

@ -0,0 +1,46 @@
import requests
from celeste.constants import PRINTABLE
from celeste.attacks import paddingoracle
from Crypto.Util.Padding import pad
import string
NIBBLESET = [n.to_bytes() for n in range(256)]
HOST = 'https://aes.cryptohack.org'
ENDPOINT = '/ecb_oracle/encrypt/{0}/'
URI = HOST + ENDPOINT
CHARSET = [c.encode() for c in string.printable]
# CHARSET = NIBBLESET # OVERRIDE
FLAG_LEN = 26 # flag length, calculated by hand
SPACER = b'\x0f' # arbitrary spacing character
BLOCK_BYTES = 16
BLOCK_NIBBLES = 32 # measured in nibbles
def mkreq(hextext: str) -> dict[str, str]:
resp = requests.get(URI.format(hextext))
if resp.status_code != 200:
raise Exception(f'[!] resp failed! {resp} : {resp.text}')
return resp.json()
def encrypt(b: bytes) -> bytes:
resp = mkreq(b.hex())
return bytes.fromhex(resp['ciphertext'])
def main() -> None:
paddingoracle.crack(encrypt, pad, CHARSET, 16, batch_size=20, debug=True)
if __name__ == '__main__':
try:
main()
except (KeyboardInterrupt, EOFError):
print('\n[!] Interrupt')

View file

@ -0,0 +1,28 @@
import string
from celeste.attacks import paddingoracle
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
CHARSET = [c.encode() for c in string.printable]
KEY = b'you wont get me!'
FLAG = b'imbaud{omg_you_catched_me}'
CIPHER = AES.new(KEY, AES.MODE_ECB)
def encrypt(b: bytes, debug=False) -> bytes:
padded = pad(b + FLAG, 16)
if debug:
print(padded)
# print(padded)
return CIPHER.encrypt(padded)
def main() -> None:
paddingoracle.crack(encrypt, pad, CHARSET, 16, batch_size=50, debug=True)
if __name__ == '__main__':
try:
main()
except (KeyboardInterrupt, EOFError):
print('\n[!] Interrupt')

View file

@ -1,9 +0,0 @@
from collections.abc import Iterable
from itertools import chain, combinations
def digits(n: int) -> int:
return len(str(n))
def powerset(iterable: Iterable) -> Iterable:
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))

220
poetry.lock generated Normal file
View file

@ -0,0 +1,220 @@
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
[[package]]
name = "certifi"
version = "2025.6.15"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.7"
files = [
{file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"},
{file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"},
]
[[package]]
name = "charset-normalizer"
version = "3.4.2"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7"
files = [
{file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"},
{file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"},
{file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"},
{file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"},
{file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"},
{file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"},
{file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"},
{file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"},
{file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"},
{file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"},
{file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"},
{file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"},
{file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"},
{file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"},
{file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"},
{file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"},
{file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"},
{file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"},
{file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"},
{file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"},
{file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"},
{file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"},
{file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"},
{file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"},
{file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"},
{file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"},
{file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"},
{file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"},
{file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"},
{file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"},
{file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"},
{file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"},
{file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"},
]
[[package]]
name = "idna"
version = "3.10"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.6"
files = [
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
]
[package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "pycryptodome"
version = "3.23.0"
description = "Cryptographic library for Python"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "pycryptodome-3.23.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a176b79c49af27d7f6c12e4b178b0824626f40a7b9fed08f712291b6d54bf566"},
{file = "pycryptodome-3.23.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:573a0b3017e06f2cffd27d92ef22e46aa3be87a2d317a5abf7cc0e84e321bd75"},
{file = "pycryptodome-3.23.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:63dad881b99ca653302b2c7191998dd677226222a3f2ea79999aa51ce695f720"},
{file = "pycryptodome-3.23.0-cp27-cp27m-win32.whl", hash = "sha256:b34e8e11d97889df57166eda1e1ddd7676da5fcd4d71a0062a760e75060514b4"},
{file = "pycryptodome-3.23.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7ac1080a8da569bde76c0a104589c4f414b8ba296c0b3738cf39a466a9fb1818"},
{file = "pycryptodome-3.23.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6fe8258e2039eceb74dfec66b3672552b6b7d2c235b2dfecc05d16b8921649a8"},
{file = "pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4"},
{file = "pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae"},
{file = "pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477"},
{file = "pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7"},
{file = "pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446"},
{file = "pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265"},
{file = "pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b"},
{file = "pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d"},
{file = "pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a"},
{file = "pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625"},
{file = "pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39"},
{file = "pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27"},
{file = "pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843"},
{file = "pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490"},
{file = "pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575"},
{file = "pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b"},
{file = "pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a"},
{file = "pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f"},
{file = "pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa"},
{file = "pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886"},
{file = "pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2"},
{file = "pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c"},
{file = "pycryptodome-3.23.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:350ebc1eba1da729b35ab7627a833a1a355ee4e852d8ba0447fafe7b14504d56"},
{file = "pycryptodome-3.23.0-pp27-pypy_73-win32.whl", hash = "sha256:93837e379a3e5fd2bb00302a47aee9fdf7940d83595be3915752c74033d17ca7"},
{file = "pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379"},
{file = "pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4"},
{file = "pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630"},
{file = "pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353"},
{file = "pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5"},
{file = "pycryptodome-3.23.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:865d83c906b0fc6a59b510deceee656b6bc1c4fa0d82176e2b77e97a420a996a"},
{file = "pycryptodome-3.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d4d56153efc4d81defe8b65fd0821ef8b2d5ddf8ed19df31ba2f00872b8002"},
{file = "pycryptodome-3.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3f2d0aaf8080bda0587d58fc9fe4766e012441e2eed4269a77de6aea981c8be"},
{file = "pycryptodome-3.23.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64093fc334c1eccfd3933c134c4457c34eaca235eeae49d69449dc4728079339"},
{file = "pycryptodome-3.23.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ce64e84a962b63a47a592690bdc16a7eaf709d2c2697ababf24a0def566899a6"},
{file = "pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef"},
]
[[package]]
name = "requests"
version = "2.32.4"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.8"
files = [
{file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"},
{file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"},
]
[package.dependencies]
certifi = ">=2017.4.17"
charset_normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "urllib3"
version = "2.5.0"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.9"
files = [
{file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"},
{file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"},
]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "859d413e44bf90cb7b04f63effef25027c68025a706462c668c1ef0d3cb3cb1f"

10
primegen.nim Normal file
View file

@ -0,0 +1,10 @@
iterator primegen(limit: int = high(BiggestInt)): int:
"""
Generates primes < limit almost lazily by a segmented sieve of Eratosthenes.
Examples:
>>> primegen().take(20)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]
>>> primegen(73).toSeq()
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]
"""

15
pyproject.toml Normal file
View file

@ -0,0 +1,15 @@
[tool.poetry]
name = "Celeste"
version = "0.1.0"
description = ""
authors = ["Emile Clark-Boman <eclarkboman@gmail.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
requests = "^2.32.4"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

12
research/aparith/README Normal file
View file

@ -0,0 +1,12 @@
Comparing the speeds of various Nim libraries for
arbitrary precision integers.
Test Targets:
https://github.com/status-im/nim-stint
https://github.com/nim-lang/bigints
https://github.com/michaeljclark/bignum
https://github.com/FedeOmoto/bignum
https://github.com/fsh/integers
Test algorithms:
https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm#Algorithm

BIN
research/aparith/aparith Executable file

Binary file not shown.

View file

@ -0,0 +1,14 @@
# Package
version = "1.0.0"
author = "Emile Clark-Boman"
description = "Arbitrary Precision Integer Test - BigInts"
license = "MIT"
srcDir = "src"
bin = @["aparith", "speedtest_bigint"]
# Dependencies
requires "nim >= 2.2.0"
requires "bigints >= 1.0.0"

View file

@ -0,0 +1,5 @@
# This is just an example to get you started. A typical binary package
# uses this file as the main entry point of the application.
when isMainModule:
echo("Hello, World!")

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,17 @@
# This is an include file, do not import it directly.
# It is needed as a workaround for Nim's parser for versions <= 1.4.
proc `'bi`*(s: string): BigInt =
## Create a `BigInt` from a literal, using the suffix `'bi`.
runnableExamples:
let
a = 123'bi
b = 0xFF'bi
c = 0b1011'bi
assert $a == "123"
assert $b == "255"
assert $c == "11"
case s[0..min(s.high, 1)]
of "0x", "0X": initBigInt(s[2..s.high], base = 16)
of "0b", "0B": initBigInt(s[2..s.high], base = 2)
else: initBigInt(s)

View file

@ -0,0 +1,43 @@
import ../bigints
import std/sequtils
import std/options
import std/random
func rand*(r: var Rand, x: Slice[BigInt]): BigInt =
## Return a random `BigInt`, within the given range, using the given state.
assert(x.a <= x.b, "invalid range")
let
spread = x.b - x.a
# number of bits *not* including leading bit
nbits = spread.fastLog2
# number of limbs to generate completely randomly
nFullLimbs = max(nbits div 32 - 1, 0)
# highest possible value of the top two limbs.
hi64Max = (spread shr (nFullLimbs*32)).toInt[:uint64].get()
while true:
# these limbs can be generated completely arbitrarily
var limbs = newSeqWith(nFullLimbs, r.rand(uint32.low..uint32.high))
# generate the top two limbs more carefully. This all but guarantees
# that the entire number is in the correct range
let hi64 = r.rand(uint64.low..hi64Max)
limbs.add(cast[uint32](hi64))
limbs.add(cast[uint32](hi64 shr 32))
result = initBigInt(limbs)
if result <= spread:
break
result += x.a
func rand*(r: var Rand, max: BigInt): BigInt =
## Return a random non-negative `BigInt`, up to `max`, using the given state.
rand(r, 0.initBigInt..max)
# backwards compatibility with 1.4
when not defined(randState):
var state = initRand(777)
proc randState(): var Rand = state
proc rand*(x: Slice[BigInt]): BigInt = rand(randState(), x)
## Return a random `BigInt`, within the given range.
proc rand*(max: BigInt): BigInt = rand(randState(), max)
## Return a random `BigInt`, up to `max`.

Binary file not shown.

View file

@ -0,0 +1,35 @@
import bigints
import std/[options, times]
func g(x: BigInt, n: BigInt): BigInt {.inline.} =
result = (x*x + 1.initBigInt) mod n
func pollardRho(n: BigInt): Option[BigInt] =
var
x: BigInt = 2.initBigInt
y: BigInt = n
d: BigInt = 1.initBigInt
while d == 1.initBigInt:
x = g(x, n)
y = g(g(y, n), n)
d = gcd(abs(x - y), n)
if d == n:
return none(BigInt)
result = some(d)
when isMainModule:
let
# num = 535006138814359.initBigInt
# num = 12.initBigInt
num = 976043389537.initBigInt * 270351207761773.initBigInt
time = cpuTime()
divisor = pollardRho(num)
elapsed = cpuTime() - time
echo "Time taken: ", elapsed
if divisor.isSome:
echo "Result: ", divisor.get()
else:
echo "Result: None(BigInt)"

7
src/imp.nim Normal file
View file

@ -0,0 +1,7 @@
# This is just an example to get you started. A typical library package
# exports the main API in this file. Note that you cannot rename this file
# but you can remove it if you wish.
proc add*(x, y: int): int =
## Adds two numbers together.
return x + y

12
src/imp/submodule.nim Normal file
View file

@ -0,0 +1,12 @@
# This is just an example to get you started. Users of your library will
# import this file by writing ``import celeste/submodule``. Feel free to rename or
# remove this file altogether. You may create additional modules alongside
# this file as required.
type
Submodule* = object
name*: string
proc initSubmodule*(): Submodule =
## Initialises a new ``Submodule`` object.
Submodule(name: "Anonymous")