diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs index adf85180244..e4f403bb0d4 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs @@ -724,7 +724,6 @@ impl DocumentTypeV1 { #[cfg(test)] mod tests { use super::*; - use crate::data_contract::document_type::DocumentTypeV0; use assert_matches::assert_matches; use platform_value::platform_value; @@ -1006,13 +1005,14 @@ mod tests { let config = DataContractConfig::default_for_version(platform_version) .expect("should create a default config"); - let result = DocumentTypeV0::try_from_schema( + let result = DocumentTypeV1::try_from_schema( Identifier::new([1; 32]), 1, config.version(), "invalid name", schema.clone(), None, + &BTreeMap::new(), &config, true, &mut vec![], diff --git a/packages/rs-dpp/src/data_contract/document_type/schema/recursive_schema_validator/mod.rs b/packages/rs-dpp/src/data_contract/document_type/schema/recursive_schema_validator/mod.rs index c6a99ed8b2c..02c1a2f1a0e 100644 --- a/packages/rs-dpp/src/data_contract/document_type/schema/recursive_schema_validator/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/schema/recursive_schema_validator/mod.rs @@ -4,10 +4,11 @@ pub use traversal_validator::*; #[cfg(test)] mod test { use super::*; + use crate::consensus::basic::data_contract::IncompatibleRe2PatternError; + use crate::consensus::basic::json_schema_compilation_error::JsonSchemaCompilationError; use crate::consensus::basic::BasicError; use crate::consensus::codes::ErrorWithCode; use crate::consensus::ConsensusError; - use assert_matches::assert_matches; use platform_value::{platform_value, Value}; use platform_version::version::PlatformVersion; @@ -17,9 +18,100 @@ mod test { .try_init(); } - #[ignore] + // ---------------------------------------------------------------- + // Test-only sub-validator callbacks. + // + // IMPORTANT: The helpers below are NOT production validators and + // are NOT wired into document-type validation. Production + // document-type schema validation does not use `traversal_validator` + // at all — it relies on `validate_max_depth`, JSON Schema meta-schema + // validation, and `json_schema_validator.compile()`. + // + // These callbacks exist solely as fixtures to exercise the + // `traversal_validator` mechanism: they prove that traversal walks + // every map/array node, invokes each provided sub-validator with the + // expected `(path, key, parent, value)` arguments, and that any + // `ConsensusError`s a callback adds (or `ProtocolError`s it returns) + // are propagated correctly. They happen to be shaped after legacy + // validators (byteArray-with-items, regex compatibility) only because + // those produce well-known consensus error variants that are + // convenient to assert on; do not read the assertions below as + // coverage of the corresponding production validation rules. + // ---------------------------------------------------------------- + + /// Test callback fixture: when traversal visits a map that contains + /// `"byteArray"` alongside `"items"` or `"prefixItems"`, emit a + /// `JsonSchemaCompilationError`. Used purely to verify the traversal + /// mechanism propagates consensus errors added by callbacks; this is + /// not the production byteArray-vs-items rule. + fn test_callback_flag_byte_array_with_items( + path: &str, + key: &str, + parent: &Value, + _value: &Value, + result: &mut crate::validation::SimpleConsensusValidationResult, + _platform_version: &PlatformVersion, + ) -> Result<(), crate::ProtocolError> { + if key != "byteArray" { + return Ok(()); + } + let Value::Map(parent_map) = parent else { + return Ok(()); + }; + let has_items = parent_map.iter().any( + |(k, _)| matches!(k.to_str(), Ok(name) if name == "items" || name == "prefixItems"), + ); + if has_items { + let message = format!( + "invalid path: '{}': byteArray cannot be used with `items` or `prefixItems`", + path + ); + result.add_error(ConsensusError::BasicError( + BasicError::JsonSchemaCompilationError(JsonSchemaCompilationError::new(message)), + )); + } + Ok(()) + } + + /// Test callback fixture: when traversal visits a `"pattern"` string + /// that fails to compile under Rust's `regex` crate (which, like + /// Re2, rejects lookarounds), emit an `IncompatibleRe2PatternError`. + /// Used purely to verify the traversal mechanism propagates + /// consensus errors added by callbacks; this is not the production + /// pattern-validation rule. + fn test_callback_flag_invalid_regex_pattern( + path: &str, + key: &str, + _parent: &Value, + value: &Value, + result: &mut crate::validation::SimpleConsensusValidationResult, + _platform_version: &PlatformVersion, + ) -> Result<(), crate::ProtocolError> { + if key != "pattern" { + return Ok(()); + } + let Value::Text(pattern) = value else { + return Ok(()); + }; + if let Err(err) = regex::Regex::new(pattern) { + result.add_error(ConsensusError::BasicError( + BasicError::IncompatibleRe2PatternError(IncompatibleRe2PatternError::new( + pattern.clone(), + path.to_string(), + err.to_string(), + )), + )); + } + Ok(()) + } + #[test] - fn should_return_error_if_bytes_array_parent_contains_items_or_prefix_items() { + fn traversal_propagates_byte_array_callback_errors_for_each_offending_parent() { + // Verifies traversal-mechanism behavior only: that the test + // callback is invoked at every map node and that each + // ConsensusError it adds reaches the result. Does not assert + // anything about production document-type byteArray validation, + // which does not run through traversal_validator. let schema: Value = platform_value!( { "type": "object", @@ -36,25 +128,31 @@ mod test { "additionalProperties": false, } ); - let mut result = traversal_validator(&schema, &[], PlatformVersion::first()) - .expect("expected traversal validator to succeed"); + let mut result = traversal_validator( + &schema, + &[test_callback_flag_byte_array_with_items], + PlatformVersion::first(), + ) + .expect("expected traversal validator to succeed"); assert_eq!(2, result.errors.len()); let first_error = get_basic_error(result.errors.pop().unwrap()); let second_error = get_basic_error(result.errors.pop().unwrap()); - assert_matches!( + assert!(matches!( first_error, - BasicError::JsonSchemaCompilationError(msg) if msg.compilation_error().starts_with("invalid path: '/properties/bar': byteArray cannot") - ); - assert_matches!( + BasicError::JsonSchemaCompilationError(msg) if msg.compilation_error().starts_with("invalid path: '/properties/bar': byteArray cannot"), + )); + assert!(matches!( second_error, - BasicError::JsonSchemaCompilationError(msg) if msg.compilation_error().starts_with("invalid path: '/properties': byteArray cannot") - ); + BasicError::JsonSchemaCompilationError(msg) if msg.compilation_error().starts_with("invalid path: '/properties': byteArray cannot"), + )); } - #[ignore] #[test] - fn should_return_valid_result() { + fn traversal_pattern_callback_adds_no_errors_for_compilable_regex() { + // Traversal-mechanism check only: verifies the test callback + // returns no errors when every visited "pattern" compiles. + // Not a production pattern-validation assertion. let schema: Value = platform_value!( { "type": "object", @@ -69,47 +167,55 @@ mod test { "additionalProperties": false, } ); - assert!(traversal_validator(&schema, &[], PlatformVersion::first()) - .expect("expected traversal validator to succeed") - .is_valid()); + assert!(traversal_validator( + &schema, + &[test_callback_flag_invalid_regex_pattern], + PlatformVersion::first() + ) + .expect("expected traversal validator to succeed") + .is_valid()); } - #[ignore] #[test] - fn should_return_invalid_result() { + fn traversal_propagates_pattern_callback_error_for_top_level_uncompilable_regex() { + // Traversal-mechanism check only: confirms the IncompatibleRe2 + // ConsensusError that the test callback emits propagates with + // the correct path/pattern. Production document-type schema + // validation does not invoke traversal_validator. + let unsafe_pattern = "^((?!-|_)[a-zA-Z0-9-_]{0,62}[a-zA-Z0-9])$"; let schema: Value = platform_value!({ "type": "object", "properties": { "foo": { "type": "integer" }, "bar": { "type": "string", - "pattern": "^((?!-|_)[a-zA-Z0-9-_]{0,62}[a-zA-Z0-9])$", + "pattern": unsafe_pattern, }, }, "required": ["foo"], "additionalProperties": false, }); - let result = traversal_validator(&schema, &[], PlatformVersion::first()) - .expect("expected traversal validator to succeed"); + let result = traversal_validator( + &schema, + &[test_callback_flag_invalid_regex_pattern], + PlatformVersion::first(), + ) + .expect("expected traversal validator to succeed"); let consensus_error = result.errors.first().expect("the error should be returned"); match consensus_error { ConsensusError::BasicError(BasicError::IncompatibleRe2PatternError(err)) => { assert_eq!(err.path(), "/properties/bar".to_string()); - assert_eq!( - err.pattern(), - "^((?!-|_)[a-zA-Z0-9-_]{0,62}[a-zA-Z0-9])$".to_string() - ); + assert_eq!(err.pattern(), unsafe_pattern.to_string()); assert_eq!(consensus_error.code(), 10202); } _ => panic!("Expected error to be IncompatibleRe2PatternError"), } } - #[ignore] #[test] - fn should_be_valid_complex_for_complex_schema() { + fn traversal_with_no_validators_visits_complex_schema_without_errors() { let schema = get_document_schema(); assert!(traversal_validator(&schema, &[], PlatformVersion::first()) @@ -117,15 +223,23 @@ mod test { .is_valid()) } - #[ignore] #[test] - fn invalid_result_for_array_of_object() { + fn traversal_propagates_pattern_callback_error_inside_array_items_object() { + // Traversal-mechanism check only: confirms traversal descends + // into `items` of an array-of-object schema and that the test + // callback's ConsensusError carries the expected path. + // Not a production pattern-validation assertion. + let unsafe_pattern = "^((?!-|_)[a-zA-Z0-9-_]{0,62}[a-zA-Z0-9])$"; let mut schema = get_document_schema(); schema["properties"]["arrayOfObject"]["items"]["properties"]["simple"]["pattern"] = - platform_value!("^((?!-|_)[a-zA-Z0-9-_]{0,62}[a-zA-Z0-9])$"); - - let result = traversal_validator(&schema, &[], PlatformVersion::first()) - .expect("expected traversal validator to exist for first protocol version"); + platform_value!(unsafe_pattern); + + let result = traversal_validator( + &schema, + &[test_callback_flag_invalid_regex_pattern], + PlatformVersion::first(), + ) + .expect("expected traversal validator to exist for first protocol version"); let consensus_error = result.errors.first().expect("the error should be returned"); match consensus_error { @@ -134,25 +248,30 @@ mod test { err.path(), "/properties/arrayOfObject/items/properties/simple".to_string() ); - assert_eq!( - err.pattern(), - "^((?!-|_)[a-zA-Z0-9-_]{0,62}[a-zA-Z0-9])$".to_string() - ); + assert_eq!(err.pattern(), unsafe_pattern.to_string()); assert_eq!(consensus_error.code(), 10202); } _ => panic!("Expected error to be IncompatibleRe2PatternError"), } } - #[ignore] #[test] - fn invalid_result_for_array_of_objects() { + fn traversal_propagates_pattern_callback_error_inside_array_items_tuple() { + // Traversal-mechanism check only: confirms traversal descends + // into the indexed entries of a tuple-form `items` array and + // that the path passed to the test callback includes the + // numeric index. Not a production pattern-validation assertion. + let unsafe_pattern = "^((?!-|_)[a-zA-Z0-9-_]{0,62}[a-zA-Z0-9])$"; let mut schema = get_document_schema(); schema["properties"]["arrayOfObjects"]["items"][0]["properties"]["simple"]["pattern"] = - platform_value!("^((?!-|_)[a-zA-Z0-9-_]{0,62}[a-zA-Z0-9])$"); - - let result = traversal_validator(&schema, &[], PlatformVersion::first()) - .expect("expected traversal validator to exist for first protocol version"); + platform_value!(unsafe_pattern); + + let result = traversal_validator( + &schema, + &[test_callback_flag_invalid_regex_pattern], + PlatformVersion::first(), + ) + .expect("expected traversal validator to exist for first protocol version"); let consensus_error = result.errors.first().expect("the error should be returned"); match consensus_error { @@ -161,16 +280,20 @@ mod test { err.path(), "/properties/arrayOfObjects/items/[0]/properties/simple".to_string() ); - assert_eq!( - err.pattern(), - "^((?!-|_)[a-zA-Z0-9-_]{0,62}[a-zA-Z0-9])$".to_string() - ); + assert_eq!(err.pattern(), unsafe_pattern.to_string()); assert_eq!(consensus_error.code(), 10202); } _ => panic!("Expected error to be IncompatibleRe2PatternError"), } } + fn get_basic_error(error: ConsensusError) -> BasicError { + if let ConsensusError::BasicError(err) = error { + return err; + } + panic!("the error: {:?} isn't a BasicError", error) + } + fn get_document_schema() -> Value { platform_value!({ "properties": { @@ -251,13 +374,6 @@ mod test { }) } - fn get_basic_error(error: ConsensusError) -> BasicError { - if let ConsensusError::BasicError(err) = error { - return err; - } - panic!("the error: {:?} isn't a BasicError", error) - } - // ================================================================ // New tests that actually run (not #[ignore]'d) to cover the // traversal_validator_v0 traversal logic. diff --git a/packages/rs-dpp/src/data_contract/v0/data_contract.rs b/packages/rs-dpp/src/data_contract/v0/data_contract.rs index d77c22d0bc8..7e990434be9 100644 --- a/packages/rs-dpp/src/data_contract/v0/data_contract.rs +++ b/packages/rs-dpp/src/data_contract/v0/data_contract.rs @@ -46,255 +46,270 @@ pub struct DataContractV0 { #[cfg(test)] mod test { + use crate::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters}; + use crate::data_contract::conversion::json::DataContractJsonConversionMethodsV0; + use crate::data_contract::conversion::value::v0::DataContractValueConversionMethodsV0; + use crate::data_contract::serialized_version::property_names; + use crate::data_contract::v0::DataContractV0; + use crate::data_contract::DataContract; + use crate::serialization::{ + PlatformDeserializableWithPotentialValidationFromVersionedStructure, + PlatformSerializableWithPlatformVersion, + }; + use crate::tests::fixtures::get_data_contract_fixture; + use crate::version::PlatformVersion; fn init() { let _ = env_logger::builder() .filter_level(log::LevelFilter::Debug) .try_init(); } - // - // #[test] - // #[cfg(feature = "cbor")] - // fn conversion_to_cbor_buffer_from_cbor_buffer() { - // init(); - // let data_contract = data_contract_fixture(None).data_contract; - // - // let data_contract_bytes = data_contract - // .to_cbor_buffer() - // .expect("data contract should be converted into the bytes"); - // let data_contract_restored = DataContractV0::from_cbor_buffer(data_contract_bytes) - // .expect("data contract should be created from bytes"); - // - // assert_eq!( - // data_contract.data_contract_protocol_version, - // data_contract_restored.data_contract_protocol_version - // ); - // assert_eq!(data_contract.schema, data_contract_restored.schema); - // assert_eq!(data_contract.version, data_contract_restored.version); - // assert_eq!(data_contract.id(), data_contract_restored.id); - // assert_eq!(data_contract.owner_id(), data_contract_restored.owner_id); - // assert_eq!( - // data_contract.binary_properties, - // data_contract_restored.binary_properties - // ); - // assert_eq!(data_contract.documents, data_contract_restored.documents); - // assert_eq!( - // data_contract.document_types, - // data_contract_restored.document_types - // ); - // } - // - // #[test] - // #[cfg(feature = "cbor")] - // fn conversion_to_cbor_buffer_from_cbor_buffer_high_version() { - // init(); - // let mut data_contract = get_data_contract_fixture(None).data_contract; - // data_contract.data_contract_protocol_version = 10000; - // - // let data_contract_bytes = data_contract - // .to_cbor_buffer() - // .expect("data contract should be converted into the bytes"); - // - // let data_contract_restored = DataContractV0::from_cbor_buffer(data_contract_bytes) - // .expect("data contract should be created from bytes"); - // - // assert_eq!( - // data_contract.data_contract_protocol_version, - // data_contract_restored.data_contract_protocol_version - // ); - // assert_eq!(data_contract.schema, data_contract_restored.schema); - // assert_eq!(data_contract.version, data_contract_restored.version); - // assert_eq!(data_contract.id(), data_contract_restored.id); - // assert_eq!(data_contract.owner_id(), data_contract_restored.owner_id); - // assert_eq!( - // data_contract.binary_properties, - // data_contract_restored.binary_properties - // ); - // assert_eq!(data_contract.documents, data_contract_restored.documents); - // assert_eq!( - // data_contract.document_types, - // data_contract_restored.document_types - // ); - // } - // - // #[test] - // fn conversion_to_cbor_buffer_from_cbor_buffer_too_high_version() { - // init(); - // let data_contract = get_data_contract_fixture(None).data_contract; - // - // let data_contract_bytes = data_contract - // .to_cbor_buffer() - // .expect("data contract should be converted into the bytes"); - // - // let mut high_protocol_version_bytes = u64::MAX.encode_var_vec(); - // - // let (_, offset) = u32::decode_var(&data_contract_bytes) - // .ok_or(ProtocolError::DecodingError( - // "contract cbor could not decode protocol version".to_string(), - // )) - // .expect("expected to decode protocol version"); - // let (_, contract_cbor_bytes) = data_contract_bytes.split_at(offset); - // - // high_protocol_version_bytes.extend_from_slice(contract_cbor_bytes); - // - // let data_contract_restored = DataContractV0::from_cbor_buffer(&high_protocol_version_bytes) - // .expect("data contract should be created from bytes"); - // - // assert_eq!( - // u32::MAX, - // data_contract_restored.data_contract_protocol_version - // ); - // assert_eq!(data_contract.schema, data_contract_restored.schema); - // assert_eq!(data_contract.version, data_contract_restored.version); - // assert_eq!(data_contract.id(), data_contract_restored.id); - // assert_eq!(data_contract.owner_id(), data_contract_restored.owner_id); - // assert_eq!( - // data_contract.binary_properties, - // data_contract_restored.binary_properties - // ); - // assert_eq!(data_contract.documents, data_contract_restored.documents); - // assert_eq!( - // data_contract.document_types, - // data_contract_restored.document_types - // ); - // } - // - // #[test] - // fn conversion_from_json() -> Result<()> { - // init(); - // - // let string_contract = get_data_from_file("src/tests/payloads/contract_example.json")?; - // let contract = DataContractV0::try_from(string_contract.as_str())?; - // assert_eq!(contract.data_contract_protocol_version, 0); - // assert_eq!( - // contract.schema, - // "https://schema.dash.org/dpp-0-4-0/meta/data-contract" - // ); - // assert_eq!(contract.version, 5); - // assert_eq!( - // contract.id.to_string(Encoding::Base58), - // "AoDzJxWSb1gUi2dSmvFeUFpSsjZQRJaqCpn7vCLkwwJj" - // ); - // assert_eq!( - // contract.documents["note"]["properties"]["message"]["type"], - // "string" - // ); - // assert!(contract.is_document_defined("note")); - // - // Ok(()) - // } - // - // #[test] - // fn conversion_to_json() -> Result<()> { - // init(); - // - // let mut string_contract = get_data_from_file("src/tests/payloads/contract_example.json")?; - // string_contract.retain(|c| !c.is_whitespace()); - // - // let contract = DataContractV0::try_from(string_contract.as_str())?; - // let serialized_contract = serde_json::to_string(&contract.to_json()?)?; - // - // // they will be out of order so won't be exactly the same - // assert_eq!(serialized_contract, string_contract); - // Ok(()) - // } - // - // #[test] - // fn conversion_to_object() -> Result<()> { - // let string_contract = get_data_from_file("src/tests/payloads/contract_example.json")?; - // let data_contract: DataContractV0 = serde_json::from_str(&string_contract)?; - // - // let raw_data_contract = data_contract.to_json_object()?; - // for path in DATA_CONTRACT_IDENTIFIER_FIELDS_V0 { - // assert!(raw_data_contract - // .get(path) - // .expect("the path should exist") - // .is_array()) - // } - // Ok(()) - // } - // - // #[test] - // fn conversion_from_object() -> Result<()> { - // init(); - // - // let string_contract = get_data_from_file("src/tests/payloads/contract_example.json")?; - // let raw_contract: JsonValue = serde_json::from_str(&string_contract)?; - // - // for path in DATA_CONTRACT_IDENTIFIER_FIELDS_V0 { - // raw_contract.get(path).expect("the path should exist"); - // } - // - // let data_contract_from_raw = DataContractV0::try_from(raw_contract)?; - // assert_eq!(data_contract_from_raw.data_contract_protocol_version, 0); - // assert_eq!( - // data_contract_from_raw.schema, - // "https://schema.dash.org/dpp-0-4-0/meta/data-contract" - // ); - // assert_eq!(data_contract_from_raw.version, 5); - // assert_eq!( - // data_contract_from_raw.id.to_string(Encoding::Base58), - // "AoDzJxWSb1gUi2dSmvFeUFpSsjZQRJaqCpn7vCLkwwJj" - // ); - // assert_eq!( - // data_contract_from_raw.documents["note"]["properties"]["message"]["type"], - // "string" - // ); - // - // Ok(()) - // } - // - // fn get_data_contract_cbor_bytes() -> Vec { - // let data_contract_cbor_hex = "01a56324696458208efef7338c0d34b2e408411b9473d724cbf9b675ca72b3126f7f8e7deb42ae516724736368656d61783468747470733a2f2f736368656d612e646173682e6f72672f6470702d302d342d302f6d6574612f646174612d636f6e7472616374676f776e657249645820962088aa3812bb3386d0c9130edbde51e4be17bb2d10031d4147c8597facee256776657273696f6e0169646f63756d656e7473a76b756e697175654461746573a56474797065666f626a65637467696e646963657382a3646e616d6566696e6465783166756e69717565f56a70726f7065727469657382a16a2463726561746564417463617363a16a2475706461746564417463617363a2646e616d6566696e646578326a70726f7065727469657381a16a2475706461746564417463617363687265717569726564836966697273744e616d656a246372656174656441746a247570646174656441746a70726f70657274696573a2686c6173744e616d65a1647479706566737472696e676966697273744e616d65a1647479706566737472696e67746164646974696f6e616c50726f70657274696573f46c6e696365446f63756d656e74a46474797065666f626a656374687265717569726564816a246372656174656441746a70726f70657274696573a1646e616d65a1647479706566737472696e67746164646974696f6e616c50726f70657274696573f46e6e6f54696d65446f63756d656e74a36474797065666f626a6563746a70726f70657274696573a1646e616d65a1647479706566737472696e67746164646974696f6e616c50726f70657274696573f46e707265747479446f63756d656e74a46474797065666f626a65637468726571756972656482686c6173744e616d656a247570646174656441746a70726f70657274696573a1686c6173744e616d65a1647479706566737472696e67746164646974696f6e616c50726f70657274696573f46e7769746842797465417272617973a56474797065666f626a65637467696e646963657381a2646e616d6566696e646578316a70726f7065727469657381a16e6279746541727261794669656c6463617363687265717569726564816e6279746541727261794669656c646a70726f70657274696573a26e6279746541727261794669656c64a36474797065656172726179686d61784974656d731069627974654172726179f56f6964656e7469666965724669656c64a56474797065656172726179686d61784974656d731820686d696e4974656d73182069627974654172726179f570636f6e74656e744d656469615479706578216170706c69636174696f6e2f782e646173682e6470702e6964656e746966696572746164646974696f6e616c50726f70657274696573f46f696e6465786564446f63756d656e74a56474797065666f626a65637467696e646963657386a3646e616d6566696e6465783166756e69717565f56a70726f7065727469657382a168246f776e6572496463617363a16966697273744e616d656464657363a3646e616d6566696e6465783266756e69717565f56a70726f7065727469657382a168246f776e6572496463617363a1686c6173744e616d656464657363a2646e616d6566696e646578336a70726f7065727469657381a1686c6173744e616d6563617363a2646e616d6566696e646578346a70726f7065727469657382a16a2463726561746564417463617363a16a2475706461746564417463617363a2646e616d6566696e646578356a70726f7065727469657381a16a2475706461746564417463617363a2646e616d6566696e646578366a70726f7065727469657381a16a2463726561746564417463617363687265717569726564846966697273744e616d656a246372656174656441746a24757064617465644174686c6173744e616d656a70726f70657274696573a2686c6173744e616d65a2647479706566737472696e67696d61784c656e677468183f6966697273744e616d65a2647479706566737472696e67696d61784c656e677468183f746164646974696f6e616c50726f70657274696573f4781d6f7074696f6e616c556e69717565496e6465786564446f63756d656e74a56474797065666f626a65637467696e646963657383a3646e616d6566696e6465783166756e69717565f56a70726f7065727469657381a16966697273744e616d656464657363a3646e616d6566696e6465783266756e69717565f56a70726f7065727469657383a168246f776e6572496463617363a16966697273744e616d6563617363a1686c6173744e616d6563617363a3646e616d6566696e6465783366756e69717565f56a70726f7065727469657382a167636f756e74727963617363a1646369747963617363687265717569726564826966697273744e616d65686c6173744e616d656a70726f70657274696573a46463697479a2647479706566737472696e67696d61784c656e677468183f67636f756e747279a2647479706566737472696e67696d61784c656e677468183f686c6173744e616d65a2647479706566737472696e67696d61784c656e677468183f6966697273744e616d65a2647479706566737472696e67696d61784c656e677468183f746164646974696f6e616c50726f70657274696573f4"; - // hex::decode(data_contract_cbor_hex).unwrap() - // } - // - // #[test] - // fn deserialize_dpp_cbor() { - // let data_contract_cbor = get_data_contract_cbor_bytes(); - // - // let data_contract = DataContractV0::from_cbor_buffer(data_contract_cbor).unwrap(); - // - // assert_eq!(data_contract.version, 1); - // assert_eq!(data_contract.data_contract_protocol_version, 1); - // assert_eq!( - // data_contract.schema, - // "https://schema.dash.org/dpp-0-4-0/meta/data-contract" - // ); - // assert_eq!( - // data_contract.owner_id(), - // Identifier::new([ - // 150, 32, 136, 170, 56, 18, 187, 51, 134, 208, 201, 19, 14, 219, 222, 81, 228, 190, - // 23, 187, 45, 16, 3, 29, 65, 71, 200, 89, 127, 172, 238, 37 - // ]) - // ); - // assert_eq!( - // data_contract.id(), - // Identifier::new([ - // 142, 254, 247, 51, 140, 13, 52, 178, 228, 8, 65, 27, 148, 115, 215, 36, 203, 249, - // 182, 117, 202, 114, 179, 18, 111, 127, 142, 125, 235, 66, 174, 81 - // ]) - // ); - // } - // - // #[test] - // fn serialize_deterministically_serialize_to_cbor() { - // let data_contract_cbor = get_data_contract_cbor_bytes(); - // - // let data_contract = DataContractV0::from_cbor_buffer(&data_contract_cbor).unwrap(); - // - // let serialized = data_contract.to_cbor_buffer().unwrap(); - // - // assert_eq!(hex::encode(data_contract_cbor), hex::encode(serialized)); - // } - // #[test] - // fn serialize_deterministically_serialize_to_bincode() { - // let data_contract_cbor = get_data_contract_cbor_bytes(); - // - // let data_contract = DataContract::from_cbor_buffer(&data_contract_cbor).unwrap(); - // - // let serialized = data_contract.to_cbor_buffer().unwrap(); - // - // assert_eq!(hex::encode(data_contract_cbor), hex::encode(serialized)); - // } + + fn get_data_contract_v0(platform_version: &PlatformVersion) -> DataContractV0 { + get_data_contract_fixture(None, 0, platform_version.protocol_version) + .data_contract_owned() + .into_v0() + .expect("expected V0 data contract for first platform version") + } + + #[test] + fn conversion_to_cbor_buffer_from_cbor_buffer() { + init(); + let platform_version = PlatformVersion::first(); + let data_contract_v0 = get_data_contract_v0(platform_version); + let data_contract: DataContract = data_contract_v0.clone().into(); + + let serialized = data_contract + .serialize_to_bytes_with_platform_version(platform_version) + .expect("data contract should be serialized"); + let restored = DataContract::versioned_deserialize(&serialized, true, platform_version) + .expect("data contract should be deserialized"); + + assert_eq!(data_contract, restored); + } + + #[test] + fn conversion_to_cbor_buffer_from_cbor_buffer_high_version() { + init(); + let platform_version = PlatformVersion::first(); + let mut data_contract_v0 = get_data_contract_v0(platform_version); + data_contract_v0.set_version(10_000); + let data_contract: DataContract = data_contract_v0.clone().into(); + + let serialized = data_contract + .serialize_to_bytes_with_platform_version(platform_version) + .expect("data contract should be serialized"); + let restored = DataContract::versioned_deserialize(&serialized, true, platform_version) + .expect("data contract should be deserialized") + .into_v0() + .expect("expected v0 data contract"); + + assert_eq!(data_contract_v0.version(), restored.version()); + assert_eq!(data_contract_v0.id(), restored.id()); + assert_eq!(data_contract_v0.owner_id(), restored.owner_id()); + } + + #[test] + fn conversion_to_cbor_buffer_from_cbor_buffer_too_high_version() { + init(); + let platform_version = PlatformVersion::first(); + let data_contract: DataContract = get_data_contract_v0(platform_version).into(); + let serialized = data_contract + .serialize_to_bytes_with_platform_version(platform_version) + .expect("data contract should be serialized"); + + // The serialized form is a `bincode::standard()`-encoded + // `DataContractInSerializationFormat` enum; its leading byte is the + // single-byte variant tag (0 = V0). Replace that tag with a + // *well-formed* bincode varint encoding of u32::MAX — the marker + // byte 0xFC (252) means "u32 follows", and four 0xFF bytes encode + // u32::MAX. The body of the original message is preserved. + // + // The resulting prefix is structurally valid (parses cleanly as a + // varint) but represents a version that no DataContract variant + // exists for, so deserialization must fail with an unknown-version + // error rather than treating the body as corrupted. + let mut too_high_version_bytes: Vec = vec![0xFC, 0xFF, 0xFF, 0xFF, 0xFF]; + too_high_version_bytes.extend_from_slice(&serialized[1..]); + + assert!( + DataContract::versioned_deserialize(&too_high_version_bytes, true, platform_version) + .is_err(), + "well-formed but oversized version prefix should fail deserialization" + ); + } + + /// Fixed external JSON payload for V0 data contract conversion tests. + /// + /// This fixture is the *external reference shape* the on-the-wire JSON + /// representation of a `DataContractV0` is expected to match. It is + /// intentionally hand-written and committed so that the round-trip tests + /// below cannot drift together with the implementation under test. + const FIXED_DATA_CONTRACT_V0_JSON: &str = + include_str!("../../tests/payloads/data_contract_v0.json"); + + const FIXED_CONTRACT_ID_BASE58: &str = "BmKTJeLL3GfH8FxEx7SUbTog4eAKj8vJRDi97gYkxB9p"; + const FIXED_CONTRACT_OWNER_ID_BASE58: &str = "HtQNfXBZJu3WnvjvCFJKgbvfgWYJxWxaFWy23TKoFjg9"; + + #[test] + fn conversion_from_json() { + init(); + let platform_version = PlatformVersion::first(); + + let fixture: serde_json::Value = serde_json::from_str(FIXED_DATA_CONTRACT_V0_JSON) + .expect("fixture should be valid JSON"); + + // Parse the fixed external payload and assert it deserializes to the + // exact identifiers/version/document types it encodes — this would + // catch silent drift in identifier decoding, version handling, or + // document-schema extraction even if `to_json` changes in lockstep. + let restored = DataContractV0::from_json(fixture, false, platform_version) + .expect("fixed fixture should convert json to contract"); + + assert_eq!( + FIXED_CONTRACT_ID_BASE58, + restored + .id() + .to_string(platform_value::string_encoding::Encoding::Base58), + ); + assert_eq!( + FIXED_CONTRACT_OWNER_ID_BASE58, + restored + .owner_id() + .to_string(platform_value::string_encoding::Encoding::Base58), + ); + assert_eq!(1, restored.version()); + assert_eq!( + 1, + restored.document_types().len(), + "fixture defines exactly one document type", + ); + assert!( + restored.document_types().contains_key("note"), + "fixture's `note` document type must round-trip through from_json", + ); + } + + #[test] + fn conversion_to_json() { + init(); + let platform_version = PlatformVersion::first(); + + let fixture: serde_json::Value = serde_json::from_str(FIXED_DATA_CONTRACT_V0_JSON) + .expect("fixture should be valid JSON"); + + // Build the contract from the fixed fixture so the *output* of + // `to_json` is being compared against an externally committed shape, + // not against data produced by the same conversion path under test. + let contract = DataContractV0::from_json(fixture.clone(), false, platform_version) + .expect("fixed fixture should convert json to contract"); + + let produced = contract + .to_json(platform_version) + .expect("should convert contract back to json"); + + assert!(produced.is_object(), "top-level JSON should be an object"); + + // Compare on consensus-relevant top-level fields. We deliberately do + // not raw-string-match: this asserts structural compatibility with + // the fixed external payload regardless of key ordering or whitespace. + for field in &["$formatVersion", "id", "ownerId", "version"] { + assert_eq!( + fixture.get(field), + produced.get(field), + "produced JSON must agree with fixed external payload on `{field}`", + ); + } + + // `documentSchemas` is the user-visible payload of a contract — it + // must round-trip byte-for-byte from the fixture through to_json. + assert_eq!( + fixture.get("documentSchemas"), + produced.get("documentSchemas"), + "documentSchemas must round-trip identically against fixed payload", + ); + } + + #[test] + fn conversion_to_object() { + let platform_version = PlatformVersion::first(); + let data_contract = get_data_contract_v0(platform_version); + + let validating_json = data_contract + .to_validating_json(platform_version) + .expect("should convert to validating json"); + for path in [property_names::ID, property_names::OWNER_ID] { + assert!(validating_json + .get(path) + .expect("the path should exist") + .is_array()); + } + } + + #[test] + fn conversion_from_object() { + init(); + let platform_version = PlatformVersion::first(); + let data_contract = get_data_contract_v0(platform_version); + + let raw_contract = data_contract + .to_value(platform_version) + .expect("contract should convert to value"); + let restored = DataContractV0::from_value(raw_contract, true, platform_version) + .expect("contract should be restored from value"); + + assert_eq!(data_contract.id(), restored.id()); + assert_eq!(data_contract.owner_id(), restored.owner_id()); + assert_eq!(data_contract.version(), restored.version()); + } + + #[test] + fn deserialize_dpp_cbor() { + let platform_version = PlatformVersion::first(); + let data_contract_v0 = get_data_contract_v0(platform_version); + let data_contract: DataContract = data_contract_v0.clone().into(); + + let serialized = data_contract + .serialize_to_bytes_with_platform_version(platform_version) + .expect("data contract should be serialized"); + let restored = DataContract::versioned_deserialize(&serialized, true, platform_version) + .expect("data contract should be deserialized") + .into_v0() + .expect("expected v0 data contract"); + + assert_eq!(data_contract_v0.version(), restored.version()); + assert_eq!(data_contract_v0.id(), restored.id()); + assert_eq!(data_contract_v0.owner_id(), restored.owner_id()); + } + + #[test] + fn serialize_deterministically_serialize_to_cbor() { + let platform_version = PlatformVersion::first(); + let data_contract: DataContract = get_data_contract_v0(platform_version).into(); + + let first = data_contract + .serialize_to_bytes_with_platform_version(platform_version) + .expect("data contract should be serialized"); + let second = data_contract + .serialize_to_bytes_with_platform_version(platform_version) + .expect("data contract should be serialized"); + + assert_eq!(first, second); + } + + #[test] + fn serialize_deterministically_serialize_to_bincode() { + let platform_version = PlatformVersion::first(); + let data_contract: DataContract = get_data_contract_v0(platform_version).into(); + + let by_ref = data_contract + .clone() + .serialize_to_bytes_with_platform_version(platform_version) + .expect("data contract should be serialized"); + let by_consume = data_contract + .serialize_consume_to_bytes_with_platform_version(platform_version) + .expect("data contract should be serialized"); + + assert_eq!(by_ref, by_consume); + } } diff --git a/packages/rs-dpp/src/data_contract/v0/methods/schema.rs b/packages/rs-dpp/src/data_contract/v0/methods/schema.rs index c08137267bc..d17becabac5 100644 --- a/packages/rs-dpp/src/data_contract/v0/methods/schema.rs +++ b/packages/rs-dpp/src/data_contract/v0/methods/schema.rs @@ -168,14 +168,6 @@ mod test { let config = DataContractConfig::default_for_version(platform_version) .expect("should create a default config"); - let defs = platform_value!({ - "test": { - "type": "string", - }, - }); - - let defs_map = Some(defs.into_btree_string_map().expect("should convert to map")); - let schema = platform_value!({ "type": "object", "properties": { @@ -188,13 +180,21 @@ mod test { "additionalProperties": false, }); + let defs = platform_value!({ + "test": { + "type": "string", + }, + }); + + let defs_map = Some(defs.into_btree_string_map().expect("should convert to map")); + let serialization_format = DataContractInSerializationFormatV0 { id: Identifier::random(), config, version: 0, owner_id: Default::default(), schema_defs: defs_map, - document_schemas: BTreeMap::from([("document_type_name".to_string(), schema)]), + document_schemas: BTreeMap::from([("document_type_name".to_string(), schema.clone())]), }; let mut data_contract = DataContractV0::try_from_platform_versioned( diff --git a/packages/rs-dpp/src/data_contract/v1/methods/schema.rs b/packages/rs-dpp/src/data_contract/v1/methods/schema.rs index 5b163d45c04..d31455448ab 100644 --- a/packages/rs-dpp/src/data_contract/v1/methods/schema.rs +++ b/packages/rs-dpp/src/data_contract/v1/methods/schema.rs @@ -168,14 +168,6 @@ mod test { let config = DataContractConfig::default_for_version(platform_version) .expect("should create a default config"); - let defs = platform_value!({ - "test": { - "type": "string", - }, - }); - - let defs_map = Some(defs.into_btree_string_map().expect("should convert to map")); - let schema = platform_value!({ "type": "object", "properties": { @@ -188,13 +180,21 @@ mod test { "additionalProperties": false, }); + let defs = platform_value!({ + "test": { + "type": "string", + }, + }); + + let defs_map = Some(defs.into_btree_string_map().expect("should convert to map")); + let serialization_format = DataContractInSerializationFormatV0 { id: Identifier::random(), config, version: 0, owner_id: Default::default(), schema_defs: defs_map, - document_schemas: BTreeMap::from([("document_type_name".to_string(), schema)]), + document_schemas: BTreeMap::from([("document_type_name".to_string(), schema.clone())]), }; let mut data_contract = DataContractV1::try_from_platform_versioned( diff --git a/packages/rs-dpp/src/state_transition/state_transitions/contract/data_contract_update_transition/json_conversion.rs b/packages/rs-dpp/src/state_transition/state_transitions/contract/data_contract_update_transition/json_conversion.rs index b1ee11537d3..ca0b85a5c5b 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/contract/data_contract_update_transition/json_conversion.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/contract/data_contract_update_transition/json_conversion.rs @@ -25,47 +25,61 @@ impl StateTransitionJsonConvert<'_> for DataContractUpdateTransition { } } } -// -// #[cfg(test)] -// mod test { -// use crate::state_transition::data_contract_update_transition::STATE_TRANSITION_PROTOCOL_VERSION; -// use crate::state_transition::JsonStateTransitionSerializationOptions; -// -// #[test] -// fn should_return_state_transition_in_json_format() { -// let data = get_test_data(); -// let mut json_object = data -// .state_transition -// .to_json(JsonStateTransitionSerializationOptions { -// skip_signature: false, -// into_validating_json: false, -// }) -// .expect("conversion to JSON shouldn't fail"); -// -// assert_eq!( -// 0, -// json_object -// .get_u64(STATE_TRANSITION_PROTOCOL_VERSION) -// .expect("the protocol version should be present") as u32 -// ); -// -// assert_eq!( -// 4, -// json_object -// .get_u64(TRANSITION_TYPE) -// .expect("the transition type should be present") as u8 -// ); -// assert_eq!( -// 0, -// json_object -// .get_u64(SIGNATURE_PUBLIC_KEY_ID) -// .expect("default public key id should be defined"), -// ); -// assert_eq!( -// "", -// json_object -// .remove_into::(SIGNATURE) -// .expect("default string value for signature should be present") -// ); -// } -// } +#[cfg(test)] +mod test { + use super::*; + use crate::state_transition::data_contract_update_transition::DataContractUpdateTransition; + use crate::state_transition::JsonStateTransitionSerializationOptions; + use crate::tests::fixtures::get_data_contract_fixture; + use platform_version::version::PlatformVersion; + use platform_version::TryIntoPlatformVersioned; + + fn get_test_data() -> DataContractUpdateTransition { + let platform_version = PlatformVersion::first(); + let data_contract = get_data_contract_fixture(None, 0, platform_version.protocol_version) + .data_contract_owned(); + + (data_contract, 1) + .try_into_platform_versioned(platform_version) + .expect("expected to get transition") + } + + #[test] + fn should_return_state_transition_in_json_format() { + let json_object = get_test_data() + .to_json(JsonStateTransitionSerializationOptions { + skip_signature: false, + into_validating_json: false, + }) + .expect("conversion to JSON shouldn't fail"); + + assert_eq!( + Some(0), + json_object + .get(STATE_TRANSITION_PROTOCOL_VERSION) + .and_then(JsonValue::as_u64) + .map(|v| v as u32), + "the protocol version should be present", + ); + assert_eq!( + None, + json_object + .get(TRANSITION_TYPE) + .and_then(JsonValue::as_u64) + .map(|v| v as u8), + "the transition type is not serialized in non-validating JSON", + ); + assert_eq!( + Some(0), + json_object + .get(SIGNATURE_PUBLIC_KEY_ID) + .and_then(JsonValue::as_u64), + "default public key id should be defined", + ); + assert_eq!( + Some(""), + json_object.get(SIGNATURE).and_then(JsonValue::as_str), + "default string value for signature should be present", + ); + } +} diff --git a/packages/rs-dpp/src/state_transition/traits/state_transition_identity_signed.rs b/packages/rs-dpp/src/state_transition/traits/state_transition_identity_signed.rs index a06d5bd69c7..a9f7e94ebd6 100644 --- a/packages/rs-dpp/src/state_transition/traits/state_transition_identity_signed.rs +++ b/packages/rs-dpp/src/state_transition/traits/state_transition_identity_signed.rs @@ -114,458 +114,595 @@ pub fn get_compressed_public_ec_key(private_key: &[u8]) -> Result<[u8; 33], Prot Ok(public_key_compressed) } -// -// #[cfg(test)] -// mod test { -// use chrono::Utc; -// use platform_value::{BinaryData, Value}; -// use rand::rngs::StdRng; -// use rand::SeedableRng; -// use serde::{Deserialize, Serialize}; -// use serde_json::json; -// use std::convert::TryInto; -// use std::vec; -// -// use crate::ProtocolError::InvalidSignaturePublicKeySecurityLevelError; -// use crate::{ -// assert_error_contains, -// identity::{KeyID, SecurityLevel}, -// state_transition::{ -// StateTransition, StateTransitionFieldTypes, StateTransitionLike, StateTransitionType, -// }, -// util::hash::ripemd160_sha256, -// NativeBlsModule, -// }; -// use platform_value::string_encoding::Encoding; -// -// use super::StateTransitionIdentitySignedV0; -// use super::*; -// use crate::serialization::PlatformDeserializable; -// use crate::serialization::PlatformSerializable; -// use crate::serialization::Signable; -// use crate::version::FeatureVersion; -// use bincode::{config, Decode, Encode}; -// use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; -// -// #[derive( -// Debug, -// Clone, -// Encode, -// Decode, -// Serialize, -// Deserialize, -// PlatformDeserialize, -// PlatformSerialize, -// PlatformSignable, -// )] -// -// #[serde(rename_all = "camelCase")] -// struct ExampleStateTransition { -// pub protocol_version: u32, -// pub transition_type: StateTransitionType, -// pub owner_id: Identifier, -// #[platform_signable(exclude_from_sig_hash)] -// pub signature: BinaryData, -// #[platform_signable(exclude_from_sig_hash)] -// pub signature_public_key_id: KeyID, -// } -// -// impl StateTransitionFieldTypes for ExampleStateTransition { -// fn binary_property_paths() -> Vec<&'static str> { -// vec!["signature"] -// } -// fn identifiers_property_paths() -> Vec<&'static str> { -// vec![] -// } -// fn signature_property_paths() -> Vec<&'static str> { -// vec!["signature", "signaturePublicKeyId"] -// } -// -// fn to_cleaned_object(&self, _skip_signature: bool) -> Result { -// todo!() -// } -// } -// -// impl From for StateTransition { -// fn from(_val: ExampleStateTransition) -> Self { -// let st = DocumentsBatchTransition::default(); -// StateTransition::DocumentsBatch(st) -// } -// } -// -// impl StateTransitionLike for ExampleStateTransition { -// fn state_transition_protocol_version(&self) -> FeatureVersion { -// 1 -// } -// fn state_transition_type(&self) -> StateTransitionType { -// StateTransitionType::DocumentsBatch -// } -// fn signature(&self) -> &BinaryData { -// &self.signature -// } -// fn set_signature(&mut self, signature: BinaryData) { -// self.signature = signature -// } -// -// fn set_signature_bytes(&mut self, signature: Vec) { -// self.signature = BinaryData::new(signature) -// } -// -// fn modified_data_ids(&self) -> Vec { -// vec![] -// } -// } -// -// impl StateTransitionIdentitySignedV0 for ExampleStateTransition { -// fn get_owner_id(&self) -> &Identifier { -// &self.owner_id -// } -// fn get_security_level_requirement(&self) -> Vec { -// vec![SecurityLevel::HIGH] -// } -// -// fn get_signature_public_key_id(&self) -> Option { -// self.signature_public_key_id -// } -// -// fn set_signature_public_key_id(&mut self, key_id: KeyID) { -// self.signature_public_key_id = key_id; -// } -// } -// -// fn get_mock_state_transition() -> ExampleStateTransition { -// let owner_id = Identifier::from_string( -// "AX5o22ARWFYZE9JZTA5SSeyvprtetBcvbQLSBZ7cR7Gw", -// Encoding::Base58, -// ) -// .unwrap(); -// ExampleStateTransition { -// protocol_version: 1, -// transition_type: StateTransitionType::DocumentsBatch, -// signature: Default::default(), -// signature_public_key_id: 1, -// owner_id, -// } -// } -// -// struct Keys { -// pub ec_private: Vec, -// pub ec_public_compressed: Vec, -// pub ec_public_uncompressed: Vec, -// pub bls_private: Vec, -// pub bls_public: Vec, -// pub identity_public_key: IdentityPublicKey, -// pub public_key_id: KeyID, -// } -// -// fn get_test_keys() -> Keys { -// let secp = dashcore::secp256k1::Secp256k1::new(); -// let mut rng = dashcore::secp256k1::rand::thread_rng(); -// let mut std_rng = StdRng::seed_from_u64(99999); -// let (private_key, public_key) = secp.generate_keypair(&mut rng); -// -// let public_key_id = 1; -// let ec_private_key_bytes = private_key.secret_bytes(); -// let ec_public_compressed_bytes = public_key.serialize(); -// let ec_public_uncompressed_bytes = public_key.serialize_uncompressed(); -// -// let bls_private = -// bls_signatures::PrivateKey::generate_dash(&mut std_rng).expect("expected private key"); -// let bls_public = bls_private -// .g1_element() -// .expect("expected to make public key"); -// let bls_private_bytes = bls_private.to_bytes().to_vec(); -// let bls_public_bytes = bls_public.to_bytes().to_vec(); -// -// let identity_public_key = IdentityPublicKey { -// id: public_key_id, -// key_type: KeyType::ECDSA_SECP256K1, -// purpose: Purpose::AUTHENTICATION, -// security_level: SecurityLevel::HIGH, -// data: BinaryData::new(ec_public_compressed_bytes.try_into().unwrap()), -// read_only: false, -// disabled_at: None, -// }; -// -// Keys { -// ec_private: ec_private_key_bytes.to_vec(), -// ec_public_compressed: ec_public_compressed_bytes.to_vec(), -// ec_public_uncompressed: ec_public_uncompressed_bytes.to_vec(), -// bls_private: bls_private_bytes, -// bls_public: bls_public_bytes, -// identity_public_key, -// public_key_id, -// } -// } -// -// #[test] -// fn to_object_with_signature() { -// let st = get_mock_state_transition(); -// let st_object = st.to_object(false).unwrap(); -// -// assert_eq!(st_object["protocolVersion"].to_integer::().unwrap(), 1); -// assert_eq!(st_object["transitionType"].to_integer::().unwrap(), 1); -// assert_eq!( -// st_object["signaturePublicKeyId"] -// .to_integer::() -// .unwrap(), -// 1 -// ); -// assert!(st_object["signature"].as_bytes().unwrap().is_empty()); -// } -// -// #[test] -// fn to_object_without_signature() { -// let st = get_mock_state_transition(); -// let st_object = st.to_object(true).unwrap(); -// -// assert_eq!(st_object["protocolVersion"].to_integer::().unwrap(), 1); -// assert_eq!(st_object["transitionType"].to_integer::().unwrap(), 1); -// assert!(!st_object.has("signaturePublicKeyId").unwrap()); -// assert!(!st_object.has("signature").unwrap()); -// } -// -// #[test] -// fn to_json() { -// let st = get_mock_state_transition(); -// let st_json = st.to_json(false).unwrap(); -// assert_eq!( -// st_json, -// json!({ -// "protocolVersion" : 1, -// "signature": "", -// "signaturePublicKeyId": 1, -// "transitionType" : 1, -// "ownerId" : "AX5o22ARWFYZE9JZTA5SSeyvprtetBcvbQLSBZ7cR7Gw" -// }) -// ); -// } -// -// #[test] -// fn to_hash() { -// let st = get_mock_state_transition(); -// let hash = st.hash(false).unwrap(); -// assert_eq!( -// "39b9c5951e5d83668f98909bb73d390d49867c47bbfe043a42ac83de898142c0", -// hex::encode(hash) -// ) -// } -// -// #[test] -// fn to_buffer() { -// let st = get_mock_state_transition(); -// let hash = st.to_cbor_buffer(false).unwrap(); -// let result = hex::encode(hash); -// -// assert_eq!("01a4676f776e6572496458208d6e06cac6cd2c4b9020806a3f1a4ec48fc90defd314330a5ce7d8548dfc2524697369676e617475726540747369676e61747572655075626c69634b65794964016e7472616e736974696f6e5479706501", result.as_str()); -// } -// -// #[test] -// fn to_buffer_no_signature() { -// let st = get_mock_state_transition(); -// let hash = st.to_cbor_buffer(true).unwrap(); -// let result = hex::encode(hash); -// -// assert_eq!("01a2676f776e6572496458208d6e06cac6cd2c4b9020806a3f1a4ec48fc90defd314330a5ce7d8548dfc25246e7472616e736974696f6e5479706501", result); -// } -// -// #[test] -// fn get_signature_public_key_id() { -// let st = get_mock_state_transition(); -// let keys = get_test_keys(); -// assert_eq!(Some(keys.public_key_id), st.get_signature_public_key_id()) -// } -// -// #[test] -// fn sign_validate_with_private_key() { -// let bls = NativeBlsModule::default(); -// let mut st = get_mock_state_transition(); -// let keys = get_test_keys(); -// -// st.sign(&keys.identity_public_key, &keys.ec_private, &bls) -// .unwrap(); -// st.verify_signature(&keys.identity_public_key, &bls) -// .expect("the verification shouldn't fail"); -// } -// -// #[test] -// fn sign_validate_signature_ecdsa_hash160() { -// let bls = NativeBlsModule::default(); -// let mut st = get_mock_state_transition(); -// let mut keys = get_test_keys(); -// keys.identity_public_key.key_type = KeyType::ECDSA_HASH160; -// keys.identity_public_key.data = -// BinaryData::new(ripemd160_sha256(keys.identity_public_key.data.as_slice()).to_vec()); -// -// st.sign(&keys.identity_public_key, &keys.ec_private, &bls) -// .unwrap(); -// st.verify_signature(&keys.identity_public_key, &bls) -// .expect("the verification shouldn't fail"); -// } -// -// #[test] -// fn error_when_sign_with_wrong_public_key() { -// let bls = NativeBlsModule::default(); -// let mut st = get_mock_state_transition(); -// let mut keys = get_test_keys(); -// -// let secp = dashcore::secp256k1::Secp256k1::new(); -// let mut rng = dashcore::secp256k1::rand::thread_rng(); -// let (_, public_key) = secp.generate_keypair(&mut rng); -// -// keys.identity_public_key.data = BinaryData::new(public_key.serialize().to_vec()); -// -// let sign_result = st.sign(&keys.identity_public_key, &keys.ec_private, &bls); -// assert_error_contains!(sign_result, "Invalid signature public key"); -// } -// -// #[test] -// fn error_if_security_level_is_not_met() { -// let bls = NativeBlsModule::default(); -// let mut st = get_mock_state_transition(); -// let mut keys = get_test_keys(); -// keys.identity_public_key.security_level = SecurityLevel::MEDIUM; -// -// let sign_error = st -// .sign(&keys.identity_public_key, &keys.ec_private, &bls) -// .unwrap_err(); -// match sign_error { -// InvalidSignaturePublicKeySecurityLevelError(err) => { -// assert_eq!(SecurityLevel::MEDIUM, err.public_key_security_level()); -// assert_eq!(vec![SecurityLevel::HIGH], err.allowed_key_security_levels()); -// } -// error => { -// panic!("invalid error type: {}", error) -// } -// }; -// } -// -// #[test] -// fn error_if_key_purpose_not_authenticated() { -// let bls = NativeBlsModule::default(); -// let mut st = get_mock_state_transition(); -// let mut keys = get_test_keys(); -// keys.identity_public_key.purpose = Purpose::ENCRYPTION; -// -// let sign_error = st -// .sign(&keys.identity_public_key, &keys.ec_private, &bls) -// .unwrap_err(); -// match sign_error { -// ProtocolError::WrongPublicKeyPurposeError(err) => { -// assert_eq!(Purpose::ENCRYPTION, err.public_key_purpose()); -// assert_eq!(Purpose::AUTHENTICATION, err.key_purpose_requirement()); -// } -// error => { -// panic!("invalid error type: {}", error) -// } -// }; -// } -// -// #[test] -// fn should_sign_validate_with_bls_signature() { -// let bls = NativeBlsModule::default(); -// let mut st = get_mock_state_transition(); -// let mut keys = get_test_keys(); -// keys.identity_public_key.key_type = KeyType::BLS12_381; -// keys.identity_public_key.data = BinaryData::new(keys.bls_public.clone()); -// -// st.sign(&keys.identity_public_key, &keys.bls_private, &bls) -// .expect("validation should be successful"); -// } -// -// #[test] -// fn error_if_transition_is_not_signed_ecdsa() { -// let bls = NativeBlsModule::default(); -// let st = get_mock_state_transition(); -// let keys = get_test_keys(); -// -// let verify_error = st -// .verify_signature(&keys.identity_public_key, &bls) -// .unwrap_err(); -// match verify_error { -// ProtocolError::StateTransitionIsNotSignedError { .. } => {} -// error => { -// panic!("invalid error type: {}", error) -// } -// }; -// } -// -// #[test] -// fn error_if_transition_is_not_signed_bls() { -// let bls = NativeBlsModule::default(); -// let st = get_mock_state_transition(); -// let mut keys = get_test_keys(); -// keys.identity_public_key.key_type = KeyType::BLS12_381; -// keys.identity_public_key.data = BinaryData::new(keys.bls_public.clone()); -// -// let verify_error = st -// .verify_signature(&keys.identity_public_key, &bls) -// .unwrap_err(); -// match verify_error { -// ProtocolError::StateTransitionIsNotSignedError { .. } => {} -// error => { -// panic!("invalid error type: {}", error) -// } -// }; -// } -// -// #[test] -// fn set_signature() { -// let mut st = get_mock_state_transition(); -// let signature = "some_signature"; -// st.set_signature(BinaryData::new(signature.as_bytes().to_owned())); -// assert_eq!(signature.as_bytes(), st.signature().as_slice()); -// } -// -// #[test] -// fn set_signature_public_key_id() { -// let mut st = get_mock_state_transition(); -// let public_key_id = 2; -// st.set_signature_public_key_id(public_key_id); -// assert_eq!(Some(public_key_id), st.get_signature_public_key_id()); -// } -// -// #[test] -// fn should_throw_public_key_is_disabled_error_if_public_key_is_disabled() { -// let bls = NativeBlsModule::default(); -// let mut st = get_mock_state_transition(); -// let mut keys = get_test_keys(); -// keys.identity_public_key -// .set_disabled_at(Utc::now().timestamp_millis() as u64); -// -// let result = st -// .sign(&keys.identity_public_key, &keys.bls_private, &bls) -// .expect_err("the protocol error should be returned"); -// -// assert!(matches!( -// result, -// ProtocolError::PublicKeyIsDisabledError { .. } -// )) -// } -// -// #[test] -// fn should_throw_invalid_signature_public_key_security_level_error() { -// let bls = NativeBlsModule::default(); -// // should throw InvalidSignaturePublicKeySecurityLevel Error if public key with master level is used to sign non update state transition -// let mut st = get_mock_state_transition(); -// let mut keys = get_test_keys(); -// -// st.transition_type = StateTransitionType::DataContractCreate; -// keys.identity_public_key.security_level = SecurityLevel::MASTER; -// -// let result = st -// .sign(&keys.identity_public_key, &keys.bls_private, &bls) -// .expect_err("the protocol error should be returned"); -// -// match result { -// ProtocolError::InvalidSignaturePublicKeySecurityLevelError(err) => { -// assert_eq!(err.public_key_security_level(), SecurityLevel::MASTER); -// assert_eq!(err.allowed_key_security_levels(), vec![SecurityLevel::HIGH]); -// } -// error => panic!( -// "expected InvalidSignaturePublicKeySecurityLevelError, got {}", -// error -// ), -// } -// } -// } +#[cfg(all( + test, + any( + feature = "state-transition-signing", + feature = "state-transition-validation" + ) +))] +mod test { + use super::*; + use crate::identity::identity_public_key::v0::IdentityPublicKeyV0; + use crate::identity::{IdentityPublicKey, KeyType}; + use crate::prelude::UserFeeIncrease; + use crate::state_transition::batch_transition::{BatchTransition, BatchTransitionV0}; + use crate::state_transition::traits::state_transition_has_user_fee_increase::StateTransitionHasUserFeeIncrease; + use crate::state_transition::{ + StateTransition, StateTransitionFieldTypes, StateTransitionSigningOptions, + StateTransitionType, + }; + use platform_value::BinaryData; + + #[derive(Clone, Debug)] + struct MockSignedTransition { + signature_public_key_id: KeyID, + required_levels: Vec, + required_purposes: Vec, + user_fee_increase: UserFeeIncrease, + } + + impl Default for MockSignedTransition { + fn default() -> Self { + Self { + signature_public_key_id: 1, + required_levels: vec![SecurityLevel::HIGH], + required_purposes: vec![Purpose::AUTHENTICATION], + user_fee_increase: 0, + } + } + } + + impl StateTransitionFieldTypes for MockSignedTransition { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } + } + + impl From for StateTransition { + fn from(_: MockSignedTransition) -> Self { + StateTransition::Batch(BatchTransition::V0(BatchTransitionV0::default())) + } + } + + impl StateTransitionLike for MockSignedTransition { + fn state_transition_protocol_version(&self) -> u16 { + 1 + } + + fn state_transition_type(&self) -> StateTransitionType { + StateTransitionType::Batch + } + + fn modified_data_ids(&self) -> Vec { + vec![] + } + + fn unique_identifiers(&self) -> Vec { + vec![] + } + } + + impl StateTransitionHasUserFeeIncrease for MockSignedTransition { + fn user_fee_increase(&self) -> UserFeeIncrease { + self.user_fee_increase + } + + fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) { + self.user_fee_increase = user_fee_increase; + } + } + + impl StateTransitionIdentitySigned for MockSignedTransition { + fn signature_public_key_id(&self) -> KeyID { + self.signature_public_key_id + } + + fn set_signature_public_key_id(&mut self, key_id: KeyID) { + self.signature_public_key_id = key_id; + } + + fn security_level_requirement(&self, _purpose: Purpose) -> Vec { + self.required_levels.clone() + } + + fn purpose_requirement(&self) -> Vec { + self.required_purposes.clone() + } + } + + fn identity_public_key( + id: KeyID, + purpose: Purpose, + security_level: SecurityLevel, + disabled_at: Option, + ) -> IdentityPublicKey { + let compressed = get_compressed_public_ec_key(&[1; 32]).expect("expected valid key"); + + IdentityPublicKey::V0(IdentityPublicKeyV0 { + id, + purpose, + security_level, + contract_bounds: None, + key_type: KeyType::ECDSA_SECP256K1, + read_only: false, + data: BinaryData::new(compressed.to_vec()), + disabled_at, + }) + } + + fn strict_options() -> StateTransitionSigningOptions { + StateTransitionSigningOptions { + allow_signing_with_any_security_level: false, + allow_signing_with_any_purpose: false, + } + } + + #[test] + fn should_accept_matching_purpose_and_security_level() { + let transition = MockSignedTransition::default(); + let key = identity_public_key(1, Purpose::AUTHENTICATION, SecurityLevel::HIGH, None); + + assert!(transition + .verify_public_key_level_and_purpose(&key, strict_options()) + .is_ok()); + } + + #[test] + fn should_reject_wrong_purpose() { + let transition = MockSignedTransition::default(); + let key = identity_public_key(1, Purpose::TRANSFER, SecurityLevel::HIGH, None); + + assert!(matches!( + transition.verify_public_key_level_and_purpose(&key, strict_options()), + Err(ProtocolError::WrongPublicKeyPurposeError(_)) + )); + } + + #[test] + fn should_reject_wrong_security_level() { + let transition = MockSignedTransition::default(); + let key = identity_public_key(1, Purpose::AUTHENTICATION, SecurityLevel::MEDIUM, None); + + assert!(matches!( + transition.verify_public_key_level_and_purpose(&key, strict_options()), + Err(ProtocolError::InvalidSignaturePublicKeySecurityLevelError( + _ + )) + )); + } + + #[test] + fn should_allow_any_purpose_when_option_enabled() { + let transition = MockSignedTransition::default(); + let key = identity_public_key(1, Purpose::TRANSFER, SecurityLevel::HIGH, None); + + assert!(transition + .verify_public_key_level_and_purpose( + &key, + StateTransitionSigningOptions { + allow_signing_with_any_security_level: false, + allow_signing_with_any_purpose: true, + }, + ) + .is_ok()); + } + + #[test] + fn should_allow_any_security_level_when_option_enabled() { + let transition = MockSignedTransition::default(); + let key = identity_public_key(1, Purpose::AUTHENTICATION, SecurityLevel::MEDIUM, None); + + assert!(transition + .verify_public_key_level_and_purpose( + &key, + StateTransitionSigningOptions { + allow_signing_with_any_security_level: true, + allow_signing_with_any_purpose: false, + }, + ) + .is_ok()); + } + + #[test] + fn should_verify_enabled_public_key() { + let transition = MockSignedTransition::default(); + let key = identity_public_key(1, Purpose::AUTHENTICATION, SecurityLevel::HIGH, None); + + assert!(transition.verify_public_key_is_enabled(&key).is_ok()); + } + + #[test] + fn should_reject_disabled_public_key() { + let transition = MockSignedTransition::default(); + let key = identity_public_key(1, Purpose::AUTHENTICATION, SecurityLevel::HIGH, Some(42)); + + assert!(matches!( + transition.verify_public_key_is_enabled(&key), + Err(ProtocolError::PublicKeyIsDisabledError(_)) + )); + } + + #[test] + fn should_default_purpose_to_authentication() { + let transition = MockSignedTransition::default(); + + assert_eq!( + vec![Purpose::AUTHENTICATION], + transition.purpose_requirement() + ); + } + + #[test] + fn should_support_transfer_purpose_requirement() { + let transition = MockSignedTransition { + required_purposes: vec![Purpose::TRANSFER], + ..Default::default() + }; + let key = identity_public_key(1, Purpose::TRANSFER, SecurityLevel::HIGH, None); + + assert!(transition + .verify_public_key_level_and_purpose(&key, strict_options()) + .is_ok()); + } + + #[test] + fn should_reject_authentication_key_for_transfer_requirement() { + let transition = MockSignedTransition { + required_purposes: vec![Purpose::TRANSFER], + ..Default::default() + }; + let key = identity_public_key(1, Purpose::AUTHENTICATION, SecurityLevel::HIGH, None); + + assert!(matches!( + transition.verify_public_key_level_and_purpose(&key, strict_options()), + Err(ProtocolError::WrongPublicKeyPurposeError(_)) + )); + } + + #[test] + fn should_set_signature_public_key_id() { + let mut transition = MockSignedTransition::default(); + + transition.set_signature_public_key_id(9); + + assert_eq!(9, transition.signature_public_key_id()); + } + + #[test] + fn should_return_signature_public_key_id() { + let transition = MockSignedTransition { + signature_public_key_id: 7, + ..Default::default() + }; + + assert_eq!(7, transition.signature_public_key_id()); + } + + #[test] + fn should_return_configured_security_level_requirement() { + let transition = MockSignedTransition { + required_levels: vec![SecurityLevel::MASTER, SecurityLevel::HIGH], + ..Default::default() + }; + + assert_eq!( + vec![SecurityLevel::MASTER, SecurityLevel::HIGH], + transition.security_level_requirement(Purpose::AUTHENTICATION) + ); + } + + #[test] + fn should_validate_master_security_level_when_required() { + let transition = MockSignedTransition { + required_levels: vec![SecurityLevel::MASTER], + ..Default::default() + }; + let key = identity_public_key(1, Purpose::AUTHENTICATION, SecurityLevel::MASTER, None); + + assert!(transition + .verify_public_key_level_and_purpose(&key, strict_options()) + .is_ok()); + } + + #[test] + fn should_reject_high_security_when_master_required() { + let transition = MockSignedTransition { + required_levels: vec![SecurityLevel::MASTER], + ..Default::default() + }; + let key = identity_public_key(1, Purpose::AUTHENTICATION, SecurityLevel::HIGH, None); + + assert!(matches!( + transition.verify_public_key_level_and_purpose(&key, strict_options()), + Err(ProtocolError::InvalidSignaturePublicKeySecurityLevelError( + _ + )) + )); + } + + #[test] + fn should_get_compressed_public_key_for_valid_private_key() { + let compressed = get_compressed_public_ec_key(&[1; 32]).expect("expected key"); + + assert_eq!(33, compressed.len()); + assert_ne!([0; 33], compressed); + } + + #[test] + fn should_return_error_for_invalid_private_key_size() { + let result = get_compressed_public_ec_key(&[1; 31]); + + assert!(result.is_err()); + } + + #[test] + fn should_return_deterministic_compressed_public_key() { + let first = get_compressed_public_ec_key(&[2; 32]).expect("expected first key"); + let second = get_compressed_public_ec_key(&[2; 32]).expect("expected second key"); + + assert_eq!(first, second); + } + + // ----------------------------------------------------------------------- + // Real sign / verify integration coverage. + // + // The tests above only exercise the trait's helper predicates against a + // local `MockSignedTransition`. The tests below drive the actual + // `StateTransition::sign_with_options` and + // `StateTransition::verify_identity_signed_signature` code paths against + // a `BatchTransition::V0` (the lightest real identity-signed transition) + // so that the ECDSA-hash160 / BLS / mismatch / not-signed branches are + // covered. + // ----------------------------------------------------------------------- + + #[cfg(all( + feature = "state-transition-signing", + feature = "state-transition-validation" + ))] + mod sign_verify { + use super::*; + use crate::util::hash::ripemd160_sha256; + use crate::{BlsModule, ProtocolError, PublicKeyValidationError}; + + // A no-op `BlsModule` for tests that exclusively exercise ECDSA paths. + // sign_with_options/verify_identity_signed_signature take a + // `&impl BlsModule` even when the key type is ECDSA, so we need + // something concrete to pass in. + struct NoopBls; + impl BlsModule for NoopBls { + fn validate_public_key(&self, _: &[u8]) -> Result<(), PublicKeyValidationError> { + Ok(()) + } + fn verify_signature( + &self, + _: &[u8], + _: &[u8], + _: &[u8], + ) -> Result { + Ok(false) + } + fn private_key_to_public_key(&self, _: &[u8]) -> Result, ProtocolError> { + Ok(vec![]) + } + fn sign(&self, _: &[u8], _: &[u8]) -> Result, ProtocolError> { + Ok(vec![]) + } + } + + fn batch_state_transition() -> StateTransition { + StateTransition::Batch(BatchTransition::V0(BatchTransitionV0::default())) + } + + fn ecdsa_secp256k1_keypair(id: KeyID, seed: u8) -> ([u8; 32], IdentityPublicKey) { + let private_key = [seed; 32]; + let compressed = + get_compressed_public_ec_key(&private_key).expect("expected valid ecdsa key"); + let key = IdentityPublicKey::V0(IdentityPublicKeyV0 { + id, + purpose: Purpose::AUTHENTICATION, + security_level: SecurityLevel::HIGH, + contract_bounds: None, + key_type: KeyType::ECDSA_SECP256K1, + read_only: false, + data: BinaryData::new(compressed.to_vec()), + disabled_at: None, + }); + (private_key, key) + } + + fn ecdsa_hash160_keypair(id: KeyID, seed: u8) -> ([u8; 32], IdentityPublicKey) { + let private_key = [seed; 32]; + let compressed = + get_compressed_public_ec_key(&private_key).expect("expected valid ecdsa key"); + let pub_key_hash = ripemd160_sha256(&compressed); + let key = IdentityPublicKey::V0(IdentityPublicKeyV0 { + id, + purpose: Purpose::AUTHENTICATION, + security_level: SecurityLevel::HIGH, + contract_bounds: None, + key_type: KeyType::ECDSA_HASH160, + read_only: false, + data: BinaryData::new(pub_key_hash.to_vec()), + disabled_at: None, + }); + (private_key, key) + } + + #[test] + fn ecdsa_hash160_sign_then_verify_succeeds() { + let (private_key, public_key) = ecdsa_hash160_keypair(7, 3); + let mut st = batch_state_transition(); + + st.sign_with_options( + &public_key, + &private_key, + &NoopBls, + StateTransitionSigningOptions::default(), + ) + .expect("ECDSA_HASH160 signing should succeed for a matching pubkey hash"); + + // After signing, signature is populated and the signing key id is set. + assert!( + !st.signature() + .expect("signature present") + .as_slice() + .is_empty(), + "signing must populate the signature" + ); + assert_eq!(st.signature_public_key_id(), Some(public_key.id())); + + st.verify_identity_signed_signature(&public_key, &NoopBls) + .expect("verification must succeed for the same public key hash"); + } + + #[cfg(feature = "bls-signatures")] + #[test] + fn bls_sign_then_verify_succeeds() { + use crate::bls::native_bls::NativeBlsModule; + + // BLS private key must be a valid scalar; [1u8; 32] is fine. + let private_key: [u8; 32] = [1u8; 32]; + let bls = NativeBlsModule; + let bls_public_key = bls + .private_key_to_public_key(&private_key) + .expect("BLS pubkey derivation should succeed"); + let public_key = IdentityPublicKey::V0(IdentityPublicKeyV0 { + id: 11, + purpose: Purpose::AUTHENTICATION, + security_level: SecurityLevel::HIGH, + contract_bounds: None, + key_type: KeyType::BLS12_381, + read_only: false, + data: BinaryData::new(bls_public_key), + disabled_at: None, + }); + + let mut st = batch_state_transition(); + st.sign_with_options( + &public_key, + &private_key, + &bls, + StateTransitionSigningOptions::default(), + ) + .expect("BLS signing should succeed for a matching pubkey"); + + assert!( + !st.signature() + .expect("signature present") + .as_slice() + .is_empty(), + "BLS signing must populate the signature" + ); + assert_eq!(st.signature_public_key_id(), Some(public_key.id())); + + st.verify_identity_signed_signature(&public_key, &bls) + .expect("BLS verification must succeed for the original key"); + } + + #[test] + fn invalid_signature_public_key_error_when_pubkey_mismatches_private_key() { + // Build the public key from one private key, then attempt to + // sign using a *different* private key — sign_with_options must + // detect the mismatch and refuse to sign. + let (_real_private_key, public_key) = ecdsa_secp256k1_keypair(3, 1); + let wrong_private_key = [9u8; 32]; + + let mut st = batch_state_transition(); + let err = st + .sign_with_options( + &public_key, + &wrong_private_key, + &NoopBls, + StateTransitionSigningOptions::default(), + ) + .expect_err("mismatched private key must be rejected"); + + assert!( + matches!(err, ProtocolError::InvalidSignaturePublicKeyError(_)), + "expected InvalidSignaturePublicKeyError, got {err:?}" + ); + + // The same check applies for ECDSA_HASH160. + let (_real_private_key, hash160_pubkey) = ecdsa_hash160_keypair(4, 2); + let mut st = batch_state_transition(); + let err = st + .sign_with_options( + &hash160_pubkey, + &wrong_private_key, + &NoopBls, + StateTransitionSigningOptions::default(), + ) + .expect_err("mismatched private key must be rejected for HASH160"); + + assert!( + matches!(err, ProtocolError::InvalidSignaturePublicKeyError(_)), + "expected InvalidSignaturePublicKeyError for HASH160, got {err:?}" + ); + } + + #[test] + fn public_key_mismatch_error_when_verify_uses_different_key_id() { + // Sign with key id = 5, then attempt to verify with a public key + // bearing a different id. verify_identity_signed_signature must + // return PublicKeyMismatchError before touching the cryptography. + let (private_key, signing_pubkey) = ecdsa_secp256k1_keypair(5, 1); + let mut st = batch_state_transition(); + st.sign_with_options( + &signing_pubkey, + &private_key, + &NoopBls, + StateTransitionSigningOptions::default(), + ) + .expect("signing should succeed"); + + // Build a second public key with a different id but otherwise valid. + let (_other_private_key, other_pubkey) = ecdsa_secp256k1_keypair(99, 2); + + let err = st + .verify_identity_signed_signature(&other_pubkey, &NoopBls) + .expect_err("verifying with a different key id must fail"); + + assert!( + matches!(err, ProtocolError::PublicKeyMismatchError(_)), + "expected PublicKeyMismatchError, got {err:?}" + ); + } + + #[test] + fn state_transition_is_not_signed_error_when_signature_empty() { + // An unsigned BatchTransitionV0 has an empty signature. + // verify_identity_signed_signature must reject it with + // StateTransitionIsNotSignedError, regardless of the supplied key. + let st = batch_state_transition(); + assert!( + st.signature() + .expect("Batch always has a signature field") + .as_slice() + .is_empty(), + "default Batch should start out unsigned" + ); + + // Use a key whose id matches the default (0) so we *don't* trip + // the PublicKeyMismatchError branch first. + let (_private_key, public_key) = ecdsa_secp256k1_keypair(0, 4); + + let err = st + .verify_identity_signed_signature(&public_key, &NoopBls) + .expect_err("unsigned transition must be rejected"); + + assert!( + matches!(err, ProtocolError::StateTransitionIsNotSignedError(_)), + "expected StateTransitionIsNotSignedError, got {err:?}" + ); + } + } +} diff --git a/packages/rs-dpp/src/tests/payloads/data_contract_v0.json b/packages/rs-dpp/src/tests/payloads/data_contract_v0.json new file mode 100644 index 00000000000..3d325a1660f --- /dev/null +++ b/packages/rs-dpp/src/tests/payloads/data_contract_v0.json @@ -0,0 +1,31 @@ +{ + "$formatVersion": "0", + "id": "BmKTJeLL3GfH8FxEx7SUbTog4eAKj8vJRDi97gYkxB9p", + "ownerId": "HtQNfXBZJu3WnvjvCFJKgbvfgWYJxWxaFWy23TKoFjg9", + "version": 1, + "config": { + "$formatVersion": "0", + "canBeDeleted": false, + "readonly": false, + "keepsHistory": false, + "documentsKeepHistoryContractDefault": false, + "documentsMutableContractDefault": true, + "documentsCanBeDeletedContractDefault": false, + "requiresIdentityEncryptionBoundedKey": null, + "requiresIdentityDecryptionBoundedKey": null + }, + "schemaDefs": null, + "documentSchemas": { + "note": { + "type": "object", + "properties": { + "message": { + "type": "string", + "position": 0 + } + }, + "required": ["message", "$createdAt", "$updatedAt"], + "additionalProperties": false + } + } +} diff --git a/packages/rs-platform-wallet/src/manager/accessors.rs b/packages/rs-platform-wallet/src/manager/accessors.rs index ed9cf89964f..eebacd40588 100644 --- a/packages/rs-platform-wallet/src/manager/accessors.rs +++ b/packages/rs-platform-wallet/src/manager/accessors.rs @@ -347,10 +347,10 @@ impl PlatformWalletManager

{ // through a helper on the manager — since the registry itself // isn't exposed, fall back to "0" until a sync getter is // added. This is intentionally a TODO surface, not a guess. - let queue_depth = match self.identity_sync_manager.try_queue_depth() { - Some(n) => n, - None => 0, - }; + let queue_depth = self + .identity_sync_manager + .try_queue_depth() + .unwrap_or_default(); IdentitySyncConfigSnapshot { interval_seconds: interval.as_secs().max(1), queue_depth, @@ -702,7 +702,7 @@ impl PlatformWalletManager

{ .map(|(reg_idx, managed)| { use dpp::identity::accessors::IdentityGettersV0; WalletIdentityRowSnapshot { - registration_index: *reg_idx as u32, + registration_index: *reg_idx, identity_id: managed.identity.id().to_buffer(), } }) @@ -739,11 +739,7 @@ fn pool_snapshot(pool: &AddressPool) -> AccountAddressPoolSnapshot { AddressPoolType::AbsentHardened => 3, }; let last_used_index: i64 = pool.highest_used.map(|i| i as i64).unwrap_or(-1); - let addresses = pool - .addresses - .values() - .map(|info| addr_info_snapshot(info)) - .collect(); + let addresses = pool.addresses.values().map(addr_info_snapshot).collect(); AccountAddressPoolSnapshot { pool_type, gap_limit: pool.gap_limit,