1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
//! mCaptcha is a proof of work based Denaial-of-Service attack protection system.
//! This is is a WASM library that you can embed in your frontend code to protect your
//! service.
//!
//! A commercial managed solution is in the works but I'd much rather prefer
//! folks host their own instances as it will make the more decentralized and free.
//!
//! ## Workflow:
//! mCaptcha workflow in the frontend is simple.
//! 1. Call service to get a proof of work(PoW) configuration
//! 2. Call into mCaptcha to get PoW
//! 3. Send PoW to mCaptcha service
//! 4. If proof is valid, the service will return a token to the client
//! 5. Submit token to your backend along with your app data(if any)
//! 6. In backend, validate client's token with mCaptcha service
//!
//! ## Example:
//!
//! generate proof-of-work
//! ```rust
//! use pow_wasm::*;
//! use mcaptcha_pow_sha256::*;
//!
//!
//! // salt using which PoW should be computed
//! const SALT: &str = "yrandomsaltisnotlongenoug";
//! // one-time phrase over which PoW should be computed
//! const PHRASE: &str = "ironmansucks";
//! // and the difficulty factor
//! const DIFFICULTY: u32 = 1000;
//!
//! // currently gen_pow() returns a JSON formated string to better communicate
//! // with JavaScript. See [PoW<T>][mcaptcha_pow_sha256::PoW] for schema
//! let serialised_work = gen_pow(SALT.into(), PHRASE.into(), DIFFICULTY);
//!
//!
//! let work: Work = serde_json::from_str(&serialised_work).unwrap();
//!
//! let work = PoWBuilder::default()
//! .result(work.result)
//! .nonce(work.nonce)
//! .build()
//! .unwrap();
//!
//! let config = ConfigBuilder::default().salt(SALT.into()).build().unwrap();
//! assert!(config.is_valid_proof(&work, &PHRASE.to_string()));
//! assert!(config.is_sufficient_difficulty(&work, DIFFICULTY));
//! ```
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
use mcaptcha_pow_sha256::{ConfigBuilder, PoW};
#[derive(Deserialize, Serialize)]
pub struct Work {
pub result: String,
pub nonce: u64,
}
impl From<PoW<String>> for Work {
fn from(p: PoW<String>) -> Self {
Work {
result: p.result,
nonce: p.nonce,
}
}
}
/// generate proof-of-work
/// ```rust
/// fn main() {
/// use pow_wasm::*;
/// use mcaptcha_pow_sha256::*;
///
///
/// // salt using which PoW should be computed
/// const SALT: &str = "yrandomsaltisnotlongenoug";
/// // one-time phrase over which PoW should be computed
/// const PHRASE: &str = "ironmansucks";
/// // and the difficulty factor
/// const DIFFICULTY: u32 = 1000;
///
/// // currently gen_pow() returns a JSON formated string to better communicate
/// // with JavaScript. See [PoW<T>][mcaptcha_pow_sha256::PoW] for schema
/// let serialised_work = gen_pow(SALT.into(), PHRASE.into(), DIFFICULTY);
///
///
/// let work: Work = serde_json::from_str(&serialised_work).unwrap();
///
/// let work = PoWBuilder::default()
/// .result(work.result)
/// .nonce(work.nonce)
/// .build()
/// .unwrap();
///
/// let config = ConfigBuilder::default().salt(SALT.into()).build().unwrap();
/// assert!(config.is_valid_proof(&work, &PHRASE.to_string()));
/// assert!(config.is_sufficient_difficulty(&work, DIFFICULTY));
/// }
/// ```
#[wasm_bindgen]
pub fn gen_pow(salt: String, phrase: String, difficulty_factor: u32) -> String {
let config = ConfigBuilder::default().salt(salt).build().unwrap();
let work = config.prove_work(&phrase, difficulty_factor).unwrap();
let work: Work = work.into();
serde_json::to_string(&work).unwrap()
}
#[wasm_bindgen]
pub fn stepped_gen_pow(
salt: String,
phrase: String,
difficulty_factor: u32,
step: usize,
f: &js_sys::Function,
) -> String {
use mcaptcha_pow_sha256::IncrementalSolve;
let config = ConfigBuilder::default().salt(salt).build().unwrap();
let this = JsValue::null();
let mut inter = None;
loop {
match config.stepped_prove_work(&phrase, difficulty_factor, step, inter) {
Ok(IncrementalSolve::Intermediate(result, nonce, prefix, difficulty)) => {
let data = JsValue::from(nonce);
let _ = f.call1(&this, &data);
inter = Some(IncrementalSolve::Intermediate(
result, nonce, prefix, difficulty,
));
continue;
}
Ok(IncrementalSolve::Work(w)) => {
let work: Work = w.into();
return serde_json::to_string(&work).unwrap();
}
Err(e) => panic!("{}", e),
};
}
}
#[cfg(test)]
mod tests {
use super::*;
use mcaptcha_pow_sha256::PoWBuilder;
const SALT: &str = "yrandomsaltisnotlongenoug";
const PHRASE: &str = "ironmansucks";
const DIFFICULTY: u32 = 1000;
#[test]
fn it_works() {
let serialised_work = gen_pow(SALT.into(), PHRASE.into(), DIFFICULTY);
let work: Work = serde_json::from_str(&serialised_work).unwrap();
let work = PoWBuilder::default()
.result(work.result)
.nonce(work.nonce)
.build()
.unwrap();
let config = ConfigBuilder::default().salt(SALT.into()).build().unwrap();
assert!(config.is_valid_proof(&work, &PHRASE.to_string()));
assert!(config.is_sufficient_difficulty(&work, DIFFICULTY));
}
}