use super::crypto_utils::secure_random_bytes;
use super::proof_tree::ProofTreeKind;
use crate::sigma_protocol::unchecked_tree::{UncheckedConjecture, UncheckedTree};
use crate::sigma_protocol::unproven_tree::{UnprovenConjecture, UnprovenTree};
use crate::sigma_protocol::ProverMessage;
use ergo_chain_types::{Base16DecodedBytes, Base16EncodedBytes};
use ergotree_ir::ergo_tree::{ErgoTree, ErgoTreeHeader};
use ergotree_ir::mir::expr::Expr;
use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWriter;
use ergotree_ir::serialization::SigmaSerializable;
use ergotree_ir::sigma_protocol::sigma_boolean::{SigmaBoolean, SigmaProp};
use sigma_util::hash::blake2b256_hash;
use std::array::TryFromSliceError;
use std::convert::{TryFrom, TryInto};
use std::fmt::Formatter;
use thiserror::Error;
#[cfg(feature = "arbitrary")]
use proptest_derive::Arbitrary;
use super::proof_tree::ProofTree;
use super::SOUNDNESS_BYTES;
#[derive(PartialEq, Eq, Clone)]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "json",
serde(
try_from = "ergo_chain_types::Base16DecodedBytes",
into = "ergo_chain_types::Base16EncodedBytes"
)
)]
pub struct FiatShamirHash(pub Box<[u8; SOUNDNESS_BYTES]>);
impl FiatShamirHash {
pub fn secure_random() -> Self {
#[allow(clippy::unwrap_used)] secure_random_bytes(SOUNDNESS_BYTES)
.as_slice()
.try_into()
.unwrap()
}
}
impl From<FiatShamirHash> for Base16EncodedBytes {
fn from(fsh: FiatShamirHash) -> Self {
(*fsh.0).as_slice().into()
}
}
impl TryFrom<Base16DecodedBytes> for FiatShamirHash {
type Error = TryFromSliceError;
fn try_from(b16: Base16DecodedBytes) -> Result<Self, Self::Error> {
let arr: [u8; SOUNDNESS_BYTES] = b16.0.as_slice().try_into()?;
Ok(FiatShamirHash(arr.into()))
}
}
pub fn fiat_shamir_hash_fn(input: &[u8]) -> FiatShamirHash {
let hash = blake2b256_hash(input);
let taken: Vec<u8> = hash.iter().copied().take(SOUNDNESS_BYTES).collect();
#[allow(clippy::unwrap_used)]
FiatShamirHash(taken.into_boxed_slice().try_into().unwrap())
}
impl From<FiatShamirHash> for [u8; SOUNDNESS_BYTES] {
fn from(v: FiatShamirHash) -> Self {
*v.0
}
}
impl TryFrom<&[u8]> for FiatShamirHash {
type Error = FiatShamirHashError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let arr: [u8; SOUNDNESS_BYTES] = value.try_into()?;
Ok(FiatShamirHash(Box::new(arr)))
}
}
impl std::fmt::Debug for FiatShamirHash {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("FSH:")?;
f.write_str(&base16::encode_lower(&(*self.0)))
}
}
#[derive(Error, Debug)]
#[error("Invalid byte array size ({0})")]
pub struct FiatShamirHashError(std::array::TryFromSliceError);
impl From<std::array::TryFromSliceError> for FiatShamirHashError {
fn from(err: std::array::TryFromSliceError) -> Self {
FiatShamirHashError(err)
}
}
pub(crate) fn fiat_shamir_tree_to_bytes(
tree: &ProofTree,
) -> Result<Vec<u8>, FiatShamirTreeSerializationError> {
let mut data = Vec::new();
let mut w = SigmaByteWriter::new(&mut data, None);
fiat_shamir_write_bytes(tree, &mut w)?;
Ok(data)
}
#[derive(Error, Debug)]
pub enum FiatShamirTreeSerializationError {
#[error("empty commitment in leaf with proposition {0:?} ")]
EmptyCommitmentInLeaf(SigmaBoolean),
#[error("io errro {0:?} ")]
IoError(#[from] std::io::Error),
}
fn fiat_shamir_write_bytes<W: SigmaByteWrite>(
tree: &ProofTree,
w: &mut W,
) -> Result<(), FiatShamirTreeSerializationError> {
const INTERNAL_NODE_PREFIX: u8 = 0;
const LEAF_PREFIX: u8 = 1;
Ok(match tree.as_tree_kind() {
ProofTreeKind::Leaf(leaf) => {
#[allow(clippy::unwrap_used)]
let prop_tree = ErgoTree::new(
ErgoTreeHeader::v0(true),
&Expr::Const(SigmaProp::new(leaf.proposition()).into()),
)
.unwrap();
#[allow(clippy::unwrap_used)]
let prop_bytes = prop_tree.sigma_serialize_bytes().unwrap();
let commitment_bytes = leaf
.commitment_opt()
.ok_or_else(|| {
FiatShamirTreeSerializationError::EmptyCommitmentInLeaf(leaf.proposition())
})?
.bytes();
w.put_u8(LEAF_PREFIX)?;
w.put_i16_be_bytes(prop_bytes.len() as i16)?;
w.write_all(prop_bytes.as_ref())?;
w.put_i16_be_bytes(commitment_bytes.len() as i16)?;
w.write_all(commitment_bytes.as_slice())?
}
ProofTreeKind::Conjecture(c) => {
w.put_u8(INTERNAL_NODE_PREFIX)?;
w.put_u8(c.conjecture_type() as u8)?;
match tree {
ProofTree::UncheckedTree(unchecked) => match unchecked {
UncheckedTree::UncheckedLeaf(_) => (),
UncheckedTree::UncheckedConjecture(conj) => {
if let UncheckedConjecture::CthresholdUnchecked {
challenge: _,
children: _,
k,
polynomial: _,
} = conj
{
w.put_u8(*k)?
}
}
},
ProofTree::UnprovenTree(unproven) => match unproven {
UnprovenTree::UnprovenLeaf(_) => (),
UnprovenTree::UnprovenConjecture(conj) => {
if let UnprovenConjecture::CthresholdUnproven(thresh) = conj {
w.put_u8(thresh.k)?
}
}
},
}
w.put_i16_be_bytes(c.children().len() as i16)?;
for child in &c.children() {
fiat_shamir_write_bytes(child, w)?;
}
}
})
}