dbc/tapret/
tapscript.rs

1// Deterministic bitcoin commitments library.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2024 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved.
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22use 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
35/// Hardcoded tapret script prefix consisting of 29 `OP_NOP` pushes,
36/// followed by `OP_RETURN` and `OP_PUSHBYTES_33`.
37pub 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/// Information about tapret commitment.
43#[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    /// LNPBP-4 multi-protocol commitment.
48    pub mpc: mpc::Commitment,
49    /// Nonce is used to put the commitment into the correct side of the tree.
50    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    /// Returns serialized representation of the commitment data.
65    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    /// Constructs information about tapret commitment.
92    pub fn with(mpc: mpc::Commitment, nonce: u8) -> Self { Self { mpc, nonce } }
93}
94
95impl CommitVerify<TapretCommitment, TapretFirst> for TapScript {
96    /// Tapret script consists of 29 `OP_NOP` pushes, followed by
97    /// `OP_RETURN`, `OP_PUSHBYTES_33` and serialized commitment data (MPC
98    /// commitment + nonce as a single slice).
99    // It was OP_RESERVER1, but with TapCode differentiation it is not there anymore,
100    // so it makes more sense to use OP_NOP here.
101    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}