wireguard-vanity-address: upgrade 0.4.0_git20200327
more features less bugs
This commit is contained in:
parent
82e46296ea
commit
a830140f00
7 changed files with 1844 additions and 128 deletions
|
@ -0,0 +1,37 @@
|
||||||
|
From 7780dadbb7d78994bd8bf1a64c4f2e60ecf569b3 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Brian Warner <warner@lothar.com>
|
||||||
|
Date: Tue, 3 Mar 2020 21:11:17 -0800
|
||||||
|
Subject: [PATCH 1/5] add curve25519-dalek dependency
|
||||||
|
|
||||||
|
---
|
||||||
|
Cargo.lock | 1 +
|
||||||
|
Cargo.toml | 1 +
|
||||||
|
2 files changed, 2 insertions(+)
|
||||||
|
|
||||||
|
diff --git a/Cargo.lock b/Cargo.lock
|
||||||
|
index 2f76cdc..938baf8 100644
|
||||||
|
--- a/Cargo.lock
|
||||||
|
+++ b/Cargo.lock
|
||||||
|
@@ -624,6 +624,7 @@ dependencies = [
|
||||||
|
"base64 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"criterion 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
+ "curve25519-dalek 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
diff --git a/Cargo.toml b/Cargo.toml
|
||||||
|
index 85a6c37..696c44e 100644
|
||||||
|
--- a/Cargo.toml
|
||||||
|
+++ b/Cargo.toml
|
||||||
|
@@ -24,6 +24,7 @@ rayon = "1.0"
|
||||||
|
base64 = "0.12"
|
||||||
|
rand_core = { version = "0.5", default-features = false, features = ["getrandom"] }
|
||||||
|
x25519-dalek = "0.6"
|
||||||
|
+curve25519-dalek = "2.0"
|
||||||
|
num_cpus = "1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
--
|
||||||
|
2.36.0
|
||||||
|
|
|
@ -0,0 +1,558 @@
|
||||||
|
From 1f7a1caa5c7cf274bdc255e480b925e521cf0a66 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Brian Warner <warner@lothar.com>
|
||||||
|
Date: Tue, 3 Mar 2020 21:11:54 -0800
|
||||||
|
Subject: [PATCH 2/5] new search algorithm: point addition, not scalarmult
|
||||||
|
|
||||||
|
The basic operation used to take 17us/iter on my 2019 mac mini (3.2GHz Core
|
||||||
|
i7). The new approach takes 3.8us/iter.
|
||||||
|
|
||||||
|
refs #12
|
||||||
|
---
|
||||||
|
src/bin.rs | 56 +++----
|
||||||
|
src/lib.rs | 429 +++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
2 files changed, 447 insertions(+), 38 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/bin.rs b/src/bin.rs
|
||||||
|
index a5a6f35..4d3f736 100644
|
||||||
|
--- a/src/bin.rs
|
||||||
|
+++ b/src/bin.rs
|
||||||
|
@@ -1,27 +1,12 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
-use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
use clap::{App, AppSettings, Arg};
|
||||||
|
use num_cpus;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
-use wireguard_vanity_lib::trial;
|
||||||
|
-
|
||||||
|
-fn estimate_one_trial() -> Duration {
|
||||||
|
- let prefix = "prefix";
|
||||||
|
- let start = SystemTime::now();
|
||||||
|
- const COUNT: u32 = 100;
|
||||||
|
- (0..COUNT).for_each(|_| {
|
||||||
|
- trial(&prefix, 0, 10);
|
||||||
|
- });
|
||||||
|
- let elapsed = start.elapsed().unwrap();
|
||||||
|
- elapsed.checked_div(COUNT).unwrap()
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-fn duration_to_f64(d: Duration) -> f64 {
|
||||||
|
- (d.as_secs() as f64) + (f64::from(d.subsec_nanos()) * 1e-9)
|
||||||
|
-}
|
||||||
|
+use wireguard_vanity_lib::{measure_rate, search_for_prefix};
|
||||||
|
+use x25519_dalek::{PublicKey, StaticSecret};
|
||||||
|
|
||||||
|
fn format_time(t: f64) -> String {
|
||||||
|
if t > 3600.0 {
|
||||||
|
@@ -61,8 +46,11 @@ fn format_rate(rate: f64) -> String {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-fn print(res: (String, String)) -> Result<(), io::Error> {
|
||||||
|
- let (private_b64, public_b64) = res;
|
||||||
|
+fn print(res: (StaticSecret, PublicKey)) -> Result<(), io::Error> {
|
||||||
|
+ let private: StaticSecret = res.0;
|
||||||
|
+ let public: PublicKey = res.1;
|
||||||
|
+ let private_b64 = base64::encode(&private.to_bytes());
|
||||||
|
+ let public_b64 = base64::encode(public.as_bytes());
|
||||||
|
writeln!(
|
||||||
|
io::stdout(),
|
||||||
|
"private {} public {}",
|
||||||
|
@@ -131,29 +119,22 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
&prefix, end, trials_per_key
|
||||||
|
);
|
||||||
|
|
||||||
|
- // todo: dividing by num_cpus will overestimate performance when the
|
||||||
|
- // cores aren't actually distinct (hyperthreading?). My Core-i7 seems to
|
||||||
|
- // run at half the speed that this predicts.
|
||||||
|
+ // get_physical() appears to be more accurate: hyperthreading doesn't
|
||||||
|
+ // help us much
|
||||||
|
|
||||||
|
if trials_per_key < 2u64.pow(32) {
|
||||||
|
- let est = estimate_one_trial();
|
||||||
|
+ let raw_rate = measure_rate();
|
||||||
|
println!(
|
||||||
|
- "one trial takes {}, CPU cores available: {}",
|
||||||
|
- format_time(duration_to_f64(est)),
|
||||||
|
- num_cpus::get()
|
||||||
|
+ "one core runs at {}, CPU cores available: {}",
|
||||||
|
+ format_rate(raw_rate),
|
||||||
|
+ num_cpus::get_physical(),
|
||||||
|
);
|
||||||
|
- let spk = duration_to_f64(
|
||||||
|
- est // sec/trial on one core
|
||||||
|
- .checked_div(num_cpus::get() as u32) // sec/trial with all cores
|
||||||
|
- .unwrap()
|
||||||
|
- .checked_mul(trials_per_key as u32) // sec/key (Duration)
|
||||||
|
- .unwrap(),
|
||||||
|
- );
|
||||||
|
- let kps = 1.0 / spk;
|
||||||
|
+ let total_rate = raw_rate * (num_cpus::get_physical() as f64) / (trials_per_key as f64);
|
||||||
|
+ let seconds_per_key = 1.0 / total_rate;
|
||||||
|
println!(
|
||||||
|
"est yield: {} per key, {}",
|
||||||
|
- format_time(spk),
|
||||||
|
- format_rate(kps)
|
||||||
|
+ format_time(seconds_per_key),
|
||||||
|
+ format_rate(total_rate)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -162,8 +143,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
// 1M trials takes about 10s on my laptop, so let it run for 1000s
|
||||||
|
(0..100_000_000)
|
||||||
|
.into_par_iter()
|
||||||
|
- .map(|_| trial(&prefix, 0, end))
|
||||||
|
- .filter_map(|r| r)
|
||||||
|
+ .map(|_| search_for_prefix(&prefix, 0, end))
|
||||||
|
.try_for_each(print)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
diff --git a/src/lib.rs b/src/lib.rs
|
||||||
|
index 31138c5..2e6b33f 100644
|
||||||
|
--- a/src/lib.rs
|
||||||
|
+++ b/src/lib.rs
|
||||||
|
@@ -1,5 +1,7 @@
|
||||||
|
use base64;
|
||||||
|
+use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, edwards::EdwardsPoint, scalar::Scalar};
|
||||||
|
use rand_core::OsRng;
|
||||||
|
+use std::time::{Duration, Instant};
|
||||||
|
use x25519_dalek::{PublicKey, StaticSecret};
|
||||||
|
|
||||||
|
pub fn trial(prefix: &str, start: usize, end: usize) -> Option<(String, String)> {
|
||||||
|
@@ -16,3 +18,430 @@ pub fn trial(prefix: &str, start: usize, end: usize) -> Option<(String, String)>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+// To perform a fast search, our basic algorithm is:
|
||||||
|
+// loop {
|
||||||
|
+// let base_privkey = new random scalar;
|
||||||
|
+// let add_privkey = 8;
|
||||||
|
+// let add_pubkey = scalarmult(8);
|
||||||
|
+//
|
||||||
|
+// let mut offset = 0;
|
||||||
|
+// let mut trial_pubkey = scalarmult(base_privkey);
|
||||||
|
+// while !encoding(trial_pubkey).meets_target() {
|
||||||
|
+// offset += add_privkey
|
||||||
|
+// trial_pubkey += add_pubkey
|
||||||
|
+// }
|
||||||
|
+// privkey = base_privkey + offset
|
||||||
|
+// yield (privkey, trial_pubkey)
|
||||||
|
+// }
|
||||||
|
+
|
||||||
|
+// We reset to a new random base_privkey after each matching keypair,
|
||||||
|
+// otherwise someone who learns one privkey can easily find the others.
|
||||||
|
+// We offset by 8 to make sure that each new privkey will meet the same
|
||||||
|
+// clamping criteria: we assume the keyspace is large enough that we're
|
||||||
|
+// unlikely to wrap around.
|
||||||
|
+
|
||||||
|
+// To implement this in curve25519, we have to dance around two different
|
||||||
|
+// representations of scalars.
|
||||||
|
+
|
||||||
|
+// x25519 private keys are scalars in the large prime-order subgroup of
|
||||||
|
+// the points on Curve25519. The order of this subgroup is named "l",
|
||||||
|
+// which is a number somewhat larger than 2^252. For convenience later,
|
||||||
|
+// we'll define this number as "big+small", where big=2^252 and small is
|
||||||
|
+// the remainder ("small" is a bit larger than 2^128). The hex
|
||||||
|
+// representation of "small" ends in 0xD3ED, so "l"%8 is 5.
|
||||||
|
+
|
||||||
|
+// The x25519-dalek implementation (StaticSecret or EphemeralSecret)
|
||||||
|
+// represents these as 256-bit integers with 5 bits "clamped" in various
|
||||||
|
+// ways: the high-order two bits are set to 0b01, and the low-order three
|
||||||
|
+// bits are set to 0b000. Therefore the integers "s" are in the range
|
||||||
|
+// 2^254 to 2^255-1, and s%8==0. These "clamped" keys can represent
|
||||||
|
+// slightly fewer than half of the possible private keys (2^251 out of
|
||||||
|
+// "l").
|
||||||
|
+
|
||||||
|
+// Why clamp? The 2^255 bit is cleared to make sure that "add" won't
|
||||||
|
+// overflow a 256-bit container. The 2^254 bit is set for the sake of an
|
||||||
|
+// unwise montgomery ladder implementation whose runtime reveals the
|
||||||
|
+// number of leading zero bits: all private keys have the same such
|
||||||
|
+// number (one), thus the attacker doesn't learn anything new. The low
|
||||||
|
+// three bits are clamped for the sake of an unwise/faster verifier which
|
||||||
|
+// doesn't check that the received point is a member of the right group,
|
||||||
|
+// enabling a small-subgroup attack that reveals the private key mod 8
|
||||||
|
+// (the curve group's cofactor). By setting those bits to a fixed value,
|
||||||
|
+// the attacker has nothing to learn. (I'm not exactly sure what they
|
||||||
|
+// *do* learn: I suspect it's ((p%l)%8), and the fact that (p%8)==0
|
||||||
|
+// doesn't necessarily mean that ((p%l)%8) is a fixed number: they might
|
||||||
|
+// learn the top three bits of p instead).
|
||||||
|
+
|
||||||
|
+// Curve25519::Scalar values don't do much clamping, but *are* always
|
||||||
|
+// reduced mod "l". Three constructors ("from_bytes_mod_order",
|
||||||
|
+// "from_bytes_mod_order_wide", and "from_canonical_bytes") can only
|
||||||
|
+// produce reduced scalars. The remaining one ("from_bits") can produce
|
||||||
|
+// non-reduced scalars: the high bit is still cleared to make sure that
|
||||||
|
+// "add" won't overflow, but the other bits are left alone. However the
|
||||||
|
+// "add" method (and others) always reduce modulo "l" before returning a
|
||||||
|
+// result, so it's not possible to keep things unreduced for very long.
|
||||||
|
+
|
||||||
|
+// Converting from an x25519-dalek StaticSecret representation to a
|
||||||
|
+// curve25519-dalek Scalar is easy:
|
||||||
|
+// Scalar::from_bytes_mod_order(ss.to_bytes()). But how can we map in the
|
||||||
|
+// opposite direction? When we call StaticSecret::from_bits(), it's going
|
||||||
|
+// to clamp both ends, and if that clamping actually changes any bits,
|
||||||
|
+// the numerical value of the private key will be wrong. So we must
|
||||||
|
+// ensure that both ends are pre-clamped before handing it over.
|
||||||
|
+
|
||||||
|
+// Since "l" is about 2^252, and a StaticSecret holds 255 bits, each
|
||||||
|
+// Scalar "s" (in the range 0.."l") has roughly 8 aliases: eight
|
||||||
|
+// different 255-bit numbers which are equivalent (mod "l"), whose values
|
||||||
|
+// are s+kl (k=0..7). The four high-order bits of a reduced scalar are
|
||||||
|
+// usually 0b0000. With vanishingly small probability ("small"/"l", ~=
|
||||||
|
+// 2^-124), the scalar might be larger than 2^252, but we can effectively
|
||||||
|
+// ignore this. The aliases (with high probability) have distinct
|
||||||
|
+// high-order bits: 0b0001, 0b0010, etc. We want one of the four aliases
|
||||||
|
+// whose high-order bits are 0b01xx: these bits will survive the high-end
|
||||||
|
+// clamping unchanged. These are where k=[4..7].
|
||||||
|
+
|
||||||
|
+// The three low-order bits will be some number N. Each alias adds l%8 to
|
||||||
|
+// this low end. So the first alias (k=1) will end in N+5, the second
|
||||||
|
+// (k=2) will end in N+2 (since (5+5)%8 == 2). Our k=4..7 yields
|
||||||
|
+// N+4,N+1,N+6,N+3. One of these values might be all zeros. That alias
|
||||||
|
+// will survive the low-end clamping unchanged.
|
||||||
|
+
|
||||||
|
+// We can't use Scalars to add "l" and produce the aliases: any addition
|
||||||
|
+// we do on the Scalar will be reduced immediately. But we can add
|
||||||
|
+// "small", and then manually adjust the high-end byte, to produce an
|
||||||
|
+// array of bytes whose value is s+kl, and hand it over to
|
||||||
|
+// StaticSecret::from(bytes) to get an x25519 private key. The private
|
||||||
|
+// key will be in the same equivalence class as our original Scalar, but
|
||||||
|
+// its binary representation will be different.
|
||||||
|
+
|
||||||
|
+// This conversion from Scalar to clamping-compatible bytes is the last
|
||||||
|
+// thing we do, both to emit a wireguard-suitable private key string, and
|
||||||
|
+// to double-check that our keypair actually works. We also do this
|
||||||
|
+// conversion at the very beginning, to make sure that the random
|
||||||
|
+// starting point is actually going to work.
|
||||||
|
+
|
||||||
|
+// the resulting algorithm is:
|
||||||
|
+// loop {
|
||||||
|
+// let x = StaticSecret::new(&mut OsRng);
|
||||||
|
+// let base_scalar = Scalar::from_bytes_mod_order(x.to_bytes());
|
||||||
|
+// if x.to_bytes() != convert(base_scalar) { break; } // try again
|
||||||
|
+// let add_privkey = Scalar::from_bytes_mod_order(to_array(8));
|
||||||
|
+// let add_pubkey = add_privkey * ED25519_BASEPOINT_POINT;
|
||||||
|
+//
|
||||||
|
+// let mut current_offset = Scalar::from_bytes_mod_order(to_array(0));
|
||||||
|
+// let mut trial_pubkey = base_scalar * ED25519_BASEPOINT_POINT;
|
||||||
|
+// while !encoding(trial_pubkey).meets_target() {
|
||||||
|
+// current_offset += add_privkey;
|
||||||
|
+// trial_pubkey += add_pubkey;
|
||||||
|
+// }
|
||||||
|
+// privkey = convert(base_scalar + offset)
|
||||||
|
+// yield (privkey, trial_pubkey)
|
||||||
|
+// }
|
||||||
|
+
|
||||||
|
+// where encoding() converts to Montgomery form, then to bytes, then to
|
||||||
|
+// base64, then applies the vanity prefix check
|
||||||
|
+
|
||||||
|
+fn add_big(input: [u8; 32], multiple: usize) -> [u8; 32] {
|
||||||
|
+ let mut out = input;
|
||||||
|
+ for _ in 0..multiple {
|
||||||
|
+ out[31] += 0b0001_0000;
|
||||||
|
+ }
|
||||||
|
+ out
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn survives_clamping(input: &[u8; 32]) -> bool {
|
||||||
|
+ *input == StaticSecret::from(*input).to_bytes()
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn print_bytes(name: &str, b: &[u8; 32]) {
|
||||||
|
+ println!("{} 0x{:02x} {:?} 0x{:02x}", name, b[0], b, b[31]);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn congruent_to(s: Scalar, b: &[u8; 32]) -> bool {
|
||||||
|
+ let s2 = Scalar::from_bytes_mod_order(*b);
|
||||||
|
+ s == s2
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+#[cfg(off)]
|
||||||
|
+fn display_scalar(s: Scalar) -> String {
|
||||||
|
+ let mut st = String::new();
|
||||||
|
+ for b in s.to_bytes().iter().rev() {
|
||||||
|
+ st.push_str(format!("{:02x}", b).as_str());
|
||||||
|
+ }
|
||||||
|
+ st
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn convert_scalar_to_privkey(s: Scalar) -> StaticSecret {
|
||||||
|
+ // L: 0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED
|
||||||
|
+ // big: 0x1000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
+ // small: 0x0000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED
|
||||||
|
+
|
||||||
|
+ let zero = Scalar::from_bytes_mod_order([0u8; 32]);
|
||||||
|
+ let mut big_bytes = [0u8; 32];
|
||||||
|
+ big_bytes[31] = 0b0001_0000;
|
||||||
|
+ let big = Scalar::from_bytes_mod_order(big_bytes);
|
||||||
|
+ let small: Scalar = zero - big;
|
||||||
|
+ //println!("small: {}", display_scalar(small));
|
||||||
|
+
|
||||||
|
+ let alias4_small = s + small + small + small + small;
|
||||||
|
+ let alias4_bytes = add_big(alias4_small.to_bytes(), 4);
|
||||||
|
+
|
||||||
|
+ let alias5_small = alias4_small + small;
|
||||||
|
+ let alias5_bytes = add_big(alias5_small.to_bytes(), 5);
|
||||||
|
+
|
||||||
|
+ let alias6_small = alias5_small + small;
|
||||||
|
+ let alias6_bytes = add_big(alias6_small.to_bytes(), 6);
|
||||||
|
+
|
||||||
|
+ let alias7_small = alias6_small + small;
|
||||||
|
+ let alias7_bytes = add_big(alias7_small.to_bytes(), 7);
|
||||||
|
+
|
||||||
|
+ if false {
|
||||||
|
+ print_bytes("orig s", &s.to_bytes());
|
||||||
|
+ print_bytes("alias4", &alias4_bytes);
|
||||||
|
+ print_bytes("alias5", &alias5_bytes);
|
||||||
|
+ print_bytes("alias6", &alias6_bytes);
|
||||||
|
+ print_bytes("alias7", &alias7_bytes);
|
||||||
|
+
|
||||||
|
+ println!(
|
||||||
|
+ "alias4 {} {}",
|
||||||
|
+ survives_clamping(&alias4_bytes),
|
||||||
|
+ congruent_to(s, &alias4_bytes)
|
||||||
|
+ );
|
||||||
|
+ println!(
|
||||||
|
+ "alias5 {} {}",
|
||||||
|
+ survives_clamping(&alias5_bytes),
|
||||||
|
+ congruent_to(s, &alias5_bytes)
|
||||||
|
+ );
|
||||||
|
+ println!(
|
||||||
|
+ "alias6 {} {}",
|
||||||
|
+ survives_clamping(&alias6_bytes),
|
||||||
|
+ congruent_to(s, &alias6_bytes)
|
||||||
|
+ );
|
||||||
|
+ println!(
|
||||||
|
+ "alias7 {} {}",
|
||||||
|
+ survives_clamping(&alias7_bytes),
|
||||||
|
+ congruent_to(s, &alias7_bytes)
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // this panics rather than returning an Option because we should
|
||||||
|
+ // always be starting from a well-behaved scalar, and should never
|
||||||
|
+ // get into the situation where we can't convert it
|
||||||
|
+ let alias_bytes = match s.to_bytes()[0] & 0b0111 {
|
||||||
|
+ 4 => alias4_bytes,
|
||||||
|
+ 7 => alias5_bytes,
|
||||||
|
+ 2 => alias6_bytes,
|
||||||
|
+ 5 => alias7_bytes,
|
||||||
|
+ _ => panic!("unable to convert scalar"),
|
||||||
|
+ };
|
||||||
|
+ let privkey = StaticSecret::from(alias_bytes);
|
||||||
|
+ assert_eq!(alias_bytes, privkey.to_bytes());
|
||||||
|
+ privkey
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+fn integer_to_scalar(int: u64) -> Scalar {
|
||||||
|
+ let bytes = int.to_le_bytes();
|
||||||
|
+ let mut scalar_bytes = [0u8; 32];
|
||||||
|
+ scalar_bytes[..8].clone_from_slice(&bytes[..8]);
|
||||||
|
+ Scalar::from_bytes_mod_order(scalar_bytes)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub struct Seed {
|
||||||
|
+ base_scalar: Scalar,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub struct Scan {
|
||||||
|
+ add_count: u64,
|
||||||
|
+ add_pubkey: EdwardsPoint,
|
||||||
|
+ count: u64,
|
||||||
|
+ current_pubkey: EdwardsPoint,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub struct ScanProgress {
|
||||||
|
+ add_count: u64,
|
||||||
|
+ add_pubkey: EdwardsPoint,
|
||||||
|
+ count: u64,
|
||||||
|
+ current_pubkey: EdwardsPoint,
|
||||||
|
+ update_interval: Duration,
|
||||||
|
+ last_update: Instant,
|
||||||
|
+ last_count: u64,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl Seed {
|
||||||
|
+ pub fn generate() -> Seed {
|
||||||
|
+ let x = StaticSecret::new(&mut OsRng);
|
||||||
|
+ let base_scalar = Scalar::from_bytes_mod_order(x.to_bytes());
|
||||||
|
+ if x.to_bytes() != convert_scalar_to_privkey(base_scalar).to_bytes() {
|
||||||
|
+ panic!("shouldn't happen");
|
||||||
|
+ // but if for some reason we can't avoid it, we could just re-roll
|
||||||
|
+ // return None;
|
||||||
|
+ }
|
||||||
|
+ Seed { base_scalar }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /// Returns an iterator that yields (count, points). The point can be
|
||||||
|
+ /// converted into a public key (and filtered for suitability). The count
|
||||||
|
+ /// can be combined with the base scalar and converted into the
|
||||||
|
+ /// corresponding private key.
|
||||||
|
+ pub fn scan(&self) -> Scan {
|
||||||
|
+ Scan {
|
||||||
|
+ add_count: 8,
|
||||||
|
+ add_pubkey: integer_to_scalar(8) * ED25519_BASEPOINT_POINT,
|
||||||
|
+ count: 0,
|
||||||
|
+ current_pubkey: self.base_scalar * ED25519_BASEPOINT_POINT,
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn scan_progress(&self) -> ScanProgress {
|
||||||
|
+ ScanProgress {
|
||||||
|
+ add_count: 8,
|
||||||
|
+ add_pubkey: integer_to_scalar(8) * ED25519_BASEPOINT_POINT,
|
||||||
|
+ count: 0,
|
||||||
|
+ current_pubkey: self.base_scalar * ED25519_BASEPOINT_POINT,
|
||||||
|
+ update_interval: Duration::new(1, 0),
|
||||||
|
+ last_update: Instant::now(),
|
||||||
|
+ last_count: 0,
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn convert_count_to_privkey(&self, count: u64) -> StaticSecret {
|
||||||
|
+ let winning_scalar = self.base_scalar + integer_to_scalar(count);
|
||||||
|
+ convert_scalar_to_privkey(winning_scalar)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pub fn convert_both(&self, both: (u64, EdwardsPoint)) -> (StaticSecret, PublicKey) {
|
||||||
|
+ let (count, point) = both;
|
||||||
|
+ let privkey = self.convert_count_to_privkey(count);
|
||||||
|
+ let pubkey_bytes = point.to_montgomery().to_bytes();
|
||||||
|
+ let pubkey = PublicKey::from(pubkey_bytes);
|
||||||
|
+ assert_eq!(PublicKey::from(&privkey).as_bytes(), pubkey.as_bytes());
|
||||||
|
+ (privkey, pubkey)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl Iterator for Scan {
|
||||||
|
+ type Item = (u64, EdwardsPoint);
|
||||||
|
+ fn next(&mut self) -> Option<(u64, EdwardsPoint)> {
|
||||||
|
+ // We try up to 2^64/8 steps from each starting Seed. At roughly
|
||||||
|
+ // 4us/step, this will take ~250k years to wrap. So this check could
|
||||||
|
+ // arguably be removed.
|
||||||
|
+ if self.count >= 0xffff_ffff_ffff_fff0 {
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
+ self.count += self.add_count;
|
||||||
|
+ self.current_pubkey += self.add_pubkey;
|
||||||
|
+ Some((self.count, self.current_pubkey))
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub enum ScanResults {
|
||||||
|
+ Trial(u64, EdwardsPoint),
|
||||||
|
+ Progress(u64, f64), // total trials, total seconds
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl ScanResults {
|
||||||
|
+ fn get_rate(&self) -> Option<f64> {
|
||||||
|
+ match self {
|
||||||
|
+ ScanResults::Progress(trials, seconds) => Some((*trials as f64) / *seconds),
|
||||||
|
+ _ => None,
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl Iterator for ScanProgress {
|
||||||
|
+ type Item = ScanResults;
|
||||||
|
+ fn next(&mut self) -> Option<ScanResults> {
|
||||||
|
+ use ScanResults::*;
|
||||||
|
+ if self.count & 1024 == 0 {
|
||||||
|
+ let now = Instant::now();
|
||||||
|
+ let elapsed = now.duration_since(self.last_update);
|
||||||
|
+ if elapsed > self.update_interval {
|
||||||
|
+ let counted = self.count - self.last_count;
|
||||||
|
+ self.last_count = self.count;
|
||||||
|
+ self.last_update = now;
|
||||||
|
+ return Some(Progress(counted, elapsed.as_secs_f64()));
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ // We try up to 2^64/8 steps from each starting Seed. At roughly
|
||||||
|
+ // 4us/step, this will take ~250k years to wrap. So this check could
|
||||||
|
+ // arguably be removed.
|
||||||
|
+ if self.count >= 0xffff_ffff_ffff_fff0 {
|
||||||
|
+ return None;
|
||||||
|
+ }
|
||||||
|
+ self.count += self.add_count;
|
||||||
|
+ self.current_pubkey += self.add_pubkey;
|
||||||
|
+ Some(Trial(self.count, self.current_pubkey))
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub fn make_check_predicate(
|
||||||
|
+ prefix: &str,
|
||||||
|
+ start: usize,
|
||||||
|
+ end: usize,
|
||||||
|
+) -> impl Fn(&EdwardsPoint) -> bool {
|
||||||
|
+ let prefix = String::from(prefix);
|
||||||
|
+ move |point| {
|
||||||
|
+ let public_b64 = base64::encode(point.to_montgomery().as_bytes());
|
||||||
|
+ //println!("trial: {}", public_b64);
|
||||||
|
+ public_b64[start..end]
|
||||||
|
+ .to_ascii_lowercase()
|
||||||
|
+ .contains(&prefix)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub fn search<T>(check: T) -> (StaticSecret, PublicKey)
|
||||||
|
+where
|
||||||
|
+ T: Fn(&EdwardsPoint) -> bool,
|
||||||
|
+{
|
||||||
|
+ let seed = Seed::generate();
|
||||||
|
+ let both = seed.scan().find(|(_, point)| check(&point)).unwrap();
|
||||||
|
+ seed.convert_both(both)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub fn search_for_prefix(prefix: &str, start: usize, end: usize) -> (StaticSecret, PublicKey) {
|
||||||
|
+ let check = make_check_predicate(prefix, start, end);
|
||||||
|
+ let seed = Seed::generate();
|
||||||
|
+ let both = seed.scan().find(|(_, point)| check(&point)).unwrap();
|
||||||
|
+ seed.convert_both(both)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/// returns checks per second
|
||||||
|
+pub fn measure_rate() -> f64 {
|
||||||
|
+ use ScanResults::*;
|
||||||
|
+ // prefix with characters that will never match
|
||||||
|
+ let check = make_check_predicate("****", 0, 10);
|
||||||
|
+ Seed::generate()
|
||||||
|
+ .scan_progress()
|
||||||
|
+ .map(|res| {
|
||||||
|
+ // timing includes the work of checking the pubkey
|
||||||
|
+ if let Trial(_count, point) = res {
|
||||||
|
+ check(&point);
|
||||||
|
+ };
|
||||||
|
+ res
|
||||||
|
+ })
|
||||||
|
+ .find_map(|res| res.get_rate())
|
||||||
|
+ .unwrap()
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+#[cfg(test)]
|
||||||
|
+mod test {
|
||||||
|
+ use super::*;
|
||||||
|
+
|
||||||
|
+ #[test]
|
||||||
|
+ fn test_search() {
|
||||||
|
+ let check = make_check_predicate("aaa", 0, 10);
|
||||||
|
+ let (privkey, pubkey) = search(check);
|
||||||
|
+ println!(
|
||||||
|
+ "priv: {}, pub: {}",
|
||||||
|
+ base64::encode(&privkey.to_bytes()),
|
||||||
|
+ base64::encode(&pubkey.as_bytes())
|
||||||
|
+ );
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ #[test]
|
||||||
|
+ fn test_rate() {
|
||||||
|
+ let speed = measure_rate();
|
||||||
|
+ println!("speed: {:.3e} keys per second", speed);
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
--
|
||||||
|
2.36.0
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
From d6473b2d99351fd5ec773dde7037e7fdd0cce18a Mon Sep 17 00:00:00 2001
|
||||||
|
From: Brian Warner <warner@lothar.com>
|
||||||
|
Date: Tue, 3 Mar 2020 21:12:01 -0800
|
||||||
|
Subject: [PATCH 3/5] update benchmarks to new scheme
|
||||||
|
|
||||||
|
---
|
||||||
|
benches/keygen.rs | 59 ++++++++++++++++++++++++++---------------------
|
||||||
|
1 file changed, 33 insertions(+), 26 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/benches/keygen.rs b/benches/keygen.rs
|
||||||
|
index a50062e..00dcc55 100644
|
||||||
|
--- a/benches/keygen.rs
|
||||||
|
+++ b/benches/keygen.rs
|
||||||
|
@@ -1,63 +1,70 @@
|
||||||
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
|
||||||
|
-use base64;
|
||||||
|
+use curve25519_dalek::{
|
||||||
|
+ constants::ED25519_BASEPOINT_POINT, edwards::EdwardsPoint, montgomery::MontgomeryPoint,
|
||||||
|
+ scalar::Scalar,
|
||||||
|
+};
|
||||||
|
use rand_core::OsRng;
|
||||||
|
-use x25519_dalek::{PublicKey, StaticSecret};
|
||||||
|
|
||||||
|
-use wireguard_vanity_lib::trial;
|
||||||
|
+use wireguard_vanity_lib::{make_check_predicate, Seed};
|
||||||
|
|
||||||
|
fn b1_point_generation(c: &mut Criterion) {
|
||||||
|
- c.bench_function("b1_point_generation", |b| {
|
||||||
|
- b.iter(|| StaticSecret::new(&mut OsRng))
|
||||||
|
- });
|
||||||
|
+ let seed = Seed::generate();
|
||||||
|
+ let mut scan = seed.scan();
|
||||||
|
+ c.bench_function("b1_point_generation", |b| b.iter(|| scan.next()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn b2a_point_conversion(c: &mut Criterion) {
|
||||||
|
- let private = StaticSecret::new(&mut OsRng);
|
||||||
|
+ let ed_point: EdwardsPoint = Scalar::random(&mut OsRng) * ED25519_BASEPOINT_POINT;
|
||||||
|
c.bench_function("b2a_point_conversion", |b| {
|
||||||
|
- b.iter(|| PublicKey::from(&private))
|
||||||
|
+ b.iter(|| ed_point.to_montgomery())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn b2b_point_to_bytes(c: &mut Criterion) {
|
||||||
|
- let private = StaticSecret::new(&mut OsRng);
|
||||||
|
- let public = PublicKey::from(&private);
|
||||||
|
- c.bench_function("b2b_point_to_bytes", |b| b.iter(|| public.as_bytes()));
|
||||||
|
+ let ed_point: EdwardsPoint = Scalar::random(&mut OsRng) * ED25519_BASEPOINT_POINT;
|
||||||
|
+ let mt_point: MontgomeryPoint = ed_point.to_montgomery();
|
||||||
|
+ c.bench_function("b2b_point_to_bytes", |b| b.iter(|| mt_point.as_bytes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn b2c_bytes_to_base64(c: &mut Criterion) {
|
||||||
|
- let private = StaticSecret::new(&mut OsRng);
|
||||||
|
- let public = PublicKey::from(&private);
|
||||||
|
- let public_bytes = public.as_bytes();
|
||||||
|
+ let ed_point: EdwardsPoint = Scalar::random(&mut OsRng) * ED25519_BASEPOINT_POINT;
|
||||||
|
+ let mt_point: MontgomeryPoint = ed_point.to_montgomery();
|
||||||
|
+ let bytes = mt_point.as_bytes();
|
||||||
|
c.bench_function("b2c_bytes_to_base64", |b| {
|
||||||
|
- b.iter(|| base64::encode(black_box(&public_bytes)))
|
||||||
|
+ b.iter(|| base64::encode(black_box(&bytes)))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn b2d_base64_contains(c: &mut Criterion) {
|
||||||
|
- let private = StaticSecret::new(&mut OsRng);
|
||||||
|
- let public = PublicKey::from(&private);
|
||||||
|
- let public_b64 = base64::encode(public.as_bytes());
|
||||||
|
+ let ed_point: EdwardsPoint = Scalar::random(&mut OsRng) * ED25519_BASEPOINT_POINT;
|
||||||
|
+ let mt_point: MontgomeryPoint = ed_point.to_montgomery();
|
||||||
|
+ let bytes = mt_point.as_bytes();
|
||||||
|
+ let public_b64 = base64::encode(bytes);
|
||||||
|
c.bench_function("b2d_base64_contains", |b| {
|
||||||
|
b.iter(|| public_b64[0..10].to_ascii_lowercase().contains("****"))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn b2e_total_point_checking(c: &mut Criterion) {
|
||||||
|
+ let check = make_check_predicate("****", 0, 10);
|
||||||
|
+ let seed = Seed::generate();
|
||||||
|
+ let mut scan = seed.scan();
|
||||||
|
+ let (_count, point) = scan.next().unwrap();
|
||||||
|
c.bench_function("b2e_total_point_checking", |b| {
|
||||||
|
- b.iter(|| {
|
||||||
|
- let private = StaticSecret::new(&mut OsRng);
|
||||||
|
- let public = PublicKey::from(&private);
|
||||||
|
- let public_b64 = base64::encode(public.as_bytes());
|
||||||
|
- public_b64[0..10].to_ascii_lowercase().contains("****")
|
||||||
|
- })
|
||||||
|
+ b.iter(|| check(black_box(&point)))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn b3_point_generation_and_checking(c: &mut Criterion) {
|
||||||
|
- let prefix: &str = "****";
|
||||||
|
+ let check = make_check_predicate("****", 0, 10);
|
||||||
|
+ let seed = Seed::generate();
|
||||||
|
+ let mut scan = seed.scan();
|
||||||
|
c.bench_function("b3_point_generation_and_checking", |b| {
|
||||||
|
- b.iter(|| trial(&prefix, 0, 10))
|
||||||
|
+ b.iter(|| {
|
||||||
|
+ let (_count, point) = scan.next().unwrap();
|
||||||
|
+ check(&point)
|
||||||
|
+ })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
--
|
||||||
|
2.36.0
|
||||||
|
|
24
wireguard-vanity-address/0004-lowercase-lol.patch
Normal file
24
wireguard-vanity-address/0004-lowercase-lol.patch
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
From c072abceaaa2250d23ed344a0b36666a831719d6 Mon Sep 17 00:00:00 2001
|
||||||
|
From: psykose <alice@ayaya.dev>
|
||||||
|
Date: Wed, 27 Apr 2022 03:54:08 +0000
|
||||||
|
Subject: [PATCH 4/5] lowercase lol
|
||||||
|
|
||||||
|
---
|
||||||
|
src/lib.rs | 1 -
|
||||||
|
1 file changed, 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/src/lib.rs b/src/lib.rs
|
||||||
|
index 2e6b33f..5dbe488 100644
|
||||||
|
--- a/src/lib.rs
|
||||||
|
+++ b/src/lib.rs
|
||||||
|
@@ -385,7 +385,6 @@ pub fn make_check_predicate(
|
||||||
|
let public_b64 = base64::encode(point.to_montgomery().as_bytes());
|
||||||
|
//println!("trial: {}", public_b64);
|
||||||
|
public_b64[start..end]
|
||||||
|
- .to_ascii_lowercase()
|
||||||
|
.contains(&prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--
|
||||||
|
2.36.0
|
||||||
|
|
1096
wireguard-vanity-address/0005-sync-deps.patch
Normal file
1096
wireguard-vanity-address/0005-sync-deps.patch
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,16 +1,22 @@
|
||||||
# Contributor: psykose <alice@ayaya.dev>
|
# Contributor: psykose <alice@ayaya.dev>
|
||||||
# Maintainer: psykose <alice@ayaya.dev>
|
# Maintainer: psykose <alice@ayaya.dev>
|
||||||
pkgname=wireguard-vanity-address
|
pkgname=wireguard-vanity-address
|
||||||
pkgver=0.4.0
|
pkgver=0.4.0_git20200327
|
||||||
pkgrel=1
|
_gitrev=d36eeac325ba3f27111ef4b17d171e695ff75552
|
||||||
|
pkgrel=2
|
||||||
pkgdesc="generate Wireguard keypairs with a given prefix string"
|
pkgdesc="generate Wireguard keypairs with a given prefix string"
|
||||||
url="https://github.com/warner/wireguard-vanity-address"
|
url="https://github.com/warner/wireguard-vanity-address"
|
||||||
arch="all"
|
arch="all"
|
||||||
license="MIT"
|
license="MIT"
|
||||||
makedepends="cargo"
|
makedepends="cargo"
|
||||||
source="https://github.com/warner/wireguard-vanity-address/archive/refs/tags/v$pkgver/wireguard-vanity-address-v$pkgver.tar.gz
|
source="https://github.com/warner/wireguard-vanity-address/archive/$_gitrev/wireguard-vanity-address-$_gitrev.tar.gz
|
||||||
more-features.patch
|
0001-add-curve25519-dalek-dependency.patch
|
||||||
|
0002-new-search-algorithm-point-addition-not-scalarmult.patch
|
||||||
|
0003-update-benchmarks-to-new-scheme.patch
|
||||||
|
0004-lowercase-lol.patch
|
||||||
|
0005-sync-deps.patch
|
||||||
"
|
"
|
||||||
|
builddir="$srcdir/$pkgname-$_gitrev"
|
||||||
options="!check" # no tests
|
options="!check" # no tests
|
||||||
|
|
||||||
export CARGO_HOME="$srcdir"
|
export CARGO_HOME="$srcdir"
|
||||||
|
@ -34,6 +40,10 @@ package() {
|
||||||
}
|
}
|
||||||
|
|
||||||
sha512sums="
|
sha512sums="
|
||||||
7be6823b01dc7741018f6e98a3a91ad84f290d586d4418c9f65f5966141c89bd5e5bd80e856286d6c7ff2f756ca20342169d4b57016ac90882a5c9f74643a11e wireguard-vanity-address-v0.4.0.tar.gz
|
60b9e79517ab221fe597b3bf2cb8a01498758768aa9bd0855c7e1b31dde7399bc004c71f4cfd0a2cd87a0542e4012aa16e5539561b1d46534b806d6940cdedb0 wireguard-vanity-address-d36eeac325ba3f27111ef4b17d171e695ff75552.tar.gz
|
||||||
e5801d0d6ad448fec2a5db7152d9f3fe17324cb79f7d09941106b7a0c9126e1d0ebce5a3157d07f69d3a8879dde6c95232d901ccbdbc054fac1aa3ae70d6f6e8 more-features.patch
|
79397a36ecd446fbbe1be4138f841289057be257cafca41f453223fa01991cc23fcc0c835a870da6f0a72ffb3e0e00901a9d941b85133f7ba3bc1a071d78ce7d 0001-add-curve25519-dalek-dependency.patch
|
||||||
|
a00d7241819bda4adb0c2233f26908798a7087171a975dcc442e7b090a3d038564bf3ec9629312f5e9d1cf5285e0a43a10076d480cb8975232ded9d619b5dbce 0002-new-search-algorithm-point-addition-not-scalarmult.patch
|
||||||
|
d81318fa5810223601d72923bc251bea08c13b850add03483cd09910eadbd2a89c0cdf48eef6e4c68c41d8ab2e3d8ad22ca6e695ed54f08cf63eccc2d5ee64d4 0003-update-benchmarks-to-new-scheme.patch
|
||||||
|
359f7ff6a90882aea8f4fc08f9e4d13c73b1e1ee927dfa5d0ffd4189c9b365887ca4582082b94036582a04037ea92dd36d12886001f224428b262d462427af75 0004-lowercase-lol.patch
|
||||||
|
de0629aa7fb5bee82ef1da4fe0e7e6f2eaa8bb932f793cb72199531c09fdc5536d7bc5a16fe1210b7e1e03206dd51176621cdb1e27866c7afe59b8b05eb68651 0005-sync-deps.patch
|
||||||
"
|
"
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
From 89b5d55f62bcfdc7c13b43981e0725b6860ebad2 Mon Sep 17 00:00:00 2001
|
|
||||||
From: ThinkChaos <ThinkChaos@users.noreply.github.com>
|
|
||||||
Date: Fri, 24 Apr 2020 22:21:55 +0200
|
|
||||||
Subject: [PATCH] Add `--case-sensitive` and allow `--in 0` meaning actual
|
|
||||||
prefix
|
|
||||||
|
|
||||||
---
|
|
||||||
src/bin.rs | 20 +++++++++++++-------
|
|
||||||
src/lib.rs | 15 +++++++++++----
|
|
||||||
2 files changed, 24 insertions(+), 11 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/src/bin.rs b/src/bin.rs
|
|
||||||
index a5a6f35..c18d430 100644
|
|
||||||
--- a/src/bin.rs
|
|
||||||
+++ b/src/bin.rs
|
|
||||||
@@ -8,12 +8,11 @@ use num_cpus;
|
|
||||||
use rayon::prelude::*;
|
|
||||||
use wireguard_vanity_lib::trial;
|
|
||||||
|
|
||||||
-fn estimate_one_trial() -> Duration {
|
|
||||||
- let prefix = "prefix";
|
|
||||||
+fn estimate_one_trial(prefix: &str, end: usize, case_sensitive: bool) -> Duration {
|
|
||||||
let start = SystemTime::now();
|
|
||||||
const COUNT: u32 = 100;
|
|
||||||
(0..COUNT).for_each(|_| {
|
|
||||||
- trial(&prefix, 0, 10);
|
|
||||||
+ trial(&prefix, end, case_sensitive);
|
|
||||||
});
|
|
||||||
let elapsed = start.elapsed().unwrap();
|
|
||||||
elapsed.checked_div(COUNT).unwrap()
|
|
||||||
@@ -86,11 +85,16 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
.version("0.3.1")
|
|
||||||
.author("Brian Warner <warner@lothar.com>")
|
|
||||||
.about("finds Wireguard keypairs with a given string prefix")
|
|
||||||
+ .arg(
|
|
||||||
+ Arg::with_name("CASE")
|
|
||||||
+ .long("case-sensitive")
|
|
||||||
+ .help("Use case-sensitive matching"),
|
|
||||||
+ )
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("RANGE")
|
|
||||||
.long("in")
|
|
||||||
.takes_value(true)
|
|
||||||
- .help("NAME must be found within first RANGE chars of pubkey (default: 10)"),
|
|
||||||
+ .help("NAME must be found within first RANGE chars of pubkey (default: 10, 0 means actual prefix)"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("NAME")
|
|
||||||
@@ -98,6 +102,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
.help("string to find near the start of the pubkey"),
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
+ let case_sensitive = matches.is_present("CASE");
|
|
||||||
let prefix = matches.value_of("NAME").unwrap().to_ascii_lowercase();
|
|
||||||
let len = prefix.len();
|
|
||||||
let end: usize = 44.min(match matches.value_of("RANGE") {
|
|
||||||
@@ -110,6 +115,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
+ let end = if end == 0 { len } else { end };
|
|
||||||
if end < len {
|
|
||||||
return Err(ParseError(format!("range {} is too short for len={}", end, len)).into());
|
|
||||||
}
|
|
||||||
@@ -119,7 +125,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
let mut num = offsets;
|
|
||||||
let mut denom = 1u64;
|
|
||||||
prefix.chars().for_each(|c| {
|
|
||||||
- if c.is_ascii_alphabetic() {
|
|
||||||
+ if !case_sensitive && c.is_ascii_alphabetic() {
|
|
||||||
num *= 2; // letters can match both uppercase and lowercase
|
|
||||||
}
|
|
||||||
denom *= 64; // base64
|
|
||||||
@@ -136,7 +142,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
// run at half the speed that this predicts.
|
|
||||||
|
|
||||||
if trials_per_key < 2u64.pow(32) {
|
|
||||||
- let est = estimate_one_trial();
|
|
||||||
+ let est = estimate_one_trial(&prefix, end, case_sensitive);
|
|
||||||
println!(
|
|
||||||
"one trial takes {}, CPU cores available: {}",
|
|
||||||
format_time(duration_to_f64(est)),
|
|
||||||
@@ -162,7 +168,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
// 1M trials takes about 10s on my laptop, so let it run for 1000s
|
|
||||||
(0..100_000_000)
|
|
||||||
.into_par_iter()
|
|
||||||
- .map(|_| trial(&prefix, 0, end))
|
|
||||||
+ .map(|_| trial(&prefix, end, case_sensitive))
|
|
||||||
.filter_map(|r| r)
|
|
||||||
.try_for_each(print)?;
|
|
||||||
Ok(())
|
|
||||||
diff --git a/src/lib.rs b/src/lib.rs
|
|
||||||
index 31138c5..fb95581 100644
|
|
||||||
--- a/src/lib.rs
|
|
||||||
+++ b/src/lib.rs
|
|
||||||
@@ -1,15 +1,22 @@
|
|
||||||
use base64;
|
|
||||||
use rand::thread_rng;
|
|
||||||
+use std::borrow::Cow;
|
|
||||||
use x25519_dalek::{PublicKey, StaticSecret};
|
|
||||||
|
|
||||||
-pub fn trial(prefix: &str, start: usize, end: usize) -> Option<(String, String)> {
|
|
||||||
+pub fn trial(prefix: &str, end: usize, case_sensitive: bool) -> Option<(String, String)> {
|
|
||||||
let mut rng = thread_rng();
|
|
||||||
let private = StaticSecret::new(&mut rng);
|
|
||||||
let public = PublicKey::from(&private);
|
|
||||||
+
|
|
||||||
let public_b64 = base64::encode(public.as_bytes());
|
|
||||||
- if public_b64[start..end]
|
|
||||||
- .to_ascii_lowercase()
|
|
||||||
- .contains(&prefix)
|
|
||||||
+
|
|
||||||
+ let b64_prefix = if case_sensitive {
|
|
||||||
+ Cow::Borrowed(&public_b64[..end])
|
|
||||||
+ } else {
|
|
||||||
+ Cow::Owned(public_b64[..end].to_ascii_lowercase())
|
|
||||||
+ };
|
|
||||||
+
|
|
||||||
+ if b64_prefix.contains(prefix)
|
|
||||||
{
|
|
||||||
let private_b64 = base64::encode(&private.to_bytes());
|
|
||||||
Some((private_b64, public_b64))
|
|
Loading…
Reference in a new issue