1use std::fmt::{self, Display, Formatter};
23use std::hash::Hash;
24use std::str::FromStr;
25
26use amplify::{hex, Bytes, Bytes32, Display};
27use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};
28use sha2::{Digest, Sha256};
29
30#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, Default)]
31#[non_exhaustive]
32pub enum Algo {
33 #[default]
34 #[display("ed25519")]
35 Ed25519,
36 #[display("bip340")]
37 Bip340,
38 #[display("other({0})")]
39 Other(u8),
40}
41
42#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error)]
43#[display("unknown algorithm '{0}'")]
44pub struct UnknownAlgo(String);
45
46impl FromStr for Algo {
47 type Err = UnknownAlgo;
48
49 fn from_str(s: &str) -> Result<Self, Self::Err> {
50 match s {
51 "ed25519" | "Ed25519" | "ED25519" => Ok(Algo::Ed25519),
52 "bip340" | "Bip340" | "BIP340" => Ok(Algo::Bip340),
53 s => Err(UnknownAlgo(s.to_owned())),
54 }
55 }
56}
57
58impl From<Algo> for u8 {
59 fn from(algo: Algo) -> Self {
60 match algo {
61 Algo::Ed25519 => 0x13,
62 Algo::Bip340 => 0,
63 Algo::Other(v) => v,
64 }
65 }
66}
67
68impl From<u8> for Algo {
69 fn from(value: u8) -> Self {
70 match value {
71 0x13 => Algo::Ed25519,
72 0 => Algo::Bip340,
73 n => Algo::Other(n),
74 }
75 }
76}
77
78#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Display)]
79#[display(lowercase)]
80#[non_exhaustive]
81pub enum Chain {
82 #[default]
83 Bitcoin,
84 Liquid,
85 #[display("other({0})")]
86 Other(u8),
87}
88
89#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error)]
90#[display("unknown chain '{0}'")]
91pub struct UnknownChain(String);
92
93impl FromStr for Chain {
94 type Err = UnknownChain;
95
96 fn from_str(s: &str) -> Result<Self, Self::Err> {
97 match s {
98 "bitcoin" => Ok(Chain::Bitcoin),
99 "liquid" => Ok(Chain::Liquid),
100 s => Err(UnknownChain(s.to_owned())),
101 }
102 }
103}
104
105impl From<Chain> for u8 {
106 fn from(chain: Chain) -> Self {
107 match chain {
108 Chain::Bitcoin => 0xB7,
109 Chain::Liquid => 0x10,
110 Chain::Other(v) => v,
111 }
112 }
113}
114
115impl From<u8> for Chain {
116 fn from(value: u8) -> Self {
117 match value {
118 0xB7 => Chain::Bitcoin,
119 0x10 => Chain::Liquid,
120 n => Chain::Other(n),
121 }
122 }
123}
124
125#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
126pub struct SsiPub {
127 pub(crate) chain: Chain,
128 pub(crate) algo: Algo,
129 pub(crate) key: Bytes<30>,
130}
131
132impl DisplayBaid64 for SsiPub {
133 const HRI: &'static str = "ssi";
134 const CHUNKING: bool = true;
135 const PREFIX: bool = true;
136 const EMBED_CHECKSUM: bool = false;
137 const MNEMONIC: bool = false;
138
139 fn to_baid64_payload(&self) -> [u8; 32] { <[u8; 32]>::from(*self) }
140}
141
142impl FromBaid64Str for SsiPub {}
143
144impl From<SsiPub> for [u8; 32] {
145 fn from(ssi: SsiPub) -> Self { ssi.to_byte_array() }
146}
147
148impl From<[u8; 32]> for SsiPub {
149 fn from(value: [u8; 32]) -> Self {
150 let key = Bytes::from_slice_unsafe(&value[0..30]);
151 let algo = Algo::from(value[30]);
152 let chain = Chain::from(value[31]);
153 Self { algo, key, chain }
154 }
155}
156
157impl SsiPub {
158 pub fn verify_text(self, text: &str, sig: SsiSig) -> Result<(), InvalidSig> {
159 let msg = Sha256::digest(text);
160 let digest = Sha256::digest(msg);
161 self.verify(digest.into(), sig)
162 }
163
164 pub fn verify(self, msg: [u8; 32], sig: SsiSig) -> Result<(), InvalidSig> {
165 match self.algo {
166 Algo::Ed25519 => self.verify_ed25519(msg, sig),
167 Algo::Bip340 => self.verify_bip360(msg, sig),
168 Algo::Other(other) => Err(InvalidSig::UnsupportedAlgo(other)),
169 }
170 }
171
172 pub fn fingerprint(self) -> Fingerprint {
173 Fingerprint([self.key[0], self.key[1], self.key[2], self.key[3], self.key[4], self.key[5]])
174 }
175
176 pub fn to_byte_array(&self) -> [u8; 32] {
177 let mut buf = [0u8; 32];
178 buf[0..30].copy_from_slice(self.key.as_slice());
179 buf[30] = self.algo.into();
180 buf[31] = self.chain.into();
181 buf
182 }
183}
184
185impl Display for SsiPub {
186 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
187 if !f.alternate() {
188 self.fmt_baid64(f)
189 } else {
190 write!(f, "{}", self.fingerprint())
191 }
192 }
193}
194
195impl FromStr for SsiPub {
196 type Err = Baid64ParseError;
197 fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
198}
199
200#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
201pub struct SsiSig(pub(crate) [u8; 64]);
202
203impl DisplayBaid64<64> for SsiSig {
204 const HRI: &'static str = "";
205 const CHUNKING: bool = false;
206 const PREFIX: bool = false;
207 const EMBED_CHECKSUM: bool = false;
208 const MNEMONIC: bool = false;
209
210 fn to_baid64_payload(&self) -> [u8; 64] { self.0 }
211}
212
213impl FromBaid64Str<64> for SsiSig {}
214
215impl FromStr for SsiSig {
216 type Err = Baid64ParseError;
217 fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
218}
219
220impl Display for SsiSig {
221 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.fmt_baid64(f) }
222}
223
224#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error)]
225#[display("invalid public key")]
226pub struct InvalidPubkey;
227
228#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error, From)]
229#[display(doc_comments)]
230pub enum InvalidSig {
231 InvalidData,
233
234 #[from(InvalidPubkey)]
236 InvalidPubkey,
237
238 InvalidSig,
240
241 UnsupportedAlgo(u8),
243}
244
245#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, From)]
246#[display(inner)]
247pub enum SsiQuery {
248 #[from]
249 Pub(SsiPub),
250 #[from]
251 Fp(Fingerprint),
252 #[from]
253 Id(String),
254}
255
256impl FromStr for SsiQuery {
257 type Err = Baid64ParseError;
258
259 fn from_str(s: &str) -> Result<Self, Self::Err> {
260 if s.len() == 8 {
261 Fingerprint::from_str(s).map(Self::Fp)
262 } else if s.starts_with("ssi:") || (s.contains('-') && (s.len() == 48 || s.len() == 52)) {
263 SsiPub::from_str(s).map(Self::Pub)
264 } else {
265 Ok(SsiQuery::Id(s.to_owned()))
266 }
267 }
268}
269
270#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, From)]
271pub struct Fingerprint([u8; 6]);
272
273impl DisplayBaid64<6> for Fingerprint {
274 const HRI: &'static str = "";
275 const CHUNKING: bool = false;
276 const PREFIX: bool = false;
277 const EMBED_CHECKSUM: bool = false;
278 const MNEMONIC: bool = false;
279
280 fn to_baid64_payload(&self) -> [u8; 6] { self.0 }
281}
282
283impl FromBaid64Str<6> for Fingerprint {}
284
285impl Display for Fingerprint {
286 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.fmt_baid64(f) }
287}
288
289impl FromStr for Fingerprint {
290 type Err = Baid64ParseError;
291
292 fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
293}
294
295#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
296pub struct SsiCert {
297 pub fp: Fingerprint,
298 pub pk: Option<SsiPub>,
299 pub msg: Bytes32,
300 pub sig: SsiSig,
301}
302
303#[derive(Debug, Display, Error, From)]
304#[display(inner)]
305pub enum VerifyError {
306 #[display("the certificate has no identity, verification impossible.")]
307 NoIdentity,
308 #[from]
309 InvalidSig(InvalidSig),
310 #[display("the provided text doesn't match the signed message")]
311 MessageMismatch,
312}
313
314impl SsiCert {
315 pub fn verify(&self) -> Result<(), VerifyError> {
316 let Some(pk) = self.pk else {
317 return Err(VerifyError::NoIdentity);
318 };
319 Ok(pk.verify(self.msg.to_byte_array(), self.sig)?)
320 }
321
322 pub fn verify_text(&self, text: &str) -> Result<(), VerifyError> {
323 let Some(pk) = self.pk else {
324 return Err(VerifyError::NoIdentity);
325 };
326 let msg = Sha256::digest(text);
327 let digest = Sha256::digest(msg);
328 let msg = <[u8; 32]>::from(digest);
329 if self.msg.to_byte_array() != msg {
330 return Err(VerifyError::MessageMismatch);
331 }
332 Ok(pk.verify(digest.into(), self.sig)?)
333 }
334}
335
336#[derive(Debug, Display, Error, From)]
337#[display(doc_comments)]
338pub enum CertParseError {
339 DataMissed,
341 InvalidFingerprint(Baid64ParseError),
343 InvalidPub(Baid64ParseError),
345 #[from]
347 InvalidMessage(hex::Error),
348 #[from]
349 InvalidSig(Baid64ParseError),
351}
352
353impl FromStr for SsiCert {
354 type Err = CertParseError;
355
356 fn from_str(s: &str) -> Result<Self, Self::Err> {
357 let (fp, rest) = s
358 .trim_start_matches("ssi:")
359 .split_once('?')
360 .ok_or(CertParseError::DataMissed)?;
361 let (msg, rest) = rest
362 .trim_start_matches("msg=")
363 .split_once('&')
364 .ok_or(CertParseError::DataMissed)?;
365 let sig = rest.trim_start_matches("sig=");
366 let (fp, pk) = match fp.len() {
367 8 => (Fingerprint::from_str(fp).map_err(CertParseError::InvalidFingerprint)?, None),
368 _ => {
369 let pk = SsiPub::from_str(fp).map_err(CertParseError::InvalidPub)?;
370 (pk.fingerprint(), Some(pk))
371 }
372 };
373 let msg = Bytes32::from_str(msg)?;
374 let sig = SsiSig::from_str(sig)?;
375 Ok(SsiCert { fp, pk, msg, sig })
376 }
377}
378
379impl Display for SsiCert {
380 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
381 if f.alternate() {
382 if let Some(pk) = self.pk {
383 return write!(f, "{pk}?msg={msg}&sig={sig}", msg = self.msg, sig = self.sig);
384 }
385 }
386 write!(f, "ssi:{fp}?msg={msg}&sig={sig}", fp = self.fp, msg = self.msg, sig = self.sig)
387 }
388}