dpp/serialization/serialization_traits.rs
1#[cfg(any(
2 feature = "message-signature-verification",
3 feature = "message-signing"
4))]
5use crate::identity::KeyType;
6
7use serde::de::DeserializeOwned;
8use serde::Serialize;
9#[cfg(feature = "json-conversion")]
10use serde_json::Value as JsonValue;
11
12#[cfg(feature = "message-signature-verification")]
13use crate::validation::SimpleConsensusValidationResult;
14use crate::version::PlatformVersion;
15#[cfg(feature = "message-signing")]
16use crate::BlsModule;
17use crate::ProtocolError;
18use platform_value::Value;
19
20pub trait Signable {
21 fn signable_bytes(&self) -> Result<Vec<u8>, ProtocolError>;
22}
23
24pub trait PlatformSerializable {
25 type Error;
26 fn serialize_to_bytes(&self) -> Result<Vec<u8>, Self::Error>;
27
28 /// If the trait is not used just do a simple serialize
29 fn serialize_consume_to_bytes(self) -> Result<Vec<u8>, Self::Error>
30 where
31 Self: Sized,
32 {
33 self.serialize_to_bytes()
34 }
35}
36
37pub trait PlatformSerializableWithPlatformVersion {
38 type Error;
39 /// Version based serialization is done based on the desired structure version.
40 /// For example we have DataContractV0 and DataContractV1 for code based Contracts
41 /// This means objects that will execute code
42 /// And we would have DataContractSerializationFormatV0 and DataContractSerializationFormatV1
43 /// which are the different ways to serialize the concept of a data contract.
44 /// The data contract would call versioned_serialize. There should be a converted for each
45 /// Data contract Version towards each DataContractSerializationFormat
46 fn serialize_to_bytes_with_platform_version(
47 &self,
48 platform_version: &PlatformVersion,
49 ) -> Result<Vec<u8>, Self::Error>;
50
51 /// If the trait is not used just do a simple serialize
52 fn serialize_consume_to_bytes_with_platform_version(
53 self,
54 platform_version: &PlatformVersion,
55 ) -> Result<Vec<u8>, Self::Error>
56 where
57 Self: Sized,
58 {
59 self.serialize_to_bytes_with_platform_version(platform_version)
60 }
61}
62
63pub trait PlatformDeserializable {
64 fn deserialize_from_bytes(data: &[u8]) -> Result<Self, ProtocolError>
65 where
66 Self: Sized,
67 {
68 Self::deserialize_from_bytes_no_limit(data)
69 }
70
71 fn deserialize_from_bytes_no_limit(data: &[u8]) -> Result<Self, ProtocolError>
72 where
73 Self: Sized;
74}
75
76pub trait PlatformDeserializableFromVersionedStructure {
77 /// We will deserialize a versioned structure into a code structure
78 /// For example we have DataContractV0 and DataContractV1
79 /// The system version will tell which version to deserialize into
80 /// This happens by first deserializing the data into a potentially versioned structure
81 /// For example we could have DataContractSerializationFormatV0 and DataContractSerializationFormatV1
82 /// Both of the structures will be valid in perpetuity as they are saved into the state.
83 /// So from the bytes we could get DataContractSerializationFormatV0.
84 /// Then the system_version given will tell to transform DataContractSerializationFormatV0 into
85 /// DataContractV1 (if system version is 1)
86 fn versioned_deserialize(
87 data: &[u8],
88 platform_version: &PlatformVersion,
89 ) -> Result<Self, ProtocolError>
90 where
91 Self: Sized;
92}
93
94pub trait PlatformDeserializableWithPotentialValidationFromVersionedStructure {
95 /// We will deserialize a versioned structure into a code structure
96 /// For example we have DataContractV0 and DataContractV1
97 /// The system version will tell which version to deserialize into
98 /// This happens by first deserializing the data into a potentially versioned structure
99 /// For example we could have DataContractSerializationFormatV0 and DataContractSerializationFormatV1
100 /// Both of the structures will be valid in perpetuity as they are saved into the state.
101 /// So from the bytes we could get DataContractSerializationFormatV0.
102 /// Then the system_version given will tell to transform DataContractSerializationFormatV0 into
103 /// DataContractV1 (if system version is 1)
104 fn versioned_deserialize(
105 data: &[u8],
106 full_validation: bool,
107 platform_version: &PlatformVersion,
108 ) -> Result<Self, ProtocolError>
109 where
110 Self: Sized;
111}
112
113pub trait PlatformDeserializableWithBytesLenFromVersionedStructure {
114 /// We will deserialize a versioned structure into a code structure
115 /// For example we have DataContractV0 and DataContractV1
116 /// The system version will tell which version to deserialize into
117 /// This happens by first deserializing the data into a potentially versioned structure
118 /// For example we could have DataContractSerializationFormatV0 and DataContractSerializationFormatV1
119 /// Both of the structures will be valid in perpetuity as they are saved into the state.
120 /// So from the bytes we could get DataContractSerializationFormatV0.
121 /// Then the system_version given will tell to transform DataContractSerializationFormatV0 into
122 /// DataContractV1 (if system version is 1)
123 fn versioned_deserialize_with_bytes_len(
124 data: &[u8],
125 full_validation: bool,
126 platform_version: &PlatformVersion,
127 ) -> Result<(Self, usize), ProtocolError>
128 where
129 Self: Sized;
130}
131
132pub trait PlatformLimitDeserializableFromVersionedStructure {
133 fn versioned_limit_deserialize(
134 data: &[u8],
135 platform_version: &PlatformVersion,
136 ) -> Result<Self, ProtocolError>
137 where
138 Self: Sized;
139}
140
141/// Convert to/from `platform_value::Value` using **non-human-readable** serde
142/// (`Identifier` = `Value::Identifier(bytes)`, binary = `Value::Bytes(bytes)`,
143/// raw byte fields preserved without stringification).
144///
145/// # ⚠️ HR / non-HR divergence (Critical-1)
146///
147/// `ValueConvertible` calls `platform_value::to_value`, which uses a serializer
148/// that reports `is_human_readable() == false`. The mirror trait
149/// [`JsonConvertible`] uses `serde_json::to_value`, which reports `true`.
150/// Types whose `Serialize` impl branches on `is_human_readable()` produce
151/// **structurally different output** between the two paths:
152///
153/// | Type | `to_json()` (HR) | `to_object()` (non-HR) |
154/// |---|---|---|
155/// | [`platform_value::Identifier`] | `"5bV6jUfh..."` (bs58 string) | `Value::Identifier([u8; 32])` |
156/// | [`platform_value::BinaryData`] | `"sg=="` (base64 string) | `Value::Bytes(Vec<u8>)` |
157/// | `Bytes20` / `Bytes32` / `Bytes36` | base64 string | `Value::Bytes32([u8; N])` etc. |
158/// | `CoreScript` | `"dqkU..."` (base64 string) | `Value::Bytes(Vec<u8>)` |
159///
160/// **Do not assume** `self.to_object()?.try_into_json()` ≡ `self.to_json()`.
161/// They render the same field as a string in one and a byte array in the
162/// other. Round-trip tests should exercise each path independently.
163///
164/// # ⚠️ `ContentDeserializer` caveat
165///
166/// Manual `Deserialize` impls that branch on `deserializer.is_human_readable()`
167/// must also handle `serde::__private::de::ContentDeserializer`, used
168/// internally by `#[serde(tag = "...")]` enums. ContentDeserializer **always
169/// reports `is_human_readable: true`** regardless of the original source, so
170/// a non-HR `platform_value::Value` flowing into a tagged enum gets shape-
171/// inferred as if it were HR. Recipe: write a dual-shape visitor accepting
172/// both shapes in the HR branch via `deserialize_any`. See
173/// [`platform_value::Bytes32::deserialize`] for the canonical example, and
174/// `rs-dpp/src/serialization/serde_bytes.rs` for `[u8; N]` / `Vec<u8>`.
175#[cfg(feature = "value-conversion")]
176pub trait ValueConvertible: Serialize + DeserializeOwned {
177 fn to_object(&self) -> Result<Value, ProtocolError>
178 where
179 Self: Sized,
180 {
181 platform_value::to_value(self).map_err(ProtocolError::ValueError)
182 }
183
184 fn into_object(self) -> Result<Value, ProtocolError>
185 where
186 Self: Sized,
187 {
188 platform_value::to_value(self).map_err(ProtocolError::ValueError)
189 }
190
191 fn from_object(value: Value) -> Result<Self, ProtocolError>
192 where
193 Self: Sized,
194 {
195 platform_value::from_value(value).map_err(ProtocolError::ValueError)
196 }
197
198 fn from_object_ref(value: &Value) -> Result<Self, ProtocolError>
199 where
200 Self: Sized,
201 {
202 platform_value::from_value(value.clone()).map_err(ProtocolError::ValueError)
203 }
204}
205
206/// Convert to/from JSON using **human-readable** serde (`Identifier` = base58,
207/// binary = base64).
208///
209/// This trait produces clean `serde_json::Value` with native number types.
210/// Any JS-boundary concerns (large number stringification) are handled by the
211/// WASM layer.
212///
213/// # ⚠️ HR / non-HR divergence (Critical-1)
214///
215/// `JsonConvertible` calls `serde_json::to_value`, which uses a serializer
216/// that reports `is_human_readable() == true`. The mirror trait
217/// [`ValueConvertible`] uses `platform_value::to_value`, which reports
218/// `false`. Types whose `Serialize` impl branches on `is_human_readable()`
219/// produce **structurally different output** between the two paths:
220///
221/// | Type | `to_json()` (HR) | `to_object()` (non-HR) |
222/// |---|---|---|
223/// | [`platform_value::Identifier`] | `"5bV6jUfh..."` (bs58 string) | `Value::Identifier([u8; 32])` |
224/// | [`platform_value::BinaryData`] | `"sg=="` (base64 string) | `Value::Bytes(Vec<u8>)` |
225/// | `Bytes20` / `Bytes32` / `Bytes36` | base64 string | `Value::Bytes32([u8; N])` etc. |
226/// | `CoreScript` | `"dqkU..."` (base64 string) | `Value::Bytes(Vec<u8>)` |
227///
228/// **Do not assume** `self.to_object()?.try_into_json()` ≡ `self.to_json()`.
229/// They render the same field as a string in one and a byte array in the
230/// other. Round-trip tests should exercise each path independently.
231///
232/// # ⚠️ `ContentDeserializer` caveat
233///
234/// Manual `Deserialize` impls that branch on `deserializer.is_human_readable()`
235/// must also handle `serde::__private::de::ContentDeserializer`, used
236/// internally by `#[serde(tag = "...")]` enums. ContentDeserializer **always
237/// reports `is_human_readable: true`** regardless of the original source — so
238/// a non-HR `platform_value::Value` flowing into a tagged enum gets shape-
239/// inferred as if it were HR. Recipe: write a dual-shape visitor accepting
240/// both shapes in the HR branch via `deserialize_any`. See
241/// [`platform_value::Bytes32::deserialize`] for the canonical example, and
242/// `rs-dpp/src/serialization/serde_bytes.rs` for `[u8; N]` / `Vec<u8>`.
243#[cfg(feature = "json-conversion")]
244pub trait JsonConvertible: Serialize + DeserializeOwned {
245 fn to_json(&self) -> Result<JsonValue, ProtocolError> {
246 serde_json::to_value(self).map_err(|e| ProtocolError::EncodingError(e.to_string()))
247 }
248
249 fn from_json(json: JsonValue) -> Result<Self, ProtocolError> {
250 serde_json::from_value(json).map_err(|e| ProtocolError::DecodingError(e.to_string()))
251 }
252}
253
254pub trait PlatformMessageSignable {
255 #[cfg(feature = "message-signature-verification")]
256 fn verify_signature(
257 &self,
258 public_key_type: KeyType,
259 public_key_data: &[u8],
260 signature: &[u8],
261 ) -> SimpleConsensusValidationResult;
262
263 #[cfg(feature = "message-signing")]
264 fn sign_by_private_key(
265 &self,
266 private_key: &[u8],
267 key_type: KeyType,
268 bls: &impl BlsModule,
269 ) -> Result<Vec<u8>, ProtocolError>;
270}