1use std::fmt::{self, Display, Formatter};
23use std::str::FromStr;
24
25use amplify::confinement::Confined;
26use bc::{TapCode, TapScript};
27use commit_verify::{mpc, CommitVerify};
28use strict_encoding::{
29 DecodeError, DeserializeError, StreamWriter, StrictDeserialize, StrictEncode, StrictSerialize,
30};
31
32use super::TapretFirst;
33use crate::LIB_NAME_BPCORE;
34
35pub const TAPRET_SCRIPT_COMMITMENT_PREFIX: [u8; 31] = [
38 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
39 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x6a, 0x21,
40];
41
42#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
44#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
45#[strict_type(lib = LIB_NAME_BPCORE)]
46pub struct TapretCommitment {
47 pub mpc: mpc::Commitment,
49 pub nonce: u8,
51}
52
53impl StrictSerialize for TapretCommitment {}
54impl StrictDeserialize for TapretCommitment {}
55
56impl From<[u8; 33]> for TapretCommitment {
57 fn from(value: [u8; 33]) -> Self {
58 let buf = Confined::from_iter_checked(value);
59 Self::from_strict_serialized::<33>(buf).expect("exact size match")
60 }
61}
62
63impl TapretCommitment {
64 pub fn to_vec(&self) -> Vec<u8> {
66 self.to_strict_serialized::<33>().expect("exact size match").release()
67 }
68}
69
70impl Display for TapretCommitment {
71 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
72 let s = base85::encode(&self.to_vec());
73 f.write_str(&s)
74 }
75}
76impl FromStr for TapretCommitment {
77 type Err = DeserializeError;
78 fn from_str(s: &str) -> Result<Self, Self::Err> {
79 let data = base85::decode(s).map_err(|err| {
80 DecodeError::DataIntegrityError(format!(
81 "invalid Base85 encoding of tapret data \"{s}\": {}",
82 err.to_string().to_lowercase()
83 ))
84 })?;
85 let data = Confined::try_from(data).map_err(DecodeError::from)?;
86 Self::from_strict_serialized::<33>(data)
87 }
88}
89
90impl TapretCommitment {
91 pub fn with(mpc: mpc::Commitment, nonce: u8) -> Self { Self { mpc, nonce } }
93}
94
95impl CommitVerify<TapretCommitment, TapretFirst> for TapScript {
96 fn commit(commitment: &TapretCommitment) -> Self {
102 let mut tapret = TapScript::with_capacity(64);
103 for _ in 0..29 {
104 tapret.push_opcode(TapCode::Nop);
105 }
106 tapret.push_opcode(TapCode::Return);
107 let mut writer = StreamWriter::in_memory::<33>();
108 commitment.strict_write(&mut writer).expect("tapret commitment must be fitting 33 bytes");
109 let data = writer.unconfine();
110 debug_assert_eq!(data.len(), 33, "tapret commitment must take exactly 33 bytes");
111 tapret.push_slice(&data);
112 tapret
113 }
114}
115
116#[cfg(feature = "serde")]
117mod _serde {
118 use amplify::{Bytes, Wrapper};
119 use serde::de::Error;
120 use serde::{Deserialize, Deserializer, Serialize, Serializer};
121
122 use super::*;
123
124 impl Serialize for TapretCommitment {
125 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
126 where S: Serializer {
127 if serializer.is_human_readable() {
128 self.to_string().serialize(serializer)
129 } else {
130 self.to_vec().serialize(serializer)
131 }
132 }
133 }
134
135 impl<'de> Deserialize<'de> for TapretCommitment {
136 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
137 where D: Deserializer<'de> {
138 if deserializer.is_human_readable() {
139 let s = String::deserialize(deserializer)?;
140 Self::from_str(&s).map_err(D::Error::custom)
141 } else {
142 let slice = Bytes::<33>::deserialize(deserializer)?;
143 Ok(Self::from(slice.into_inner()))
144 }
145 }
146 }
147}
148
149#[cfg(test)]
150mod test {
151 #![cfg_attr(coverage_nightly, coverage(off))]
152
153 use amplify::ByteArray;
154 use commit_verify::{Digest, Sha256};
155
156 use super::*;
157
158 pub fn commitment() -> TapretCommitment {
159 let msg = Sha256::digest("test data");
160 TapretCommitment {
161 mpc: mpc::Commitment::from_byte_array(msg),
162 nonce: 8,
163 }
164 }
165
166 #[test]
167 pub fn commitment_prefix() {
168 let script = TapScript::commit(&commitment());
169 assert_eq!(TAPRET_SCRIPT_COMMITMENT_PREFIX, script[0..31]);
170 }
171
172 #[test]
173 pub fn commiment_serialization() {
174 let commitment = commitment();
175 let script = TapScript::commit(&commitment);
176 assert_eq!(script[63], commitment.nonce);
177 assert_eq!(&script[31..63], commitment.mpc.as_slice());
178 }
179
180 #[test]
181 pub fn tapret_commitment_baid64() {
182 let commitment = commitment();
183 let s = commitment.to_string();
184 assert_eq!(s, "k#7JerF92P=PEN7cf&`GWfS*?rIEdfEup1%zausI2m");
185 assert_eq!(Ok(commitment.clone()), TapretCommitment::from_str(&s));
186 }
187}