1use chia_protocol::Bytes32;
9use chia_sdk_utils::Address;
10
11use crate::error::{DidError, DidResult};
12
13pub const DID_CHIA_PREFIX: &str = "did:chia:";
15
16pub fn did_string_from_launcher_id(launcher_id: Bytes32) -> String {
21 Address::new(launcher_id, DID_CHIA_PREFIX.to_string())
22 .encode()
23 .expect("encoding a 32-byte launcher id under a fixed valid prefix never fails")
24}
25
26pub fn launcher_id_from_did_string(did: &str) -> DidResult<Bytes32> {
36 let candidate = did.trim();
37 let address = Address::decode(candidate)
38 .map_err(|error| DidError::InvalidDidString(error.to_string()))?;
39
40 if address.prefix != DID_CHIA_PREFIX {
41 return Err(DidError::InvalidDidString(format!(
42 "expected the '{DID_CHIA_PREFIX}' prefix, got '{}'",
43 address.prefix
44 )));
45 }
46
47 Ok(address.puzzle_hash)
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 fn sample_launcher_id() -> Bytes32 {
57 clvm_utils::tree_hash_atom(b"dig-did::did_string::sample-launcher-id").into()
58 }
59
60 #[test]
61 fn roundtrips_through_the_canonical_string_form() {
62 let launcher_id = sample_launcher_id();
63
64 let did = did_string_from_launcher_id(launcher_id);
65 assert!(did.starts_with(DID_CHIA_PREFIX));
66
67 let decoded = launcher_id_from_did_string(&did).expect("a freshly-encoded DID must decode");
68 assert_eq!(decoded, launcher_id);
69 }
70
71 #[test]
72 fn trims_surrounding_whitespace_before_decoding() {
73 let launcher_id = sample_launcher_id();
74 let did = did_string_from_launcher_id(launcher_id);
75 let padded = format!(" {did}\n");
76
77 let decoded =
78 launcher_id_from_did_string(&padded).expect("padding must not break decoding");
79 assert_eq!(decoded, launcher_id);
80 }
81
82 #[test]
83 fn rejects_malformed_bech32m() {
84 let error = launcher_id_from_did_string("not-a-valid-bech32m-string")
85 .expect_err("garbage input must fail closed");
86 assert!(matches!(error, DidError::InvalidDidString(_)));
87 }
88
89 #[test]
90 fn rejects_a_wrong_human_readable_prefix() {
91 let not_a_did = Address::new(sample_launcher_id(), "xch".to_string())
93 .encode()
94 .expect("a 32-byte payload under the 'xch' prefix encodes fine");
95
96 let error = launcher_id_from_did_string(¬_a_did)
97 .expect_err("a non-did:chia prefix must be rejected, never silently accepted");
98 assert!(matches!(error, DidError::InvalidDidString(_)));
99 }
100
101 #[test]
102 fn byte_agrees_with_the_chia_sdk_utils_address_codec() {
103 let launcher_id = sample_launcher_id();
107 let via_address = Address::new(launcher_id, DID_CHIA_PREFIX.to_string())
108 .encode()
109 .expect("encoding must succeed for a well-formed 32-byte payload");
110 let via_dig_did = did_string_from_launcher_id(launcher_id);
111 assert_eq!(via_address, via_dig_did);
112 }
113}