use keychain::BlindingFactor;
use std::cmp;
use std::sync::Arc;
use util::secp::key::SecretKey;
use util::secp::pedersen::Commitment;
use util::secp::Secp256k1;
use util::{secp, secp_static};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, Serialize, Deserialize)]
pub enum Error {
#[error("Keychain error {0}")]
Keychain(keychain::Error),
#[error("Secp error {0}")]
Secp(secp::Error),
#[error("Kernel sum mismatch")]
KernelSumMismatch,
#[error("Invalid value")]
InvalidValue,
}
impl From<secp::Error> for Error {
fn from(e: secp::Error) -> Error {
Error::Secp(e)
}
}
impl From<keychain::Error> for Error {
fn from(e: keychain::Error) -> Error {
Error::Keychain(e)
}
}
pub trait Committed {
fn sum_kernel_excesses(
&self,
offset: &BlindingFactor,
secp: &Secp256k1,
) -> Result<(Commitment, Commitment), Error> {
let kernel_commits = self.kernels_committed();
let kernel_sum = sum_commits(kernel_commits, vec![], secp)?;
let kernel_sum_plus_offset = {
let mut commits = vec![kernel_sum];
if *offset != BlindingFactor::zero() {
let key = offset.secret_key(&secp)?;
let offset_commit = secp.commit(0, key)?;
commits.push(offset_commit);
}
secp.commit_sum(commits, vec![])?
};
Ok((kernel_sum, kernel_sum_plus_offset))
}
fn sum_commitments(&self, overage: i64, secp: &Secp256k1) -> Result<Commitment, Error> {
let mut input_commits = self.inputs_committed();
let mut output_commits = self.outputs_committed();
if overage != 0 {
let over_commit = {
let overage_abs = overage.checked_abs().ok_or_else(|| Error::InvalidValue)? as u64;
secp.commit_value(overage_abs)?
};
if overage < 0 {
input_commits.push(over_commit);
} else {
output_commits.push(over_commit);
}
}
sum_commits(output_commits, input_commits, secp)
}
fn inputs_committed(&self) -> Vec<Commitment>;
fn outputs_committed(&self) -> Vec<Commitment>;
fn kernels_committed(&self) -> Vec<Commitment>;
fn verify_kernel_sums(
&self,
overage: i64,
kernel_offset: BlindingFactor,
secp: &Secp256k1,
) -> Result<(Commitment, Commitment), Error> {
let utxo_sum = self.sum_commitments(overage, secp)?;
let (kernel_sum, kernel_sum_plus_offset) =
self.sum_kernel_excesses(&kernel_offset, secp)?;
if utxo_sum != kernel_sum_plus_offset {
return Err(Error::KernelSumMismatch);
}
Ok((utxo_sum, kernel_sum))
}
}
pub fn sum_commits(
mut positive: Vec<Commitment>,
mut negative: Vec<Commitment>,
secp: &Secp256k1,
) -> Result<Commitment, Error> {
let zero_commit = secp_static::commit_to_zero_value();
positive.retain(|x| *x != zero_commit);
negative.retain(|x| *x != zero_commit);
if positive.len() + negative.len() < 100 {
Ok(secp.commit_sum(positive, negative)?)
} else {
let num_cores = num_cpus::get();
let secp = Arc::new(secp);
let sum_result = crossbeam::thread::scope(|s| {
const COMMITS_PER_THREAD_LIMIT: usize = 20;
let mut pos_handles = Vec::with_capacity(num_cores);
if !positive.is_empty() {
let pos_thr_num = cmp::max(
1,
cmp::min(num_cores, positive.len() / COMMITS_PER_THREAD_LIMIT),
);
for thr_idx in 0..pos_thr_num {
let idx1 = positive.len() * thr_idx / pos_thr_num;
let idx2 = positive.len() * (thr_idx + 1) / pos_thr_num;
let secp = secp.clone();
let pos_chunk: Vec<Commitment> = positive[idx1..idx2].to_vec();
let handle = s.spawn(move |_| secp.commit_sum(pos_chunk, vec![]));
pos_handles.push(handle);
}
}
let mut neg_handles = Vec::with_capacity(num_cores);
if !negative.is_empty() {
let neg_thr_num = cmp::max(
1,
cmp::min(num_cores, negative.len() / COMMITS_PER_THREAD_LIMIT),
);
for thr_idx in 0..neg_thr_num {
let idx1 = negative.len() * thr_idx / neg_thr_num;
let idx2 = negative.len() * (thr_idx + 1) / neg_thr_num;
let secp = secp.clone();
let neg_chunk: Vec<Commitment> = negative[idx1..idx2].to_vec();
let handle = s.spawn(move |_| {
secp.commit_sum(neg_chunk, vec![])
});
neg_handles.push(handle);
}
}
let mut pos_sum: Vec<Commitment> = Vec::new();
for handle in pos_handles {
match handle.join().expect("Crossbeam runtime failure") {
Ok(com) => pos_sum.push(com),
Err(e) => return Err(e),
}
}
let mut neg_sum: Vec<Commitment> = Vec::new();
for handle in neg_handles {
match handle.join().expect("Crossbeam runtime failure") {
Ok(com) => neg_sum.push(com),
Err(e) => return Err(e),
}
}
Ok(secp.commit_sum(pos_sum, neg_sum)?)
})
.expect("Crossbeam runtime failure");
Ok(sum_result?)
}
}
pub fn sum_kernel_offsets(
positive: Vec<BlindingFactor>,
negative: Vec<BlindingFactor>,
secp: &Secp256k1,
) -> Result<BlindingFactor, Error> {
let positive = to_secrets(positive, &secp);
let negative = to_secrets(negative, &secp);
if positive.is_empty() {
Ok(BlindingFactor::zero())
} else {
let sum = secp.blind_sum(positive, negative)?;
Ok(BlindingFactor::from_secret_key(sum))
}
}
fn to_secrets(bf: Vec<BlindingFactor>, secp: &secp::Secp256k1) -> Vec<SecretKey> {
bf.into_iter()
.filter(|x| *x != BlindingFactor::zero())
.filter_map(|x| x.secret_key(secp).ok())
.collect::<Vec<_>>()
}