1use std::str::FromStr;
12
13use bitcoin_hashes::{sha256, sha256t};
14use commit_verify::{
15 commit_encode, CommitVerify, ConsensusCommit, PrehashedProtocol, TaggedHash,
16};
17use lnpbp_bech32::{FromBech32Str, ToBech32String};
18use stens::AsciiString;
19use strict_encoding::{MediumVec, StrictEncode};
20
21use crate::{ChunkId, MesgId};
22
23static MIDSTATE_CONTAINER_ID: [u8; 32] = [
25 213, 126, 121, 18, 141, 34, 64, 215, 78, 163, 37, 117, 217, 166, 67, 226,
26 137, 2, 113, 215, 156, 29, 193, 183, 154, 55, 48, 250, 208, 149, 206, 14,
27];
28
29pub const STORM_CONTAINER_ID_HRP: &str = "storm";
30
31pub struct ContainerIdTag;
33
34impl sha256t::Tag for ContainerIdTag {
35 #[inline]
36 fn engine() -> sha256::HashEngine {
37 let midstate = sha256::Midstate::from_inner(MIDSTATE_CONTAINER_ID);
38 sha256::HashEngine::from_midstate(midstate, 64)
39 }
40}
41
42#[derive(
44 Wrapper, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default,
45 Display, From
46)]
47#[derive(StrictEncode, StrictDecode)]
48#[wrapper(Debug, BorrowSlice)]
49#[display(ContainerId::to_bech32_string)]
50pub struct ContainerId(sha256t::Hash<ContainerIdTag>);
51
52impl<Msg> CommitVerify<Msg, PrehashedProtocol> for ContainerId
53where Msg: AsRef<[u8]>
54{
55 #[inline]
56 fn commit(msg: &Msg) -> ContainerId { ContainerId::hash(msg) }
57}
58
59impl commit_encode::Strategy for ContainerId {
60 type Strategy = commit_encode::strategies::UsingStrict;
61}
62
63impl lnpbp_bech32::Strategy for ContainerId {
64 const HRP: &'static str = STORM_CONTAINER_ID_HRP;
65 type Strategy = lnpbp_bech32::strategies::UsingStrictEncoding;
66}
67
68#[cfg(feature = "serde")]
70impl serde::Serialize for ContainerId {
71 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
72 where S: serde::Serializer {
73 if serializer.is_human_readable() {
74 serializer.serialize_str(&self.to_bech32_string())
75 } else {
76 serializer.serialize_bytes(&self[..])
77 }
78 }
79}
80
81#[cfg(feature = "serde")]
82impl<'de> serde::Deserialize<'de> for ContainerId {
83 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
84 where D: serde::Deserializer<'de> {
85 struct Visitor;
86 impl serde::de::Visitor<'_> for Visitor {
87 type Value = ContainerId;
88
89 fn expecting(
90 &self,
91 formatter: &mut std::fmt::Formatter,
92 ) -> std::fmt::Result {
93 write!(
94 formatter,
95 "Bech32 string with `{}` HRP",
96 STORM_CONTAINER_ID_HRP
97 )
98 }
99
100 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
101 where E: serde::de::Error {
102 ContainerId::from_str(v).map_err(serde::de::Error::custom)
103 }
104
105 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
106 where E: serde::de::Error {
107 self.visit_str(&v)
108 }
109
110 fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
111 where E: serde::de::Error {
112 ContainerId::from_bytes(&v).map_err(|_| {
113 serde::de::Error::invalid_length(v.len(), &"32 bytes")
114 })
115 }
116 }
117
118 if deserializer.is_human_readable() {
119 deserializer.deserialize_str(Visitor)
120 } else {
121 deserializer.deserialize_byte_buf(Visitor)
122 }
123 }
124}
125
126impl FromStr for ContainerId {
127 type Err = lnpbp_bech32::Error;
128
129 fn from_str(s: &str) -> Result<Self, Self::Err> {
130 ContainerId::from_bech32_str(s)
131 }
132}
133
134#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)]
135#[derive(StrictEncode, StrictDecode)]
136#[cfg_attr(
137 feature = "serde",
138 derive(Serialize, Deserialize),
139 serde(crate = "serde_crate")
140)]
141#[display("{container_id}@{message_id}")]
142pub struct ContainerFullId {
143 pub message_id: MesgId,
145 pub container_id: ContainerId,
146}
147
148#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, AsAny)]
149#[derive(StrictEncode, StrictDecode)]
150#[cfg_attr(
151 feature = "serde",
152 derive(Serialize, Deserialize),
153 serde(crate = "serde_crate")
154)]
155pub struct ContainerHeader {
156 pub version: u16,
158 pub mime: AsciiString, pub info: String,
162 pub size: u64,
172}
173
174#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug, Display)]
178#[derive(NetworkEncode, NetworkDecode)]
179#[display("{id}")]
180pub struct ContainerInfo {
181 pub header: ContainerHeader,
182 pub id: ContainerFullId,
183}
184
185#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, AsAny)]
186#[derive(StrictEncode, StrictDecode)]
187#[cfg_attr(
188 feature = "serde",
189 derive(Serialize, Deserialize),
190 serde(crate = "serde_crate")
191)]
192pub struct Container {
193 pub header: ContainerHeader,
194 pub chunks: MediumVec<ChunkId>,
195}
196
197impl commit_encode::Strategy for Container {
198 type Strategy = commit_encode::strategies::UsingStrict;
199}
200
201impl ConsensusCommit for Container {
202 type Commitment = ContainerId;
203}
204
205impl Container {
206 pub fn container_id(&self) -> ContainerId { self.consensus_commit() }
207}
208
209#[cfg(test)]
210mod test {
211 use amplify::Wrapper;
212 use commit_verify::tagged_hash;
213
214 use super::*;
215
216 #[test]
217 fn test_container_id_midstate() {
218 let midstate = tagged_hash::Midstate::with(b"storm:container");
219 assert_eq!(midstate.into_inner().into_inner(), MIDSTATE_CONTAINER_ID);
220 }
221}