110
README.md
110
README.md
@@ -1,2 +1,112 @@
|
||||
# chidori-pow
|
||||
|
||||
Anti-bruteforce proof-of-work.
|
||||
|
||||
## Design
|
||||
|
||||
The challenge flow uses an RSA trapdoor repeated-squaring puzzle:
|
||||
|
||||
- the server generates or loads RSA factors at process start;
|
||||
- the signed binary challenge payload contains the public modulus and exact
|
||||
difficulty step count;
|
||||
- the client performs scheduled sequential modular squaring;
|
||||
- the server verifies cheaply with the private trapdoor.
|
||||
|
||||
The default RSA modulus is 2048 bits, and applications may set a different
|
||||
modulus at initialization. Generated moduli use at least 512 bits. The default
|
||||
difficulty is `450000` scheduled RSA work steps. Applications should choose
|
||||
modulus and step counts from their own benchmarks and risk policy.
|
||||
|
||||
Challenges are signed with Ed25519. The default builder generates a fresh
|
||||
signing key and RSA factors at process start. Persist or inject keys and factors
|
||||
if restart continuity matters.
|
||||
|
||||
`ChallengerBuilder::build()` and `Challenger::issue_challenge()` return
|
||||
`Result`s. Generated factors are clamped to the minimum modulus size.
|
||||
`with_factors(p, q)` injects persisted RSA prime factors and validates that they
|
||||
are distinct primes with a large enough product.
|
||||
|
||||
The challenge string is `base64url(payload || signature)`. `payload` is the
|
||||
bincode-serialized `Puzzle` bytes, `signature` is the fixed 64-byte Ed25519
|
||||
signature suffix, and the signature covers the raw payload bytes.
|
||||
|
||||
## Binding Data
|
||||
|
||||
Applications may bind their own opaque bytes into the puzzle without sending
|
||||
those bytes in the challenge:
|
||||
|
||||
```rust
|
||||
let solution = solve_challenge(&challenge, app_binding_data);
|
||||
challenger.verify_challenge(&challenge, &solution, expected_binding_data);
|
||||
```
|
||||
|
||||
The library does not interpret `binding_data`; callers are responsible for
|
||||
canonicalizing it.
|
||||
|
||||
Recommended `binding_data` contents are app-specific request context such as flow
|
||||
name, route, normalized username hash, CSRF token, form nonce, and a versioned
|
||||
site-specific prefix. Do not put passwords or raw secrets in `binding_data`.
|
||||
|
||||
Mismatched binding data fails exactly like an invalid proof-of-work solution.
|
||||
Verification returns `false` for malformed, expired, replayed, mismatched, or
|
||||
internally unavailable challenges.
|
||||
|
||||
## Native Example
|
||||
|
||||
```rust
|
||||
use chidori_pow::{ChallengerBuilder, solve_challenge};
|
||||
|
||||
let binding_data = b"site-login-v1\0/login\0user-hash";
|
||||
|
||||
let challenger = ChallengerBuilder::new()
|
||||
.with_modulus_bits(2048)
|
||||
.with_difficulty(450_000)
|
||||
.build()?;
|
||||
|
||||
let challenge = challenger.issue_challenge()?;
|
||||
let solution = solve_challenge(&challenge, binding_data);
|
||||
|
||||
assert!(challenger.verify_challenge(
|
||||
&challenge,
|
||||
&solution,
|
||||
binding_data,
|
||||
));
|
||||
```
|
||||
|
||||
## Browser/WASM
|
||||
|
||||
The wasm package exports:
|
||||
|
||||
```ts
|
||||
solve_challenge(challenge: string, binding_data: Uint8Array): string
|
||||
```
|
||||
|
||||
The browser/app code is responsible for constructing the same canonical
|
||||
`binding_data` bytes that the server will later use for verification. Pass an
|
||||
empty `Uint8Array` when no app-specific binding data is needed.
|
||||
|
||||
The browser solver checks the decoded puzzle before solving. It accepts modulus
|
||||
sizes from 512 to 8192 bits and difficulty up to `10000000` scheduled steps.
|
||||
|
||||
## Benchmark
|
||||
|
||||
Run the native benchmark with:
|
||||
|
||||
```sh
|
||||
cargo run --release --features native-bin --bin bench -- \
|
||||
--modulus-bits 2048 \
|
||||
--difficulty 450000 \
|
||||
--rounds 5 \
|
||||
--binding login:user=a
|
||||
```
|
||||
|
||||
The benchmark builds one challenger, then measures repeated issue/solve/verify
|
||||
rounds and prints min/average/p50/max timings.
|
||||
|
||||
## Replay Cache
|
||||
|
||||
Solved challenge tickets are remembered for the current and previous validity
|
||||
windows. The default cache capacity is `250000` tickets per window. When the
|
||||
current window is full, verification fails closed instead of evicting entries,
|
||||
so replay protection is preserved at the cost of rejecting additional solves
|
||||
until the next rotation.
|
||||
|
||||
Reference in New Issue
Block a user