Skip to content

Commit

Permalink
feat: add simple bulletproofs plus interface(#105)
Browse files Browse the repository at this point in the history
Added simple bulletproofs plus interface that does not support extended masks or aggregated proofs.
  • Loading branch information
hansieodendaal authored Jun 20, 2022
1 parent a564ba8 commit 4f9500c
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 19 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", t

base64 = "0.10.1"
blake2 = "0.9.1"
bulletproofs = { package = "tari_bulletproofs", git = "https://github.com/tari-project/bulletproofs", commit = "0033c45538164eb1d81891dad054e217d8b364f4" }
bulletproofs_plus = { package = "tari_bulletproofs_plus", git = "https://github.com/tari-project/bulletproofs-plus", commit = "28725cdd7d753478c9508cae629a8063fc46be37" }
bulletproofs = { package = "tari_bulletproofs", git = "https://github.com/tari-project/bulletproofs", rev = "0033c45538164eb1d81891dad054e217d8b364f4" }
bulletproofs_plus = { package = "tari_bulletproofs_plus", git = "https://github.com/tari-project/bulletproofs-plus", rev = "28725cdd7d753478c9508cae629a8063fc46be37" }
curve25519-dalek = { package = "curve25519-dalek-ng", version = "4.1", default-features = false, features = ["u64_backend", "serde", "alloc"] }
digest = "0.9.0"
getrandom = { version = "0.2.3", default-features = false, optional = true }
Expand Down
33 changes: 29 additions & 4 deletions src/extended_range_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,31 @@ pub trait ExtendedRangeProofService {
type K: SecretKey;
type PK: PublicKey<K = Self::K>;

/// Construct a simple non-aggregated range proof with default minimum value promise of `0` and the ability to
/// rewind it. Requires a rewind key (seed nonce) to be included in the range proof.
fn construct_proof_with_recovery_seed_nonce(
&self,
mask: &Self::K,
value: u64,
seed_nonce: &Self::K,
) -> Result<Self::Proof, RangeProofError>;

/// Recover the (unverified) mask for a simple non-aggregated proof using the provided seed-nonce.
fn recover_mask(
&self,
proof: &Self::Proof,
commitment: &HomomorphicCommitment<Self::PK>,
seed_nonce: &Self::K,
) -> Result<Self::K, RangeProofError>;

/// Verify a recovered mask for a simple non-aggregated proof against the commitment.
fn verify_mask(
&self,
commitment: &HomomorphicCommitment<Self::PK>,
mask: &Self::K,
value: u64,
) -> Result<bool, RangeProofError>;

/// Constructs a new extended range proof, which may be aggregated, for the given set(s) of secret key(s) value(s)
/// and minimum value promise(s). Other optional inputs are seed nonce(s) and mask(s) for mask embedding
/// and recovery. If no mask(s) are provided together with the seed nonce(s), the secret key(s), will be embedded.
Expand Down Expand Up @@ -67,15 +92,15 @@ pub trait ExtendedRangeProofService {
statements: Vec<&AggregatedPublicStatement<Self::PK>>,
) -> Result<(), RangeProofError>;

/// Recover the (unverified) mask for a non-aggregated proof using the provided seed-nonce.
fn recover_mask(
/// Recover the (unverified) extended mask for a non-aggregated proof using the provided seed-nonce.
fn recover_extended_mask(
&self,
proof: &Self::Proof,
statement: &AggregatedPrivateStatement<Self::PK>,
) -> Result<Option<ExtendedMask<Self::K>>, RangeProofError>;

/// Verify a recovered mask for a non-aggregated proof against the commitment.
fn verify_mask(
/// Verify a recovered extended mask for a non-aggregated proof against the commitment.
fn verify_extended_mask(
&self,
commitment: &HomomorphicCommitment<Self::PK>,
extended_mask: &ExtendedMask<Self::K>,
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub mod ristretto;
pub mod wasm;

pub mod errors;
mod extended_range_proof;
pub mod extended_range_proof;
#[cfg(feature = "ffi")]
pub mod ffi;

Expand Down
4 changes: 2 additions & 2 deletions src/rewindable_range_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub trait RewindableRangeProofService {
type K: SecretKey;
type PK: PublicKey<K = Self::K>;

/// Construct a rangeproof with the ability to rewind it. Requires two rewind keys and a 19-byte message to be
/// Construct a range proof with the ability to rewind it. Requires two rewind keys and a 19-byte message to be
/// included in the range proof. The proof can contain 23 bytes but 4 bytes are used to confirm that a rewind
/// was performed correctly
fn construct_proof_with_rewind_key(
Expand Down Expand Up @@ -65,7 +65,7 @@ pub trait RewindableRangeProofService {
) -> Result<FullRewindResult<Self::K>, RangeProofError>;
}

/// Rewind data extracted from a rangeproof containing the committed value and the 19 byte proof message.
/// Rewind data extracted from a range proof containing the committed value and the 19 byte proof message.
#[derive(Debug, PartialEq)]
pub struct RewindResult {
pub committed_value: u64,
Expand Down
164 changes: 154 additions & 10 deletions src/ristretto/bulletproofs_plus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,34 @@ impl ExtendedRangeProofService for BulletproofsPlusService {
type PK = RistrettoPublicKey;
type Proof = Vec<u8>;

fn construct_proof_with_recovery_seed_nonce(
&self,
mask: &Self::K,
value: u64,
seed_nonce: &Self::K,
) -> Result<Self::Proof, RangeProofError> {
let commitment = self
.generators
.pc_gens()
.commit(&Scalar::from(value), &[mask.0])
.map_err(|e| RangeProofError::ProofConstructionError(e.to_string()))?;
let opening = CommitmentOpening::new(value, vec![mask.0]);
let witness =
RangeWitness::init(vec![opening]).map_err(|e| RangeProofError::ProofConstructionError(e.to_string()))?;
let statement = RangeStatement::init(
self.generators.clone(),
vec![commitment],
vec![None],
Some(seed_nonce.0),
)
.map_err(|e| RangeProofError::ProofConstructionError(e.to_string()))?;

let proof = RistrettoRangeProof::prove(self.transcript_label, &statement, &witness)
.map_err(|e| RangeProofError::ProofConstructionError(e.to_string()))?;

Ok(proof.to_bytes())
}

fn construct_extended_proof(
&self,
extended_witnesses: Vec<RistrettoExtendedWitness>,
Expand Down Expand Up @@ -392,6 +420,53 @@ impl ExtendedRangeProofService for BulletproofsPlusService {
}

fn recover_mask(
&self,
proof: &Self::Proof,
commitment: &HomomorphicCommitment<Self::PK>,
seed_nonce: &Self::K,
) -> Result<Self::K, RangeProofError> {
match RistrettoRangeProof::from_bytes(proof).map_err(|e| RangeProofError::InvalidRangeProof(e.to_string())) {
Ok(rp) => {
let statement = RangeStatement {
generators: self.generators.clone(),
commitments: vec![commitment.0.point()],
commitments_compressed: vec![*commitment.0.compressed()],
minimum_value_promises: vec![None],
seed_nonce: Some(seed_nonce.0),
};
// Prepare the range statement

match RistrettoRangeProof::recover_masks_ony(self.transcript_label, &vec![statement], &[rp]) {
Ok(recovered_mask) => {
if recovered_mask.is_empty() {
Err(RangeProofError::InvalidRewind(
"Mask could not be recovered".to_string(),
))
} else if let Some(mask) = &recovered_mask[0] {
Ok(RistrettoSecretKey(
mask.blindings()
.map_err(|e| RangeProofError::InvalidRewind(e.to_string()))?[0],
))
} else {
Err(RangeProofError::InvalidRewind(
"Mask could not be recovered".to_string(),
))
}
},
Err(e) => Err(RangeProofError::InvalidRangeProof(format!(
"Internal range proof error ({})",
e
))),
}
},
Err(e) => Err(RangeProofError::InvalidRangeProof(format!(
"Range proof could not be deserialized ({})",
e
))),
}
}

fn recover_extended_mask(
&self,
proof: &Self::Proof,
statement: &RistrettoAggregatedPrivateStatement,
Expand Down Expand Up @@ -427,6 +502,23 @@ impl ExtendedRangeProofService for BulletproofsPlusService {
}

fn verify_mask(
&self,
commitment: &HomomorphicCommitment<Self::PK>,
mask: &Self::K,
value: u64,
) -> Result<bool, RangeProofError> {
match self
.generators
.pc_gens()
.commit(&Scalar::from(value), &[mask.0])
.map_err(|e| RangeProofError::ExtensionDegree(e.to_string()))
{
Ok(val) => Ok(val == commitment.0.point()),
Err(e) => Err(e),
}
}

fn verify_extended_mask(
&self,
commitment: &HomomorphicCommitment<Self::PK>,
extended_mask: &RistrettoExtendedMask,
Expand Down Expand Up @@ -506,7 +598,7 @@ mod test {
/// The 'BulletproofsPlusService' interface 'construct_proof' should only accept Pedersen generators of
/// 'ExtensionDegree::Zero' with 'aggregation_size == 1' and values proportional to the bit length
#[test]
fn test_construct_verify_proof() {
fn test_construct_verify_proof_no_recovery() {
let mut rng = rand::thread_rng();
for extension_degree in EXTENSION_DEGREE {
let factory = ExtendedPedersenCommitmentFactory::new_with_extension_degree(extension_degree).unwrap();
Expand Down Expand Up @@ -536,7 +628,7 @@ mod test {

#[test]
#[allow(clippy::too_many_lines)]
fn test_construct_verify_extended_proof() {
fn test_construct_verify_extended_proof_with_recovery() {
static BIT_LENGTH: [usize; 2] = [2, 64];
static AGGREGATION_SIZE: [usize; 2] = [2, 64];
let mut rng = rand::thread_rng();
Expand Down Expand Up @@ -622,13 +714,13 @@ mod test {
// --- Only recover the masks
for (i, proof) in proofs.iter().enumerate() {
let recovered_private_mask = bulletproofs_plus_service
.recover_mask(proof, &statements_private[i])
.recover_extended_mask(proof, &statements_private[i])
.unwrap();
assert_eq!(private_masks[i], recovered_private_mask);
for statement in &statements_private[i].statements {
if let Some(this_mask) = recovered_private_mask.clone() {
assert!(bulletproofs_plus_service
.verify_mask(
.verify_extended_mask(
&statement.commitment,
&this_mask,
*commitment_value_map_private.get(&statement.commitment).unwrap()
Expand All @@ -649,7 +741,7 @@ mod test {
if let Some(this_mask) = recovered_private_masks[index].clone() {
// Verify the recovered mask
assert!(bulletproofs_plus_service
.verify_mask(
.verify_extended_mask(
&statement.commitment,
&this_mask,
*commitment_value_map_private.get(&statement.commitment).unwrap()
Expand Down Expand Up @@ -679,7 +771,7 @@ mod test {
}

#[test]
fn test_construct_verify_simple_extended_proof() {
fn test_construct_verify_simple_extended_proof_with_recovery() {
let bit_length = 64usize;
let aggregation_size = 1usize;
let extension_degree = CommitmentExtensionDegree::DefaultPedersen;
Expand Down Expand Up @@ -727,18 +819,18 @@ mod test {
.unwrap();
// --- Only recover the mask (use the wrong transcript label for the service - will fail)
let recovered_private_mask = verifiers_bulletproofs_plus_service
.recover_mask(&proof, &statement_private)
.recover_extended_mask(&proof, &statement_private)
.unwrap();
assert_ne!(private_mask, recovered_private_mask);
// --- Only recover the mask (use the correct transcript label for the service)
verifiers_bulletproofs_plus_service.custom_transcript_label("123 range proof");
let recovered_private_mask = verifiers_bulletproofs_plus_service
.recover_mask(&proof, &statement_private)
.recover_extended_mask(&proof, &statement_private)
.unwrap();
assert_eq!(private_mask, recovered_private_mask);
if let Some(this_mask) = recovered_private_mask {
assert!(verifiers_bulletproofs_plus_service
.verify_mask(
.verify_extended_mask(
&statement_private.statements[0].commitment,
&this_mask,
extended_witness.value,
Expand All @@ -755,7 +847,7 @@ mod test {
if let Some(this_mask) = recovered_private_masks[0].clone() {
// Verify the recovered mask
assert!(verifiers_bulletproofs_plus_service
.verify_mask(
.verify_extended_mask(
&statement_private.statements[0].commitment,
&this_mask,
extended_witness.value,
Expand Down Expand Up @@ -784,4 +876,56 @@ mod test {
.verify_batch(vec![&proof], vec![&statement_public])
.is_ok());
}

#[test]
fn test_construct_verify_simple_proof_with_recovery() {
let bit_length = 64usize;
let aggregation_size = 1usize;
let extension_degree = CommitmentExtensionDegree::DefaultPedersen;
let mut rng = rand::thread_rng();
let factory = ExtendedPedersenCommitmentFactory::new_with_extension_degree(extension_degree).unwrap();
#[allow(clippy::cast_possible_truncation)]
let (value_min, value_max) = (0u64, (1u128 << (bit_length - 1)) as u64);
// 1. Prover's service
let mut provers_bulletproofs_plus_service =
BulletproofsPlusService::init(bit_length, aggregation_size, factory.clone()).unwrap();
provers_bulletproofs_plus_service.custom_transcript_label("123 range proof");

// 2. Create witness data
let value = rng.gen_range(value_min..value_max);
let mask = RistrettoSecretKey(Scalar::random_not_zero(&mut rng));
let commitment = factory.commit_value(&mask, value);

// 4. Create the proof
let seed_nonce = RistrettoSecretKey(Scalar::random_not_zero(&mut rng));
let proof = provers_bulletproofs_plus_service
.construct_proof_with_recovery_seed_nonce(&mask, value, &seed_nonce)
.unwrap();

// 5. Verifier's service
let mut verifiers_bulletproofs_plus_service =
BulletproofsPlusService::init(bit_length, aggregation_size, factory.clone()).unwrap();

// 6. Mask recovery as the commitment owner, i.e. the prover self
// --- Recover the mask (use the wrong transcript label for the service - will fail)
let recovered_mask = verifiers_bulletproofs_plus_service
.recover_mask(&proof, &commitment, &seed_nonce)
.unwrap();
assert_ne!(mask, recovered_mask);
// --- Recover the mask (use the correct transcript label for the service)
verifiers_bulletproofs_plus_service.custom_transcript_label("123 range proof");
let recovered_mask = verifiers_bulletproofs_plus_service
.recover_mask(&proof, &commitment, &seed_nonce)
.unwrap();
assert_eq!(mask, recovered_mask);
// --- Verify that the mask opens the commitment
assert!(verifiers_bulletproofs_plus_service
.verify_mask(&commitment, &recovered_mask, value)
.unwrap());
// --- Also verify that the commitment factory can open the commitment
assert!(factory.open_value(&recovered_mask, value, &commitment));

// 7. Verify the proof as private or public entity
assert!(verifiers_bulletproofs_plus_service.verify(&proof, &commitment));
}
}
6 changes: 6 additions & 0 deletions src/ristretto/ristretto_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ impl From<u64> for RistrettoSecretKey {
}
}

impl From<Scalar> for RistrettoSecretKey {
fn from(s: Scalar) -> Self {
RistrettoSecretKey(s)
}
}

//--------------------------------------------- Borrow impl -------------------------------------------------//

impl<'a> Borrow<Scalar> for &'a RistrettoSecretKey {
Expand Down

0 comments on commit 4f9500c

Please sign in to comment.