1use crate::data_contract::document_type::accessors::DocumentTypeV0Getters;
2use crate::data_contract::document_type::DocumentTypeRef;
3use crate::document::{Document, DocumentV0Getters, DocumentV0Setters};
4use crate::prelude::IdentityNonce;
5use crate::ProtocolError;
6use crate::{prelude::Identifier, util::hash::hash_double, util::hash::hash_double_to_vec};
7use platform_version::version::PlatformVersion;
8
9const DOCUMENT_ID_V1_DOMAIN_TAG: &[u8] = b"dash:document-id:v1";
14
15impl Document {
16 pub fn generate_document_id(
29 contract_id: &Identifier,
30 owner_id: &Identifier,
31 document_type_name: &str,
32 entropy: &[u8],
33 identity_contract_nonce: IdentityNonce,
34 platform_version: &PlatformVersion,
35 ) -> Result<Identifier, ProtocolError> {
36 match platform_version
37 .dpp
38 .document_versions
39 .document_method_versions
40 .generate_document_id
41 {
42 0 => Ok(Self::generate_document_id_v0(
43 contract_id,
44 owner_id,
45 document_type_name,
46 entropy,
47 )),
48 1 => Ok(Self::generate_document_id_v1(
49 contract_id,
50 owner_id,
51 document_type_name,
52 entropy,
53 identity_contract_nonce,
54 )),
55 version => Err(ProtocolError::UnknownVersionMismatch {
56 method: "Document::generate_document_id".to_string(),
57 known_versions: vec![0, 1],
58 received: version,
59 }),
60 }
61 }
62
63 pub fn set_id_for_creation(
72 &mut self,
73 document_type: DocumentTypeRef,
74 entropy: &[u8; 32],
75 identity_contract_nonce: IdentityNonce,
76 platform_version: &PlatformVersion,
77 ) -> Result<(), ProtocolError> {
78 let id = Self::generate_document_id(
79 &document_type.data_contract_id(),
80 &self.owner_id(),
81 document_type.name(),
82 entropy,
83 identity_contract_nonce,
84 platform_version,
85 )?;
86 self.set_id(id);
87 Ok(())
88 }
89
90 pub fn generate_document_id_sha256_blocks(
100 document_type_name: &str,
101 platform_version: &PlatformVersion,
102 ) -> Result<u16, ProtocolError> {
103 match platform_version
104 .dpp
105 .document_versions
106 .document_method_versions
107 .generate_document_id
108 {
109 0 => Ok(2),
110 1 => {
111 let padded_len = DOCUMENT_ID_V1_DOMAIN_TAG.len()
114 + 32
115 + 32
116 + document_type_name.len()
117 + 32
118 + 8
119 + 9;
120 let first_pass_blocks = padded_len.div_ceil(64) as u16;
121 let second_pass_blocks = 1;
123 Ok(first_pass_blocks + second_pass_blocks)
124 }
125 version => Err(ProtocolError::UnknownVersionMismatch {
126 method: "Document::generate_document_id_sha256_blocks".to_string(),
127 known_versions: vec![0, 1],
128 received: version,
129 }),
130 }
131 }
132
133 pub fn document_id_depends_on_nonce(
137 platform_version: &PlatformVersion,
138 ) -> Result<bool, ProtocolError> {
139 match platform_version
140 .dpp
141 .document_versions
142 .document_method_versions
143 .generate_document_id
144 {
145 0 => Ok(false),
146 1 => Ok(true),
147 version => Err(ProtocolError::UnknownVersionMismatch {
148 method: "Document::document_id_depends_on_nonce".to_string(),
149 known_versions: vec![0, 1],
150 received: version,
151 }),
152 }
153 }
154
155 pub fn generate_document_id_v0(
157 contract_id: &Identifier,
158 owner_id: &Identifier,
159 document_type_name: &str,
160 entropy: &[u8],
161 ) -> Identifier {
162 let mut buf: Vec<u8> = vec![];
163
164 buf.extend_from_slice(&contract_id.to_buffer());
165 buf.extend_from_slice(&owner_id.to_buffer());
166 buf.extend_from_slice(document_type_name.as_bytes());
167 buf.extend_from_slice(entropy);
168
169 Identifier::from_bytes(&hash_double_to_vec(&buf)).unwrap()
170 }
171
172 pub fn generate_document_id_v1(
175 contract_id: &Identifier,
176 owner_id: &Identifier,
177 document_type_name: &str,
178 entropy: &[u8],
179 identity_contract_nonce: IdentityNonce,
180 ) -> Identifier {
181 let mut buf: Vec<u8> = Vec::with_capacity(
182 DOCUMENT_ID_V1_DOMAIN_TAG.len() + 64 + document_type_name.len() + entropy.len() + 8,
183 );
184
185 buf.extend_from_slice(DOCUMENT_ID_V1_DOMAIN_TAG);
186 buf.extend_from_slice(contract_id.as_slice());
187 buf.extend_from_slice(owner_id.as_slice());
188 buf.extend_from_slice(document_type_name.as_bytes());
189 buf.extend_from_slice(entropy);
190 buf.extend_from_slice(&identity_contract_nonce.to_be_bytes());
191
192 Identifier::from(hash_double(&buf))
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 const ENTROPY: [u8; 32] = [7u8; 32];
201
202 fn ids() -> (Identifier, Identifier) {
203 (Identifier::from([1u8; 32]), Identifier::from([2u8; 32]))
204 }
205
206 #[test]
207 fn should_derive_the_entropy_only_id_before_protocol_version_14() {
208 let (contract_id, owner_id) = ids();
209 let platform_version = PlatformVersion::get(13).expect("expected version 13");
210
211 let first = Document::generate_document_id(
212 &contract_id,
213 &owner_id,
214 "note",
215 &ENTROPY,
216 1,
217 platform_version,
218 )
219 .expect("expected an id");
220 let second = Document::generate_document_id(
221 &contract_id,
222 &owner_id,
223 "note",
224 &ENTROPY,
225 2,
226 platform_version,
227 )
228 .expect("expected an id");
229
230 assert_eq!(
231 first,
232 Document::generate_document_id_v0(&contract_id, &owner_id, "note", &ENTROPY)
233 );
234 assert_eq!(first, second);
235 assert!(
236 !Document::document_id_depends_on_nonce(platform_version).expect("expected a version")
237 );
238 }
239
240 #[test]
241 fn should_derive_a_different_id_for_every_nonce() {
242 let (contract_id, owner_id) = ids();
243 let platform_version = PlatformVersion::latest();
244
245 let first = Document::generate_document_id(
246 &contract_id,
247 &owner_id,
248 "note",
249 &ENTROPY,
250 1,
251 platform_version,
252 )
253 .expect("expected an id");
254 let second = Document::generate_document_id(
255 &contract_id,
256 &owner_id,
257 "note",
258 &ENTROPY,
259 2,
260 platform_version,
261 )
262 .expect("expected an id");
263
264 assert_ne!(first, second);
265 assert_ne!(
266 first,
267 Document::generate_document_id_v0(&contract_id, &owner_id, "note", &ENTROPY)
268 );
269 assert!(
270 Document::document_id_depends_on_nonce(platform_version).expect("expected a version")
271 );
272 }
273
274 #[test]
275 fn should_bill_the_nonce_derived_id_by_the_length_of_its_preimage() {
276 let latest = PlatformVersion::latest();
277 let version_13 = PlatformVersion::get(13).expect("expected version 13");
278 let blocks = |name: &str, version| {
279 Document::generate_document_id_sha256_blocks(name, version).expect("expected blocks")
280 };
281
282 assert_eq!(blocks("note", version_13), 2);
284 assert_eq!(blocks(&"n".repeat(64), version_13), 2);
285
286 assert_eq!(blocks("note", latest), 4);
287 assert_eq!(blocks(&"n".repeat(60), latest), 4);
288 assert_eq!(blocks(&"n".repeat(61), latest), 5);
289 }
290
291 #[test]
292 fn should_keep_the_entropy_in_the_nonce_derived_id() {
293 let (contract_id, owner_id) = ids();
294
295 assert_ne!(
296 Document::generate_document_id_v1(&contract_id, &owner_id, "note", &ENTROPY, 1),
297 Document::generate_document_id_v1(&contract_id, &owner_id, "note", &[8u8; 32], 1),
298 );
299 }
300
301 #[test]
302 fn should_pin_the_nonce_derived_id() {
303 let (contract_id, owner_id) = ids();
304
305 assert_eq!(
308 Document::generate_document_id_v1(&contract_id, &owner_id, "note", &ENTROPY, 1)
309 .to_string(platform_value::string_encoding::Encoding::Hex),
310 PINNED_V1_ID
311 );
312 }
313
314 const PINNED_V1_ID: &str = "e574ae73396611a517691d1f89275b6e99642cb9c176ce8cf879b1665c50f15f";
315}