aph_core/crypto/
did_key.rs1const ED25519_MULTICODEC: [u8; 2] = [0xed, 0x01];
16const P256_MULTICODEC: [u8; 2] = [0x80, 0x24];
18
19const DID_KEY_PREFIX: &str = "did:key:";
21
22#[derive(std::fmt::Debug, std::clone::Clone, std::cmp::PartialEq, std::cmp::Eq)]
24pub enum DecodedDidKey {
25 Ed25519(std::boxed::Box<ed25519_dalek::VerifyingKey>),
27 P256(std::vec::Vec<u8>),
30}
31
32pub fn encode_ed25519(key: &ed25519_dalek::VerifyingKey) -> String {
37 let mut payload = std::vec::Vec::with_capacity(2 + 32);
38 payload.extend_from_slice(&ED25519_MULTICODEC);
39 payload.extend_from_slice(key.as_bytes());
40 std::format!("{}{}", DID_KEY_PREFIX, super::multibase::base58btc_encode(&payload))
41}
42
43pub fn encode_p256(key: &p256::ecdsa::VerifyingKey) -> String {
54 let point = key.to_encoded_point(true);
55 let mut payload = std::vec::Vec::with_capacity(2 + 33);
56 payload.extend_from_slice(&P256_MULTICODEC);
57 payload.extend_from_slice(point.as_bytes());
58 std::format!("{}{}", DID_KEY_PREFIX, super::multibase::base58btc_encode(&payload))
59}
60
61pub fn decode(did: &str) -> std::result::Result<DecodedDidKey, crate::errors::AphError> {
66 let encoded = match did.strip_prefix(DID_KEY_PREFIX) {
67 std::option::Option::Some(rest) => rest,
68 std::option::Option::None => {
69 return std::result::Result::Err(crate::errors::AphError::InvalidEnvelopeSignature);
70 }
71 };
72 decode_multibase_key(encoded)
73}
74
75pub fn decode_multibase_key(
81 encoded: &str,
82) -> std::result::Result<DecodedDidKey, crate::errors::AphError> {
83 let bytes = super::multibase::base58btc_decode(encoded)?;
84 if bytes.len() < 3 {
85 return std::result::Result::Err(crate::errors::AphError::InvalidEnvelopeSignature);
86 }
87 let (prefix, key_bytes) = bytes.split_at(2);
88
89 if prefix == ED25519_MULTICODEC {
90 let arr: [u8; 32] = match key_bytes.try_into() {
91 std::result::Result::Ok(a) => a,
92 std::result::Result::Err(_) => {
94 return std::result::Result::Err(crate::errors::AphError::InvalidEnvelopeSignature);
95 }
96 };
97 return match ed25519_dalek::VerifyingKey::from_bytes(&arr) {
98 std::result::Result::Ok(k) => {
99 std::result::Result::Ok(DecodedDidKey::Ed25519(std::boxed::Box::new(k)))
100 }
101 std::result::Result::Err(_) => {
103 std::result::Result::Err(crate::errors::AphError::InvalidEnvelopeSignature)
104 }
105 };
106 }
107
108 if prefix == P256_MULTICODEC {
109 if key_bytes.len() != 33 {
111 return std::result::Result::Err(crate::errors::AphError::InvalidEnvelopeSignature);
112 }
113 return std::result::Result::Ok(DecodedDidKey::P256(key_bytes.to_vec()));
114 }
115
116 std::result::Result::Err(crate::errors::AphError::unsupported_algorithm(std::format!(
118 "did:key multicodec 0x{:02x}{:02x}",
119 prefix[0],
120 prefix[1]
121 )))
122}
123
124#[cfg(test)]
125mod tests {
126 fn sample_key() -> ed25519_dalek::VerifyingKey {
127 let sk = ed25519_dalek::SigningKey::from_bytes(&[7u8; 32]);
128 sk.verifying_key()
129 }
130
131 #[test]
132 fn encode_decode_round_trip() {
133 let key = sample_key();
136 let did = super::encode_ed25519(&key);
137 match super::decode(&did).unwrap() {
138 super::DecodedDidKey::Ed25519(decoded) => std::assert_eq!(*decoded, key),
139 other => std::panic!("expected Ed25519, got {:?}", other),
140 }
141 }
142
143 #[test]
144 fn ed25519_dids_use_the_z6mk_prefix() {
145 let did = super::encode_ed25519(&sample_key());
149 std::assert!(did.starts_with("did:key:z6Mk"), "got {}", did);
150 }
151
152 #[test]
153 fn published_example_issuer_decodes() {
154 let did = "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSdoVTwBaPaeT1KhFmkV";
158 std::assert!(matches!(
159 super::decode(did).unwrap(),
160 super::DecodedDidKey::Ed25519(_)
161 ));
162 }
163
164 #[test]
165 fn p256_encode_decode_round_trip() {
166 let signing = p256::ecdsa::SigningKey::from_slice(&[
172 0xc9, 0xaf, 0xa9, 0xd8, 0x45, 0xba, 0x75, 0x16, 0x6b, 0x5c, 0x21, 0x57, 0x67, 0xb1, 0xd6,
173 0x93, 0x4e, 0x50, 0xc3, 0xdb, 0x36, 0xe8, 0x9b, 0x12, 0x7b, 0x8a, 0x62, 0x2b, 0x12, 0x0f,
174 0x67, 0x21,
175 ])
176 .expect("the RFC 6979 A.2.5 sample key is a valid P-256 scalar");
177 let key = *signing.verifying_key();
178 let did = super::encode_p256(&key);
179 std::assert!(did.starts_with("did:key:zDn"), "got {}", did);
183 match super::decode(&did).unwrap() {
184 super::DecodedDidKey::P256(bytes) => {
185 std::assert_eq!(bytes, key.to_encoded_point(true).as_bytes().to_vec());
186 }
187 other => std::panic!("expected P256, got {:?}", other),
188 }
189 }
190
191 #[test]
192 fn non_did_key_input_is_rejected() {
193 std::assert!(super::decode("did:web:notary.example").is_err());
196 std::assert!(super::decode("z6MkfAkf").is_err());
197 }
198
199 #[test]
200 fn unknown_multicodec_reports_unsupported_algorithm() {
201 let payload = super::super::multibase::base58btc_encode(&[0x99u8, 0x01, 1, 2, 3]);
204 let err = super::decode(&std::format!("did:key:{}", payload)).unwrap_err();
205 std::assert_eq!(err.code(), "APH_E010");
206 }
207
208 #[test]
209 fn truncated_key_is_rejected_not_panicking() {
210 let payload = super::super::multibase::base58btc_encode(&[0xed, 0x01, 1, 2, 3]);
213 std::assert!(super::decode(&std::format!("did:key:{}", payload)).is_err());
214 }
215}