diff --git a/Cargo.lock b/Cargo.lock index 0ed55ddb..14eb66b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,30 +10,22 @@ dependencies = [ ] [[package]] -name = "argon2" -version = "0.5.3" +name = "arrayvec" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" -dependencies = [ - "base64ct", - "blake2", - "cpufeatures", - "password-hash", -] +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] -name = "argon2id" -version = "0.1.0" -dependencies = [ - "anubis", - "argon2", -] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "base64ct" -version = "1.8.0" +name = "bitflags" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "blake2" @@ -62,6 +54,12 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cfg-if" version = "0.1.10" @@ -130,6 +128,58 @@ dependencies = [ "crypto-common 0.2.0-rc.4", ] +[[package]] +name = "dynasm" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7d4c414c94bc830797115b8e5f434d58e7e80cb42ba88508c14bc6ea270625" +dependencies = [ + "bitflags", + "byteorder", + "lazy_static", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dynasmrt" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602f7458a3859195fb840e6e0cce5f4330dd9dfbfece0edaf31fe427af346f55" +dependencies = [ + "byteorder", + "dynasm", + "fnv", + "memmap2", +] + +[[package]] +name = "equix" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b48d834668afc862e90cda69065c0d85c192fe0a9364d1c5c05baf21c7454fc9" +dependencies = [ + "arrayvec", + "hashx 0.4.0", + "num-traits", + "thiserror", + "visibility", +] + +[[package]] +name = "fixed-capacity-vec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b31a14f5ee08ed1a40e1252b35af18bed062e3f39b69aab34decde36bc43e40" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "generic-array" version = "0.14.7" @@ -140,6 +190,36 @@ dependencies = [ "version_check", ] +[[package]] +name = "hashx" +version = "0.1.0" +dependencies = [ + "anubis", + "equix", + "hashx 0.4.0", +] + +[[package]] +name = "hashx" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8af708f5ff697572d409331c8714984a0ec1bb2eac190dd5bd75785bad6e764" +dependencies = [ + "arrayvec", + "blake2", + "dynasmrt", + "fixed-capacity-vec", + "hex", + "rand_core", + "thiserror", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hybrid-array" version = "0.4.2" @@ -149,12 +229,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "memmap2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +dependencies = [ + "libc", +] + [[package]] name = "memory_units" version = "0.4.0" @@ -162,21 +257,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] -name = "password-hash" -version = "0.5.0" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "base64ct", - "rand_core", - "subtle", + "autocfg", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", ] [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" [[package]] name = "sha2" @@ -203,18 +336,66 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "wee_alloc" version = "0.4.5" diff --git a/lib/challenge/wasm/wasm.go b/lib/challenge/wasm/wasm.go index 037ed741..5c711be5 100644 --- a/lib/challenge/wasm/wasm.go +++ b/lib/challenge/wasm/wasm.go @@ -18,7 +18,7 @@ import ( //go:generate go tool github.com/a-h/templ/cmd/templ generate func init() { - chall.Register("argon2id", &Impl{algorithm: "argon2id"}) + chall.Register("hashx", &Impl{algorithm: "argon2id"}) chall.Register("sha256", &Impl{algorithm: "sha256"}) } diff --git a/wasm/pow/argon2id/Cargo.toml b/wasm/pow/hashx/Cargo.toml similarity index 89% rename from wasm/pow/argon2id/Cargo.toml rename to wasm/pow/hashx/Cargo.toml index b96b2933..eeb2b430 100644 --- a/wasm/pow/argon2id/Cargo.toml +++ b/wasm/pow/hashx/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "argon2id" +name = "hashx" version = "0.1.0" edition = "2024" @@ -7,7 +7,8 @@ edition = "2024" crate-type = ["cdylib"] [dependencies] -argon2 = "0.5" +equix = "0.3" +hashx = "0.4" anubis = { path = "../../anubis" } diff --git a/wasm/pow/argon2id/src/lib.rs b/wasm/pow/hashx/src/lib.rs similarity index 79% rename from wasm/pow/argon2id/src/lib.rs rename to wasm/pow/hashx/src/lib.rs index 1cdf709d..1a340ca4 100644 --- a/wasm/pow/argon2id/src/lib.rs +++ b/wasm/pow/hashx/src/lib.rs @@ -1,11 +1,12 @@ use anubis::{DATA_BUFFER, DATA_LENGTH, update_nonce}; -use argon2::Argon2; +use hashx::HashX; use std::boxed::Box; use std::sync::{LazyLock, Mutex}; /// SHA-256 hashes are 32 bytes (256 bits). These are stored in static buffers due to the /// fact that you cannot easily pass data from host space to WebAssembly space. -pub static RESULT_HASH: LazyLock> = LazyLock::new(|| Mutex::new([0; 32])); +pub static RESULT_HASH: LazyLock>> = + LazyLock::new(|| Box::new(Mutex::new([0; 32]))); pub static VERIFICATION_HASH: LazyLock>> = LazyLock::new(|| Box::new(Mutex::new([0; 32]))); @@ -44,40 +45,43 @@ fn validate(hash: &[u8], difficulty: u32) -> bool { true } -/// Computes hash for given nonce. -/// -/// This differs from the JavaScript implementations by constructing the hash differently. In -/// JavaScript implementations, the SHA-256 input is the result of appending the nonce as an -/// integer to the hex-formatted challenge, eg: -/// -/// sha256(`${challenge}${nonce}`); -/// -/// This **does work**, however I think that this can be done a bit better by operating on the -/// challenge bytes _directly_ and treating the nonce as a salt. -/// -/// The nonce is also randomly encoded in either big or little endian depending on the last -/// byte of the data buffer in an effort to make it more annoying to automate with GPUs. -fn compute_hash(nonce: u32) -> [u8; 32] { +fn anubis_work_inner( + difficulty: u32, + initial_nonce: u32, + iterand: u32, +) -> Result { + let mut nonce: u32 = initial_nonce; + let data = &DATA_BUFFER; let data_len = *DATA_LENGTH.lock().unwrap(); - let use_le = data[data_len - 1] >= 128; - let mut result = [0u8; 32]; - - let nonce = nonce as u64; - let data_slice = &data[..data_len]; - let nonce = if use_le { - nonce.to_le_bytes() - } else { - nonce.to_be_bytes() - }; + let h = HashX::new(data_slice)?; - let argon2 = Argon2::default(); - argon2 - .hash_password_into(&data_slice, &nonce, &mut result) - .unwrap(); - result + loop { + let hash = h.hash_to_bytes(nonce as u64); + + if validate(&hash, difficulty) { + // If the challenge worked, copy the bytes into `RESULT_HASH` so the runtime + // can pick it up. + let mut challenge = RESULT_HASH.lock().unwrap(); + challenge.copy_from_slice(&hash); + return Ok(nonce); + } + + let old_nonce = nonce; + nonce = nonce.wrapping_add(iterand); + + // send a progress update every 1024 iterations. since each thread checks + // separate values, one simple way to do this is by bit masking the + // nonce for multiples of 1024. unfortunately, if the number of threads + // is not prime, only some of the threads will be sending the status + // update and they will get behind the others. this is slightly more + // complicated but ensures an even distribution between threads. + if nonce > old_nonce | 1023 && (nonce >> 10) % iterand == initial_nonce { + update_nonce(nonce); + } + } } /// This function is the main entrypoint for the Anubis proof of work implementation. @@ -100,32 +104,7 @@ fn compute_hash(nonce: u32) -> [u8; 32] { /// wasting CPU time retrying a hash+nonce pair that likely won't work. #[unsafe(no_mangle)] pub extern "C" fn anubis_work(difficulty: u32, initial_nonce: u32, iterand: u32) -> u32 { - let mut nonce = initial_nonce; - - loop { - let hash = compute_hash(nonce); - - if validate(&hash, difficulty) { - // If the challenge worked, copy the bytes into `RESULT_HASH` so the runtime - // can pick it up. - let mut challenge = RESULT_HASH.lock().unwrap(); - challenge.copy_from_slice(&hash); - return nonce; - } - - let old_nonce = nonce; - nonce = nonce.wrapping_add(iterand); - - // send a progress update every 1024 iterations. since each thread checks - // separate values, one simple way to do this is by bit masking the - // nonce for multiples of 1024. unfortunately, if the number of threads - // is not prime, only some of the threads will be sending the status - // update and they will get behind the others. this is slightly more - // complicated but ensures an even distribution between threads. - if nonce > old_nonce + 1023 && (nonce >> 10) % iterand == initial_nonce { - update_nonce(nonce); - } - } + anubis_work_inner(difficulty, initial_nonce, iterand).unwrap() } /// This function is called by the server in order to validate a proof-of-work challenge. @@ -138,7 +117,12 @@ pub extern "C" fn anubis_work(difficulty: u32, initial_nonce: u32, iterand: u32) /// for now. #[unsafe(no_mangle)] pub extern "C" fn anubis_validate(nonce: u32, difficulty: u32) -> bool { - let computed = compute_hash(nonce); + let data = &DATA_BUFFER; + let data_len = *DATA_LENGTH.lock().unwrap(); + let data_slice = &data[..data_len]; + + let h: HashX = HashX::new(data_slice).unwrap(); + let computed = h.hash_to_bytes(nonce as u64); let valid = validate(&computed, difficulty); if !valid { return false; diff --git a/wasm/wasm_test.go b/wasm/wasm_test.go index cc35c613..f1409f3f 100644 --- a/wasm/wasm_test.go +++ b/wasm/wasm_test.go @@ -58,9 +58,8 @@ func TestAlgos(t *testing.T) { for _, kind := range []string{"baseline", "simd128"} { for _, fname := range fnames { - fname := fname - t.Run(fname.Name(), func(t *testing.T) { - abiTest(t, kind, fname.Name(), 4) + t.Run(kind+"/"+fname.Name(), func(t *testing.T) { + abiTest(t, kind, fname.Name(), 16) }) } } @@ -112,10 +111,10 @@ func BenchmarkSHA256(b *testing.B) { } } -func BenchmarkArgon2ID(b *testing.B) { +func BenchmarkHashX(b *testing.B) { for _, kind := range []string{"baseline", "simd128"} { b.Run(kind, func(b *testing.B) { - bench(b, kind, "argon2id.wasm", []uint32{4, 6, 8}) + bench(b, kind, "hashx.wasm", []uint32{4, 6, 8, 10, 12, 14, 16}) }) } }