Skip to main content

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}