rgbcore/operation/
commit.rs

1// RGB Consensus Library: consensus layer for RGB smart contracts.
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// Copyright (C) 2019-2024 Dr Maxim Orlovsky. All rights reserved.
10//
11// Licensed under the Apache License, Version 2.0 (the "License");
12// you may not use this file except in compliance with the License.
13// You may obtain a copy of the License at
14//
15//     http://www.apache.org/licenses/LICENSE-2.0
16//
17// Unless required by applicable law or agreed to in writing, software
18// distributed under the License is distributed on an "AS IS" BASIS,
19// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20// See the License for the specific language governing permissions and
21// limitations under the License.
22
23use std::collections::BTreeSet;
24use std::fmt::{Display, Formatter};
25use std::str::FromStr;
26use std::{fmt, vec};
27
28use amplify::confinement::{Confined, MediumOrdMap, U16 as U16MAX};
29use amplify::hex::{FromHex, ToHex};
30use amplify::num::u256;
31use amplify::{hex, ByteArray, Bytes32, FromSliceError, Wrapper};
32use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};
33use commit_verify::{
34    mpc, CommitEncode, CommitEngine, CommitId, CommitmentId, Conceal, DigestExt, MerkleHash,
35    MerkleLeaves, Sha256, StrictHash,
36};
37use strict_encoding::StrictDumb;
38
39use crate::{
40    impl_serde_baid64, Assign, AssignmentType, Assignments, BundleId, ChainNet, ExposedSeal,
41    ExposedState, Ffv, Genesis, GlobalState, GlobalStateType, Operation, RevealedData,
42    RevealedState, RevealedValue, SchemaId, SealClosingStrategy, SecretSeal, Transition,
43    TransitionBundle, TransitionType, TypedAssigns, LIB_NAME_RGB_COMMIT,
44};
45
46/// Unique contract identifier equivalent to the contract genesis commitment
47#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
48#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
49#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
50#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
51pub struct ContractId(
52    #[from]
53    #[from([u8; 32])]
54    Bytes32,
55);
56
57impl PartialEq<OpId> for ContractId {
58    fn eq(&self, other: &OpId) -> bool { self.to_byte_array() == other.to_byte_array() }
59}
60impl PartialEq<ContractId> for OpId {
61    fn eq(&self, other: &ContractId) -> bool { self.to_byte_array() == other.to_byte_array() }
62}
63
64impl ContractId {
65    pub fn copy_from_slice(slice: impl AsRef<[u8]>) -> Result<Self, FromSliceError> {
66        Bytes32::copy_from_slice(slice).map(Self)
67    }
68}
69
70impl DisplayBaid64 for ContractId {
71    const HRI: &'static str = "rgb";
72    const CHUNKING: bool = true;
73    const PREFIX: bool = true;
74    const EMBED_CHECKSUM: bool = false;
75    const MNEMONIC: bool = false;
76    fn to_baid64_payload(&self) -> [u8; 32] { self.to_byte_array() }
77}
78impl FromBaid64Str for ContractId {}
79impl FromStr for ContractId {
80    type Err = Baid64ParseError;
81    fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
82}
83impl Display for ContractId {
84    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.fmt_baid64(f) }
85}
86
87impl From<mpc::ProtocolId> for ContractId {
88    fn from(id: mpc::ProtocolId) -> Self { ContractId(id.into_inner()) }
89}
90
91impl From<ContractId> for mpc::ProtocolId {
92    fn from(id: ContractId) -> Self { mpc::ProtocolId::from_inner(id.into_inner()) }
93}
94
95impl_serde_baid64!(ContractId);
96
97/// Unique operation (genesis & state transition) identifier
98/// equivalent to the commitment hash
99#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
100#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
101#[display(Self::to_hex)]
102#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
103#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
104#[cfg_attr(
105    feature = "serde",
106    derive(Serialize, Deserialize),
107    serde(crate = "serde_crate", transparent)
108)]
109pub struct OpId(
110    #[from]
111    #[from([u8; 32])]
112    Bytes32,
113);
114
115impl From<Sha256> for OpId {
116    fn from(hasher: Sha256) -> Self { hasher.finish().into() }
117}
118
119impl CommitmentId for OpId {
120    const TAG: &'static str = "urn:lnp-bp:rgb:operation#2024-02-03";
121}
122
123impl FromStr for OpId {
124    type Err = hex::Error;
125    fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_hex(s) }
126}
127
128impl OpId {
129    pub fn copy_from_slice(slice: impl AsRef<[u8]>) -> Result<Self, FromSliceError> {
130        Bytes32::copy_from_slice(slice).map(Self)
131    }
132}
133
134/// Hash committing to all data which are disclosed by a contract or some part
135/// of it (operation, bundle, consignment, disclosure).
136#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
137#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
138#[display(Self::to_hex)]
139#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
140#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
141#[cfg_attr(
142    feature = "serde",
143    derive(Serialize, Deserialize),
144    serde(crate = "serde_crate", transparent)
145)]
146pub struct DiscloseHash(
147    #[from]
148    #[from([u8; 32])]
149    Bytes32,
150);
151
152impl From<Sha256> for DiscloseHash {
153    fn from(hasher: Sha256) -> Self { hasher.finish().into() }
154}
155
156impl CommitmentId for DiscloseHash {
157    const TAG: &'static str = "urn:lnp-bp:rgb:disclose#2024-02-16";
158}
159
160impl FromStr for DiscloseHash {
161    type Err = hex::Error;
162    fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_hex(s) }
163}
164
165impl DiscloseHash {
166    pub fn copy_from_slice(slice: impl AsRef<[u8]>) -> Result<Self, FromSliceError> {
167        Bytes32::copy_from_slice(slice).map(Self)
168    }
169}
170
171#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
172#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
173#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
174pub struct AssignmentIndex {
175    pub ty: AssignmentType,
176    pub pos: u16,
177}
178
179impl AssignmentIndex {
180    pub fn new(ty: AssignmentType, pos: u16) -> Self { AssignmentIndex { ty, pos } }
181}
182
183#[derive(Clone, Eq, PartialEq, Hash, Debug)]
184#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
185#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
186#[derive(CommitEncode)]
187#[commit_encode(strategy = strict, id = DiscloseHash)]
188pub struct OpDisclose {
189    pub id: OpId,
190    pub seals: MediumOrdMap<AssignmentIndex, SecretSeal>,
191    pub fungible: MediumOrdMap<AssignmentIndex, RevealedValue>,
192    pub data: MediumOrdMap<AssignmentIndex, RevealedData>,
193}
194
195#[derive(Clone, Eq, PartialEq, Hash, Debug)]
196#[derive(StrictType, StrictEncode, StrictDecode)]
197#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
198#[derive(CommitEncode)]
199#[commit_encode(strategy = strict, id = DiscloseHash)]
200pub struct BundleDisclosure {
201    pub id: BundleId,
202    pub known_transitions: Confined<BTreeSet<DiscloseHash>, 1, U16MAX>,
203}
204
205impl StrictDumb for BundleDisclosure {
206    fn strict_dumb() -> Self {
207        Self {
208            id: strict_dumb!(),
209            known_transitions: Confined::with(strict_dumb!()),
210        }
211    }
212}
213
214impl TransitionBundle {
215    /// Provides summary about parts of the bundle which are revealed.
216    pub fn disclose(&self) -> BundleDisclosure {
217        BundleDisclosure {
218            id: self.bundle_id(),
219            known_transitions: Confined::from_iter_checked(
220                self.known_transitions
221                    .iter()
222                    .map(|kt| kt.transition.disclose_hash()),
223            ),
224        }
225    }
226
227    /// Returns commitment to the bundle plus revealed data within it.
228    pub fn disclose_hash(&self) -> DiscloseHash { self.disclose().commit_id() }
229}
230
231#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
232#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
233#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
234pub struct BaseCommitment {
235    pub schema_id: SchemaId,
236    pub timestamp: i64,
237    pub issuer: StrictHash,
238    pub chain_net: ChainNet,
239    pub seal_closing_strategy: SealClosingStrategy,
240}
241
242#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
243#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
244#[strict_type(lib = LIB_NAME_RGB_COMMIT, tags = custom, dumb = Self::Transition(strict_dumb!(), strict_dumb!()))]
245pub enum TypeCommitment {
246    #[strict_type(tag = 0)]
247    Genesis(BaseCommitment),
248
249    #[strict_type(tag = 1)]
250    Transition(ContractId, TransitionType),
251}
252
253#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
254#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
255#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
256#[derive(CommitEncode)]
257#[commit_encode(strategy = strict, id = OpId)]
258pub struct OpCommitment {
259    pub ffv: Ffv,
260    pub nonce: u64,
261    pub op_type: TypeCommitment,
262    pub metadata: StrictHash,
263    pub globals: MerkleHash,
264    pub inputs: MerkleHash,
265    pub assignments: MerkleHash,
266}
267
268impl Genesis {
269    pub fn commit(&self) -> OpCommitment {
270        let base = BaseCommitment {
271            schema_id: self.schema_id,
272            timestamp: self.timestamp,
273            chain_net: self.chain_net,
274            seal_closing_strategy: self.seal_closing_strategy,
275            issuer: self.issuer.commit_id(),
276        };
277        OpCommitment {
278            ffv: self.ffv,
279            nonce: u64::MAX,
280            op_type: TypeCommitment::Genesis(base),
281            metadata: self.metadata.commit_id(),
282            globals: MerkleHash::merklize(&self.globals),
283            inputs: MerkleHash::void(0, u256::ZERO),
284            assignments: MerkleHash::merklize(&self.assignments),
285        }
286    }
287
288    pub fn disclose_hash(&self) -> DiscloseHash { self.disclose().commit_id() }
289}
290
291impl Transition {
292    pub fn commit(&self) -> OpCommitment {
293        OpCommitment {
294            ffv: self.ffv,
295            nonce: self.nonce,
296            op_type: TypeCommitment::Transition(self.contract_id, self.transition_type),
297            metadata: self.metadata.commit_id(),
298            globals: MerkleHash::merklize(&self.globals),
299            inputs: MerkleHash::merklize(&self.inputs),
300            assignments: MerkleHash::merklize(&self.assignments),
301        }
302    }
303}
304
305impl RevealedState {
306    fn commit_encode(&self, e: &mut CommitEngine) {
307        match self {
308            Self::Void => {}
309            Self::Fungible(val) => e.commit_to_serialized(&val),
310            Self::Structured(dat) => e.commit_to_serialized(dat),
311        }
312    }
313}
314
315#[derive(Clone, Eq, PartialEq, Debug)]
316pub struct AssignmentCommitment {
317    pub ty: AssignmentType,
318    pub state: RevealedState,
319    pub seal: SecretSeal,
320}
321
322impl CommitEncode for AssignmentCommitment {
323    type CommitmentId = MerkleHash;
324
325    fn commit_encode(&self, e: &mut CommitEngine) {
326        e.commit_to_serialized(&self.ty);
327        self.state.commit_encode(e);
328        e.commit_to_serialized(&self.seal);
329        e.set_finished();
330    }
331}
332
333impl<State: ExposedState, Seal: ExposedSeal> Assign<State, Seal> {
334    pub fn commitment(&self, ty: AssignmentType) -> AssignmentCommitment {
335        let Self::ConfidentialSeal { seal, state } = self.conceal() else {
336            unreachable!();
337        };
338        AssignmentCommitment {
339            ty,
340            state: state.state_data(),
341            seal,
342        }
343    }
344}
345
346impl<Seal: ExposedSeal> MerkleLeaves for Assignments<Seal> {
347    type Leaf = AssignmentCommitment;
348    type LeafIter<'tmp>
349        = vec::IntoIter<AssignmentCommitment>
350    where Seal: 'tmp;
351
352    fn merkle_leaves(&self) -> Self::LeafIter<'_> {
353        self.iter()
354            .flat_map(|(ty, a)| {
355                match a {
356                    TypedAssigns::Declarative(list) => {
357                        list.iter().map(|a| a.commitment(*ty)).collect::<Vec<_>>()
358                    }
359                    TypedAssigns::Fungible(list) => {
360                        list.iter().map(|a| a.commitment(*ty)).collect()
361                    }
362                    TypedAssigns::Structured(list) => {
363                        list.iter().map(|a| a.commitment(*ty)).collect()
364                    }
365                }
366                .into_iter()
367            })
368            .collect::<Vec<_>>()
369            .into_iter()
370    }
371}
372
373#[derive(Clone, Eq, PartialEq, Hash, Debug)]
374pub struct GlobalCommitment {
375    pub ty: GlobalStateType,
376    pub state: RevealedData,
377}
378
379impl CommitEncode for GlobalCommitment {
380    type CommitmentId = MerkleHash;
381
382    fn commit_encode(&self, e: &mut CommitEngine) {
383        e.commit_to_serialized(&self.ty);
384        e.commit_to_serialized(&self.state);
385        e.set_finished();
386    }
387}
388
389impl MerkleLeaves for GlobalState {
390    type Leaf = GlobalCommitment;
391    type LeafIter<'tmp> = vec::IntoIter<GlobalCommitment>;
392
393    fn merkle_leaves(&self) -> Self::LeafIter<'_> {
394        self.iter()
395            .flat_map(|(ty, list)| {
396                list.iter().map(|val| GlobalCommitment {
397                    ty: *ty,
398                    state: val.clone(),
399                })
400            })
401            .collect::<Vec<_>>()
402            .into_iter()
403    }
404}