dpp/identity/identity_public_key/contract_bounds/
mod.rs

1use crate::identifier::Identifier;
2use crate::identity::identity_public_key::contract_bounds::ContractBounds::{
3    SingleContract, SingleContractDocumentType,
4};
5#[cfg(feature = "json-conversion")]
6use crate::serialization::JsonConvertible;
7#[cfg(feature = "value-conversion")]
8use crate::serialization::ValueConvertible;
9use crate::ProtocolError;
10use bincode::{Decode, Encode};
11use serde::{Deserialize, Serialize};
12
13pub type ContractBoundsType = u8;
14
15/// A contract bounds is the bounds that the key has influence on.
16/// For authentication keys the bounds mean that the keys can only be used to sign
17/// within the specified contract.
18/// For encryption decryption this tells clients to only use these keys for specific
19/// contracts.
20///
21#[cfg_attr(feature = "json-conversion", derive(JsonConvertible))]
22#[repr(u8)]
23#[derive(
24    Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, Ord, PartialOrd, Hash,
25)]
26#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
27#[serde(tag = "type", rename_all = "camelCase")]
28pub enum ContractBounds {
29    /// this key can only be used within a specific contract
30    #[serde(rename = "singleContract")]
31    SingleContract { id: Identifier } = 0,
32    /// this key can only be used within a specific contract and for a specific document type
33    #[serde(rename = "documentType", rename_all = "camelCase")]
34    SingleContractDocumentType {
35        id: Identifier,
36        document_type_name: String,
37    } = 1,
38    // /// this key can only be used within contracts owned by a specified owner
39    // #[serde(rename = "multipleContractsOfSameOwner")]
40    // MultipleContractsOfSameOwner { owner_id: Identifier } = 2,
41}
42
43impl ContractBounds {
44    /// Creates a new contract bounds for the key
45    pub fn new_from_type(
46        contract_bounds_type: u8,
47        identifier: Vec<u8>,
48        document_type: String,
49    ) -> Result<Self, ProtocolError> {
50        Ok(match contract_bounds_type {
51            0 => SingleContract {
52                id: Identifier::from_bytes(identifier.as_slice())?,
53            },
54            1 => SingleContractDocumentType {
55                id: Identifier::from_bytes(identifier.as_slice())?,
56                document_type_name: document_type,
57            },
58            _ => {
59                return Err(ProtocolError::InvalidKeyContractBoundsError(format!(
60                    "unrecognized contract bounds type: {}",
61                    contract_bounds_type
62                )))
63            }
64        })
65    }
66
67    /// Gets the contract bounds type
68    pub fn contract_bounds_type(&self) -> ContractBoundsType {
69        match self {
70            SingleContract { .. } => 0,
71            SingleContractDocumentType { .. } => 1,
72            // MultipleContractsOfSameOwner { .. } => 2,
73        }
74    }
75
76    pub fn contract_bounds_type_from_str(str: &str) -> Result<ContractBoundsType, ProtocolError> {
77        match str {
78            "singleContract" => Ok(0),
79            "documentType" => Ok(1),
80            _ => Err(ProtocolError::DecodingError(String::from(
81                "Expected type to be one of none, singleContract or singleContractDocumentType",
82            ))),
83        }
84    }
85    /// Gets the contract bounds type
86    pub fn contract_bounds_type_string(&self) -> &str {
87        match self {
88            SingleContract { .. } => "singleContract",
89            SingleContractDocumentType { .. } => "documentType",
90            // MultipleContractsOfSameOwner { .. } => "multipleContractsOfSameOwner",
91        }
92    }
93
94    /// Gets the identifier
95    pub fn identifier(&self) -> &Identifier {
96        match self {
97            SingleContract { id } => id,
98            SingleContractDocumentType { id, .. } => id,
99            // MultipleContractsOfSameOwner { owner_id } => owner_id,
100        }
101    }
102
103    /// Gets the document type
104    pub fn document_type(&self) -> Option<&String> {
105        match self {
106            SingleContract { .. } => None,
107            SingleContractDocumentType {
108                document_type_name: document_type,
109                ..
110            } => Some(document_type),
111            // MultipleContractsOfSameOwner { .. } => None,
112        }
113    }
114    //
115    // /// Gets the cbor value
116    // pub fn to_cbor_value(&self) -> CborValue {
117    //     let mut pk_map = CborCanonicalMap::new();
118    //
119    //     let contract_bounds_type = self.contract_bounds_type();
120    //     pk_map.insert("type", self.contract_bounds_type_string());
121    //
122    //     pk_map.insert("identifier", self.identifier().to_buffer_vec());
123    //
124    //     if contract_bounds_type == 1 {
125    //         pk_map.insert("documentType", self.document_type().unwrap().clone());
126    //     }
127    //     pk_map.to_value_sorted()
128    // }
129    //
130    // /// Gets the cbor value
131    // pub fn from_cbor_value(cbor_value: &CborValue) -> Result<Self, ProtocolError> {
132    //     let key_value_map = cbor_value.as_map().ok_or_else(|| {
133    //         ProtocolError::DecodingError(String::from(
134    //             "Expected identity public key to be a key value map",
135    //         ))
136    //     })?;
137    //
138    //     let contract_bounds_type_string =
139    //         key_value_map.as_string("type", "Contract bounds must have a type")?;
140    //     let contract_bounds_type =
141    //         Self::contract_bounds_type_from_str(contract_bounds_type_string.as_str())?;
142    //     let contract_bounds_identifier = if contract_bounds_type > 0 {
143    //         key_value_map.as_vec(
144    //             "identifier",
145    //             "Contract bounds must have an identifier if it is not type 0",
146    //         )?
147    //     } else {
148    //         vec![]
149    //     };
150    //     let contract_bounds_document_type = if contract_bounds_type == 2 {
151    //         key_value_map.as_string(
152    //             "documentType",
153    //             "Contract bounds must have a document type if it is type 2",
154    //         )?
155    //     } else {
156    //         String::new()
157    //     };
158    //     ContractBounds::new_from_type(
159    //         contract_bounds_type,
160    //         contract_bounds_identifier,
161    //         contract_bounds_document_type,
162    //     )
163    // }
164}
165
166#[cfg(all(test, feature = "json-conversion"))]
167mod tests {
168    use super::*;
169    use crate::serialization::JsonConvertible;
170
171    #[test]
172    fn contract_bounds_single_contract_json_round_trip() {
173        let id = Identifier::from([0xABu8; 32]);
174        let bounds = ContractBounds::SingleContract { id };
175
176        let json = bounds.to_json().expect("to_json should succeed");
177        assert!(
178            json["id"].is_string(),
179            "Identifier should be a base58 string, got: {:?}",
180            json["id"]
181        );
182
183        let expected_base58 = id.to_string(platform_value::string_encoding::Encoding::Base58);
184        assert_eq!(json["id"].as_str().unwrap(), expected_base58);
185
186        let restored = ContractBounds::from_json(json).expect("from_json should succeed");
187        assert_eq!(bounds, restored);
188    }
189
190    #[test]
191    fn contract_bounds_document_type_json_round_trip() {
192        let id = Identifier::from([0xCDu8; 32]);
193        let bounds = ContractBounds::SingleContractDocumentType {
194            id,
195            document_type_name: "myDocument".to_string(),
196        };
197
198        let json = bounds.to_json().expect("to_json should succeed");
199        assert!(json["id"].is_string());
200        assert_eq!(json["documentTypeName"].as_str().unwrap(), "myDocument");
201
202        let restored = ContractBounds::from_json(json).expect("from_json should succeed");
203        assert_eq!(bounds, restored);
204    }
205
206    #[test]
207    fn contract_bounds_value_round_trip() {
208        let id = Identifier::from([0x55u8; 32]);
209        let bounds = ContractBounds::SingleContractDocumentType {
210            id,
211            document_type_name: "note".to_string(),
212        };
213
214        let obj = bounds.to_object().expect("to_object should succeed");
215        let restored = ContractBounds::from_object(obj).expect("from_object should succeed");
216        assert_eq!(bounds, restored);
217    }
218}