Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@
"identity",
"contract",
"token",
"permanentDocument"
"permanentDocument",
"identityPublicKey"
]
},
"contractId": {
Expand Down Expand Up @@ -129,25 +130,50 @@
"minLength": 1,
"maxLength": 64,
"pattern": "^[a-zA-Z0-9-_]{1,64}$"
},
"keyIdProperty": {
"description": "The property of the same document type whose value carries the referenced key id; the reference property's value carries the identity id",
"type": "string",
"minLength": 1,
"maxLength": 256,
"pattern": "^[a-zA-Z0-9-_]{1,64}(\\.[a-zA-Z0-9-_]{1,64})*$"
}
},
"required": [
"type"
],
"additionalProperties": false,
"if": {
"properties": { "type": { "const": "permanentDocument" } },
"required": ["type"]
},
"then": {
"required": ["type", "documentType"]
},
"else": {
"properties": {
"contractId": false,
"documentType": false
"allOf": [
{
"if": {
"properties": { "type": { "const": "permanentDocument" } },
"required": ["type"]
},
"then": {
"required": ["type", "documentType"]
},
"else": {
"properties": {
"contractId": false,
"documentType": false
}
}
},
{
"if": {
"properties": { "type": { "const": "identityPublicKey" } },
"required": ["type"]
},
"then": {
"required": ["type", "keyIdProperty"]
},
"else": {
"properties": {
"keyIdProperty": false
}
}
}
}
]
},
"contains": {
"$ref": "https://json-schema.org/draft/2020-12/meta/applicator#/properties/contains"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,22 @@ fn apply_property_reference_v0(
document_type_name: document_type_name.to_string(),
}
}
"identityPublicKey" => {
let key_id_property = refers_to_map
.get_str(property_names::KEY_ID_PROPERTY)
.map_err(|e| DataContractError::ValueWrongType(e.to_string()))?;

if key_id_property.is_empty() || key_id_property.len() > 256 {
return Err(DataContractError::InvalidContractStructure(
"identityPublicKey refersTo keyIdProperty must be between 1 and 256 characters"
.to_string(),
));
}

DocumentPropertyReferenceTarget::IdentityPublicKey {
key_id_property: key_id_property.to_string(),
}
}
other => {
return Err(DataContractError::InvalidContractStructure(format!(
"invalid refersTo type {other}"
Expand Down Expand Up @@ -658,6 +674,73 @@ mod tests {
.expect_err("should fail");
}

#[test]
fn should_parse_identity_public_key_refers_to() {
let document_type = try_document_type_from_schema(json!({
"type": "object",
"properties": {
"toUserId": {
"type": "array",
"byteArray": true,
"minItems": 32,
"maxItems": 32,
"contentMediaType": "application/x.dash.dpp.identifier",
"position": 0,
"refersTo": {
"type": "identityPublicKey",
"keyIdProperty": "toKeyIndex"
}
},
"toKeyIndex": {
"type": "integer",
"position": 1
}
},
"required": [],
"additionalProperties": false
}))
.expect("should parse");

let property_type = document_type
.as_ref()
.flattened_properties()
.get("toUserId")
.map(|p| p.property_type.clone())
.expect("property should be present");

assert_eq!(
property_type,
DocumentPropertyType::IdentifierWithReference(
DocumentPropertyReferenceTarget::IdentityPublicKey {
key_id_property: "toKeyIndex".to_string(),
}
)
);
}

#[test]
fn should_reject_identity_public_key_refers_to_without_key_id_property() {
try_document_type_from_schema(json!({
"type": "object",
"properties": {
"toUserId": {
"type": "array",
"byteArray": true,
"minItems": 32,
"maxItems": 32,
"contentMediaType": "application/x.dash.dpp.identifier",
"position": 0,
"refersTo": {
"type": "identityPublicKey"
}
}
},
"required": [],
"additionalProperties": false
}))
.expect_err("should fail");
}

#[test]
fn should_ignore_refers_to_on_platform_versions_predating_it() {
// Platform versions whose tables carry `apply_property_reference: None`
Expand Down
1 change: 1 addition & 0 deletions packages/rs-dpp/src/data_contract/document_type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub(crate) mod property_names {
pub const REFERS_TO: &str = "refersTo";
pub const CONTRACT_ID: &str = "contractId";
pub const DOCUMENT_TYPE: &str = "documentType";
pub const KEY_ID_PROPERTY: &str = "keyIdProperty";
pub const DOCUMENTS_COUNTABLE: &str = "documentsCountable";
pub const RANGE_COUNTABLE: &str = "rangeCountable";
/// Doctype-level flag naming the property whose values are summed into
Expand Down
14 changes: 14 additions & 0 deletions packages/rs-dpp/src/data_contract/document_type/property/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ pub enum DocumentPropertyReferenceTarget {
contract_id: Option<Identifier>,
document_type_name: String,
},
/// A specific public key of an identity: the property value holds the
/// identity id and the named sibling property of the same document type
/// holds the key id. Identity keys can be disabled but never removed, so
/// an existing reference can never dangle; at write time the key must
/// exist and must not be disabled.
#[serde(rename = "identityPublicKey")]
IdentityPublicKey {
/// The property of the same document type whose value carries the
/// referenced key id
key_id_property: String,
},
}

impl std::fmt::Display for DocumentPropertyReferenceTarget {
Expand All @@ -97,6 +108,9 @@ impl std::fmt::Display for DocumentPropertyReferenceTarget {
f,
"permanent document (own contract, document type {document_type_name})"
),
DocumentPropertyReferenceTarget::IdentityPublicKey { key_id_property } => {
write!(f, "identity public key (key id property {key_id_property})")
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/rs-dpp/src/errors/consensus/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,9 @@ impl ErrorWithCode for StateError {
Self::ReferencedEntityNotFoundError(_) => 40120,
Self::ReferencedDocumentTypeNotFoundError(_) => 40121,
Self::ReferencedDocumentTypeDeletableError(_) => 40122,
Self::ReferencedIdentityKeyNotFoundError(_) => 40123,
Self::ReferencedIdentityKeyDisabledError(_) => 40124,
Self::ReferencedKeyIdPropertyInvalidError(_) => 40125,

// Identity Errors: 40200-40299
Self::IdentityAlreadyExistsError(_) => 40200,
Expand Down
3 changes: 3 additions & 0 deletions packages/rs-dpp/src/errors/consensus/state/document/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ pub mod invalid_document_revision_error;
pub mod referenced_document_type_deletable_error;
pub mod referenced_document_type_not_found_error;
pub mod referenced_entity_not_found_error;
pub mod referenced_identity_key_disabled_error;
pub mod referenced_identity_key_not_found_error;
pub mod referenced_key_id_property_invalid_error;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use crate::consensus::state::state_error::StateError;
use crate::consensus::ConsensusError;
use crate::identity::KeyID;
use crate::ProtocolError;
use bincode::{Decode, Encode};
use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
use platform_value::Identifier;
use thiserror::Error;

#[derive(
Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize,
)]
#[error("referenced public key {key_id} of identity {identity_id} is disabled for path {path}")]
#[platform_serialize(unversioned)]
pub struct ReferencedIdentityKeyDisabledError {
/*

DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION

*/
identity_id: Identifier,
key_id: KeyID,
path: String,
}

impl ReferencedIdentityKeyDisabledError {
pub fn new(identity_id: Identifier, key_id: KeyID, path: String) -> Self {
Self {
identity_id,
key_id,
path,
}
}

pub fn identity_id(&self) -> &Identifier {
&self.identity_id
}

pub fn key_id(&self) -> KeyID {
self.key_id
}

pub fn path(&self) -> &str {
&self.path
}
}

impl From<ReferencedIdentityKeyDisabledError> for ConsensusError {
fn from(err: ReferencedIdentityKeyDisabledError) -> Self {
Self::StateError(StateError::ReferencedIdentityKeyDisabledError(err))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use crate::consensus::state::state_error::StateError;
use crate::consensus::ConsensusError;
use crate::identity::KeyID;
use crate::ProtocolError;
use bincode::{Decode, Encode};
use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
use platform_value::Identifier;
use thiserror::Error;

#[derive(
Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize,
)]
#[error("referenced public key {key_id} of identity {identity_id} not found for path {path}")]
#[platform_serialize(unversioned)]
pub struct ReferencedIdentityKeyNotFoundError {
/*

DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION

*/
identity_id: Identifier,
key_id: KeyID,
path: String,
}

impl ReferencedIdentityKeyNotFoundError {
pub fn new(identity_id: Identifier, key_id: KeyID, path: String) -> Self {
Self {
identity_id,
key_id,
path,
}
}

pub fn identity_id(&self) -> &Identifier {
&self.identity_id
}

pub fn key_id(&self) -> KeyID {
self.key_id
}

pub fn path(&self) -> &str {
&self.path
}
}

impl From<ReferencedIdentityKeyNotFoundError> for ConsensusError {
fn from(err: ReferencedIdentityKeyNotFoundError) -> Self {
Self::StateError(StateError::ReferencedIdentityKeyNotFoundError(err))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use crate::consensus::state::state_error::StateError;
use crate::consensus::ConsensusError;
use crate::ProtocolError;
use bincode::{Decode, Encode};
use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
use thiserror::Error;

#[derive(
Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize,
)]
#[error("keyIdProperty {key_id_property} referenced at path {path} is invalid: {message}")]
#[platform_serialize(unversioned)]
pub struct ReferencedKeyIdPropertyInvalidError {
/*

DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION

*/
key_id_property: String,
path: String,
message: String,
}

impl ReferencedKeyIdPropertyInvalidError {
pub fn new(key_id_property: String, path: String, message: String) -> Self {
Self {
key_id_property,
path,
message,
}
}

pub fn key_id_property(&self) -> &str {
&self.key_id_property
}

pub fn path(&self) -> &str {
&self.path
}

pub fn message(&self) -> &str {
&self.message
}
}

impl From<ReferencedKeyIdPropertyInvalidError> for ConsensusError {
fn from(err: ReferencedKeyIdPropertyInvalidError) -> Self {
Self::StateError(StateError::ReferencedKeyIdPropertyInvalidError(err))
}
}
Loading
Loading