use std::fmt::{self, Display, Formatter};
use std::io;
use std::str::FromStr;
use amplify::confinement::Confined;
use bc::{TapCode, TapScript};
use commit_verify::{mpc, CommitEncode, CommitVerify};
use strict_encoding::{DecodeError, DeserializeError, StrictDeserialize, StrictSerialize};
use super::Lnpbp12;
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)]
#[derive(CommitEncode)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
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::try_from_iter(value).expect("exact size match");
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")
.into_inner()
}
}
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, Lnpbp12> 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 data = io::Cursor::new([0u8; 33]);
commitment.commit_encode(&mut data);
tapret.push_slice(&data.into_inner());
tapret
}
}
#[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_baid58() {
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));
}
}