// Copyright © 2023 Denis Morel
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License and
// a copy of the GNU General Public License along with this program. If not, see
// <https://www.gnu.org/licenses/>
use super::matrix::{Matrix, MatrixError};
use crate::{
elgamal::EncryptionParameters, integer::ModExponentiateError, ConstantsTrait, HashError,
HashableMessage, Integer, IntegerOperationError, OperationsTrait, RecursiveHashTrait,
};
use thiserror::Error;
/// Structure for the verifiable commitment key according specification of swiss post
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommitmentKey {
pub h: Integer,
pub gs: Vec<Integer>,
}
#[derive(Error, Debug)]
pub enum CommitmentError {
#[error("get_verifiable_commitment_key: nu too big. Must less or equal q-3.")]
NuTooBig,
#[error("get_verifiable_commitment_key: Error calculating u")]
U { source: HashError },
#[error("get_verifiable_commitment_key: Error calculating w")]
W { source: IntegerOperationError },
#[error("Size of commitment key to small")]
SmallCommitmentKey,
#[error("get_commitment: Error calculating product")]
CommitmentProd { source: IntegerOperationError },
#[error("get_commitment: Error calculating ck.h^r mod p")]
GetCommitmentHExpRModP { source: ModExponentiateError },
#[error("get_commitment_matrix: Matrix is mallformed in")]
MalformedMatrix,
#[error("get_commitment_matrix: Size {0} of random vector must be {1}")]
RandomSizeWrong(usize, usize),
#[error("get_commitment_matrix: error calculating the commitments")]
MatrixCommitment { source: Box<CommitmentError> },
#[error("get_commitment_vector: error with the matrix")]
VecMatrixError { source: MatrixError },
#[error("get_commitment_vector: error calculating the commitments")]
VecCommitment { source: Box<CommitmentError> },
}
impl CommitmentKey {
/// GetVerifiableCommitmentKey (algorithnm 9.6)
pub fn get_verifiable_commitment_key(
ep: &EncryptionParameters,
nu: usize,
) -> Result<Self, CommitmentError> {
if nu > Integer::from(ep.q() - 3) {
return Err(CommitmentError::NuTooBig);
}
let mut count = 0;
let mut i = 0;
let mut v = vec![];
while count <= nu {
let u = HashableMessage::from(vec![
HashableMessage::from("commitmentKey"),
HashableMessage::from(&i),
HashableMessage::from(&count),
])
.recursive_hash_to_zq(ep.q())
.map_err(|e| CommitmentError::U { source: e })?
+ Integer::one();
let w = u
.mod_square(ep.p())
.map_err(|e| CommitmentError::W { source: e })?;
if &w != Integer::one() && &w != ep.g() && !v.contains(&w) {
v.push(w);
count += 1;
}
i += 1;
}
let gs = v.drain(1..).collect();
let h = v[0].clone();
Ok(Self { h, gs })
}
/// nu: size of the vector gs
pub fn nu(&self) -> usize {
self.gs.len()
}
/// Structur to vector, putting `h` before `g`
pub fn to_vec(&self) -> Vec<Integer> {
let mut res: Vec<Integer> = self.gs.clone();
res.insert(0, self.h.clone());
res
}
}
impl<'a> From<&'a CommitmentKey> for HashableMessage<'a> {
fn from(value: &'a CommitmentKey) -> Self {
let mut res: Vec<HashableMessage> = value.gs.iter().map(HashableMessage::from).collect();
res.insert(0, HashableMessage::from(&value.h));
HashableMessage::from(res)
}
}
pub fn get_commitment(
ep: &EncryptionParameters,
a: &[Integer],
r: &Integer,
ck: &CommitmentKey,
) -> Result<Integer, CommitmentError> {
if ck.gs.len() < a.len() {
return Err(CommitmentError::SmallCommitmentKey);
}
let prod = Integer::mod_multi_exponentiate(&ck.gs, a, ep.p())
.map_err(|e| CommitmentError::CommitmentProd { source: e })?;
let c =
ck.h.mod_exponentiate(r, ep.p())
.map_err(|e| CommitmentError::GetCommitmentHExpRModP { source: e })?
.mod_multiply(&prod, ep.p());
Ok(c)
}
pub fn get_commitment_matrix(
ep: &EncryptionParameters,
a: &Matrix<Integer>,
rs: &[Integer],
ck: &CommitmentKey,
) -> Result<Vec<Integer>, CommitmentError> {
if a.is_malformed() {
return Err(CommitmentError::MalformedMatrix);
}
if a.nb_columns() != rs.len() {
return Err(CommitmentError::RandomSizeWrong(rs.len(), a.nb_columns()));
}
a.columns_cloned_iter()
.zip(rs.iter())
.map(|(a_i, r_i)| get_commitment(ep, &a_i, r_i, ck))
.collect::<Result<Vec<Integer>, CommitmentError>>()
.map_err(|e| CommitmentError::MatrixCommitment {
source: Box::new(e),
})
}
#[allow(dead_code)]
pub fn get_commitment_vector(
ep: &EncryptionParameters,
ds: &[Integer],
ts: &[Integer],
ck: &CommitmentKey,
) -> Result<Vec<Integer>, CommitmentError> {
let a = Matrix::to_matrix(ds, (1, ds.len()))
.map_err(|e| CommitmentError::VecMatrixError { source: e })?;
get_commitment_matrix(ep, &a, ts, ck).map_err(|e| CommitmentError::VecCommitment {
source: Box::new(e),
})
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
test_json_data::{
get_test_cases_from_json_file, json_64_value_to_integer,
json_array_64_value_to_array_integer, json_value_to_encryption_parameters,
},
Hexa,
};
use serde_json::Value;
#[test]
fn test_recursive_hash() {
let ck = CommitmentKey {
h: Integer::from_hexa_string(
"0x7C3B68684EB1670CA19A45722B6F0DE570E8CE25DBB6C85E6612884C6C44B1C98B0BE3395B9CB3503F68E24DAF676F05EAF7C1C4D92257A58288D4903BB5987A537DCB005758AB6FAEB9612412E17042E1971E5C5E6C3C4363E3D8DDC3435756992762C7E4BE0E19A231570697A3785E8DCC48637F1D98BC2C2482A05189C21DF9529D7EE39D8FB1249BD05F3018B0019DFEE6EE7EBAA2308CA96B893B620F6719ECAA0BDCDF8A2F39E8A8D2AFF75A41FAAF5C425D0ECD121B4C3B248430C96D72F0457E0DA46E5797724B00F14609D59359BC3CC8D77E0B2773CC4A4F4DE39216534D69D5D74980839322ED8A9D99BF78BE563B2F93B2BF41427EABBEB2A6AE2294B2C949A04EA2FC8AF14CBB639ECF98A1AB04B1A689564038A67B6B726C18B0F6EF205A3D48B816EC5FB1C6815528DB759074DEFF3EFD96735AAF4951FC18A8AC11EBAA05AC2D826640FCCF93DF578B573848ECC6ADB3C691ABB2D878751FA550198EFA4F5034701A7A667D4C8EACFD9AD311071D87EF8E99B4A2583D84C8"
).unwrap(),
gs: [
"0x898963C17A657A324B26F3DFAA7BDBAB2975756DCF642F8274B6EB777AE48F87D14F76EFFE24FF8020EFF39D8D552598DDF85A7FC7D58053B1793D73F14AE876D0A8D06044588D3047C0E37B769D42D9897287C78D50E5B77ED8AA77048B30C0029512F64850316C80D69564846EAB1C1A13E5B3812DD46DA1D8AC20FD3B41F240257BBC7F66930F536D5099E1F8D67E300CDA1013308267A70EBCE95C349EF6CC74DB60FDD3901E33CB60CE31E122E661CD141B32F3A0242C64D342063439F3B5B192A75AD53146198D567C4B008D8185601A9C22E577DB2433103556E07C0ADB8EDDDDF3A45B6BC9FE022BC49EFC9B49490A69D87DE4B2AF03C90933FC25DDC61D6808101C93BF2665702479C4FF0EFB771A400ADAB49392CCC5CA210CC729C47A2EFDB0152ADD43D0291E42B8703D50C983C4E26AC0A9E8906E7A7D595B49B7C5A6C9114278BDDA90AC06202220E77E50DADC6B4961B170651E83988B59E0B9EE859FEE6EE11FD38C63EE96E2F21A613332171FF34EBE0261D8534FDC3C14",
"0x2AB61BA8120F77DB556FB02543109D64F0D1B0468DC350C3EA4CECA0764E0F6C5F6E233AC4B3F58FB57D5B2AD23EF71694F6152D04BA8E107C1E5E3EC07E78FC78FB37970194BC66FD5D1A0A4D86EEE5A8DA50AB3C734FAB7ED89104381CCC31ECCF4DCC42548AB7D1134F244717661C29AB363C410D6A83421B98589A24884A2C12B84AFDE75E9F8B6CD88B23AC571770185C3866464CDC00DC2D418889E42C455226709FD93B1C75E58C623C7A45023B18C8841CCFEB7AF874D5E2F8131D832BDEB98622FB8DAD444F616CC236863B3A951999788ED98819D4F61E0FF495C13A3FEA7304428D41CA17B6ADC0B81CDB59EDB642F645A1AAC171A48C134E868200123B0F5F4088066751AE37D9116A8E5152FDAF6AC1E44E50B95985DE42DC564110D388CC02F38BDAE5AEECAED3DC5DF718F700B0DA39D708AEA3A50C751BCBEB740222C7620F3A5EDEFD6606D05DDF7A073FDA93545DA2F96E227AD61075C24E66BB043DFB9F268D1FA760792814BB2AB45EC312E5303358203A7A7499A24B",
]
.iter()
.map(|v| Integer::from_hexa_string(v).unwrap())
.collect(),
};
let h = &ck.h;
let gs_1 = &ck.gs[0];
let gs_2 = &ck.gs[1];
let expected = HashableMessage::from(vec![
HashableMessage::from(h),
HashableMessage::from(gs_1),
HashableMessage::from(gs_2),
])
.recursive_hash()
.unwrap();
assert_eq!(
HashableMessage::from(&ck).recursive_hash().unwrap(),
expected
);
}
fn get_output(value: &Value) -> CommitmentKey {
CommitmentKey {
h: json_64_value_to_integer(&value["h"]),
gs: json_array_64_value_to_array_integer(&value["g"]),
}
}
#[test]
fn test_verifiable_commitment_key() {
for tc in
get_test_cases_from_json_file("mixnet", "get-verifiable-commitment-key.json").iter()
{
let description = tc["description"].as_str().unwrap();
let ep = json_value_to_encryption_parameters(&tc["context"]);
let k = tc["input"]["k"].as_number().unwrap().as_u64().unwrap() as usize;
let expected = get_output(&tc["output"]);
let r = CommitmentKey::get_verifiable_commitment_key(&ep, k);
assert!(r.is_ok(), "{}", description);
assert_eq!(r.unwrap(), expected, "{description}");
}
}
}