├── requirements.txt ├── factor └── __init__.py ├── .gitignore ├── examples ├── hsctf_8_regulus_calendula │ ├── flag.txt │ ├── README.md │ ├── solve.py │ └── server.py ├── seccon_2020_this_is_rsa │ ├── README.md │ └── solve.py └── factor_cui.rs ├── setup.py ├── src ├── factor │ ├── mod.rs │ ├── dfs.rs │ └── bfs.rs ├── util.rs └── lib.rs ├── Dockerfile ├── Cargo.toml ├── tests └── test.py ├── LICENSE-MIT ├── benches └── bench.rs ├── README.md ├── LICENSE-APACHE └── Cargo.lock /requirements.txt: -------------------------------------------------------------------------------- 1 | setuptools-rust>=0.12.1 2 | -------------------------------------------------------------------------------- /factor/__init__.py: -------------------------------------------------------------------------------- 1 | from .factor import from_str, from_vector 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | factor.egg-info 3 | build 4 | dist 5 | __pycache__ 6 | *.so 7 | -------------------------------------------------------------------------------- /examples/hsctf_8_regulus_calendula/flag.txt: -------------------------------------------------------------------------------- 1 | flag{P0g_Po5_pOG_i_Sh0Ok_mY-pHoN3_t0_5O_kMs_leTs_goOOoO0oO} 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from setuptools_rust import RustExtension 3 | 4 | setup( 5 | name="factor", 6 | version="0.1.0", 7 | classifiers=[ 8 | "License :: OSI Approved :: MIT License", 9 | "Programming Language :: Python", 10 | "Programming Language :: Rust", 11 | "Operating System :: POSIX", 12 | ], 13 | packages=["factor"], 14 | rust_extensions=[RustExtension("factor.factor", "Cargo.toml", debug=False)], 15 | include_package_data=True, 16 | zip_safe=False, 17 | ) 18 | -------------------------------------------------------------------------------- /src/factor/mod.rs: -------------------------------------------------------------------------------- 1 | mod bfs; 2 | mod dfs; 3 | 4 | use rug::Integer; 5 | 6 | use crate::factor::bfs::factor_bfs; 7 | use crate::factor::dfs::factor_dfs; 8 | 9 | pub fn factor_core( 10 | n: &Integer, 11 | p_bits: Vec, 12 | q_bits: Vec, 13 | bit_len: usize, 14 | verbose: bool, 15 | search: String, 16 | ) -> Option<(Integer, Integer)> { 17 | match search.as_str() { 18 | "dfs" => factor_dfs(n, p_bits, q_bits, bit_len, verbose), 19 | _ => factor_bfs(n, p_bits, q_bits, bit_len, verbose), 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/hsctf_8_regulus_calendula/README.md: -------------------------------------------------------------------------------- 1 | # HSCTF 8 regulus-calendula 2 | ## Run challenge server 3 | 4 | ```bash 5 | socat tcp4-listen:1337,reuseaddr,fork "system:python3 server.py" 6 | ``` 7 | This `server.py` is modified to use fixed primes generated in advance because `getPrime(4096)` takes too much time... 8 | 9 | ## Writeup 10 | 11 | The payload `88...88`, `99...99`, ..., `ff...ff` reveals about the half of the bits of `p` and `q`. We also know that unknown positions' MSB is 0. So over 50% bits are known. This is enough to factor. 12 | 13 | ## Run solver 14 | 15 | ```bash 16 | python solver.py 17 | ``` 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-buster as builder 2 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/install_rust.sh && sh /tmp/install_rust.sh -y && rm /tmp/install_rust.sh 3 | WORKDIR /opt/app 4 | COPY setup.py /opt/app/setup.py 5 | COPY requirements.txt /opt/app/requirements.txt 6 | COPY Cargo.toml /opt/app/Cargo.toml 7 | COPY factor /opt/app/factor 8 | COPY src /opt/app/src 9 | RUN bash -c "source $HOME/.cargo/env && pip install -r requirements.txt && python setup.py install" 10 | 11 | FROM python:3.9-slim-buster as runner 12 | COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages 13 | CMD python 14 | -------------------------------------------------------------------------------- /examples/seccon_2020_this_is_rsa/README.md: -------------------------------------------------------------------------------- 1 | # SECCON 2020 This is RSA 2 | 3 | ```ruby 4 | require 'openssl' 5 | 6 | def get_prime 7 | i = OpenSSL::BN.rand(512).to_s.unpack1('H*').hex 8 | OpenSSL::BN.new(i).prime? ? i : get_prime 9 | end 10 | 11 | p = get_prime 12 | q = get_prime 13 | n = p * q 14 | e = 65537 15 | m = File.read('flag.txt').unpack1('H*').hex 16 | c = m.pow(e, n) 17 | 18 | puts "N = #{n}" 19 | puts "c = #{c}" 20 | ``` 21 | 22 | ## Writeup 23 | 24 | `OpenSSL::BN.rand(512).to_s.unpack1('H*').hex` returns `0x3_3_3_...`. 25 | So we know that `p` and `q` are `"...0011____0011____...0011____"` in binary. 26 | 27 | ## Run solver 28 | 29 | ```bash 30 | python solver.py 31 | ``` 32 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "factor" 3 | version = "0.1.0" 4 | authors = ["y011d4 "] 5 | edition = "2018" 6 | license = "MIT or Apache-2.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [lib] 11 | name = "factor" 12 | path = "src/lib.rs" 13 | crate-type = ["lib", "cdylib"] 14 | 15 | [[example]] 16 | name = "factor_cui" 17 | path = "examples/factor_cui.rs" 18 | 19 | [dependencies] 20 | num-bigint = "0.4.0" 21 | once_cell = "1.8.0" 22 | concurrent-queue = "1.2.2" 23 | ctrlc = "3.1.9" 24 | 25 | [dependencies.pyo3] 26 | version = "0.14.1" 27 | features = ["extension-module", "num-bigint"] 28 | 29 | [dependencies.rug] 30 | version = "1.12.0" 31 | default-features = false 32 | features = ["integer"] 33 | -------------------------------------------------------------------------------- /examples/factor_cui.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | use rug::Integer; 3 | use std::env; 4 | extern crate test; 5 | 6 | use factor::factor::factor_core; 7 | use factor::util::str_to_vec; 8 | 9 | fn main() { 10 | let args: Vec = env::args().collect(); 11 | let n: Integer = args[1].parse().unwrap(); 12 | let p_bits_str = args[2].as_bytes(); 13 | let q_bits_str = args[3].as_bytes(); 14 | let bit_len = p_bits_str.len().max(q_bits_str.len()); 15 | let p_bits = str_to_vec(p_bits_str, bit_len); 16 | let q_bits = str_to_vec(q_bits_str, bit_len); 17 | assert_eq!(p_bits.len(), q_bits.len()); 18 | match factor_core(&n, p_bits, q_bits, bit_len, false, "bfs".to_string()) { 19 | Some((p, q)) => println!("{}, {}", p, q), 20 | None => println!("Not found"), 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import factor 4 | 5 | 6 | class TestFactor(unittest.TestCase): 7 | def test_from_vector(self): 8 | p_q = factor.from_vector(91, [1, 1, 0, -1], [0, -1, -1, 1]) 9 | assert p_q is not None 10 | p, q = p_q 11 | assert p == 13 12 | assert q == 7 13 | 14 | # obviously fail 15 | p_q = factor.from_vector(1337, [0, 0], [0, 0]) 16 | assert p_q is None 17 | 18 | def test_from_str(self): 19 | p_q = factor.from_str(91, "110_", "0__1") 20 | assert p_q is not None 21 | p, q = p_q 22 | assert p == 13 23 | assert q == 7 24 | 25 | # obviously fail 26 | p_q = factor.from_str(1337, "00", "00") 27 | assert p_q is None 28 | 29 | 30 | if __name__ == "__main__": 31 | unittest.main() 32 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 y011d4 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/hsctf_8_regulus_calendula/solve.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from pwn import remote 4 | 5 | import factor 6 | 7 | 8 | e = 0x10001 9 | 10 | _r = remote("localhost", 1337) 11 | 12 | bit_len = 4096 13 | 14 | _r.sendlineafter(": ", "2") 15 | _r.recvline() 16 | n = int(_r.recvline().split()[2]) 17 | 18 | # guess 19 | p_mask = {} 20 | for c in "fedcba98": 21 | _r.sendlineafter(": ", "4") 22 | _r.sendlineafter(": ", c * (bit_len // 4)) 23 | ret = _r.recvline().strip().decode() 24 | p_mask[c] = ret 25 | q_mask = {} 26 | for c in "fedcba98": 27 | _r.sendlineafter(": ", "4") 28 | _r.sendlineafter(": ", c * (bit_len // 4)) 29 | ret = _r.recvline().strip().decode() 30 | q_mask[c] = ret 31 | 32 | 33 | # generate bits string describing known bits 34 | p_known = [False] * bit_len 35 | q_known = [False] * bit_len 36 | p_sim = 0 37 | q_sim = 0 38 | for i, c in enumerate("fedcba98"): 39 | for j, b in enumerate(p_mask[c][::-1]): 40 | if b == "1": 41 | for k in range(4): 42 | p_known[j * 4 + k] = True 43 | p_sim += 16 ** j * (15 - i) 44 | for i, c in enumerate("fedcba98"): 45 | for j, b in enumerate(q_mask[c][::-1]): 46 | if b == "1": 47 | for k in range(4): 48 | q_known[j * 4 + k] = True 49 | q_sim += 16 ** j * (15 - i) 50 | # 8未満なので 51 | for i in range(3, bit_len, 4): 52 | p_known[i] = True 53 | q_known[i] = True 54 | p_bits_str = "".join( 55 | map(lambda x: str(x[0]) if x[1] else "_", zip(bin(p_sim)[2:], p_known[::-1])) 56 | ) 57 | q_bits_str = "".join( 58 | map(lambda x: str(x[0]) if x[1] else "_", zip(bin(q_sim)[2:], q_known[::-1])) 59 | ) 60 | 61 | now = time.perf_counter() 62 | p_q = factor.from_str(n, p_bits_str, q_bits_str, verbose=False) 63 | print(time.perf_counter() - now) 64 | assert p_q is not None 65 | p, q = p_q 66 | phi = (p - 1) * (q - 1) 67 | d = pow(e, -1, phi) 68 | 69 | _r.sendlineafter(": ", "3") 70 | _r.sendlineafter(": ", str(d)) 71 | _r.recvline() 72 | print(_r.recvline()) 73 | 74 | _r.close() 75 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate test; 3 | use rug::Integer; 4 | use test::Bencher; 5 | 6 | use factor::factor::factor_core; 7 | use factor::util::str_to_vec; 8 | 9 | #[bench] 10 | fn bench_factor(b: &mut Bencher) { 11 | // 110,366,524 ns/iter (+/- 8,439,610) 12 | // 8,601,455 ns/iter (+/- 263,670) bits_to_num に saved_p を導入 13 | // 7,060,782 ns/iter (+/- 606,827) 細かい部分で Integer を再利用 14 | let n: Integer = "104158954646372695568095796310479805403678314919693272509836778997179683485437763692891984254171869987446475357518587344178264028334102088429629785065036660148146855007349113784322098795994839040721664806905084554147298456659074384855277678993200563966327086005547016327991986225930798076081014377904788085807".parse().unwrap(); 15 | let bit_len = 512; 16 | let p_bits = str_to_vec("1010101111000NNN11000NNN11100NNN11010NNN0NNN0NNN0NNN100110000NNN0NNN0NNN0NNN11000NNN0NNN0NNN110110010NNN11001100100111010NNN100011000NNN0NNN0NNN0NNN11111000111111100NNN1101110010000NNN0NNN0NNN0NNN10110NNN0NNN0NNN0NNN0NNN0NNN1100101111000NNN0NNN1001111011110NNN0NNN10000NNN0NNN0NNN11010NNN1010101110110NNN0NNN0NNN0NNN0NNN10010NNN1011101011100NNN110111010NNN0NNN0NNN0NNN101010110NNN0NNN10000NNN1000101011000NNN0NNN0NNN0NNN101010000NNN11010NNN111010000NNN0NNN11110NNN0NNN10010NNN111010010NNN0NNN0NNN10100NNN0NNN0NNN".as_bytes(), bit_len); 17 | let q_bits = str_to_vec("110111010NNN111011110NNN0NNN1000100110001110100111100NNN0NNN10110NNN11000NNN0NNN10110NNN11100NNN10000NNN0NNN0NNN11111100110010100NNN10000NNN11100NNN0NNN110010110NNN101110010NNN10010NNN11110NNN11110NNN0NNN1101111011000NNN101010110NNN10100NNN0NNN10100NNN1010101011010NNN0NNN0NNN100110110NNN0NNN10000NNN0NNN0NNN1000101110010NNN1111110010110NNN0NNN0NNN0NNN101110100NNN0NNN1100101111000NNN10100NNN0NNN0NNN0NNN0NNN0NNN0NNN10010NNN0NNN0NNN10100NNN10010NNN0NNN0NNN0NNN101011110NNN0NNN111110000NNN0NNN11110NNN0NNN10100NNN".as_bytes(), bit_len); 18 | b.iter(|| { 19 | factor_core( 20 | &n, 21 | p_bits.clone(), 22 | q_bits.clone(), 23 | bit_len, 24 | false, 25 | "bfs".to_string(), 26 | ) 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /examples/seccon_2020_this_is_rsa/solve.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import factor 4 | 5 | 6 | e = 65537 7 | N = 13234306273608973531555502334446720401597326792644624514228362685813698571322410829494757436628326246629203126562441757712029708148508660279739210512110734001019285095467352938553972438629039005820507697493315650840705745518918873979766056584458077636454673830866061550714002346318865318536544606580475852690351622415519854730947773248376978689711597597169469401661488756669849772658771813742926651925442468141895198767553183304485662688033274567173210826233405235701905642383704395846192587563843422713499468379304400363773291993404144432403315463931374682824546730098380872658106314368520370995385913965019067624762624652495458399359096083188938802975032297056646831904294336374652136926975731836556951432035301855715375295216481079863945383657 8 | c = 9094564357254217771457579638296343398667095069849711922513911147179424647045593821415928967849073271368133854458732106409023539482401316282328817488781771665657515880026432487444729168909088425021111879152492812216384426360971681055941907554538267523250780508925995498013624610554177330113234686073838491261974164065812534037687990653834520243512128393881497418722817552604416319729143988970277812550536939775865310487081108925130229024749287074763499871216498398695877450736179371920963283041212502898938555288461797406895266037211533065670904218278235604002573401193114111627382958428536968266964975362791704067660270952933411608299947663325963289383426020609754934510085150774508301734516652467839087341415815719569669955613063226205647580528 9 | 10 | bit_len = 8 * len(str(2 ** 512 - 1)) 11 | p_bits = [-1] * bit_len 12 | q_bits = [-1] * bit_len 13 | 14 | i = 0 15 | while i < bit_len: 16 | if i % 8 == 4 or i % 8 == 5: 17 | tmp = 1 18 | elif i % 8 == 6 or i % 8 == 7: 19 | tmp = 0 20 | else: 21 | tmp = -1 22 | p_bits[bit_len - 1 - i] = tmp 23 | q_bits[bit_len - 1 - i] = tmp 24 | i += 1 25 | 26 | now = time.perf_counter() 27 | p_q = factor.from_vector(N, p_bits, q_bits, search="dfs") 28 | print(time.perf_counter() - now) 29 | now = time.perf_counter() 30 | p_q = factor.from_vector(N, p_bits, q_bits, search="bfs") 31 | print(time.perf_counter() - now) 32 | assert p_q is not None 33 | p, q = p_q 34 | 35 | phi = (p - 1) * (q - 1) 36 | d = pow(e, -1, phi) 37 | m = pow(c, d, N) 38 | print(bytes.fromhex(f"{m:x}")) 39 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use rug::{ops::Pow, Integer}; 3 | 4 | pub static TWO: Lazy = Lazy::new(|| "2".parse().unwrap()); 5 | 6 | pub fn bits_to_num(bits: &Vec, n: i32, saved: &Integer, saved_idx: i32) -> Integer { 7 | let mut ret = saved.clone(); 8 | for i in (saved_idx as usize + 1)..(n as usize) { 9 | assert_ne!(bits[i], -1); 10 | if bits[i] == 1 { 11 | ret += TWO.clone().pow(i as u32) * bits[i] 12 | } 13 | } 14 | return ret; 15 | } 16 | 17 | pub fn str_to_vec(bits_str: &[u8], bit_len: usize) -> Vec { 18 | assert!(bits_str.len() <= bit_len); 19 | let mut bits: Vec = vec![]; 20 | for i in 0..bit_len { 21 | if bits_str.len() < i + 1 { 22 | bits.push(0); 23 | continue; 24 | } 25 | match bits_str[bits_str.len() - i - 1] as char { 26 | '0' => bits.push(0), 27 | '1' => bits.push(1), 28 | _ => bits.push(-1), 29 | }; 30 | } 31 | bits 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | 38 | #[test] 39 | fn test_bits_to_num() { 40 | let bits: Vec = vec![1, 0, 1, 0, 1]; 41 | let n = 3; 42 | let saved: Integer = "1".parse().unwrap(); 43 | let saved_idx = 1; 44 | let expected: Integer = "5".parse().unwrap(); 45 | assert_eq!(bits_to_num(&bits, n, &saved, saved_idx), expected); 46 | 47 | let n = 4; 48 | let saved: Integer = "1".parse().unwrap(); 49 | let saved_idx = 1; 50 | let expected: Integer = "5".parse().unwrap(); 51 | assert_eq!(bits_to_num(&bits, n, &saved, saved_idx), expected); 52 | 53 | let n = 5; 54 | let saved: Integer = "1".parse().unwrap(); 55 | let saved_idx = 1; 56 | let expected: Integer = "21".parse().unwrap(); 57 | assert_eq!(bits_to_num(&bits, n, &saved, saved_idx), expected); 58 | } 59 | 60 | #[test] 61 | fn test_str_to_vec() { 62 | assert_eq!(str_to_vec("101".as_bytes(), 3), [1, 0, 1]); 63 | assert_eq!(str_to_vec("101".as_bytes(), 4), [1, 0, 1, 0]); 64 | assert_eq!(str_to_vec("101".as_bytes(), 5), [1, 0, 1, 0, 0]); 65 | 66 | assert_eq!(str_to_vec("_01".as_bytes(), 3), [1, 0, -1]); 67 | assert_eq!(str_to_vec("?01".as_bytes(), 3), [1, 0, -1]); 68 | } 69 | 70 | #[test] 71 | #[should_panic] 72 | fn test_str_to_vec_panic() { 73 | str_to_vec("101".as_bytes(), 2); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/factor/dfs.rs: -------------------------------------------------------------------------------- 1 | use rug::{Assign, Integer}; 2 | 3 | pub fn factor_dfs( 4 | n: &Integer, 5 | p_bits: Vec, 6 | q_bits: Vec, 7 | bit_len: usize, 8 | _verbose: bool, 9 | ) -> Option<(Integer, Integer)> { 10 | let mut p: Integer = "0".parse().unwrap(); 11 | for i in 0..p_bits.len() { 12 | if p_bits[i] == 1 { 13 | p += Integer::from(1) << (i as u32); 14 | } 15 | } 16 | let mut q: Integer = "0".parse().unwrap(); 17 | for i in 0..q_bits.len() { 18 | if q_bits[i] == 1 { 19 | q += Integer::from(1) << (i as u32); 20 | } 21 | } 22 | fn dfs( 23 | n: &Integer, 24 | p: &mut Integer, 25 | q: &mut Integer, 26 | p_bits: &Vec, 27 | q_bits: &Vec, 28 | bit: usize, 29 | max_bit: usize, 30 | ) -> bool { 31 | let mut bit = bit; 32 | while bit < max_bit { 33 | if p_bits[bit] == -1 || q_bits[bit] == -1 { 34 | break; 35 | } 36 | bit += 1; 37 | } 38 | if bit == max_bit { 39 | let mut tmp_n = p.clone(); 40 | tmp_n *= q.clone(); 41 | return &tmp_n == n; 42 | } 43 | for i in 0..2 { 44 | for j in 0..2 { 45 | let mut p_ = Integer::new(); 46 | if p_bits[bit] == -1 && i == 1 { 47 | p_.assign(Integer::from(1) << bit as u32); 48 | } else { 49 | p_.assign(0); 50 | } 51 | let mut q_ = Integer::new(); 52 | if q_bits[bit] == -1 && j == 1 { 53 | q_.assign(Integer::from(1) << bit as u32); 54 | } else { 55 | q_.assign(0); 56 | } 57 | *p += &p_; 58 | *q += &q_; 59 | let mut tmp_n = p.clone(); 60 | tmp_n *= q.clone(); 61 | tmp_n -= n; 62 | tmp_n %= Integer::from(1) << (bit + 1) as u32; 63 | if tmp_n == 0 && dfs(n, p, q, p_bits, q_bits, bit + 1, max_bit) { 64 | return true; 65 | } 66 | *p -= p_; 67 | *q -= q_; 68 | } 69 | } 70 | false 71 | } 72 | match dfs(n, &mut p, &mut q, &p_bits, &q_bits, 0, bit_len) { 73 | true => Some((p, q)), 74 | false => None, 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use super::*; 81 | 82 | #[test] 83 | fn test_factor() { 84 | let n: Integer = "323".parse().unwrap(); 85 | let p_bits = vec![-1, -1, -1, -1, -1]; // NNNNN 86 | let q_bits = vec![-1, 1, -1, -1, -1]; // NNN1N 87 | let bit_len = 5; 88 | let expected: Option<(Integer, Integer)> = 89 | Some(("17".parse().unwrap(), "19".parse().unwrap())); 90 | assert_eq!( 91 | factor_dfs(&n, p_bits.clone(), q_bits.clone(), bit_len, false), 92 | expected 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Factor from random known bits 2 | 3 | Python's library written in Rust to quickly factor `n = pq` when around >50% bits of `p` and `q` are known which are distributed at random. 4 | Used mainly for CTF challenges, especially about RSA. 5 | 6 | ```python 7 | >>> import factor 8 | >>> factor.from_str(91, "110_", "0__1") 9 | (13, 7) 10 | >>> factor.from_vector(91, [1, 1, 0, -1], [0, -1, -1, 1]) 11 | (13, 7) 12 | >>> n = 104158954646372695568095796310479805403678314919693272509836778997179683485437763692891984254171869987446475357518587344178264028334102088429629785065036660148146855007349113784322098795994839040721664806905084554147298456659074384855277678993200563966327086005547016327991986225930798076081014377904788085807 13 | >>> p_bits_str = "1010101111000___11000___11100___11010___0___0___0___100110000___0___0___0___11000___0___0___110110010___11001100100111010___100011000___0___0___0___11111000111111100___1101110010000___0___0___0___10110___0___0___0___0___0___1100101111000___0___1001111011110___0___10000___0___0___11010___1010101110110___0___0___0___0___10010___1011101011100___110111010___0___0___0___101010110___0___10000___1000101011000___0___0___0___101010000___11010___111010000___0___11110___0___10010___111010010___0___0___10100___0___0___" 14 | >>> q_bits_str = "110111010___111011110___0___1000100110001110100111100___0___10110___11000___0___10110___11100___10000___0___0___11111100110010100___10000___11100___0___110010110___101110010___10010___11110___11110___0___1101111011000___101010110___10100___0___10100___1010101011010___0___0___100110110___0___10000___0___0___1000101110010___1111110010110___0___0___0___101110100___0___1100101111000___10100___0___0___0___0___0___0___10010___0___0___10100___10010___0___0___0___101011110___0___111110000___0___11110___0___10100___" 15 | >>> p_q = factor.from_str(n, p_bits_str, q_bits_str) 16 | >>> assert p_q is not None 17 | >>> p, q = p_q 18 | >>> assert p * q == n 19 | ``` 20 | 21 | In addition, of course, this can be used in Rust program. See `examples/factor_cui.rs`. 22 | ```bash 23 | $ cargo run --release --example factor_cui 3233 1_0__1 1___01 24 | 53, 61 25 | ``` 26 | 27 | ## Install 28 | 29 | ```bash 30 | # If you have not yet installed Rust, run command below 31 | $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 32 | 33 | # Install as a Python package 34 | $ pip install -r requirements.txt 35 | $ python setup.py install 36 | ``` 37 | 38 | You can also use a docker environment. 39 | 40 | ```bash 41 | $ docker run -it --rm y011d4/factor-from-random-known-bits:0.1.0 42 | Python 3.9.6 (default, Jun 29 2021, 19:27:32) 43 | [GCC 8.3.0] on linux 44 | Type "help", "copyright", "credits" or "license" for more information. 45 | >>> 46 | ``` 47 | 48 | ## Examples in CTF 49 | 50 | There are some examples in `examples` directory. 51 | - "This is RSA" in SECCON CTF 2020 52 | - "regulus-calendula" in HSCTF 8 53 | 54 | ## License 55 | 56 | Licensed under either of 57 | 58 | * Apache License, Version 2.0 59 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 60 | * MIT license 61 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 62 | 63 | at your option. 64 | 65 | ## Contribution 66 | 67 | Unless you explicitly state otherwise, any contribution intentionally submitted 68 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 69 | dual licensed as above, without any additional terms or conditions. 70 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // use ctrlc; 2 | pub mod factor; 3 | pub mod util; 4 | 5 | use num_bigint::BigInt; 6 | use pyo3::prelude::*; 7 | use pyo3::wrap_pyfunction; 8 | use rug::Integer; 9 | 10 | use crate::factor::factor_core; 11 | use crate::util::str_to_vec; 12 | 13 | /// from_vector(n, p_bits, q_bits, verbose=False, search="bfs") 14 | /// -- 15 | /// 16 | /// factor `n` from `p` (`list` like `[-1, 1, 0, -1, 0, 1, -1]`) and `q` whose bits are known around more than 50%. 17 | /// 18 | /// Args: 19 | /// n (int): `n` to be factored. `str` of decimal number. 20 | /// p_bits (list[int]): `list[int]` of `p`'s bits like `[-1, 1, 0, -1, 0, 1, -1]` (big endian). 21 | /// q_bits (list[int]): the same as `p_bits` 22 | /// Returns: 23 | /// (int, int) or None: (p, q) such that n == p * q. 24 | /// Examples: 25 | /// >>> import factor 26 | /// >>> factor.from_vector(35, [1, 1, -1], [-1, 0, 1]) 27 | /// (7, 5) 28 | /// >>> factor.from_vector(35, [0, 1, -1], [-1, 0, 1]) is None 29 | /// True 30 | #[pyfunction] 31 | fn from_vector( 32 | n: BigInt, 33 | p_bits: Vec, 34 | q_bits: Vec, 35 | verbose: Option, 36 | search: Option, 37 | ) -> PyResult> { 38 | // ctrlc::set_handler(|| std::process::exit(2)); 39 | let n: Integer = n.to_str_radix(10).parse().unwrap(); 40 | let bit_len = p_bits.len().max(q_bits.len()); 41 | let mut p_bits = p_bits.clone(); 42 | let mut q_bits = q_bits.clone(); 43 | p_bits.reverse(); 44 | q_bits.reverse(); 45 | match factor_core( 46 | &n, 47 | p_bits, 48 | q_bits, 49 | bit_len, 50 | verbose.unwrap_or(false), 51 | search.unwrap_or("bfs".to_string()), 52 | ) { 53 | Some((p, q)) => Ok(Some(( 54 | p.to_string_radix(10).parse().unwrap(), 55 | q.to_string_radix(10).parse().unwrap(), 56 | ))), 57 | None => Ok(None), 58 | } 59 | } 60 | 61 | /// from_str(n, p_bits_str, q_bits_str, verbose=False, search="bfs") 62 | /// -- 63 | /// 64 | /// factor `n` from `p` (str like "_10_01__1") and `q` whose bits are known around more than 50%. 65 | /// 66 | /// Args: 67 | /// n (int): `n` to be factored. 68 | /// p_bits_str (str): string of `p`'s bits like "?10?01??1" (big endian). 69 | /// q_bits_str (str): the same as `p_bits_str` 70 | /// Returns: 71 | /// (int, int) or None: (p, q) such that n == p * q. 72 | /// Examples: 73 | /// >>> import factor 74 | /// >>> factor.from_str(35, "11_", "_01") 75 | /// (7, 5) 76 | /// >>> factor.from_str(35, "11?", "?01") 77 | /// (7, 5) 78 | /// >>> factor.from_str(35, "01_", "_01") is None 79 | /// True 80 | #[pyfunction] 81 | fn from_str( 82 | n: BigInt, 83 | p_bits_str: String, 84 | q_bits_str: String, 85 | verbose: Option, 86 | search: Option, 87 | ) -> PyResult> { 88 | // ctrlc::set_handler(|| std::process::exit(2)); 89 | let n: Integer = n.to_str_radix(10).parse().unwrap(); 90 | let bit_len = p_bits_str.len().max(q_bits_str.len()); 91 | let p_bits = str_to_vec(p_bits_str.as_bytes(), bit_len); 92 | let q_bits = str_to_vec(q_bits_str.as_bytes(), bit_len); 93 | match factor_core( 94 | &n, 95 | p_bits, 96 | q_bits, 97 | bit_len, 98 | verbose.unwrap_or(false), 99 | search.unwrap_or("bfs".to_string()), 100 | ) { 101 | Some((p, q)) => Ok(Some(( 102 | p.to_string_radix(10).parse().unwrap(), 103 | q.to_string_radix(10).parse().unwrap(), 104 | ))), 105 | None => Ok(None), 106 | } 107 | } 108 | 109 | #[pymodule] 110 | fn factor(_py: Python, m: &PyModule) -> PyResult<()> { 111 | m.add_function(wrap_pyfunction!(from_vector, m)?)?; 112 | m.add_function(wrap_pyfunction!(from_str, m)?)?; 113 | 114 | Ok(()) 115 | } 116 | -------------------------------------------------------------------------------- /examples/hsctf_8_regulus_calendula/server.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | from Crypto.Util.number import * 3 | import sys 4 | import random 5 | import sympy 6 | 7 | flag = open("flag.txt", "rb").read() 8 | print("Loading... this may take a while.") 9 | # p, q = getPrime(4096), getPrime(4096) 10 | p = 1026425489811410154939564330855126910294054624327594068393599330304159964093796641513751922314812554020064574891490673912596072620168714995403643835061896188790756223382222617571855106445326142739551265677862164207875957516478182236814947302551022524484925137164027825054079357474914319847907281582251076726102493643151425255226777384202284599156369363373563642604745988351539992375932670122375621885209873017785436016020435017978053427514364179198035581419291064193143912481690001354300057585042828156962675239775071887979684757536819513306850763668517928489517504927452552162939872700807640351677493869972758820579169090676582753434717869612768936969479106103677792036592591525253773791964700899607724830220666704363012347297530024777101853264936262593009663488486590961760704732696757346560710898582595069246914920687953157305824804491250148764002866105497661178187082037356062332307348555641200201094332852115589136147061683254988284911411038232213180436604722460221221683317461752299144307920409776427279433732673595593846759912534482804113536355504073009187275379612098049501463654478795374584247337053340452322946778347546797205954284458371217689790568891843847863509978597873910159042514411161754488700822562383501114340695307 11 | q = 1043395680645709616430238458303195628267595941826074884197610956272518685169237892257685338085515270472552822744247261654423963842191142037642837920964800633693576392267555249330411657032553703156253205694716092034139527191916079517847830974370662628631791588855728122525062546019057198352330260052007852705437549448884983289498940758993888339215908248039577148907368366410455106129378046322061894612017658889241375715407634414995796890538237944336144443711878126149718493789401770495535773111695769971508315510017254311001534617670751777218597367572636211463332971996559344483815910952592503307235782641373579572043768640608292900021752003390020186654466042409056433113656899144822544860046502540981609954568853744595384279026309061485766510484430418314523778355368947845899338306665838799449847600194704170302644891201942590145707084408673363236338735320909623421936216080709297429099298246664201573778101685813476833025304391362768460926143100965693130874923384062590760046469772424813296120707214816425685695593470565364151176866332389941154164234137981648148342496747222830095027767141778639485792584690124754600956527467686001491244941041338137797069982744687435642509704783437673181296992620594796363880090948363387722267765401 12 | e = 0x10001 13 | n = p * q 14 | m = random.randrange(0, n) 15 | c = pow(m, e, n) 16 | d = sympy.mod_inverse(e, (p - 1) * (q - 1)) 17 | 18 | 19 | def menu(guesses): 20 | print() 21 | print("1. Source") 22 | print("2. Public key") 23 | print("3. Decrypt") 24 | print("4. Play") 25 | print("\nYou have " + str(guesses) + " guesses left.") 26 | choice = input(": ").strip() 27 | if choice == "1": 28 | f = open(__file__) 29 | print() 30 | print(f.read()) 31 | print() 32 | menu(guesses) 33 | elif choice == "2": 34 | print("\nn = " + str(n)) 35 | print("e = 65537") 36 | menu(guesses) 37 | elif choice == "3": 38 | d_ = int(input("What is the private key?\n: ")) 39 | if pow(c, d_, n) == m: 40 | print("Congrats! Here is your flag:") 41 | print(flag) 42 | sys.exit() 43 | else: 44 | print("\nSorry, that is incorrect.") 45 | menu(guesses) 46 | elif choice == "4": 47 | if guesses == 0: 48 | print("Sorry, you have no more guesses.") 49 | menu(0) 50 | else: 51 | if guesses > 8: 52 | code = list(hex(p)[2:]) 53 | else: 54 | code = list(hex(q)[2:]) 55 | guess = input("Make a guess.\n: ") 56 | while len(guess) != 1024: 57 | guess = input("Try again.\n: ") 58 | guess = list(guess) 59 | a = "".join(["1" if guess[i] == code[i] else "0" for i in range(1024)]) 60 | print(a) 61 | guesses -= 1 62 | menu(guesses) 63 | else: 64 | print("That is not a valid choice.") 65 | menu(guesses) 66 | 67 | 68 | while 1: 69 | menu(16) 70 | -------------------------------------------------------------------------------- /src/factor/bfs.rs: -------------------------------------------------------------------------------- 1 | use crate::util::{bits_to_num, TWO}; 2 | use rug::{ops::Pow, Integer}; 3 | use std::collections::VecDeque; 4 | 5 | fn check(p: &Integer, q: &Integer, n: &Integer, m: u32) -> bool { 6 | let two_pow = TWO.clone().pow(m); 7 | let mut tmp_n = p.clone(); 8 | tmp_n *= q; 9 | tmp_n %= &two_pow; 10 | let mut n_two_pow = n.clone(); 11 | n_two_pow %= &two_pow; 12 | return tmp_n == n_two_pow; 13 | } 14 | 15 | pub fn factor_bfs( 16 | n: &Integer, 17 | p_bits: Vec, 18 | q_bits: Vec, 19 | bit_len: usize, 20 | verbose: bool, 21 | ) -> Option<(Integer, Integer)> { 22 | let mut ans: Option<(Integer, Integer)> = None; 23 | let mut queue: VecDeque<((Vec, Integer), (Vec, Integer), i32)> = VecDeque::new(); 24 | queue.push_back(( 25 | (p_bits, "0".parse().unwrap()), 26 | (q_bits, "0".parse().unwrap()), 27 | -1, 28 | )); 29 | let mut fixed_idx: i32; 30 | while queue.len() != 0 { 31 | let tmp = queue.pop_front().unwrap(); 32 | let (p_bits, saved_p) = tmp.0; 33 | let (q_bits, saved_q) = tmp.1; 34 | let saved_idx = tmp.2; 35 | let mut idx = 0.max(saved_idx); 36 | while idx < bit_len as i32 { 37 | if p_bits[idx as usize] == -1 || q_bits[idx as usize] == -1 { 38 | break; 39 | } 40 | idx += 1; 41 | } 42 | fixed_idx = idx - 1; 43 | if verbose { 44 | print!( 45 | "\rSearched index: {:<8?} Queue size: {:<8?}", 46 | fixed_idx, 47 | queue.len() 48 | ); 49 | } 50 | let tmp_p: Integer = bits_to_num(&p_bits, fixed_idx + 1, &saved_p, saved_idx); 51 | let tmp_q: Integer = bits_to_num(&q_bits, fixed_idx + 1, &saved_q, saved_idx); 52 | let two_pow = TWO.clone().pow((fixed_idx + 1) as u32); 53 | let mut tmp_n = tmp_p.clone(); 54 | tmp_n *= &tmp_q; 55 | if &tmp_n == n { 56 | ans = Some((tmp_p, tmp_q)); 57 | break; 58 | } 59 | tmp_n %= &two_pow; 60 | let mut n_two_pow = n.clone(); 61 | n_two_pow %= &two_pow; 62 | if tmp_n != n_two_pow { 63 | continue; 64 | } 65 | if fixed_idx + 1 == bit_len as i32 { 66 | continue; 67 | } 68 | let tmp_p_two_pow = match p_bits[idx as usize] { 69 | -1 => { 70 | let mut ret = tmp_p.clone(); 71 | ret += &two_pow; 72 | ret 73 | } 74 | _ => "0".parse().unwrap(), 75 | }; 76 | let tmp_q_two_pow = match q_bits[idx as usize] { 77 | -1 => { 78 | let mut ret = tmp_q.clone(); 79 | ret += &two_pow; 80 | ret 81 | } 82 | _ => "0".parse().unwrap(), 83 | }; 84 | if (p_bits[idx as usize] == -1) && (q_bits[idx as usize] == -1) { 85 | if check(&tmp_p, &tmp_q, &n, (fixed_idx + 2) as u32) { 86 | let mut tmp_p_bits_0 = p_bits.clone(); 87 | tmp_p_bits_0[idx as usize] = 0; 88 | let mut tmp_q_bits_0 = q_bits.clone(); 89 | tmp_q_bits_0[idx as usize] = 0; 90 | queue.push_back(( 91 | (tmp_p_bits_0, tmp_p.clone()), 92 | (tmp_q_bits_0, tmp_q.clone()), 93 | fixed_idx + 1, 94 | )); 95 | } 96 | if check(&tmp_p, &tmp_q_two_pow, &n, (fixed_idx + 2) as u32) { 97 | let mut tmp_p_bits_0 = p_bits.clone(); 98 | tmp_p_bits_0[idx as usize] = 0; 99 | let mut tmp_q_bits_1 = q_bits.clone(); 100 | tmp_q_bits_1[idx as usize] = 1; 101 | queue.push_back(( 102 | (tmp_p_bits_0, tmp_p.clone()), 103 | (tmp_q_bits_1, tmp_q_two_pow.clone()), 104 | fixed_idx + 1, 105 | )); 106 | } 107 | if check(&tmp_p_two_pow, &tmp_q, &n, (fixed_idx + 2) as u32) { 108 | let mut tmp_p_bits_1 = p_bits.clone(); 109 | tmp_p_bits_1[idx as usize] = 1; 110 | let mut tmp_q_bits_0 = q_bits.clone(); 111 | tmp_q_bits_0[idx as usize] = 0; 112 | queue.push_back(( 113 | (tmp_p_bits_1, tmp_p_two_pow.clone()), 114 | (tmp_q_bits_0, tmp_q.clone()), 115 | fixed_idx + 1, 116 | )); 117 | } 118 | if check(&tmp_p_two_pow, &tmp_q_two_pow, &n, (fixed_idx + 2) as u32) { 119 | let mut tmp_p_bits_1 = p_bits.clone(); 120 | tmp_p_bits_1[idx as usize] = 1; 121 | let mut tmp_q_bits_1 = q_bits.clone(); 122 | tmp_q_bits_1[idx as usize] = 1; 123 | queue.push_back(( 124 | (tmp_p_bits_1, tmp_p_two_pow), 125 | (tmp_q_bits_1, tmp_q_two_pow), 126 | fixed_idx + 1, 127 | )); 128 | } 129 | } else if p_bits[idx as usize] == -1 { 130 | let tmp_q_next = match q_bits[(fixed_idx + 1) as usize] { 131 | 0 => tmp_q.clone(), 132 | 1 => { 133 | let mut ret = tmp_q.clone(); 134 | ret += two_pow; 135 | ret 136 | } 137 | _ => panic!(), 138 | }; 139 | if check(&tmp_p, &tmp_q_next, &n, (fixed_idx + 2) as u32) { 140 | let mut tmp_p_bits_0 = p_bits.clone(); 141 | tmp_p_bits_0[idx as usize] = 0; 142 | queue.push_back(( 143 | (tmp_p_bits_0, tmp_p.clone()), 144 | (q_bits.clone(), tmp_q_next.clone()), 145 | fixed_idx + 1, 146 | )); 147 | } 148 | if check(&tmp_p_two_pow, &tmp_q_next, &n, (fixed_idx + 2) as u32) { 149 | let mut tmp_p_bits_1 = p_bits.clone(); 150 | tmp_p_bits_1[idx as usize] = 1; 151 | queue.push_back(( 152 | (tmp_p_bits_1, tmp_p_two_pow.clone()), 153 | (q_bits.clone(), tmp_q_next.clone()), 154 | fixed_idx + 1, 155 | )); 156 | } 157 | } else if q_bits[idx as usize] == -1 { 158 | let tmp_p_next = match p_bits[(fixed_idx + 1) as usize] { 159 | 0 => tmp_p.clone(), 160 | 1 => { 161 | let mut ret = tmp_p.clone(); 162 | ret += two_pow; 163 | ret 164 | } 165 | _ => panic!(), 166 | }; 167 | if check(&tmp_p_next, &tmp_q, &n, (fixed_idx + 2) as u32) { 168 | let mut tmp_q_bits_0 = q_bits.clone(); 169 | tmp_q_bits_0[idx as usize] = 0; 170 | queue.push_back(( 171 | (p_bits.clone(), tmp_p_next.clone()), 172 | (tmp_q_bits_0, tmp_q.clone()), 173 | fixed_idx + 1, 174 | )); 175 | } 176 | if check(&tmp_p_next, &tmp_q_two_pow, &n, (fixed_idx + 2) as u32) { 177 | let mut tmp_q_bits_1 = q_bits.clone(); 178 | tmp_q_bits_1[idx as usize] = 1; 179 | queue.push_back(( 180 | (p_bits.clone(), tmp_p_next.clone()), 181 | (tmp_q_bits_1, tmp_q_two_pow.clone()), 182 | fixed_idx + 1, 183 | )); 184 | } 185 | } else { 186 | panic!("そうはならんやろ"); 187 | } 188 | } 189 | if verbose { 190 | println!(); 191 | } 192 | ans 193 | } 194 | 195 | #[cfg(test)] 196 | mod tests { 197 | use super::*; 198 | 199 | #[test] 200 | fn test_factor() { 201 | let n: Integer = "323".parse().unwrap(); 202 | let p_bits = vec![-1, -1, -1, -1, -1]; // NNNNN 203 | let q_bits = vec![-1, 1, -1, -1, -1]; // NNN1N 204 | let bit_len = 5; 205 | let expected: Option<(Integer, Integer)> = 206 | Some(("17".parse().unwrap(), "19".parse().unwrap())); 207 | assert_eq!( 208 | factor_bfs(&n, p_bits.clone(), q_bits.clone(), bit_len, false), 209 | expected 210 | ); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.0.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 10 | 11 | [[package]] 12 | name = "az" 13 | version = "1.1.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "822d7d63e0c0260a050f6b1f0d316f5c79b9eab830aca526ed904e1011bd64ca" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.2.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 22 | 23 | [[package]] 24 | name = "cache-padded" 25 | version = "1.1.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" 28 | 29 | [[package]] 30 | name = "cc" 31 | version = "1.0.68" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" 34 | 35 | [[package]] 36 | name = "cfg-if" 37 | version = "1.0.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 40 | 41 | [[package]] 42 | name = "concurrent-queue" 43 | version = "1.2.2" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" 46 | dependencies = [ 47 | "cache-padded", 48 | ] 49 | 50 | [[package]] 51 | name = "ctrlc" 52 | version = "3.1.9" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "232295399409a8b7ae41276757b5a1cc21032848d42bff2352261f958b3ca29a" 55 | dependencies = [ 56 | "nix", 57 | "winapi", 58 | ] 59 | 60 | [[package]] 61 | name = "factor" 62 | version = "0.1.0" 63 | dependencies = [ 64 | "concurrent-queue", 65 | "ctrlc", 66 | "num-bigint", 67 | "once_cell", 68 | "pyo3", 69 | "rug", 70 | ] 71 | 72 | [[package]] 73 | name = "gmp-mpfr-sys" 74 | version = "1.4.5" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "18e70045792b1d584ef8d8ac5e890a98d9d6499d2d709cf2b23725b6dcb10064" 77 | dependencies = [ 78 | "libc", 79 | "winapi", 80 | ] 81 | 82 | [[package]] 83 | name = "indoc" 84 | version = "0.3.6" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8" 87 | dependencies = [ 88 | "indoc-impl", 89 | "proc-macro-hack", 90 | ] 91 | 92 | [[package]] 93 | name = "indoc-impl" 94 | version = "0.3.6" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0" 97 | dependencies = [ 98 | "proc-macro-hack", 99 | "proc-macro2", 100 | "quote", 101 | "syn", 102 | "unindent", 103 | ] 104 | 105 | [[package]] 106 | name = "instant" 107 | version = "0.1.9" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 110 | dependencies = [ 111 | "cfg-if", 112 | ] 113 | 114 | [[package]] 115 | name = "libc" 116 | version = "0.2.97" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" 119 | 120 | [[package]] 121 | name = "lock_api" 122 | version = "0.4.4" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" 125 | dependencies = [ 126 | "scopeguard", 127 | ] 128 | 129 | [[package]] 130 | name = "nix" 131 | version = "0.20.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" 134 | dependencies = [ 135 | "bitflags", 136 | "cc", 137 | "cfg-if", 138 | "libc", 139 | ] 140 | 141 | [[package]] 142 | name = "num-bigint" 143 | version = "0.4.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512" 146 | dependencies = [ 147 | "autocfg", 148 | "num-integer", 149 | "num-traits", 150 | ] 151 | 152 | [[package]] 153 | name = "num-integer" 154 | version = "0.1.44" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 157 | dependencies = [ 158 | "autocfg", 159 | "num-traits", 160 | ] 161 | 162 | [[package]] 163 | name = "num-traits" 164 | version = "0.2.14" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 167 | dependencies = [ 168 | "autocfg", 169 | ] 170 | 171 | [[package]] 172 | name = "once_cell" 173 | version = "1.8.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 176 | 177 | [[package]] 178 | name = "parking_lot" 179 | version = "0.11.1" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 182 | dependencies = [ 183 | "instant", 184 | "lock_api", 185 | "parking_lot_core", 186 | ] 187 | 188 | [[package]] 189 | name = "parking_lot_core" 190 | version = "0.8.3" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" 193 | dependencies = [ 194 | "cfg-if", 195 | "instant", 196 | "libc", 197 | "redox_syscall", 198 | "smallvec", 199 | "winapi", 200 | ] 201 | 202 | [[package]] 203 | name = "paste" 204 | version = "0.1.18" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" 207 | dependencies = [ 208 | "paste-impl", 209 | "proc-macro-hack", 210 | ] 211 | 212 | [[package]] 213 | name = "paste-impl" 214 | version = "0.1.18" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" 217 | dependencies = [ 218 | "proc-macro-hack", 219 | ] 220 | 221 | [[package]] 222 | name = "proc-macro-hack" 223 | version = "0.5.19" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 226 | 227 | [[package]] 228 | name = "proc-macro2" 229 | version = "1.0.27" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" 232 | dependencies = [ 233 | "unicode-xid", 234 | ] 235 | 236 | [[package]] 237 | name = "pyo3" 238 | version = "0.14.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "338f7f3701e11fd7f76508c91fbcaabc982564bcaf4d1ca7e1574ff2b4778aec" 241 | dependencies = [ 242 | "cfg-if", 243 | "indoc", 244 | "libc", 245 | "num-bigint", 246 | "parking_lot", 247 | "paste", 248 | "pyo3-build-config", 249 | "pyo3-macros", 250 | "unindent", 251 | ] 252 | 253 | [[package]] 254 | name = "pyo3-build-config" 255 | version = "0.14.1" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "dcb2e98cc9ccc83d4f7115c8f925e0057e88c8d324b1bc4c2db4a7270c06ac9d" 258 | dependencies = [ 259 | "once_cell", 260 | ] 261 | 262 | [[package]] 263 | name = "pyo3-macros" 264 | version = "0.14.1" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "cfb8671a42d0ecc4bec8cc107ae96d49292ca20cd1968e09b98af4aafd516adf" 267 | dependencies = [ 268 | "pyo3-macros-backend", 269 | "quote", 270 | "syn", 271 | ] 272 | 273 | [[package]] 274 | name = "pyo3-macros-backend" 275 | version = "0.14.1" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "9addf6dc422f05d4949cc0990195ee74fa43e3c3780cc9a1972fe9e7b68a9f48" 278 | dependencies = [ 279 | "proc-macro2", 280 | "pyo3-build-config", 281 | "quote", 282 | "syn", 283 | ] 284 | 285 | [[package]] 286 | name = "quote" 287 | version = "1.0.9" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 290 | dependencies = [ 291 | "proc-macro2", 292 | ] 293 | 294 | [[package]] 295 | name = "redox_syscall" 296 | version = "0.2.9" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" 299 | dependencies = [ 300 | "bitflags", 301 | ] 302 | 303 | [[package]] 304 | name = "rug" 305 | version = "1.12.0" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "32f75e07492c4d17d12f226d17e689fdb698db7a1e875fca0fac15626283bf24" 308 | dependencies = [ 309 | "az", 310 | "gmp-mpfr-sys", 311 | "libc", 312 | ] 313 | 314 | [[package]] 315 | name = "scopeguard" 316 | version = "1.1.0" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 319 | 320 | [[package]] 321 | name = "smallvec" 322 | version = "1.6.1" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 325 | 326 | [[package]] 327 | name = "syn" 328 | version = "1.0.73" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" 331 | dependencies = [ 332 | "proc-macro2", 333 | "quote", 334 | "unicode-xid", 335 | ] 336 | 337 | [[package]] 338 | name = "unicode-xid" 339 | version = "0.2.2" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 342 | 343 | [[package]] 344 | name = "unindent" 345 | version = "0.1.7" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" 348 | 349 | [[package]] 350 | name = "winapi" 351 | version = "0.3.9" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 354 | dependencies = [ 355 | "winapi-i686-pc-windows-gnu", 356 | "winapi-x86_64-pc-windows-gnu", 357 | ] 358 | 359 | [[package]] 360 | name = "winapi-i686-pc-windows-gnu" 361 | version = "0.4.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 364 | 365 | [[package]] 366 | name = "winapi-x86_64-pc-windows-gnu" 367 | version = "0.4.0" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 370 | --------------------------------------------------------------------------------