use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
use amplify::confinement::Confined;
use bc::{TapCode, TapScript};
use commit_verify::{mpc, CommitVerify};
use strict_encoding::{
DecodeError, DeserializeError, StreamWriter, StrictDeserialize, StrictEncode, StrictSerialize,
};
use super::TapretFirst;
use crate::LIB_NAME_BPCORE;
pub const TAPRET_SCRIPT_COMMITMENT_PREFIX: [u8; 31] = [
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50,
0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x6a, 0x21,
];
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BPCORE)]
pub struct TapretCommitment {
pub mpc: mpc::Commitment,
pub nonce: u8,
}
impl StrictSerialize for TapretCommitment {}
impl StrictDeserialize for TapretCommitment {}
impl From<[u8; 33]> for TapretCommitment {
fn from(value: [u8; 33]) -> Self {
let buf = Confined::from_iter_checked(value);
Self::from_strict_serialized::<33>(buf).expect("exact size match")
}
}
impl TapretCommitment {
pub fn to_vec(&self) -> Vec<u8> {
self.to_strict_serialized::<33>().expect("exact size match").release()
}
}
impl Display for TapretCommitment {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let s = base85::encode(&self.to_vec());
f.write_str(&s)
}
}
impl FromStr for TapretCommitment {
type Err = DeserializeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let data = base85::decode(s).map_err(|err| {
DecodeError::DataIntegrityError(format!(
"invalid Base85 encoding of tapret data \"{s}\": {}",
err.to_string().to_lowercase()
))
})?;
let data = Confined::try_from(data).map_err(DecodeError::from)?;
Self::from_strict_serialized::<33>(data)
}
}
impl TapretCommitment {
pub fn with(mpc: mpc::Commitment, nonce: u8) -> Self { Self { mpc, nonce } }
}
impl CommitVerify<TapretCommitment, TapretFirst> for TapScript {
fn commit(commitment: &TapretCommitment) -> Self {
let mut tapret = TapScript::with_capacity(64);
for _ in 0..29 {
tapret.push_opcode(TapCode::Reserved);
}
tapret.push_opcode(TapCode::Return);
let mut writer = StreamWriter::in_memory::<33>();
commitment.strict_write(&mut writer).expect("tapret commitment must be fitting 33 bytes");
let data = writer.unconfine();
debug_assert_eq!(data.len(), 33, "tapret commitment must take exactly 33 bytes");
tapret.push_slice(&data);
tapret
}
}
#[cfg(feature = "serde")]
mod _serde {
use amplify::{Bytes, Wrapper};
use serde_crate::de::Error;
use serde_crate::{Deserialize, Deserializer, Serialize, Serializer};
use super::*;
impl Serialize for TapretCommitment {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer {
if serializer.is_human_readable() {
self.to_string().serialize(serializer)
} else {
self.to_vec().serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for TapretCommitment {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de> {
if deserializer.is_human_readable() {
let s = String::deserialize(deserializer)?;
Self::from_str(&s).map_err(D::Error::custom)
} else {
let slice = Bytes::<33>::deserialize(deserializer)?;
Ok(Self::from(slice.into_inner()))
}
}
}
}
#[cfg(test)]
mod test {
use amplify::ByteArray;
use commit_verify::{Digest, Sha256};
use super::*;
pub fn commitment() -> TapretCommitment {
let msg = Sha256::digest("test data");
TapretCommitment {
mpc: mpc::Commitment::from_byte_array(msg),
nonce: 8,
}
}
#[test]
pub fn commitment_prefix() {
let script = TapScript::commit(&commitment());
assert_eq!(TAPRET_SCRIPT_COMMITMENT_PREFIX, script[0..31]);
}
#[test]
pub fn commiment_serialization() {
let commitment = commitment();
let script = TapScript::commit(&commitment);
assert_eq!(script[63], commitment.nonce);
assert_eq!(&script[31..63], commitment.mpc.as_slice());
}
#[test]
pub fn tapret_commitment_baid64() {
let commitment = commitment();
let s = commitment.to_string();
assert_eq!(s, "k#7JerF92P=PEN7cf&`GWfS*?rIEdfEup1%zausI2m");
assert_eq!(Ok(commitment.clone()), TapretCommitment::from_str(&s));
}
}