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));
    }
}