did_simple/methods/
key.rs

1//! An implementation of the [did:key] method.
2//!
3//! [did:key]: https://w3c-ccg.github.io/did-method-key/
4
5use std::fmt::Display;
6
7use crate::{
8	key_algos::{Ed25519, KeyAlgo, StaticSigningAlgo},
9	url::{DidMethod, DidUrl},
10	utf8bytes::Utf8Bytes,
11	varint::decode_varint,
12};
13
14/// An implementation of the `did:key` method. See the [module](self) docs for more
15/// info.
16#[derive(Debug, Eq, PartialEq, Hash, Clone)]
17pub struct DidKey {
18	/// The string representation of the DID.
19	s: Utf8Bytes,
20	/// The decoded multibase portion of the DID.
21	mb_value: Vec<u8>,
22	key_algo: KeyAlgo,
23	/// The index into [`Self::mb_value`] that is the public key.
24	pubkey_bytes: std::ops::RangeFrom<usize>,
25}
26
27pub const PREFIX: &str = "did:key:";
28
29impl DidKey {
30	pub const PREFIX: &'static str = PREFIX;
31
32	/// Gets the buffer representing the did:key url as a str.
33	pub fn as_str(&self) -> &str {
34		self.s.as_str()
35	}
36
37	/// Gets the buffer representing the did:key url as a byte slice.
38	pub fn as_slice(&self) -> &[u8] {
39		self.s.as_slice()
40	}
41
42	/// Gets the buffer representing the did:key url as a reference counted slice
43	/// that is guaranteed to be utf8.
44	pub fn as_utf8_bytes(&self) -> &Utf8Bytes {
45		&self.s
46	}
47
48	pub fn key_algo(&self) -> KeyAlgo {
49		self.key_algo
50	}
51
52	/// Gets the decoded bytes of the public key.
53	pub fn pub_key(&self) -> &[u8] {
54		let result = match self.key_algo {
55			KeyAlgo::Ed25519 => &self.mb_value[self.pubkey_bytes.clone()],
56		};
57		debug_assert_eq!(result.len(), self.key_algo.verifying_key_len());
58		result
59	}
60}
61
62fn decode_multibase(
63	s: &Utf8Bytes,
64	out_buf: &mut Vec<u8>,
65) -> Result<(), MultibaseDecodeError> {
66	out_buf.clear();
67	// did:key only uses base58-btc, so its not actually any arbitrary multibase.
68	let multibase_part = &s.as_slice()[PREFIX.len()..];
69	// the first character should always be 'z'
70	let base = multibase_part[0];
71	if base != b'z' {
72		return Err(MultibaseDecodeError::WrongBase(base));
73	}
74	bs58::decode(&multibase_part[1..])
75		.with_alphabet(bs58::Alphabet::BITCOIN)
76		.onto(out_buf)?;
77	Ok(())
78}
79
80#[derive(thiserror::Error, Debug)]
81pub enum MultibaseDecodeError {
82	#[error(
83		"Expected \"base58-btc\" encoding which should be identified in multibase as ascii 'z' (0x7a) but got {0:x}"
84	)]
85	WrongBase(u8),
86	#[error(transparent)]
87	Bs58(#[from] bs58::decode::Error),
88}
89
90impl TryFrom<DidUrl> for DidKey {
91	type Error = FromUrlError;
92
93	fn try_from(value: DidUrl) -> Result<Self, Self::Error> {
94		let m = value.method();
95		if m != DidMethod::Key {
96			return Err(FromUrlError::WrongMethod(m));
97		}
98		debug_assert_eq!(
99			value.as_slice().len() - value.method_specific_id().as_slice().len(),
100			PREFIX.len(),
101			"sanity check that prefix has expected length"
102		);
103
104		let s = value.as_utf8_bytes().clone();
105		let mut decoded_multibase = Vec::new();
106		decode_multibase(&s, &mut decoded_multibase)?;
107
108		// tail bytes will end up being the pubkey bytes if everything passes validation
109		let (multicodec_key_algo, tail_bytes) = decode_varint(&decoded_multibase)?;
110		let (key_algo, pub_key_len) = match multicodec_key_algo {
111			Ed25519::MULTICODEC_VALUE => (KeyAlgo::Ed25519, Ed25519::SIGNING_KEY_LEN),
112			_ => return Err(FromUrlError::UnknownKeyAlgo(multicodec_key_algo)),
113		};
114
115		if tail_bytes.len() != pub_key_len {
116			return Err(FromUrlError::MismatchedPubkeyLen(key_algo, pub_key_len));
117		}
118
119		let pubkey_bytes = (decoded_multibase.len() - pub_key_len)..;
120
121		Ok(Self {
122			s,
123			mb_value: decoded_multibase,
124			key_algo,
125			pubkey_bytes,
126		})
127	}
128}
129
130#[derive(thiserror::Error, Debug)]
131pub enum FromUrlError {
132	#[error("Expected \"key\" method but got {0:?}")]
133	WrongMethod(DidMethod),
134	#[error(transparent)]
135	MultibaseDecode(#[from] MultibaseDecodeError),
136	#[error("unknown multicodec value for key algorithm: decoded varint as {0}")]
137	UnknownKeyAlgo(u16),
138	#[error(transparent)]
139	Varint(#[from] crate::varint::DecodeError),
140	#[error("{0:?} requires pubkeys of length {} but got {1} bytes", .0.verifying_key_len())]
141	MismatchedPubkeyLen(KeyAlgo, usize),
142}
143
144impl Display for DidKey {
145	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146		self.as_str().fmt(f)
147	}
148}
149
150#[cfg(test)]
151mod test {
152	use super::*;
153
154	use eyre::WrapErr;
155	use hex_literal::hex;
156	use std::str::FromStr;
157	//0xed
158	// From: https://w3c-ccg.github.io/did-method-key/#example-5
159	fn ed25519_examples() -> &'static [&'static str] {
160		&[
161			"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
162			"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
163			"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
164		]
165	}
166
167	#[test]
168	fn test_try_from_url() -> eyre::Result<()> {
169		for &example in ed25519_examples() {
170			let url = DidUrl::from_str(example)
171				.wrap_err_with(|| format!("failed to parse DidUrl from {example}"))?;
172			assert_eq!(example, url.as_str());
173			let key_from_url = DidKey::try_from(url.clone())
174				.wrap_err_with(|| format!("failed to parse DidKey from {url}"))?;
175			assert_eq!(example, key_from_url.as_str());
176			assert_eq!(key_from_url.key_algo(), Ed25519);
177		}
178		Ok(())
179	}
180
181	#[test]
182	fn test_decode_multibase() -> eyre::Result<()> {
183		#[derive(Debug)]
184		struct Example {
185			decoded: &'static [u8],
186			encoded: &'static str,
187		}
188		// from: https://datatracker.ietf.org/doc/html/draft-msporny-base58-03#section-5
189		let examples = [
190			Example {
191				decoded: b"Hello World!",
192				encoded: "2NEpo7TZRRrLZSi2U",
193			},
194			Example {
195				decoded: b"The quick brown fox jumps over the lazy dog.",
196				encoded: "USm3fpXnKG5EUBx2ndxBDMPVciP5hGey2Jh4NDv6gmeo1LkMeiKrLJUUBk6Z",
197			},
198			Example {
199				decoded: &hex!("0000287fb4cd"),
200				encoded: "11233QC4",
201			},
202		];
203
204		let mut buf = Vec::new();
205		for e @ Example { decoded, encoded } in examples {
206			let s = format!("{}z{encoded}", PREFIX).into();
207			decode_multibase(&s, &mut buf)
208				.wrap_err_with(|| format!("Failed to decode example {e:?}"))?;
209			assert_eq!(buf, decoded, "failed comparison in example {e:?}");
210		}
211
212		Ok(())
213	}
214}