diff --git a/.gitignore b/.gitignore
index 5399fa4..5b431b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,9 @@
-/target
/.idea
-/fuzz/corpus/
-/fuzz/Cargo.lock
-/fuzz/target/
-/fuzz/artifacts
-/fuzz/coverage
-/dudect/Cargo.lock
-/dudect/target/
-/ct_cm4/target/
**/Cargo.lock
+**/artifacts
+**/corpus
+**/coverate
+**/target
+**/pkg
+**/node_modules
+**/package-lock.json
diff --git a/Cargo.toml b/Cargo.toml
index e43b6e0..a803358 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,4 +1,4 @@
-workspace = { members = ['ffi'], exclude = ["ct_cm4", "dudect", "fuzz"] }
+workspace = { members = ['ffi'], exclude = ["ct_cm4", "dudect", "fuzz", "wasm"] }
[package]
name = "fips203"
diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml
new file mode 100644
index 0000000..3085d03
--- /dev/null
+++ b/wasm/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "fips203-wasm"
+version = "0.1.3"
+authors = ["Eric Schorn "]
+description = "Sample web page utilizing FIPS 203 code"
+repository = ""
+publish = false
+edition = "2021"
+
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+
+[features]
+default = ["console_error_panic_hook"]
+
+
+[dependencies]
+wasm-bindgen = "0.2.84"
+fips203 = { path = "../../fips203", default-features = false, features = ["ml-kem-512"] }
+rand_chacha = "0.3.1"
+console_error_panic_hook = { version = "0.1.7", optional = true }
+rand = "0.8.5"
+getrandom = { version = "0.2", features = ["js"] }
+hex = "0.4.3"
+
+
+[dev-dependencies]
+wasm-bindgen-test = "0.3.34"
+
+
+[profile.release]
+opt-level = "s"
diff --git a/wasm/README.md b/wasm/README.md
new file mode 100644
index 0000000..8829e51
--- /dev/null
+++ b/wasm/README.md
@@ -0,0 +1,17 @@
+One-off installation
+
+~~~
+$ cargo install wasm-pack
+$ sudo npm install npm@latest -g
+~~~
+
+To run:
+
+~~~
+$ cd wasm # this directory
+$ wasm-pack build
+$ cd www
+$ npm install
+$ export NODE_OPTIONS=--openssl-legacy-provider
+$ npm run start
+~~~
diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs
new file mode 100644
index 0000000..74364e3
--- /dev/null
+++ b/wasm/src/lib.rs
@@ -0,0 +1,51 @@
+use fips203::ml_kem_512;
+// Could also be ml_kem_768 or ml_kem_1024.
+use fips203::traits::{Decaps, Encaps, KeyGen, SerDes};
+use rand_chacha::rand_core::SeedableRng;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub fn run(seed: &str) -> String {
+ let seed = seed.parse();
+ if seed.is_err() { return "Unable to parse number".to_string(); };
+ let seed = seed.unwrap();
+
+ let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(seed);
+
+ // Alice runs `key_gen()` and then serializes the encaps key `ek` for Bob (to bytes).
+ let (alice_ek, alice_dk) = ml_kem_512::KG::try_keygen_with_rng_vt(&mut rng).expect("keygen failed");
+ let alice_ek_bytes = alice_ek.into_bytes();
+
+ // Alice sends the encaps key `ek_bytes` to Bob.
+ let bob_ek_bytes = alice_ek_bytes;
+
+ // Bob deserializes the encaps `ek_bytes` and then runs `encaps() to get the shared
+ // secret `ssk` and ciphertext `ct`. He serializes the ciphertext `ct` for Alice (to bytes).
+ let bob_ek = ml_kem_512::EncapsKey::try_from_bytes(bob_ek_bytes).expect("ek deser failed");
+ let (bob_ssk, bob_ct) = bob_ek.try_encaps_with_rng_vt(&mut rng).expect("encaps failed");
+ let bob_ct_bytes = bob_ct.into_bytes();
+
+ // Bob sends the ciphertext `ct_bytes` to Alice
+ let alice_ct_bytes = bob_ct_bytes;
+
+ // Alice deserializes the ciphertext `ct` and runs `decaps()` with her decaps key
+ let alice_ct = ml_kem_512::CipherText::try_from_bytes(alice_ct_bytes).expect("ct deser failed");
+ let alice_ssk = alice_dk.try_decaps_vt(&alice_ct).expect("decaps failed");
+
+ // Alice and Bob will now have the same secret key
+ assert_eq!(bob_ssk.into_bytes(), alice_ssk.clone().into_bytes(), "shared secret not identical");
+
+ let ek_hex = hex::encode(&bob_ek_bytes);
+ let ct_hex = hex::encode(&bob_ct_bytes);
+ let dk_hex = hex::encode(alice_dk.into_bytes());
+ let ssk_hex = hex::encode(alice_ssk.into_bytes());
+
+ let s0 = format!("The seed used to generate the keys is: {}\n\n", seed);
+ let s1 = format!("The generated encaps key is: {}\n", ek_hex);
+ let s2 = format!("The generated decaps key is: {}\n\n", dk_hex);
+ let s3 = format!("The generated ciphertext is: {}\n", ct_hex);
+ let s4 = format!("The shared secret is: {}\n", ssk_hex);
+ let s5 = "Alice and Bob have an identical shared secret."; // because the above assert! passed
+
+ (s0 + &s1 + &s2 + &s3 + &s4 + &s5).into()
+}
diff --git a/wasm/www/bootstrap.js b/wasm/www/bootstrap.js
new file mode 100644
index 0000000..c9c7c75
--- /dev/null
+++ b/wasm/www/bootstrap.js
@@ -0,0 +1,5 @@
+// A dependency graph that contains any wasm must all be imported
+// asynchronously. This `bootstrap.js` file does the single async import, so
+// that no one else needs to worry about it again.
+import("./index.js")
+ .catch(e => console.error("Error importing `index.js`:", e));
diff --git a/wasm/www/index.html b/wasm/www/index.html
new file mode 100644
index 0000000..622779b
--- /dev/null
+++ b/wasm/www/index.html
@@ -0,0 +1,40 @@
+
+
+
+
+ FIPS 203 WASM Demo
+
+
+
+
+
+
+
FIPS 203 WASM Demo
+
+
This page is a simple demonstration of code from the FIPS 203
+ Rust crate running in the browser via wasm. This demo has a seedable random number generator so that
+ its results can be compared to
+ native test cases. Please see section 3.3 of the
+ FIPS 203 standard for critical
+ requirements on randomness when utilizing FIPS 203 in production.
+
+
+
Enter a (u64 decimal) seed number below and click on 'Submit'. The encaps and decaps keys will be generated,
+ the encaps() function run to generate the ciphertext and shared secret, the decaps() function run to calculate
+ the 'other' shared secret, the shared secrets confirmed to match, and all resulting values displayed.