Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add bulletproof_plus to wasm #107

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ To generate a module for use in node.js, use this command:

$ wasm-pack build --target nodejs -d tari_js . -- --features "wasm"

To run the wasm bindings unit tests, use this command:

$ wasm-pack test --node --features wasm

Note: Node v10+ is needed for the WASM

## Example (Node.js)
Expand Down
207 changes: 204 additions & 3 deletions src/wasm/range_proofs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,16 @@ use tari_utilities::hex::Hex;
use wasm_bindgen::prelude::*;

use crate::{
extended_range_proof::ExtendedRangeProofService,
range_proof::RangeProofService,
ristretto::{
bulletproofs_plus::BulletproofsPlusService,
dalek_range_proof::DalekRangeProofService,
pedersen::{commitment_factory::PedersenCommitmentFactory, PedersenCommitment},
pedersen::{
commitment_factory::PedersenCommitmentFactory,
extended_commitment_factory::ExtendedPedersenCommitmentFactory,
PedersenCommitment,
},
RistrettoSecretKey,
},
tari_utilities::hex::from_hex,
Expand All @@ -46,6 +52,12 @@ pub struct VerificationResult {
error: String,
}

#[derive(Default, Serialize, Deserialize)]
pub struct RecoverResult {
mask: String,
error: String,
}

#[wasm_bindgen]
pub struct RangeProofFactory {
rpf: DalekRangeProofService,
Expand Down Expand Up @@ -106,6 +118,144 @@ impl Default for RangeProofFactory {
}
}

#[wasm_bindgen]
pub struct ExtendedRangeProofFactory {
rpf: BulletproofsPlusService,
}

#[wasm_bindgen]
impl ExtendedRangeProofFactory {
/// Create a new `RangeProofFactory`
pub fn new() -> Self {
let cf = ExtendedPedersenCommitmentFactory::default();
let rpf = BulletproofsPlusService::init(64, 1, cf).unwrap();
ExtendedRangeProofFactory { rpf }
}

/// Creates a new range proof for the given key-value pair.
pub fn create_proof(&self, key: &str, value: u64) -> JsValue {
let mut result = RangeProofResult::default();
let key = match RistrettoSecretKey::from_hex(key) {
Ok(k) => k,
_ => {
result.error = "Invalid private key".to_string();
return JsValue::from_serde(&result).unwrap();
},
};
match self.rpf.construct_proof(&key, value) {
Ok(p) => result.proof = p.to_hex(),
Err(e) => result.error = e.to_string(),
};
JsValue::from_serde(&result).unwrap()
}

/// Verifies the given range proof and commitment.
pub fn verify(&self, commitment: &str, proof: &str) -> JsValue {
let mut result = VerificationResult::default();
let commitment = match PedersenCommitment::from_hex(commitment) {
Ok(commitment) => commitment,
_ => {
result.error = "Invalid private key".to_string();
return JsValue::from_serde(&result).unwrap();
},
};
let proof = match from_hex(proof) {
Ok(v) => v,
Err(e) => {
result.error = format!("Range proof is invalid. {}", e);
return JsValue::from_serde(&result).unwrap();
},
};
result.valid = self.rpf.verify(&proof, &commitment);
JsValue::from_serde(&result).unwrap()
}

pub fn construct_proof_with_recovery_seed_nonce(&self, mask: &str, value: u64, seed_nonce: &str) -> JsValue {
let mut result = RangeProofResult::default();
let mask = match RistrettoSecretKey::from_hex(mask) {
Ok(k) => k,
_ => {
result.error = "Invalid mask".to_string();
return JsValue::from_serde(&result).unwrap();
},
};
let seed_nonce = match RistrettoSecretKey::from_hex(seed_nonce) {
Ok(k) => k,
_ => {
result.error = "Invalid seed nonce".to_string();
return JsValue::from_serde(&result).unwrap();
},
};
match self
.rpf
.construct_proof_with_recovery_seed_nonce(&mask, value, &seed_nonce)
{
Ok(p) => result.proof = p.to_hex(),
Err(e) => result.error = e.to_string(),
};
JsValue::from_serde(&result).unwrap()
}

pub fn recover_mask(&self, proof: &str, commitment: &str, seed_nonce: &str) -> JsValue {
let mut result = RecoverResult::default();
let proof = match from_hex(proof) {
Ok(v) => v,
Err(e) => {
result.error = format!("Range proof is invalid. {}", e);
return JsValue::from_serde(&result).unwrap();
},
};
let commitment = match PedersenCommitment::from_hex(commitment) {
Ok(commitment) => commitment,
_ => {
result.error = "Invalid commitment".to_string();
return JsValue::from_serde(&result).unwrap();
},
};
let seed_nonce = match RistrettoSecretKey::from_hex(seed_nonce) {
Ok(k) => k,
_ => {
result.error = "Invalid seed nonce".to_string();
return JsValue::from_serde(&result).unwrap();
},
};
match self.rpf.recover_mask(&proof, &commitment, &seed_nonce) {
Ok(p) => result.mask = p.to_hex(),
Err(e) => result.error = e.to_string(),
};
JsValue::from_serde(&result).unwrap()
}

pub fn verify_mask(&self, commitment: &str, mask: &str, value: u64) -> JsValue {
let mut result = VerificationResult::default();
let commitment = match PedersenCommitment::from_hex(commitment) {
Ok(commitment) => commitment,
_ => {
result.error = "Invalid commitment".to_string();
return JsValue::from_serde(&result).unwrap();
},
};
let mask = match RistrettoSecretKey::from_hex(mask) {
Ok(k) => k,
_ => {
result.error = "Invalid mask".to_string();
return JsValue::from_serde(&result).unwrap();
},
};
match self.rpf.verify_mask(&commitment, &mask, value) {
Ok(p) => result.valid = p,
Err(e) => result.error = e.to_string(),
};
JsValue::from_serde(&result).unwrap()
}
}

impl Default for ExtendedRangeProofFactory {
fn default() -> Self {
Self::new()
}
}

#[cfg(test)]
mod test {
use rand::rngs::OsRng;
Expand All @@ -115,15 +265,15 @@ mod test {
use crate::{commitment::HomomorphicCommitmentFactory, keys::PublicKey, ristretto::RistrettoPublicKey};

#[wasm_bindgen_test]
fn it_fails_with_invalid_hex_input() {
fn dalek_range_proof_fails_with_invalid_hex_input() {
let factory = RangeProofFactory::new();
let result = factory.create_proof("", 123).into_serde::<RangeProofResult>().unwrap();
assert!(!result.error.is_empty());
assert!(result.proof.is_empty());
}

#[wasm_bindgen_test]
fn it_creates_a_valid_proof() {
fn dalek_range_proof_creates_a_valid_proof() {
let factory = RangeProofFactory::new();
let (sk, _) = RistrettoPublicKey::random_keypair(&mut OsRng);
let result = factory
Expand All @@ -138,4 +288,55 @@ mod test {
.unwrap();
assert!(result.valid);
}

#[wasm_bindgen_test]
fn bulletproof_plus_fails_with_invalid_hex_input() {
let factory = ExtendedRangeProofFactory::new();
let result = factory.create_proof("", 123).into_serde::<RangeProofResult>().unwrap();
assert!(!result.error.is_empty());
assert!(result.proof.is_empty());
}

#[wasm_bindgen_test]
fn bulletproof_plus_creates_a_valid_proof() {
let factory = ExtendedRangeProofFactory::new();
let (sk, _) = RistrettoPublicKey::random_keypair(&mut OsRng);
let value = 123;
let commitment = ExtendedPedersenCommitmentFactory::default().commit_value(&sk, value);

// Non-rewindable range proof
let proof_result = factory
.create_proof(&sk.to_hex(), value)
.into_serde::<RangeProofResult>()
.unwrap();
let proof_verification_result = factory
.verify(&commitment.to_hex(), &proof_result.proof)
.into_serde::<VerificationResult>()
.unwrap();
assert!(proof_verification_result.valid);

// Rewindable range proof
// - Create
let (seed_nonce, _) = RistrettoPublicKey::random_keypair(&mut OsRng);
let proof_result = factory
.construct_proof_with_recovery_seed_nonce(&sk.to_hex(), value, &seed_nonce.to_hex())
.into_serde::<RangeProofResult>()
.unwrap();
assert!(factory.rpf.verify(&from_hex(&proof_result.proof).unwrap(), &commitment));
// - Recover the blinding factor (mask)
let recover_result = factory
.recover_mask(&proof_result.proof, &commitment.to_hex(), &seed_nonce.to_hex())
.into_serde::<RecoverResult>()
.unwrap();
let mask_verification_result = factory
.verify_mask(&commitment.to_hex(), &recover_result.mask, value)
.into_serde::<VerificationResult>()
.unwrap();
assert!(mask_verification_result.valid);

// To print to `console.log`:
// use crate::wasm::range_proofs::test::__rt::log;
// log(&format_args!("blinding_factor: {}", &sk.to_hex()));
// log(&format_args!("mask : {}", &recover_result.mask));
}
}