Skip to content

Commit

Permalink
feat: add bulletproof_plus to wasm (#107)
Browse files Browse the repository at this point in the history
Added bulletproof_plus interfaces to wasm
  • Loading branch information
hansieodendaal committed Jun 23, 2022
1 parent 4f9500c commit 62cb98d
Show file tree
Hide file tree
Showing 2 changed files with 208 additions and 3 deletions.
4 changes: 4 additions & 0 deletions README.md
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
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));
}
}

0 comments on commit 62cb98d

Please sign in to comment.