wireguard-vanity-address: upgrade 0.4.0_git20200327

more features less bugs
This commit is contained in:
psykose 2022-04-27 03:57:56 +00:00
parent 82e46296ea
commit a830140f00
Signed by: psykose
SSH key fingerprint: SHA256:HwlGVJNLZqHoOvMtkshyGaXLkcdbEJahYMlepHFL+Uk
7 changed files with 1844 additions and 128 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View 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

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,22 @@
# Contributor: psykose <alice@ayaya.dev>
# Maintainer: psykose <alice@ayaya.dev>
pkgname=wireguard-vanity-address
pkgver=0.4.0
pkgrel=1
pkgver=0.4.0_git20200327
_gitrev=d36eeac325ba3f27111ef4b17d171e695ff75552
pkgrel=2
pkgdesc="generate Wireguard keypairs with a given prefix string"
url="https://github.com/warner/wireguard-vanity-address"
arch="all"
license="MIT"
makedepends="cargo"
source="https://github.com/warner/wireguard-vanity-address/archive/refs/tags/v$pkgver/wireguard-vanity-address-v$pkgver.tar.gz
more-features.patch
source="https://github.com/warner/wireguard-vanity-address/archive/$_gitrev/wireguard-vanity-address-$_gitrev.tar.gz
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
export CARGO_HOME="$srcdir"
@ -34,6 +40,10 @@ package() {
}
sha512sums="
7be6823b01dc7741018f6e98a3a91ad84f290d586d4418c9f65f5966141c89bd5e5bd80e856286d6c7ff2f756ca20342169d4b57016ac90882a5c9f74643a11e wireguard-vanity-address-v0.4.0.tar.gz
e5801d0d6ad448fec2a5db7152d9f3fe17324cb79f7d09941106b7a0c9126e1d0ebce5a3157d07f69d3a8879dde6c95232d901ccbdbc054fac1aa3ae70d6f6e8 more-features.patch
60b9e79517ab221fe597b3bf2cb8a01498758768aa9bd0855c7e1b31dde7399bc004c71f4cfd0a2cd87a0542e4012aa16e5539561b1d46534b806d6940cdedb0 wireguard-vanity-address-d36eeac325ba3f27111ef4b17d171e695ff75552.tar.gz
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
"

View file

@ -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))