use crate::error::{OptimError, Result};
use scirs2_core::ndarray::Array1;
use scirs2_core::numeric::Float;
use scirs2_core::random::{rngs::StdRng, thread_rng, Random, Rng, SeedableRng};
use sha2::{Digest, Sha256};
use std::fmt::Debug;
use super::primitives::CommunicationSecurity;
pub const SHAMIR_PRIME: u128 = (1u128 << 127) - 1;
pub const FIXED_POINT_BITS: u32 = 60;
pub(super) const FIXED_POINT_SCALE: f64 = 1_152_921_504_606_846_976.0;
pub(super) const FIELD_POSITIVE_LIMIT: u128 = (SHAMIR_PRIME - 1) / 2;
pub(super) const COMMITMENT_DOMAIN: &[u8] = b"optirs.smpc.commitment.v1";
pub(super) const VERIFICATION_DOMAIN: &[u8] = b"optirs.smpc.verification-tag.v1";
pub(super) const AGGREGATE_DOMAIN: &[u8] = b"optirs.smpc.aggregate-digest.v1";
pub(super) const VALUE_DIGEST_DOMAIN: &[u8] = b"optirs.smpc.value-digest.v1";
pub(super) const COMPUTATION_DOMAIN: &[u8] = b"optirs.smpc.computation-digest.v1";
pub const COMMITMENT_NONCE_LEN: usize = 32;
pub const DIGEST_LEN: usize = 32;
pub(super) type SecureRng = Random<StdRng>;
pub(super) fn os_seeded_rng() -> SecureRng {
SeedableRng::from_rng(&mut thread_rng())
}
pub(super) fn random_bytes(rng: &mut SecureRng, len: usize) -> Vec<u8> {
let mut buffer = vec![0u8; len];
rng.fill_bytes(&mut buffer);
buffer
}
pub(super) fn ct_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut difference = 0u8;
for (x, y) in a.iter().zip(b.iter()) {
difference |= x ^ y;
}
difference == 0
}
pub(super) fn hash_values<T: Float + Debug + Send + Sync + 'static>(
domain: &[u8],
salt: &[u8],
values: &Array1<T>,
) -> Result<Vec<u8>> {
let mut hasher = Sha256::new();
hasher.update(domain);
hasher.update((salt.len() as u64).to_le_bytes());
hasher.update(salt);
hasher.update((values.len() as u64).to_le_bytes());
for &value in values.iter() {
let as_f64 = value.to_f64().ok_or_else(|| {
OptimError::InvalidConfig("value cannot be converted to f64 for hashing".to_string())
})?;
hasher.update(as_f64.to_le_bytes());
}
Ok(hasher.finalize().to_vec())
}
pub fn max_representable_magnitude() -> f64 {
FIELD_POSITIVE_LIMIT as f64 / FIXED_POINT_SCALE
}
#[inline]
pub(super) fn add_mod(a: u128, b: u128) -> u128 {
let sum = a + b;
if sum >= SHAMIR_PRIME {
sum - SHAMIR_PRIME
} else {
sum
}
}
#[inline]
pub(super) fn sub_mod(a: u128, b: u128) -> u128 {
if a >= b {
a - b
} else {
SHAMIR_PRIME - (b - a)
}
}
#[inline]
pub(super) fn neg_mod(a: u128) -> u128 {
if a == 0 {
0
} else {
SHAMIR_PRIME - a
}
}
pub(super) fn mul_mod(a: u128, b: u128) -> u128 {
let mut multiplicand = a % SHAMIR_PRIME;
let mut multiplier = b % SHAMIR_PRIME;
if multiplicand < (1u128 << 63) && multiplier < (1u128 << 63) {
return (multiplicand * multiplier) % SHAMIR_PRIME;
}
let mut result = 0u128;
while multiplier > 0 {
if multiplier & 1 == 1 {
result = add_mod(result, multiplicand);
}
multiplicand = add_mod(multiplicand, multiplicand);
multiplier >>= 1;
}
result
}
pub(super) fn pow_mod(base: u128, exponent: u128) -> u128 {
let mut result = 1u128;
let mut acc = base % SHAMIR_PRIME;
let mut remaining = exponent;
while remaining > 0 {
if remaining & 1 == 1 {
result = mul_mod(result, acc);
}
acc = mul_mod(acc, acc);
remaining >>= 1;
}
result
}
pub(super) fn inv_mod(a: u128) -> Result<u128> {
if a.is_multiple_of(SHAMIR_PRIME) {
return Err(OptimError::InvalidConfig(
"cannot invert zero in the Shamir field (duplicate share x-coordinate?)".to_string(),
));
}
Ok(pow_mod(a, SHAMIR_PRIME - 2))
}
pub(super) fn value_to_field<T: Float + Debug + Send + Sync + 'static>(value: T) -> Result<u128> {
let as_f64 = value.to_f64().ok_or_else(|| {
OptimError::InvalidConfig("value cannot be converted to f64 for sharing".to_string())
})?;
if !as_f64.is_finite() {
return Err(OptimError::InvalidConfig(
"cannot secret-share a non-finite value".to_string(),
));
}
let scaled = (as_f64 * FIXED_POINT_SCALE).round();
if scaled.abs() >= FIELD_POSITIVE_LIMIT as f64 {
return Err(OptimError::InvalidConfig(format!(
"value {} exceeds the representable magnitude {:e} of the Shamir field",
as_f64,
max_representable_magnitude()
)));
}
let quantised = scaled as i128;
Ok(if quantised < 0 {
SHAMIR_PRIME - quantised.unsigned_abs()
} else {
quantised as u128
})
}
pub(super) fn field_to_value<T: Float + Debug + Send + Sync + 'static>(element: u128) -> Result<T> {
let reduced = element % SHAMIR_PRIME;
let signed = if reduced > FIELD_POSITIVE_LIMIT {
-((SHAMIR_PRIME - reduced) as f64)
} else {
reduced as f64
};
T::from(signed / FIXED_POINT_SCALE).ok_or_else(|| {
OptimError::InvalidConfig("reconstructed value is not representable in T".to_string())
})
}
pub(super) fn evaluate_polynomial(coefficients: &[u128], x: u128) -> u128 {
let mut accumulator = 0u128;
for &coefficient in coefficients.iter().rev() {
accumulator = add_mod(mul_mod(accumulator, x), coefficient % SHAMIR_PRIME);
}
accumulator
}
pub(super) fn require_supported_security(security: CommunicationSecurity) -> Result<()> {
match security {
CommunicationSecurity::SemiHonest => Ok(()),
CommunicationSecurity::MaliciousAbort | CommunicationSecurity::MaliciousGuaranteed => {
Err(OptimError::UnsupportedOperation(
"malicious-adversary SMPC is not implemented: no authenticated channels, \
signatures or verifiable secret sharing exist in this module"
.to_string(),
))
}
}
}
pub(super) fn unimplemented_homomorphic(operation: &str) -> OptimError {
OptimError::UnsupportedOperation(format!(
"HomomorphicEngine {operation} is not homomorphic encryption — unimplemented, \
do not use for confidentiality; use privacy::secure_aggregation for additive \
aggregation instead"
))
}
pub(super) fn unimplemented_zero_knowledge(operation: &str) -> OptimError {
OptimError::UnsupportedOperation(format!(
"zero-knowledge {operation} is not cryptographically secure — unimplemented; \
`ComputationDigestSystem` only provides a non-hiding integrity digest via \
digest_computation/verify_digest"
))
}